Home | History | Annotate | Download | only in flip_server
      1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/tools/flip_server/spdy_interface.h"
      6 
      7 #include <string>
      8 
      9 #include "net/spdy/spdy_framer.h"
     10 #include "net/spdy/spdy_protocol.h"
     11 #include "net/tools/dump_cache/url_utilities.h"
     12 #include "net/tools/flip_server/flip_config.h"
     13 #include "net/tools/flip_server/http_interface.h"
     14 #include "net/tools/flip_server/spdy_util.h"
     15 
     16 using spdy::kSpdyStreamMaximumWindowSize;
     17 using spdy::CONTROL_FLAG_NONE;
     18 using spdy::DATA_FLAG_COMPRESSED;
     19 using spdy::DATA_FLAG_FIN;
     20 using spdy::RST_STREAM;
     21 using spdy::SETTINGS_MAX_CONCURRENT_STREAMS;
     22 using spdy::SYN_REPLY;
     23 using spdy::SYN_STREAM;
     24 using spdy::SettingsFlagsAndId;
     25 using spdy::SpdyControlFrame;
     26 using spdy::SpdySettingsControlFrame;
     27 using spdy::SpdyDataFlags;
     28 using spdy::SpdyDataFrame;
     29 using spdy::SpdyRstStreamControlFrame;
     30 using spdy::SpdyFrame;
     31 using spdy::SpdyFramer;
     32 using spdy::SpdyFramerVisitorInterface;
     33 using spdy::SpdyHeaderBlock;
     34 using spdy::SpdySetting;
     35 using spdy::SpdySettings;
     36 using spdy::SpdyStreamId;
     37 using spdy::SpdySynReplyControlFrame;
     38 using spdy::SpdySynStreamControlFrame;
     39 
     40 namespace net {
     41 
     42 // static
     43 bool SpdySM::disable_data_compression_ = true;
     44 // static
     45 std::string SpdySM::forward_ip_header_;
     46 
     47 class SpdyFrameDataFrame : public DataFrame {
     48  public:
     49   SpdyFrameDataFrame(SpdyFrame* spdy_frame)
     50     : frame(spdy_frame) {
     51     data = spdy_frame->data();
     52     size = spdy_frame->length() + SpdyFrame::size();
     53   }
     54 
     55   virtual ~SpdyFrameDataFrame() {
     56     delete frame;
     57   }
     58 
     59   const SpdyFrame* frame;
     60 };
     61 
     62 SpdySM::SpdySM(SMConnection* connection,
     63                SMInterface* sm_http_interface,
     64                EpollServer* epoll_server,
     65                MemoryCache* memory_cache,
     66                FlipAcceptor* acceptor)
     67     : seq_num_(0),
     68       spdy_framer_(new SpdyFramer),
     69       valid_spdy_session_(false),
     70       connection_(connection),
     71       client_output_list_(connection->output_list()),
     72       client_output_ordering_(connection),
     73       next_outgoing_stream_id_(2),
     74       epoll_server_(epoll_server),
     75       acceptor_(acceptor),
     76       memory_cache_(memory_cache),
     77       close_on_error_(false) {
     78   spdy_framer_->set_visitor(this);
     79 }
     80 
     81 SpdySM::~SpdySM() {
     82   delete spdy_framer_;
     83 }
     84 
     85 void SpdySM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
     86                               SMInterface* sm_interface,
     87                               EpollServer* epoll_server,
     88                               int fd,
     89                               std::string server_ip,
     90                               std::string server_port,
     91                               std::string remote_ip,
     92                               bool use_ssl) {
     93   VLOG(2) << ACCEPTOR_CLIENT_IDENT
     94           << "SpdySM: Initializing server connection.";
     95   connection_->InitSMConnection(connection_pool, sm_interface,
     96                                 epoll_server, fd, server_ip, server_port,
     97                                 remote_ip, use_ssl);
     98 }
     99 
    100 SMInterface* SpdySM::NewConnectionInterface() {
    101   SMConnection* server_connection =
    102     SMConnection::NewSMConnection(epoll_server_,
    103                                   NULL,
    104                                   memory_cache_,
    105                                   acceptor_,
    106                                   "http_conn: ");
    107   if (server_connection == NULL) {
    108     LOG(ERROR) << "SpdySM: Could not create server connection";
    109     return NULL;
    110   }
    111   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Creating new HTTP interface";
    112   SMInterface *sm_http_interface = new HttpSM(server_connection,
    113                                               this,
    114                                               epoll_server_,
    115                                               memory_cache_,
    116                                               acceptor_);
    117   return sm_http_interface;
    118 }
    119 
    120 SMInterface* SpdySM::FindOrMakeNewSMConnectionInterface(
    121     std::string server_ip, std::string server_port) {
    122   SMInterface *sm_http_interface;
    123   int32 server_idx;
    124   if (unused_server_interface_list.empty()) {
    125     sm_http_interface = NewConnectionInterface();
    126     server_idx = server_interface_list.size();
    127     server_interface_list.push_back(sm_http_interface);
    128     VLOG(2) << ACCEPTOR_CLIENT_IDENT
    129             << "SpdySM: Making new server connection on index: "
    130             << server_idx;
    131   } else {
    132     server_idx = unused_server_interface_list.back();
    133     unused_server_interface_list.pop_back();
    134     sm_http_interface = server_interface_list.at(server_idx);
    135     VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reusing connection on "
    136             << "index: " << server_idx;
    137   }
    138 
    139   sm_http_interface->InitSMInterface(this, server_idx);
    140   sm_http_interface->InitSMConnection(NULL, sm_http_interface,
    141                                       epoll_server_, -1,
    142                                       server_ip, server_port, "", false);
    143 
    144   return sm_http_interface;
    145 }
    146 
    147 int SpdySM::SpdyHandleNewStream(const SpdyControlFrame* frame,
    148                                 std::string &http_data,
    149                                 bool *is_https_scheme) {
    150   bool parsed_headers = false;
    151   SpdyHeaderBlock headers;
    152   const SpdySynStreamControlFrame* syn_stream =
    153     reinterpret_cast<const SpdySynStreamControlFrame*>(frame);
    154 
    155   *is_https_scheme = false;
    156   parsed_headers = spdy_framer_->ParseHeaderBlock(frame, &headers);
    157   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSyn("
    158           << syn_stream->stream_id() << ")";
    159   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: headers parsed?: "
    160           << (parsed_headers? "yes": "no");
    161   if (parsed_headers) {
    162     VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: # headers: "
    163             << headers.size();
    164   }
    165   SpdyHeaderBlock::iterator url = headers.find("url");
    166   SpdyHeaderBlock::iterator method = headers.find("method");
    167   if (url == headers.end() || method == headers.end()) {
    168     VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: didn't find method or url "
    169             << "or method. Not creating stream";
    170     return 0;
    171   }
    172 
    173   SpdyHeaderBlock::iterator scheme = headers.find("scheme");
    174   if (scheme->second.compare("https") == 0) {
    175     *is_https_scheme = true;
    176   }
    177 
    178   // url->second here only ever seems to contain just the path. When this
    179   // path contains a query string with a http:// in one of its values,
    180   // UrlUtilities::GetUrlPath will fail and always return a / breaking
    181   // the request. GetUrlPath assumes the absolute URL is being passed in.
    182   std::string uri;
    183   if (url->second.compare(0,4,"http") == 0)
    184     uri = UrlUtilities::GetUrlPath(url->second);
    185   else
    186     uri = std::string(url->second);
    187   if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
    188     SpdyHeaderBlock::iterator referer = headers.find("referer");
    189     std::string host = UrlUtilities::GetUrlHost(url->second);
    190     VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second
    191             << " " << uri;
    192     std::string filename = EncodeURL(uri, host, method->second);
    193     NewStream(syn_stream->stream_id(),
    194               reinterpret_cast<const SpdySynStreamControlFrame*>
    195                   (frame)->priority(),
    196               filename);
    197   } else {
    198     SpdyHeaderBlock::iterator version = headers.find("version");
    199     http_data += method->second + " " + uri + " " + version->second + "\r\n";
    200     VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " "
    201             << uri << " " << version->second;
    202     for (SpdyHeaderBlock::iterator i = headers.begin();
    203          i != headers.end(); ++i) {
    204       http_data += i->first + ": " + i->second + "\r\n";
    205       VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first.c_str() << ":"
    206               << i->second.c_str();
    207     }
    208     if (forward_ip_header_.length()) {
    209       // X-Client-Cluster-IP header
    210       http_data += forward_ip_header_ + ": " +
    211                     connection_->client_ip() + "\r\n";
    212     }
    213     http_data += "\r\n";
    214   }
    215 
    216   VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data;
    217   return 1;
    218 }
    219 
    220 void SpdySM::OnControl(const SpdyControlFrame* frame) {
    221   SpdyHeaderBlock headers;
    222   bool parsed_headers = false;
    223   switch (frame->type()) {
    224     case SYN_STREAM:
    225       {
    226       const SpdySynStreamControlFrame* syn_stream =
    227           reinterpret_cast<const SpdySynStreamControlFrame*>(frame);
    228 
    229         std::string http_data;
    230         bool is_https_scheme;
    231         int ret = SpdyHandleNewStream(frame, http_data, &is_https_scheme);
    232         if (!ret) {
    233           LOG(ERROR) << "SpdySM: Could not convert spdy into http.";
    234           break;
    235         }
    236         // We've seen a valid looking SYN_STREAM, consider this to have
    237         // been a real spdy session.
    238         valid_spdy_session_ = true;
    239 
    240         if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
    241           std::string server_ip;
    242           std::string server_port;
    243           if (is_https_scheme) {
    244             server_ip = acceptor_->https_server_ip_;
    245             server_port = acceptor_->https_server_port_;
    246           } else {
    247             server_ip = acceptor_->http_server_ip_;
    248             server_port = acceptor_->http_server_port_;
    249           }
    250           SMInterface *sm_http_interface =
    251             FindOrMakeNewSMConnectionInterface(server_ip, server_port);
    252           stream_to_smif_[syn_stream->stream_id()] = sm_http_interface;
    253           sm_http_interface->SetStreamID(syn_stream->stream_id());
    254           sm_http_interface->ProcessWriteInput(http_data.c_str(),
    255                                                http_data.size());
    256         }
    257       }
    258       break;
    259 
    260     case SYN_REPLY:
    261       parsed_headers = spdy_framer_->ParseHeaderBlock(frame, &headers);
    262       VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSynReply(" <<
    263         reinterpret_cast<const SpdySynReplyControlFrame*>(frame)->stream_id()
    264         << ")";
    265       break;
    266     case RST_STREAM:
    267       {
    268       const SpdyRstStreamControlFrame* rst_stream =
    269           reinterpret_cast<const SpdyRstStreamControlFrame*>(frame);
    270         VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRst("
    271                 << rst_stream->stream_id() << ")";
    272         client_output_ordering_.RemoveStreamId(rst_stream ->stream_id());
    273       }
    274       break;
    275 
    276     default:
    277       LOG(ERROR) << "SpdySM: Unknown control frame type";
    278   }
    279 }
    280 
    281 bool SpdySM::OnControlFrameHeaderData(spdy::SpdyStreamId stream_id,
    282                                       const char* header_data,
    283                                       size_t len) {
    284   DCHECK(false);
    285   return false;
    286 }
    287 
    288 void SpdySM::OnDataFrameHeader(const spdy::SpdyDataFrame* frame) {
    289   DCHECK(false);
    290 }
    291 
    292 void SpdySM::OnStreamFrameData(SpdyStreamId stream_id,
    293                                const char* data, size_t len) {
    294   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id
    295           << ", [" << len << "])";
    296   StreamToSmif::iterator it = stream_to_smif_.find(stream_id);
    297   if (it == stream_to_smif_.end()) {
    298     VLOG(2) << "Dropping frame from unknown stream " << stream_id;
    299     if (!valid_spdy_session_)
    300       close_on_error_ = true;
    301     return;
    302   }
    303 
    304   SMInterface* interface = it->second;
    305   if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
    306     interface->ProcessWriteInput(data, len);
    307 }
    308 
    309 size_t SpdySM::ProcessReadInput(const char* data, size_t len) {
    310   return spdy_framer_->ProcessInput(data, len);
    311 }
    312 
    313 size_t SpdySM::ProcessWriteInput(const char* data, size_t len) {
    314   return 0;
    315 }
    316 
    317 bool SpdySM::MessageFullyRead() const {
    318   return spdy_framer_->MessageFullyRead();
    319 }
    320 
    321 bool SpdySM::Error() const {
    322   return close_on_error_ || spdy_framer_->HasError();
    323 }
    324 
    325 const char* SpdySM::ErrorAsString() const {
    326   DCHECK(Error());
    327   return SpdyFramer::ErrorCodeToString(spdy_framer_->error_code());
    328 }
    329 
    330 void SpdySM::ResetForNewInterface(int32 server_idx) {
    331   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: "
    332           << "server_idx: " << server_idx;
    333   unused_server_interface_list.push_back(server_idx);
    334 }
    335 
    336 void SpdySM::ResetForNewConnection() {
    337   // seq_num is not cleared, intentionally.
    338   delete spdy_framer_;
    339   spdy_framer_ = new SpdyFramer;
    340   spdy_framer_->set_visitor(this);
    341   valid_spdy_session_ = false;
    342   client_output_ordering_.Reset();
    343   next_outgoing_stream_id_ = 2;
    344 }
    345 
    346 // Send a settings frame
    347 int SpdySM::PostAcceptHook() {
    348   SpdySettings settings;
    349   SettingsFlagsAndId settings_id(SETTINGS_MAX_CONCURRENT_STREAMS);
    350   settings.push_back(SpdySetting(settings_id, 100));
    351   SpdySettingsControlFrame* settings_frame =
    352       spdy_framer_->CreateSettings(settings);
    353 
    354   VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame";
    355   EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame));
    356   return 1;
    357 }
    358 
    359 void SpdySM::NewStream(uint32 stream_id,
    360                        uint32 priority,
    361                        const std::string& filename) {
    362   MemCacheIter mci;
    363   mci.stream_id = stream_id;
    364   mci.priority = priority;
    365   if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
    366     if (!memory_cache_->AssignFileData(filename, &mci)) {
    367       // error creating new stream.
    368       VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
    369       SendErrorNotFound(stream_id);
    370     } else {
    371       AddToOutputOrder(mci);
    372     }
    373   } else {
    374     AddToOutputOrder(mci);
    375   }
    376 }
    377 
    378 void SpdySM::AddToOutputOrder(const MemCacheIter& mci) {
    379   client_output_ordering_.AddToOutputOrder(mci);
    380 }
    381 
    382 void SpdySM::SendEOF(uint32 stream_id) {
    383   SendEOFImpl(stream_id);
    384 }
    385 
    386 void SpdySM::SendErrorNotFound(uint32 stream_id) {
    387   SendErrorNotFoundImpl(stream_id);
    388 }
    389 
    390 void SpdySM::SendOKResponse(uint32 stream_id, std::string* output) {
    391   SendOKResponseImpl(stream_id, output);
    392 }
    393 
    394 size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
    395   return SendSynStreamImpl(stream_id, headers);
    396 }
    397 
    398 size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
    399   return SendSynReplyImpl(stream_id, headers);
    400 }
    401 
    402 void SpdySM::SendDataFrame(uint32 stream_id, const char* data, int64 len,
    403                    uint32 flags, bool compress) {
    404   SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags);
    405   SendDataFrameImpl(stream_id, data, len, spdy_flags, compress);
    406 }
    407 
    408 void SpdySM::SendEOFImpl(uint32 stream_id) {
    409   SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false);
    410   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id;
    411   KillStream(stream_id);
    412   stream_to_smif_.erase(stream_id);
    413 }
    414 
    415 void SpdySM::SendErrorNotFoundImpl(uint32 stream_id) {
    416   BalsaHeaders my_headers;
    417   my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
    418   SendSynReplyImpl(stream_id, my_headers);
    419   SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false);
    420   client_output_ordering_.RemoveStreamId(stream_id);
    421 }
    422 
    423 void SpdySM::SendOKResponseImpl(uint32 stream_id, std::string* output) {
    424   BalsaHeaders my_headers;
    425   my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
    426   SendSynReplyImpl(stream_id, my_headers);
    427   SendDataFrame(
    428       stream_id, output->c_str(), output->size(), DATA_FLAG_FIN, false);
    429   client_output_ordering_.RemoveStreamId(stream_id);
    430 }
    431 
    432 void SpdySM::KillStream(uint32 stream_id) {
    433   client_output_ordering_.RemoveStreamId(stream_id);
    434 }
    435 
    436 void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) {
    437   for (BalsaHeaders::const_header_lines_iterator hi =
    438        headers.header_lines_begin();
    439        hi != headers.header_lines_end();
    440        ++hi) {
    441     // It is illegal to send SPDY headers with empty value or header
    442     // names.
    443     if (!hi->first.length() || !hi->second.length())
    444       continue;
    445 
    446     SpdyHeaderBlock::iterator fhi = dest.find(hi->first.as_string());
    447     if (fhi == dest.end()) {
    448       dest[hi->first.as_string()] = hi->second.as_string();
    449     } else {
    450       dest[hi->first.as_string()] = (
    451           std::string(fhi->second.data(), fhi->second.size()) + "\0" +
    452           std::string(hi->second.data(), hi->second.size()));
    453     }
    454   }
    455 
    456   // These headers have no value
    457   dest.erase("X-Associated-Content");  // TODO(mbelshe): case-sensitive
    458   dest.erase("X-Original-Url");  // TODO(mbelshe): case-sensitive
    459 }
    460 
    461 size_t SpdySM::SendSynStreamImpl(uint32 stream_id,
    462                                  const BalsaHeaders& headers) {
    463   SpdyHeaderBlock block;
    464   block["method"] = headers.request_method().as_string();
    465   if (!headers.HasHeader("status"))
    466     block["status"] = headers.response_code().as_string();
    467   if (!headers.HasHeader("version"))
    468     block["version"] =headers.response_version().as_string();
    469   if (headers.HasHeader("X-Original-Url")) {
    470     std::string original_url = headers.GetHeader("X-Original-Url").as_string();
    471     block["path"] = UrlUtilities::GetUrlPath(original_url);
    472   } else {
    473     block["path"] = headers.request_uri().as_string();
    474   }
    475   CopyHeaders(block, headers);
    476 
    477   SpdySynStreamControlFrame* fsrcf =
    478     spdy_framer_->CreateSynStream(stream_id, 0, 0, CONTROL_FLAG_NONE, true,
    479                                   &block);
    480   size_t df_size = fsrcf->length() + SpdyFrame::size();
    481   EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
    482 
    483   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader "
    484           << stream_id;
    485   return df_size;
    486 }
    487 
    488 size_t SpdySM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
    489   SpdyHeaderBlock block;
    490   CopyHeaders(block, headers);
    491   block["status"] = headers.response_code().as_string() + " " +
    492                     headers.response_reason_phrase().as_string();
    493   block["version"] = headers.response_version().as_string();
    494 
    495   SpdySynReplyControlFrame* fsrcf =
    496     spdy_framer_->CreateSynReply(stream_id, CONTROL_FLAG_NONE, true, &block);
    497   size_t df_size = fsrcf->length() + SpdyFrame::size();
    498   EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
    499 
    500   VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader "
    501           << stream_id;
    502   return df_size;
    503 }
    504 
    505 void SpdySM::SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
    506                        SpdyDataFlags flags, bool compress) {
    507   // Force compression off if disabled via command line.
    508   if (disable_data_compression())
    509     flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_COMPRESSED);
    510 
    511   // TODO(mbelshe):  We can't compress here - before going into the
    512   //                 priority queue.  Compression needs to be done
    513   //                 with late binding.
    514   if (len == 0) {
    515     SpdyDataFrame* fdf = spdy_framer_->CreateDataFrame(stream_id, data, len,
    516                                                        flags);
    517     EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
    518     return;
    519   }
    520 
    521   // Chop data frames into chunks so that one stream can't monopolize the
    522   // output channel.
    523   while (len > 0) {
    524     int64 size = std::min(len, static_cast<int64>(kSpdySegmentSize));
    525     SpdyDataFlags chunk_flags = flags;
    526 
    527     // If we chunked this block, and the FIN flag was set, there is more
    528     // data coming.  So, remove the flag.
    529     if ((size < len) && (flags & DATA_FLAG_FIN))
    530       chunk_flags = static_cast<SpdyDataFlags>(chunk_flags & ~DATA_FLAG_FIN);
    531 
    532     SpdyDataFrame* fdf = spdy_framer_->CreateDataFrame(stream_id, data, size,
    533                                                        chunk_flags);
    534     EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
    535 
    536     VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame "
    537             << stream_id << " [" << size << "] shrunk to " << fdf->length()
    538             << ", flags=" << flags;
    539 
    540     data += size;
    541     len -= size;
    542   }
    543 }
    544 
    545 void SpdySM::EnqueueDataFrame(DataFrame* df) {
    546   connection_->EnqueueDataFrame(df);
    547 }
    548 
    549 void SpdySM::GetOutput() {
    550   while (client_output_list_->size() < 2) {
    551     MemCacheIter* mci = client_output_ordering_.GetIter();
    552     if (mci == NULL) {
    553       VLOG(2) << ACCEPTOR_CLIENT_IDENT
    554               << "SpdySM: GetOutput: nothing to output!?";
    555       return;
    556     }
    557     if (!mci->transformed_header) {
    558       mci->transformed_header = true;
    559       VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput transformed "
    560               << "header stream_id: [" << mci->stream_id << "]";
    561       if ((mci->stream_id % 2) == 0) {
    562         // this is a server initiated stream.
    563         // Ideally, we'd do a 'syn-push' here, instead of a syn-reply.
    564         BalsaHeaders headers;
    565         headers.CopyFrom(*(mci->file_data->headers));
    566         headers.ReplaceOrAppendHeader("status", "200");
    567         headers.ReplaceOrAppendHeader("version", "http/1.1");
    568         headers.SetRequestFirstlineFromStringPieces("PUSH",
    569                                                     mci->file_data->filename,
    570                                                     "");
    571         mci->bytes_sent = SendSynStream(mci->stream_id, headers);
    572       } else {
    573         BalsaHeaders headers;
    574         headers.CopyFrom(*(mci->file_data->headers));
    575         mci->bytes_sent = SendSynReply(mci->stream_id, headers);
    576       }
    577       return;
    578     }
    579     if (mci->body_bytes_consumed >= mci->file_data->body.size()) {
    580       VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput "
    581               << "remove_stream_id: [" << mci->stream_id << "]";
    582       SendEOF(mci->stream_id);
    583       return;
    584     }
    585     size_t num_to_write =
    586       mci->file_data->body.size() - mci->body_bytes_consumed;
    587     if (num_to_write > mci->max_segment_size)
    588       num_to_write = mci->max_segment_size;
    589 
    590     bool should_compress = false;
    591     if (!mci->file_data->headers->HasHeader("content-encoding")) {
    592       if (mci->file_data->headers->HasHeader("content-type")) {
    593         std::string content_type =
    594             mci->file_data->headers->GetHeader("content-type").as_string();
    595         if (content_type.find("image") == content_type.npos)
    596           should_compress = true;
    597       }
    598     }
    599 
    600     SendDataFrame(mci->stream_id,
    601                   mci->file_data->body.data() + mci->body_bytes_consumed,
    602                   num_to_write, 0, should_compress);
    603     VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame["
    604             << mci->stream_id << "]: " << num_to_write;
    605     mci->body_bytes_consumed += num_to_write;
    606     mci->bytes_sent += num_to_write;
    607   }
    608 }
    609 
    610 }  // namespace net
    611 
    612