spdy_interface.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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::const_iterator method = headers.end(); 134 SpdyHeaderBlock::const_iterator host = headers.end(); 135 SpdyHeaderBlock::const_iterator path = headers.end(); 136 SpdyHeaderBlock::const_iterator scheme = headers.end(); 137 SpdyHeaderBlock::const_iterator version = headers.end(); 138 SpdyHeaderBlock::const_iterator url = headers.end(); 139 140 std::string path_string, host_string, version_string; 141 142 if (spdy_version() == SPDY2) { 143 url = headers.find("url"); 144 method = headers.find("method"); 145 version = headers.find("version"); 146 scheme = headers.find("scheme"); 147 if (url == headers.end() || method == headers.end() || 148 version == headers.end() || scheme == headers.end()) { 149 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: A mandatory header is " 150 << "missing. Not creating stream"; 151 return 0; 152 } 153 // url->second here only ever seems to contain just the path. When this 154 // path contains a query string with a http:// in one of its values, 155 // UrlUtilities::GetUrlPath will fail and always return a / breaking 156 // the request. GetUrlPath assumes the absolute URL is being passed in. 157 path_string = UrlUtilities::GetUrlPath(url->second); 158 host_string = UrlUtilities::GetUrlHost(url->second); 159 version_string = version->second; 160 } else { 161 method = headers.find(":method"); 162 host = headers.find(":host"); 163 path = headers.find(":path"); 164 scheme = headers.find(":scheme"); 165 if (method == headers.end() || host == headers.end() || 166 path == headers.end() || scheme == headers.end()) { 167 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: A mandatory header is " 168 << "missing. Not creating stream"; 169 return 0; 170 } 171 host_string = host->second; 172 path_string = path->second; 173 version_string = "HTTP/1.1"; 174 } 175 176 if (scheme->second.compare("https") == 0) { 177 *is_https_scheme = true; 178 } 179 180 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) { 181 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second 182 << " " << path_string; 183 std::string filename = EncodeURL(path_string, 184 host_string, 185 method->second); 186 NewStream(stream_id, priority, filename); 187 } else { 188 http_data += 189 method->second + " " + path_string + " " + version_string + "\r\n"; 190 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " " 191 << path_string << " " << version_string; 192 http_data += "Host: " + (*is_https_scheme ? 193 acceptor_->https_server_ip_ : 194 acceptor_->http_server_ip_) + "\r\n"; 195 for (SpdyHeaderBlock::const_iterator i = headers.begin(); 196 i != headers.end(); ++i) { 197 if ((i->first.size() > 0 && i->first[0] == ':') || 198 i->first == "host" || 199 i == method || 200 i == host || 201 i == path || 202 i == scheme || 203 i == version || 204 i == url) { 205 // Ignore the entry. 206 } else { 207 http_data += i->first + ": " + i->second + "\r\n"; 208 VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first.c_str() << ":" 209 << i->second.c_str(); 210 } 211 } 212 if (forward_ip_header_.length()) { 213 // X-Client-Cluster-IP header 214 http_data += forward_ip_header_ + ": " + 215 connection_->client_ip() + "\r\n"; 216 } 217 http_data += "\r\n"; 218 } 219 220 VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data; 221 return 1; 222} 223 224void SpdySM::OnStreamFrameData(SpdyStreamId stream_id, 225 const char* data, 226 size_t len, 227 bool fin) { 228 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id 229 << ", [" << len << "])"; 230 StreamToSmif::iterator it = stream_to_smif_.find(stream_id); 231 if (it == stream_to_smif_.end()) { 232 VLOG(2) << "Dropping frame from unknown stream " << stream_id; 233 if (!valid_spdy_session_) 234 close_on_error_ = true; 235 return; 236 } 237 238 SMInterface* interface = it->second; 239 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) 240 interface->ProcessWriteInput(data, len); 241} 242 243void SpdySM::OnSynStream(SpdyStreamId stream_id, 244 SpdyStreamId associated_stream_id, 245 SpdyPriority priority, 246 bool fin, 247 bool unidirectional, 248 const SpdyHeaderBlock& headers) { 249 std::string http_data; 250 bool is_https_scheme; 251 int ret = SpdyHandleNewStream( 252 stream_id, priority, headers, http_data, &is_https_scheme); 253 if (!ret) { 254 LOG(ERROR) << "SpdySM: Could not convert spdy into http."; 255 return; 256 } 257 // We've seen a valid looking SYN_STREAM, consider this to have 258 // been a real spdy session. 259 valid_spdy_session_ = true; 260 261 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) { 262 std::string server_ip; 263 std::string server_port; 264 if (is_https_scheme) { 265 server_ip = acceptor_->https_server_ip_; 266 server_port = acceptor_->https_server_port_; 267 } else { 268 server_ip = acceptor_->http_server_ip_; 269 server_port = acceptor_->http_server_port_; 270 } 271 SMInterface* sm_http_interface = 272 FindOrMakeNewSMConnectionInterface(server_ip, server_port); 273 stream_to_smif_[stream_id] = sm_http_interface; 274 sm_http_interface->SetStreamID(stream_id); 275 sm_http_interface->ProcessWriteInput(http_data.c_str(), http_data.size()); 276 } 277} 278 279void SpdySM::OnSynReply(SpdyStreamId stream_id, 280 bool fin, 281 const SpdyHeaderBlock& headers) { 282 // TODO(willchan): if there is an error parsing headers, we 283 // should send a RST_STREAM. 284 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSynReply(" << stream_id << ")"; 285} 286 287void SpdySM::OnHeaders(SpdyStreamId stream_id, 288 bool fin, 289 const SpdyHeaderBlock& headers) { 290 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnHeaders(" << stream_id << ")"; 291} 292 293void SpdySM::OnRstStream(SpdyStreamId stream_id, SpdyRstStreamStatus status) { 294 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRstStream(" << stream_id 295 << ")"; 296 client_output_ordering_.RemoveStreamId(stream_id); 297} 298 299size_t SpdySM::ProcessReadInput(const char* data, size_t len) { 300 return buffered_spdy_framer_->ProcessInput(data, len); 301} 302 303size_t SpdySM::ProcessWriteInput(const char* data, size_t len) { return 0; } 304 305bool SpdySM::MessageFullyRead() const { 306 return buffered_spdy_framer_->MessageFullyRead(); 307} 308 309bool SpdySM::Error() const { 310 return close_on_error_ || buffered_spdy_framer_->HasError(); 311} 312 313const char* SpdySM::ErrorAsString() const { 314 DCHECK(Error()); 315 return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_->error_code()); 316} 317 318void SpdySM::ResetForNewInterface(int32 server_idx) { 319 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: " 320 << "server_idx: " << server_idx; 321 unused_server_interface_list.push_back(server_idx); 322} 323 324void SpdySM::ResetForNewConnection() { 325 // seq_num is not cleared, intentionally. 326 delete buffered_spdy_framer_; 327 buffered_spdy_framer_ = new BufferedSpdyFramer(SPDY2, true); 328 buffered_spdy_framer_->set_visitor(this); 329 valid_spdy_session_ = false; 330 client_output_ordering_.Reset(); 331 next_outgoing_stream_id_ = 2; 332} 333 334// Send a settings frame 335int SpdySM::PostAcceptHook() { 336 SettingsMap settings; 337 settings[SETTINGS_MAX_CONCURRENT_STREAMS] = 338 SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 100); 339 SpdyFrame* settings_frame = buffered_spdy_framer_->CreateSettings(settings); 340 341 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame"; 342 EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame)); 343 return 1; 344} 345 346void SpdySM::NewStream(uint32 stream_id, 347 uint32 priority, 348 const std::string& filename) { 349 MemCacheIter mci; 350 mci.stream_id = stream_id; 351 mci.priority = priority; 352 // TODO(yhirano): The program will crash when 353 // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER. 354 // It should be fixed or an assertion should be placed. 355 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) { 356 if (!memory_cache_->AssignFileData(filename, &mci)) { 357 // error creating new stream. 358 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound"; 359 SendErrorNotFound(stream_id); 360 } else { 361 AddToOutputOrder(mci); 362 } 363 } else { 364 AddToOutputOrder(mci); 365 } 366} 367 368void SpdySM::AddToOutputOrder(const MemCacheIter& mci) { 369 client_output_ordering_.AddToOutputOrder(mci); 370} 371 372void SpdySM::SendEOF(uint32 stream_id) { SendEOFImpl(stream_id); } 373 374void SpdySM::SendErrorNotFound(uint32 stream_id) { 375 SendErrorNotFoundImpl(stream_id); 376} 377 378size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) { 379 return SendSynStreamImpl(stream_id, headers); 380} 381 382size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) { 383 return SendSynReplyImpl(stream_id, headers); 384} 385 386void SpdySM::SendDataFrame(uint32 stream_id, 387 const char* data, 388 int64 len, 389 uint32 flags, 390 bool compress) { 391 SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags); 392 SendDataFrameImpl(stream_id, data, len, spdy_flags, compress); 393} 394 395void SpdySM::SendEOFImpl(uint32 stream_id) { 396 SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false); 397 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id; 398 KillStream(stream_id); 399 stream_to_smif_.erase(stream_id); 400} 401 402void SpdySM::SendErrorNotFoundImpl(uint32 stream_id) { 403 BalsaHeaders my_headers; 404 my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found"); 405 SendSynReplyImpl(stream_id, my_headers); 406 SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false); 407 client_output_ordering_.RemoveStreamId(stream_id); 408} 409 410void SpdySM::KillStream(uint32 stream_id) { 411 client_output_ordering_.RemoveStreamId(stream_id); 412} 413 414void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) { 415 for (BalsaHeaders::const_header_lines_iterator hi = 416 headers.header_lines_begin(); 417 hi != headers.header_lines_end(); 418 ++hi) { 419 // It is illegal to send SPDY headers with empty value or header 420 // names. 421 if (!hi->first.length() || !hi->second.length()) 422 continue; 423 424 // Key must be all lower case in SPDY headers. 425 std::string key = hi->first.as_string(); 426 std::transform(key.begin(), key.end(), key.begin(), ::tolower); 427 SpdyHeaderBlock::iterator fhi = dest.find(key); 428 if (fhi == dest.end()) { 429 dest[key] = hi->second.as_string(); 430 } else { 431 dest[key] = (std::string(fhi->second.data(), fhi->second.size()) + "\0" + 432 std::string(hi->second.data(), hi->second.size())); 433 } 434 } 435 436 // These headers have no value 437 dest.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive 438 dest.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive 439} 440 441size_t SpdySM::SendSynStreamImpl(uint32 stream_id, 442 const BalsaHeaders& headers) { 443 SpdyHeaderBlock block; 444 CopyHeaders(block, headers); 445 if (spdy_version() == SPDY2) { 446 block["method"] = headers.request_method().as_string(); 447 if (!headers.HasHeader("version")) 448 block["version"] = headers.request_version().as_string(); 449 if (headers.HasHeader("X-Original-Url")) { 450 std::string original_url = 451 headers.GetHeader("X-Original-Url").as_string(); 452 block["url"] = UrlUtilities::GetUrlPath(original_url); 453 } else { 454 block["url"] = headers.request_uri().as_string(); 455 } 456 } else { 457 block[":method"] = headers.request_method().as_string(); 458 block[":version"] = headers.request_version().as_string(); 459 if (headers.HasHeader("X-Original-Url")) { 460 std::string original_url = 461 headers.GetHeader("X-Original-Url").as_string(); 462 block[":path"] = UrlUtilities::GetUrlPath(original_url); 463 block[":host"] = UrlUtilities::GetUrlPath(original_url); 464 } else { 465 block[":path"] = headers.request_uri().as_string(); 466 if (block.find("host") != block.end()) { 467 block[":host"] = headers.GetHeader("Host").as_string(); 468 block.erase("host"); 469 } 470 } 471 } 472 473 SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynStream( 474 stream_id, 0, 0, CONTROL_FLAG_NONE, &block); 475 size_t df_size = fsrcf->size(); 476 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf)); 477 478 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader " 479 << stream_id; 480 return df_size; 481} 482 483size_t SpdySM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) { 484 SpdyHeaderBlock block; 485 CopyHeaders(block, headers); 486 if (spdy_version() == SPDY2) { 487 block["status"] = headers.response_code().as_string() + " " + 488 headers.response_reason_phrase().as_string(); 489 block["version"] = headers.response_version().as_string(); 490 } else { 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 496 SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynReply( 497 stream_id, CONTROL_FLAG_NONE, &block); 498 size_t df_size = fsrcf->size(); 499 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf)); 500 501 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader " 502 << stream_id; 503 return df_size; 504} 505 506void SpdySM::SendDataFrameImpl(uint32 stream_id, 507 const char* data, 508 int64 len, 509 SpdyDataFlags flags, 510 bool compress) { 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 SpdyFrame* fdf = 516 buffered_spdy_framer_->CreateDataFrame(stream_id, data, len, 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 SpdyFrame* fdf = buffered_spdy_framer_->CreateDataFrame( 533 stream_id, data, size, chunk_flags); 534 EnqueueDataFrame(new SpdyFrameDataFrame(fdf)); 535 536 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame " 537 << stream_id << " [" << size << "] shrunk to " 538 << (fdf->size() - kSpdyOverhead) << ", 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( 569 "PUSH", mci->file_data->filename(), ""); 570 mci->bytes_sent = SendSynStream(mci->stream_id, headers); 571 } else { 572 BalsaHeaders headers; 573 headers.CopyFrom(*(mci->file_data->headers())); 574 mci->bytes_sent = SendSynReply(mci->stream_id, headers); 575 } 576 return; 577 } 578 if (mci->body_bytes_consumed >= mci->file_data->body().size()) { 579 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput " 580 << "remove_stream_id: [" << mci->stream_id << "]"; 581 SendEOF(mci->stream_id); 582 return; 583 } 584 size_t num_to_write = 585 mci->file_data->body().size() - mci->body_bytes_consumed; 586 if (num_to_write > mci->max_segment_size) 587 num_to_write = mci->max_segment_size; 588 589 bool should_compress = false; 590 if (!mci->file_data->headers()->HasHeader("content-encoding")) { 591 if (mci->file_data->headers()->HasHeader("content-type")) { 592 std::string content_type = 593 mci->file_data->headers()->GetHeader("content-type").as_string(); 594 if (content_type.find("image") == content_type.npos) 595 should_compress = true; 596 } 597 } 598 599 SendDataFrame(mci->stream_id, 600 mci->file_data->body().data() + mci->body_bytes_consumed, 601 num_to_write, 602 0, 603 should_compress); 604 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame[" 605 << mci->stream_id << "]: " << num_to_write; 606 mci->body_bytes_consumed += num_to_write; 607 mci->bytes_sent += num_to_write; 608 } 609} 610 611} // namespace net 612