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
16using spdy::kSpdyStreamMaximumWindowSize;
17using spdy::CONTROL_FLAG_NONE;
18using spdy::DATA_FLAG_COMPRESSED;
19using spdy::DATA_FLAG_FIN;
20using spdy::RST_STREAM;
21using spdy::SETTINGS_MAX_CONCURRENT_STREAMS;
22using spdy::SYN_REPLY;
23using spdy::SYN_STREAM;
24using spdy::SettingsFlagsAndId;
25using spdy::SpdyControlFrame;
26using spdy::SpdySettingsControlFrame;
27using spdy::SpdyDataFlags;
28using spdy::SpdyDataFrame;
29using spdy::SpdyRstStreamControlFrame;
30using spdy::SpdyFrame;
31using spdy::SpdyFramer;
32using spdy::SpdyFramerVisitorInterface;
33using spdy::SpdyHeaderBlock;
34using spdy::SpdySetting;
35using spdy::SpdySettings;
36using spdy::SpdyStreamId;
37using spdy::SpdySynReplyControlFrame;
38using spdy::SpdySynStreamControlFrame;
39
40namespace net {
41
42// static
43bool SpdySM::disable_data_compression_ = true;
44// static
45std::string SpdySM::forward_ip_header_;
46
47class 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
62SpdySM::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
81SpdySM::~SpdySM() {
82  delete spdy_framer_;
83}
84
85void 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
100SMInterface* 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
120SMInterface* 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
147int 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
220void 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
281bool SpdySM::OnControlFrameHeaderData(spdy::SpdyStreamId stream_id,
282                                      const char* header_data,
283                                      size_t len) {
284  DCHECK(false);
285  return false;
286}
287
288void SpdySM::OnDataFrameHeader(const spdy::SpdyDataFrame* frame) {
289  DCHECK(false);
290}
291
292void 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
309size_t SpdySM::ProcessReadInput(const char* data, size_t len) {
310  return spdy_framer_->ProcessInput(data, len);
311}
312
313size_t SpdySM::ProcessWriteInput(const char* data, size_t len) {
314  return 0;
315}
316
317bool SpdySM::MessageFullyRead() const {
318  return spdy_framer_->MessageFullyRead();
319}
320
321bool SpdySM::Error() const {
322  return close_on_error_ || spdy_framer_->HasError();
323}
324
325const char* SpdySM::ErrorAsString() const {
326  DCHECK(Error());
327  return SpdyFramer::ErrorCodeToString(spdy_framer_->error_code());
328}
329
330void 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
336void 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
347int 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
359void 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
378void SpdySM::AddToOutputOrder(const MemCacheIter& mci) {
379  client_output_ordering_.AddToOutputOrder(mci);
380}
381
382void SpdySM::SendEOF(uint32 stream_id) {
383  SendEOFImpl(stream_id);
384}
385
386void SpdySM::SendErrorNotFound(uint32 stream_id) {
387  SendErrorNotFoundImpl(stream_id);
388}
389
390void SpdySM::SendOKResponse(uint32 stream_id, std::string* output) {
391  SendOKResponseImpl(stream_id, output);
392}
393
394size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
395  return SendSynStreamImpl(stream_id, headers);
396}
397
398size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
399  return SendSynReplyImpl(stream_id, headers);
400}
401
402void 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
408void 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
415void 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
423void 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
432void SpdySM::KillStream(uint32 stream_id) {
433  client_output_ordering_.RemoveStreamId(stream_id);
434}
435
436void 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
461size_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
488size_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
505void 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
545void SpdySM::EnqueueDataFrame(DataFrame* df) {
546  connection_->EnqueueDataFrame(df);
547}
548
549void 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