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