| // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/tools/flip_server/spdy_interface.h" |
| |
| #include <string> |
| |
| #include "net/spdy/spdy_framer.h" |
| #include "net/spdy/spdy_protocol.h" |
| #include "net/tools/dump_cache/url_utilities.h" |
| #include "net/tools/flip_server/flip_config.h" |
| #include "net/tools/flip_server/http_interface.h" |
| #include "net/tools/flip_server/spdy_util.h" |
| |
| using spdy::kSpdyStreamMaximumWindowSize; |
| using spdy::CONTROL_FLAG_NONE; |
| using spdy::DATA_FLAG_COMPRESSED; |
| using spdy::DATA_FLAG_FIN; |
| using spdy::RST_STREAM; |
| using spdy::SETTINGS_MAX_CONCURRENT_STREAMS; |
| using spdy::SYN_REPLY; |
| using spdy::SYN_STREAM; |
| using spdy::SettingsFlagsAndId; |
| using spdy::SpdyControlFrame; |
| using spdy::SpdySettingsControlFrame; |
| using spdy::SpdyDataFlags; |
| using spdy::SpdyDataFrame; |
| using spdy::SpdyRstStreamControlFrame; |
| using spdy::SpdyFrame; |
| using spdy::SpdyFramer; |
| using spdy::SpdyFramerVisitorInterface; |
| using spdy::SpdyHeaderBlock; |
| using spdy::SpdySetting; |
| using spdy::SpdySettings; |
| using spdy::SpdyStreamId; |
| using spdy::SpdySynReplyControlFrame; |
| using spdy::SpdySynStreamControlFrame; |
| |
| namespace net { |
| |
| // static |
| bool SpdySM::disable_data_compression_ = true; |
| // static |
| std::string SpdySM::forward_ip_header_; |
| |
| class SpdyFrameDataFrame : public DataFrame { |
| public: |
| SpdyFrameDataFrame(SpdyFrame* spdy_frame) |
| : frame(spdy_frame) { |
| data = spdy_frame->data(); |
| size = spdy_frame->length() + SpdyFrame::size(); |
| } |
| |
| virtual ~SpdyFrameDataFrame() { |
| delete frame; |
| } |
| |
| const SpdyFrame* frame; |
| }; |
| |
| SpdySM::SpdySM(SMConnection* connection, |
| SMInterface* sm_http_interface, |
| EpollServer* epoll_server, |
| MemoryCache* memory_cache, |
| FlipAcceptor* acceptor) |
| : seq_num_(0), |
| spdy_framer_(new SpdyFramer), |
| valid_spdy_session_(false), |
| connection_(connection), |
| client_output_list_(connection->output_list()), |
| client_output_ordering_(connection), |
| next_outgoing_stream_id_(2), |
| epoll_server_(epoll_server), |
| acceptor_(acceptor), |
| memory_cache_(memory_cache), |
| close_on_error_(false) { |
| spdy_framer_->set_visitor(this); |
| } |
| |
| SpdySM::~SpdySM() { |
| delete spdy_framer_; |
| } |
| |
| void SpdySM::InitSMConnection(SMConnectionPoolInterface* connection_pool, |
| SMInterface* sm_interface, |
| EpollServer* epoll_server, |
| int fd, |
| std::string server_ip, |
| std::string server_port, |
| std::string remote_ip, |
| bool use_ssl) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT |
| << "SpdySM: Initializing server connection."; |
| connection_->InitSMConnection(connection_pool, sm_interface, |
| epoll_server, fd, server_ip, server_port, |
| remote_ip, use_ssl); |
| } |
| |
| SMInterface* SpdySM::NewConnectionInterface() { |
| SMConnection* server_connection = |
| SMConnection::NewSMConnection(epoll_server_, |
| NULL, |
| memory_cache_, |
| acceptor_, |
| "http_conn: "); |
| if (server_connection == NULL) { |
| LOG(ERROR) << "SpdySM: Could not create server connection"; |
| return NULL; |
| } |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Creating new HTTP interface"; |
| SMInterface *sm_http_interface = new HttpSM(server_connection, |
| this, |
| epoll_server_, |
| memory_cache_, |
| acceptor_); |
| return sm_http_interface; |
| } |
| |
| SMInterface* SpdySM::FindOrMakeNewSMConnectionInterface( |
| std::string server_ip, std::string server_port) { |
| SMInterface *sm_http_interface; |
| int32 server_idx; |
| if (unused_server_interface_list.empty()) { |
| sm_http_interface = NewConnectionInterface(); |
| server_idx = server_interface_list.size(); |
| server_interface_list.push_back(sm_http_interface); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT |
| << "SpdySM: Making new server connection on index: " |
| << server_idx; |
| } else { |
| server_idx = unused_server_interface_list.back(); |
| unused_server_interface_list.pop_back(); |
| sm_http_interface = server_interface_list.at(server_idx); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reusing connection on " |
| << "index: " << server_idx; |
| } |
| |
| sm_http_interface->InitSMInterface(this, server_idx); |
| sm_http_interface->InitSMConnection(NULL, sm_http_interface, |
| epoll_server_, -1, |
| server_ip, server_port, "", false); |
| |
| return sm_http_interface; |
| } |
| |
| int SpdySM::SpdyHandleNewStream(const SpdyControlFrame* frame, |
| std::string &http_data, |
| bool *is_https_scheme) { |
| bool parsed_headers = false; |
| SpdyHeaderBlock headers; |
| const SpdySynStreamControlFrame* syn_stream = |
| reinterpret_cast<const SpdySynStreamControlFrame*>(frame); |
| |
| *is_https_scheme = false; |
| parsed_headers = spdy_framer_->ParseHeaderBlock(frame, &headers); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSyn(" |
| << syn_stream->stream_id() << ")"; |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: headers parsed?: " |
| << (parsed_headers? "yes": "no"); |
| if (parsed_headers) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: # headers: " |
| << headers.size(); |
| } |
| SpdyHeaderBlock::iterator url = headers.find("url"); |
| SpdyHeaderBlock::iterator method = headers.find("method"); |
| if (url == headers.end() || method == headers.end()) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: didn't find method or url " |
| << "or method. Not creating stream"; |
| return 0; |
| } |
| |
| SpdyHeaderBlock::iterator scheme = headers.find("scheme"); |
| if (scheme->second.compare("https") == 0) { |
| *is_https_scheme = true; |
| } |
| |
| // url->second here only ever seems to contain just the path. When this |
| // path contains a query string with a http:// in one of its values, |
| // UrlUtilities::GetUrlPath will fail and always return a / breaking |
| // the request. GetUrlPath assumes the absolute URL is being passed in. |
| std::string uri; |
| if (url->second.compare(0,4,"http") == 0) |
| uri = UrlUtilities::GetUrlPath(url->second); |
| else |
| uri = std::string(url->second); |
| if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) { |
| SpdyHeaderBlock::iterator referer = headers.find("referer"); |
| std::string host = UrlUtilities::GetUrlHost(url->second); |
| VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second |
| << " " << uri; |
| std::string filename = EncodeURL(uri, host, method->second); |
| NewStream(syn_stream->stream_id(), |
| reinterpret_cast<const SpdySynStreamControlFrame*> |
| (frame)->priority(), |
| filename); |
| } else { |
| SpdyHeaderBlock::iterator version = headers.find("version"); |
| http_data += method->second + " " + uri + " " + version->second + "\r\n"; |
| VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " " |
| << uri << " " << version->second; |
| for (SpdyHeaderBlock::iterator i = headers.begin(); |
| i != headers.end(); ++i) { |
| http_data += i->first + ": " + i->second + "\r\n"; |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first.c_str() << ":" |
| << i->second.c_str(); |
| } |
| if (forward_ip_header_.length()) { |
| // X-Client-Cluster-IP header |
| http_data += forward_ip_header_ + ": " + |
| connection_->client_ip() + "\r\n"; |
| } |
| http_data += "\r\n"; |
| } |
| |
| VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data; |
| return 1; |
| } |
| |
| void SpdySM::OnControl(const SpdyControlFrame* frame) { |
| SpdyHeaderBlock headers; |
| bool parsed_headers = false; |
| switch (frame->type()) { |
| case SYN_STREAM: |
| { |
| const SpdySynStreamControlFrame* syn_stream = |
| reinterpret_cast<const SpdySynStreamControlFrame*>(frame); |
| |
| std::string http_data; |
| bool is_https_scheme; |
| int ret = SpdyHandleNewStream(frame, http_data, &is_https_scheme); |
| if (!ret) { |
| LOG(ERROR) << "SpdySM: Could not convert spdy into http."; |
| break; |
| } |
| // We've seen a valid looking SYN_STREAM, consider this to have |
| // been a real spdy session. |
| valid_spdy_session_ = true; |
| |
| if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) { |
| std::string server_ip; |
| std::string server_port; |
| if (is_https_scheme) { |
| server_ip = acceptor_->https_server_ip_; |
| server_port = acceptor_->https_server_port_; |
| } else { |
| server_ip = acceptor_->http_server_ip_; |
| server_port = acceptor_->http_server_port_; |
| } |
| SMInterface *sm_http_interface = |
| FindOrMakeNewSMConnectionInterface(server_ip, server_port); |
| stream_to_smif_[syn_stream->stream_id()] = sm_http_interface; |
| sm_http_interface->SetStreamID(syn_stream->stream_id()); |
| sm_http_interface->ProcessWriteInput(http_data.c_str(), |
| http_data.size()); |
| } |
| } |
| break; |
| |
| case SYN_REPLY: |
| parsed_headers = spdy_framer_->ParseHeaderBlock(frame, &headers); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSynReply(" << |
| reinterpret_cast<const SpdySynReplyControlFrame*>(frame)->stream_id() |
| << ")"; |
| break; |
| case RST_STREAM: |
| { |
| const SpdyRstStreamControlFrame* rst_stream = |
| reinterpret_cast<const SpdyRstStreamControlFrame*>(frame); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRst(" |
| << rst_stream->stream_id() << ")"; |
| client_output_ordering_.RemoveStreamId(rst_stream ->stream_id()); |
| } |
| break; |
| |
| default: |
| LOG(ERROR) << "SpdySM: Unknown control frame type"; |
| } |
| } |
| |
| bool SpdySM::OnControlFrameHeaderData(spdy::SpdyStreamId stream_id, |
| const char* header_data, |
| size_t len) { |
| DCHECK(false); |
| return false; |
| } |
| |
| void SpdySM::OnDataFrameHeader(const spdy::SpdyDataFrame* frame) { |
| DCHECK(false); |
| } |
| |
| void SpdySM::OnStreamFrameData(SpdyStreamId stream_id, |
| const char* data, size_t len) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id |
| << ", [" << len << "])"; |
| StreamToSmif::iterator it = stream_to_smif_.find(stream_id); |
| if (it == stream_to_smif_.end()) { |
| VLOG(2) << "Dropping frame from unknown stream " << stream_id; |
| if (!valid_spdy_session_) |
| close_on_error_ = true; |
| return; |
| } |
| |
| SMInterface* interface = it->second; |
| if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) |
| interface->ProcessWriteInput(data, len); |
| } |
| |
| size_t SpdySM::ProcessReadInput(const char* data, size_t len) { |
| return spdy_framer_->ProcessInput(data, len); |
| } |
| |
| size_t SpdySM::ProcessWriteInput(const char* data, size_t len) { |
| return 0; |
| } |
| |
| bool SpdySM::MessageFullyRead() const { |
| return spdy_framer_->MessageFullyRead(); |
| } |
| |
| bool SpdySM::Error() const { |
| return close_on_error_ || spdy_framer_->HasError(); |
| } |
| |
| const char* SpdySM::ErrorAsString() const { |
| DCHECK(Error()); |
| return SpdyFramer::ErrorCodeToString(spdy_framer_->error_code()); |
| } |
| |
| void SpdySM::ResetForNewInterface(int32 server_idx) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: " |
| << "server_idx: " << server_idx; |
| unused_server_interface_list.push_back(server_idx); |
| } |
| |
| void SpdySM::ResetForNewConnection() { |
| // seq_num is not cleared, intentionally. |
| delete spdy_framer_; |
| spdy_framer_ = new SpdyFramer; |
| spdy_framer_->set_visitor(this); |
| valid_spdy_session_ = false; |
| client_output_ordering_.Reset(); |
| next_outgoing_stream_id_ = 2; |
| } |
| |
| // Send a settings frame |
| int SpdySM::PostAcceptHook() { |
| SpdySettings settings; |
| SettingsFlagsAndId settings_id(SETTINGS_MAX_CONCURRENT_STREAMS); |
| settings.push_back(SpdySetting(settings_id, 100)); |
| SpdySettingsControlFrame* settings_frame = |
| spdy_framer_->CreateSettings(settings); |
| |
| VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame"; |
| EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame)); |
| return 1; |
| } |
| |
| void SpdySM::NewStream(uint32 stream_id, |
| uint32 priority, |
| const std::string& filename) { |
| MemCacheIter mci; |
| mci.stream_id = stream_id; |
| mci.priority = priority; |
| if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) { |
| if (!memory_cache_->AssignFileData(filename, &mci)) { |
| // error creating new stream. |
| VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound"; |
| SendErrorNotFound(stream_id); |
| } else { |
| AddToOutputOrder(mci); |
| } |
| } else { |
| AddToOutputOrder(mci); |
| } |
| } |
| |
| void SpdySM::AddToOutputOrder(const MemCacheIter& mci) { |
| client_output_ordering_.AddToOutputOrder(mci); |
| } |
| |
| void SpdySM::SendEOF(uint32 stream_id) { |
| SendEOFImpl(stream_id); |
| } |
| |
| void SpdySM::SendErrorNotFound(uint32 stream_id) { |
| SendErrorNotFoundImpl(stream_id); |
| } |
| |
| void SpdySM::SendOKResponse(uint32 stream_id, std::string* output) { |
| SendOKResponseImpl(stream_id, output); |
| } |
| |
| size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) { |
| return SendSynStreamImpl(stream_id, headers); |
| } |
| |
| size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) { |
| return SendSynReplyImpl(stream_id, headers); |
| } |
| |
| void SpdySM::SendDataFrame(uint32 stream_id, const char* data, int64 len, |
| uint32 flags, bool compress) { |
| SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags); |
| SendDataFrameImpl(stream_id, data, len, spdy_flags, compress); |
| } |
| |
| void SpdySM::SendEOFImpl(uint32 stream_id) { |
| SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id; |
| KillStream(stream_id); |
| stream_to_smif_.erase(stream_id); |
| } |
| |
| void SpdySM::SendErrorNotFoundImpl(uint32 stream_id) { |
| BalsaHeaders my_headers; |
| my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found"); |
| SendSynReplyImpl(stream_id, my_headers); |
| SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false); |
| client_output_ordering_.RemoveStreamId(stream_id); |
| } |
| |
| void SpdySM::SendOKResponseImpl(uint32 stream_id, std::string* output) { |
| BalsaHeaders my_headers; |
| my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "200", "OK"); |
| SendSynReplyImpl(stream_id, my_headers); |
| SendDataFrame( |
| stream_id, output->c_str(), output->size(), DATA_FLAG_FIN, false); |
| client_output_ordering_.RemoveStreamId(stream_id); |
| } |
| |
| void SpdySM::KillStream(uint32 stream_id) { |
| client_output_ordering_.RemoveStreamId(stream_id); |
| } |
| |
| void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) { |
| for (BalsaHeaders::const_header_lines_iterator hi = |
| headers.header_lines_begin(); |
| hi != headers.header_lines_end(); |
| ++hi) { |
| // It is illegal to send SPDY headers with empty value or header |
| // names. |
| if (!hi->first.length() || !hi->second.length()) |
| continue; |
| |
| SpdyHeaderBlock::iterator fhi = dest.find(hi->first.as_string()); |
| if (fhi == dest.end()) { |
| dest[hi->first.as_string()] = hi->second.as_string(); |
| } else { |
| dest[hi->first.as_string()] = ( |
| std::string(fhi->second.data(), fhi->second.size()) + "\0" + |
| std::string(hi->second.data(), hi->second.size())); |
| } |
| } |
| |
| // These headers have no value |
| dest.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive |
| dest.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive |
| } |
| |
| size_t SpdySM::SendSynStreamImpl(uint32 stream_id, |
| const BalsaHeaders& headers) { |
| SpdyHeaderBlock block; |
| block["method"] = headers.request_method().as_string(); |
| if (!headers.HasHeader("status")) |
| block["status"] = headers.response_code().as_string(); |
| if (!headers.HasHeader("version")) |
| block["version"] =headers.response_version().as_string(); |
| if (headers.HasHeader("X-Original-Url")) { |
| std::string original_url = headers.GetHeader("X-Original-Url").as_string(); |
| block["path"] = UrlUtilities::GetUrlPath(original_url); |
| } else { |
| block["path"] = headers.request_uri().as_string(); |
| } |
| CopyHeaders(block, headers); |
| |
| SpdySynStreamControlFrame* fsrcf = |
| spdy_framer_->CreateSynStream(stream_id, 0, 0, CONTROL_FLAG_NONE, true, |
| &block); |
| size_t df_size = fsrcf->length() + SpdyFrame::size(); |
| EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf)); |
| |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader " |
| << stream_id; |
| return df_size; |
| } |
| |
| size_t SpdySM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) { |
| SpdyHeaderBlock block; |
| CopyHeaders(block, headers); |
| block["status"] = headers.response_code().as_string() + " " + |
| headers.response_reason_phrase().as_string(); |
| block["version"] = headers.response_version().as_string(); |
| |
| SpdySynReplyControlFrame* fsrcf = |
| spdy_framer_->CreateSynReply(stream_id, CONTROL_FLAG_NONE, true, &block); |
| size_t df_size = fsrcf->length() + SpdyFrame::size(); |
| EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf)); |
| |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader " |
| << stream_id; |
| return df_size; |
| } |
| |
| void SpdySM::SendDataFrameImpl(uint32 stream_id, const char* data, int64 len, |
| SpdyDataFlags flags, bool compress) { |
| // Force compression off if disabled via command line. |
| if (disable_data_compression()) |
| flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_COMPRESSED); |
| |
| // TODO(mbelshe): We can't compress here - before going into the |
| // priority queue. Compression needs to be done |
| // with late binding. |
| if (len == 0) { |
| SpdyDataFrame* fdf = spdy_framer_->CreateDataFrame(stream_id, data, len, |
| flags); |
| EnqueueDataFrame(new SpdyFrameDataFrame(fdf)); |
| return; |
| } |
| |
| // Chop data frames into chunks so that one stream can't monopolize the |
| // output channel. |
| while (len > 0) { |
| int64 size = std::min(len, static_cast<int64>(kSpdySegmentSize)); |
| SpdyDataFlags chunk_flags = flags; |
| |
| // If we chunked this block, and the FIN flag was set, there is more |
| // data coming. So, remove the flag. |
| if ((size < len) && (flags & DATA_FLAG_FIN)) |
| chunk_flags = static_cast<SpdyDataFlags>(chunk_flags & ~DATA_FLAG_FIN); |
| |
| SpdyDataFrame* fdf = spdy_framer_->CreateDataFrame(stream_id, data, size, |
| chunk_flags); |
| EnqueueDataFrame(new SpdyFrameDataFrame(fdf)); |
| |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame " |
| << stream_id << " [" << size << "] shrunk to " << fdf->length() |
| << ", flags=" << flags; |
| |
| data += size; |
| len -= size; |
| } |
| } |
| |
| void SpdySM::EnqueueDataFrame(DataFrame* df) { |
| connection_->EnqueueDataFrame(df); |
| } |
| |
| void SpdySM::GetOutput() { |
| while (client_output_list_->size() < 2) { |
| MemCacheIter* mci = client_output_ordering_.GetIter(); |
| if (mci == NULL) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT |
| << "SpdySM: GetOutput: nothing to output!?"; |
| return; |
| } |
| if (!mci->transformed_header) { |
| mci->transformed_header = true; |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput transformed " |
| << "header stream_id: [" << mci->stream_id << "]"; |
| if ((mci->stream_id % 2) == 0) { |
| // this is a server initiated stream. |
| // Ideally, we'd do a 'syn-push' here, instead of a syn-reply. |
| BalsaHeaders headers; |
| headers.CopyFrom(*(mci->file_data->headers)); |
| headers.ReplaceOrAppendHeader("status", "200"); |
| headers.ReplaceOrAppendHeader("version", "http/1.1"); |
| headers.SetRequestFirstlineFromStringPieces("PUSH", |
| mci->file_data->filename, |
| ""); |
| mci->bytes_sent = SendSynStream(mci->stream_id, headers); |
| } else { |
| BalsaHeaders headers; |
| headers.CopyFrom(*(mci->file_data->headers)); |
| mci->bytes_sent = SendSynReply(mci->stream_id, headers); |
| } |
| return; |
| } |
| if (mci->body_bytes_consumed >= mci->file_data->body.size()) { |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput " |
| << "remove_stream_id: [" << mci->stream_id << "]"; |
| SendEOF(mci->stream_id); |
| return; |
| } |
| size_t num_to_write = |
| mci->file_data->body.size() - mci->body_bytes_consumed; |
| if (num_to_write > mci->max_segment_size) |
| num_to_write = mci->max_segment_size; |
| |
| bool should_compress = false; |
| if (!mci->file_data->headers->HasHeader("content-encoding")) { |
| if (mci->file_data->headers->HasHeader("content-type")) { |
| std::string content_type = |
| mci->file_data->headers->GetHeader("content-type").as_string(); |
| if (content_type.find("image") == content_type.npos) |
| should_compress = true; |
| } |
| } |
| |
| SendDataFrame(mci->stream_id, |
| mci->file_data->body.data() + mci->body_bytes_consumed, |
| num_to_write, 0, should_compress); |
| VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame[" |
| << mci->stream_id << "]: " << num_to_write; |
| mci->body_bytes_consumed += num_to_write; |
| mci->bytes_sent += num_to_write; |
| } |
| } |
| |
| } // namespace net |
| |