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 18 namespace net { 19 20 // static 21 std::string SpdySM::forward_ip_header_; 22 23 class 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 35 SpdySM::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 54 SpdySM::~SpdySM() { delete buffered_spdy_framer_; } 55 56 void 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 75 SMInterface* 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 92 SMInterface* 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 124 int 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 222 void 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 241 void 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 278 void 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 286 void SpdySM::OnHeaders(SpdyStreamId stream_id, 287 bool fin, 288 const SpdyHeaderBlock& headers) { 289 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnHeaders(" << stream_id << ")"; 290 } 291 292 void 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 298 size_t SpdySM::ProcessReadInput(const char* data, size_t len) { 299 return buffered_spdy_framer_->ProcessInput(data, len); 300 } 301 302 size_t SpdySM::ProcessWriteInput(const char* data, size_t len) { return 0; } 303 304 bool SpdySM::MessageFullyRead() const { 305 return buffered_spdy_framer_->MessageFullyRead(); 306 } 307 308 bool SpdySM::Error() const { 309 return close_on_error_ || buffered_spdy_framer_->HasError(); 310 } 311 312 const char* SpdySM::ErrorAsString() const { 313 DCHECK(Error()); 314 return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_->error_code()); 315 } 316 317 void 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 323 void 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 334 int 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 345 void 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 367 void SpdySM::AddToOutputOrder(const MemCacheIter& mci) { 368 client_output_ordering_.AddToOutputOrder(mci); 369 } 370 371 void SpdySM::SendEOF(uint32 stream_id) { SendEOFImpl(stream_id); } 372 373 void SpdySM::SendErrorNotFound(uint32 stream_id) { 374 SendErrorNotFoundImpl(stream_id); 375 } 376 377 size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) { 378 return SendSynStreamImpl(stream_id, headers); 379 } 380 381 size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) { 382 return SendSynReplyImpl(stream_id, headers); 383 } 384 385 void 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 394 void 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 401 void 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 409 void SpdySM::KillStream(uint32 stream_id) { 410 client_output_ordering_.RemoveStreamId(stream_id); 411 } 412 413 void 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 440 size_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 482 size_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 505 void 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 544 void SpdySM::EnqueueDataFrame(DataFrame* df) { 545 connection_->EnqueueDataFrame(df); 546 } 547 548 void 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