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.
5 #include "net/tools/flip_server/spdy_interface.h"
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"
21 std::string SpdySM::forward_ip_header_;
23 class SpdyFrameDataFrame : public DataFrame {
25 explicit SpdyFrameDataFrame(SpdyFrame* spdy_frame)
27 data = spdy_frame->data();
28 size = spdy_frame->size();
31 virtual ~SpdyFrameDataFrame() {
35 const SpdyFrame* frame;
38 SpdySM::SpdySM(SMConnection* connection,
39 SMInterface* sm_http_interface,
40 EpollServer* epoll_server,
41 MemoryCache* memory_cache,
42 FlipAcceptor* acceptor)
43 : buffered_spdy_framer_(new BufferedSpdyFramer(SPDY2, true)),
44 valid_spdy_session_(false),
45 connection_(connection),
46 client_output_list_(connection->output_list()),
47 client_output_ordering_(connection),
48 next_outgoing_stream_id_(2),
49 epoll_server_(epoll_server),
51 memory_cache_(memory_cache),
52 close_on_error_(false) {
53 buffered_spdy_framer_->set_visitor(this);
57 delete buffered_spdy_framer_;
60 void SpdySM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
61 SMInterface* sm_interface,
62 EpollServer* epoll_server,
64 std::string server_ip,
65 std::string server_port,
66 std::string remote_ip,
68 VLOG(2) << ACCEPTOR_CLIENT_IDENT
69 << "SpdySM: Initializing server connection.";
70 connection_->InitSMConnection(connection_pool, sm_interface,
71 epoll_server, fd, server_ip, server_port,
75 SMInterface* SpdySM::NewConnectionInterface() {
76 SMConnection* server_connection =
77 SMConnection::NewSMConnection(epoll_server_,
82 if (server_connection == NULL) {
83 LOG(ERROR) << "SpdySM: Could not create server connection";
86 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Creating new HTTP interface";
87 SMInterface *sm_http_interface = new HttpSM(server_connection,
91 return sm_http_interface;
94 SMInterface* SpdySM::FindOrMakeNewSMConnectionInterface(
95 const std::string& server_ip,
96 const std::string& server_port) {
97 SMInterface *sm_http_interface;
99 if (unused_server_interface_list.empty()) {
100 sm_http_interface = NewConnectionInterface();
101 server_idx = server_interface_list.size();
102 server_interface_list.push_back(sm_http_interface);
103 VLOG(2) << ACCEPTOR_CLIENT_IDENT
104 << "SpdySM: Making new server connection on index: "
107 server_idx = unused_server_interface_list.back();
108 unused_server_interface_list.pop_back();
109 sm_http_interface = server_interface_list.at(server_idx);
110 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reusing connection on "
111 << "index: " << server_idx;
114 sm_http_interface->InitSMInterface(this, server_idx);
115 sm_http_interface->InitSMConnection(NULL,
124 return sm_http_interface;
127 int SpdySM::SpdyHandleNewStream(
128 SpdyStreamId stream_id,
129 SpdyPriority priority,
130 const SpdyHeaderBlock& headers,
131 std::string &http_data,
132 bool* is_https_scheme) {
133 *is_https_scheme = false;
134 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSyn("
136 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: # headers: "
139 SpdyHeaderBlock::const_iterator url = headers.find("url");
140 SpdyHeaderBlock::const_iterator method = headers.find("method");
141 if (url == headers.end() || method == headers.end()) {
142 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: didn't find method or url "
143 << "or method. Not creating stream";
147 SpdyHeaderBlock::const_iterator scheme = headers.find("scheme");
148 if (scheme->second.compare("https") == 0) {
149 *is_https_scheme = true;
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.
157 if (url->second.compare(0, 4, "http") == 0)
158 uri = UrlUtilities::GetUrlPath(url->second);
160 uri = std::string(url->second);
161 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
162 std::string host = UrlUtilities::GetUrlHost(url->second);
163 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second
165 std::string filename = EncodeURL(uri, host, method->second);
166 NewStream(stream_id, priority, filename);
168 SpdyHeaderBlock::const_iterator version = headers.find("version");
169 http_data += method->second + " " + uri + " " + version->second + "\r\n";
170 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " "
171 << uri << " " << version->second;
172 for (SpdyHeaderBlock::const_iterator i = headers.begin();
173 i != headers.end(); ++i) {
174 http_data += i->first + ": " + i->second + "\r\n";
175 VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first.c_str() << ":"
176 << i->second.c_str();
178 if (forward_ip_header_.length()) {
179 // X-Client-Cluster-IP header
180 http_data += forward_ip_header_ + ": " +
181 connection_->client_ip() + "\r\n";
186 VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data;
190 void SpdySM::OnStreamFrameData(SpdyStreamId stream_id,
194 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id
195 << ", [" << len << "])";
196 StreamToSmif::iterator it = stream_to_smif_.find(stream_id);
197 if (it == stream_to_smif_.end()) {
198 VLOG(2) << "Dropping frame from unknown stream " << stream_id;
199 if (!valid_spdy_session_)
200 close_on_error_ = true;
204 SMInterface* interface = it->second;
205 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
206 interface->ProcessWriteInput(data, len);
209 void SpdySM::OnSynStream(SpdyStreamId stream_id,
210 SpdyStreamId associated_stream_id,
211 SpdyPriority priority,
212 uint8 credential_slot,
215 const SpdyHeaderBlock& headers) {
216 std::string http_data;
217 bool is_https_scheme;
218 int ret = SpdyHandleNewStream(stream_id, priority, headers, http_data,
221 LOG(ERROR) << "SpdySM: Could not convert spdy into http.";
224 // We've seen a valid looking SYN_STREAM, consider this to have
225 // been a real spdy session.
226 valid_spdy_session_ = true;
228 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
229 std::string server_ip;
230 std::string server_port;
231 if (is_https_scheme) {
232 server_ip = acceptor_->https_server_ip_;
233 server_port = acceptor_->https_server_port_;
235 server_ip = acceptor_->http_server_ip_;
236 server_port = acceptor_->http_server_port_;
238 SMInterface* sm_http_interface =
239 FindOrMakeNewSMConnectionInterface(server_ip, server_port);
240 stream_to_smif_[stream_id] = sm_http_interface;
241 sm_http_interface->SetStreamID(stream_id);
242 sm_http_interface->ProcessWriteInput(http_data.c_str(),
247 void SpdySM::OnSynReply(SpdyStreamId stream_id,
249 const SpdyHeaderBlock& headers) {
250 // TODO(willchan): if there is an error parsing headers, we
251 // should send a RST_STREAM.
252 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSynReply("
256 void SpdySM::OnHeaders(SpdyStreamId stream_id,
258 const SpdyHeaderBlock& headers) {
259 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnHeaders("
263 void SpdySM::OnRstStream(SpdyStreamId stream_id,
264 SpdyRstStreamStatus status) {
265 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRstStream("
267 client_output_ordering_.RemoveStreamId(stream_id);
270 size_t SpdySM::ProcessReadInput(const char* data, size_t len) {
271 return buffered_spdy_framer_->ProcessInput(data, len);
274 size_t SpdySM::ProcessWriteInput(const char* data, size_t len) {
278 bool SpdySM::MessageFullyRead() const {
279 return buffered_spdy_framer_->MessageFullyRead();
282 bool SpdySM::Error() const {
283 return close_on_error_ || buffered_spdy_framer_->HasError();
286 const char* SpdySM::ErrorAsString() const {
288 return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_->error_code());
291 void SpdySM::ResetForNewInterface(int32 server_idx) {
292 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: "
293 << "server_idx: " << server_idx;
294 unused_server_interface_list.push_back(server_idx);
297 void SpdySM::ResetForNewConnection() {
298 // seq_num is not cleared, intentionally.
299 delete buffered_spdy_framer_;
300 buffered_spdy_framer_ = new BufferedSpdyFramer(SPDY2, true);
301 buffered_spdy_framer_->set_visitor(this);
302 valid_spdy_session_ = false;
303 client_output_ordering_.Reset();
304 next_outgoing_stream_id_ = 2;
307 // Send a settings frame
308 int SpdySM::PostAcceptHook() {
309 SettingsMap settings;
310 settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
311 SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 100);
312 SpdyFrame* settings_frame =
313 buffered_spdy_framer_->CreateSettings(settings);
315 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame";
316 EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame));
320 void SpdySM::NewStream(uint32 stream_id,
322 const std::string& filename) {
324 mci.stream_id = stream_id;
325 mci.priority = priority;
326 // TODO(yhirano): The program will crash when
327 // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER.
328 // It should be fixed or an assertion should be placed.
329 if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
330 if (!memory_cache_->AssignFileData(filename, &mci)) {
331 // error creating new stream.
332 VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
333 SendErrorNotFound(stream_id);
335 AddToOutputOrder(mci);
338 AddToOutputOrder(mci);
342 void SpdySM::AddToOutputOrder(const MemCacheIter& mci) {
343 client_output_ordering_.AddToOutputOrder(mci);
346 void SpdySM::SendEOF(uint32 stream_id) {
347 SendEOFImpl(stream_id);
350 void SpdySM::SendErrorNotFound(uint32 stream_id) {
351 SendErrorNotFoundImpl(stream_id);
354 size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
355 return SendSynStreamImpl(stream_id, headers);
358 size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
359 return SendSynReplyImpl(stream_id, headers);
362 void SpdySM::SendDataFrame(uint32 stream_id, const char* data, int64 len,
363 uint32 flags, bool compress) {
364 SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags);
365 SendDataFrameImpl(stream_id, data, len, spdy_flags, compress);
368 void SpdySM::SendEOFImpl(uint32 stream_id) {
369 SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false);
370 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id;
371 KillStream(stream_id);
372 stream_to_smif_.erase(stream_id);
375 void SpdySM::SendErrorNotFoundImpl(uint32 stream_id) {
376 BalsaHeaders my_headers;
377 my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
378 SendSynReplyImpl(stream_id, my_headers);
379 SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false);
380 client_output_ordering_.RemoveStreamId(stream_id);
383 void SpdySM::KillStream(uint32 stream_id) {
384 client_output_ordering_.RemoveStreamId(stream_id);
387 void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) {
388 for (BalsaHeaders::const_header_lines_iterator hi =
389 headers.header_lines_begin();
390 hi != headers.header_lines_end();
392 // It is illegal to send SPDY headers with empty value or header
394 if (!hi->first.length() || !hi->second.length())
397 // Key must be all lower case in SPDY headers.
398 std::string key = hi->first.as_string();
399 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
400 SpdyHeaderBlock::iterator fhi = dest.find(key);
401 if (fhi == dest.end()) {
402 dest[key] = hi->second.as_string();
405 std::string(fhi->second.data(), fhi->second.size()) + "\0" +
406 std::string(hi->second.data(), hi->second.size()));
410 // These headers have no value
411 dest.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive
412 dest.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive
415 size_t SpdySM::SendSynStreamImpl(uint32 stream_id,
416 const BalsaHeaders& headers) {
417 SpdyHeaderBlock block;
418 block["method"] = headers.request_method().as_string();
419 if (!headers.HasHeader("version"))
420 block["version"] =headers.request_version().as_string();
421 if (headers.HasHeader("X-Original-Url")) {
422 std::string original_url = headers.GetHeader("X-Original-Url").as_string();
423 block["url"] = UrlUtilities::GetUrlPath(original_url);
425 block["url"] = headers.request_uri().as_string();
427 CopyHeaders(block, headers);
429 SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynStream(
430 stream_id, 0, 0, 0, CONTROL_FLAG_NONE, true, &block);
431 size_t df_size = fsrcf->size();
432 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
434 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader "
439 size_t SpdySM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
440 SpdyHeaderBlock block;
441 CopyHeaders(block, headers);
442 block["status"] = headers.response_code().as_string() + " " +
443 headers.response_reason_phrase().as_string();
444 block["version"] = headers.response_version().as_string();
446 SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynReply(
447 stream_id, CONTROL_FLAG_NONE, true, &block);
448 size_t df_size = fsrcf->size();
449 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
451 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader "
456 void SpdySM::SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
457 SpdyDataFlags flags, bool compress) {
458 // TODO(mbelshe): We can't compress here - before going into the
459 // priority queue. Compression needs to be done
460 // with late binding.
462 SpdyFrame* fdf = buffered_spdy_framer_->CreateDataFrame(
463 stream_id, data, len, flags);
464 EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
468 // Chop data frames into chunks so that one stream can't monopolize the
471 int64 size = std::min(len, static_cast<int64>(kSpdySegmentSize));
472 SpdyDataFlags chunk_flags = flags;
474 // If we chunked this block, and the FIN flag was set, there is more
475 // data coming. So, remove the flag.
476 if ((size < len) && (flags & DATA_FLAG_FIN))
477 chunk_flags = static_cast<SpdyDataFlags>(chunk_flags & ~DATA_FLAG_FIN);
479 SpdyFrame* fdf = buffered_spdy_framer_->CreateDataFrame(
480 stream_id, data, size, chunk_flags);
481 EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
483 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame "
484 << stream_id << " [" << size << "] shrunk to "
485 << (fdf->size() - kSpdyOverhead) << ", flags=" << flags;
492 void SpdySM::EnqueueDataFrame(DataFrame* df) {
493 connection_->EnqueueDataFrame(df);
496 void SpdySM::GetOutput() {
497 while (client_output_list_->size() < 2) {
498 MemCacheIter* mci = client_output_ordering_.GetIter();
500 VLOG(2) << ACCEPTOR_CLIENT_IDENT
501 << "SpdySM: GetOutput: nothing to output!?";
504 if (!mci->transformed_header) {
505 mci->transformed_header = true;
506 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput transformed "
507 << "header stream_id: [" << mci->stream_id << "]";
508 if ((mci->stream_id % 2) == 0) {
509 // this is a server initiated stream.
510 // Ideally, we'd do a 'syn-push' here, instead of a syn-reply.
511 BalsaHeaders headers;
512 headers.CopyFrom(*(mci->file_data->headers()));
513 headers.ReplaceOrAppendHeader("status", "200");
514 headers.ReplaceOrAppendHeader("version", "http/1.1");
515 headers.SetRequestFirstlineFromStringPieces("PUSH",
516 mci->file_data->filename(),
518 mci->bytes_sent = SendSynStream(mci->stream_id, headers);
520 BalsaHeaders headers;
521 headers.CopyFrom(*(mci->file_data->headers()));
522 mci->bytes_sent = SendSynReply(mci->stream_id, headers);
526 if (mci->body_bytes_consumed >= mci->file_data->body().size()) {
527 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput "
528 << "remove_stream_id: [" << mci->stream_id << "]";
529 SendEOF(mci->stream_id);
532 size_t num_to_write =
533 mci->file_data->body().size() - mci->body_bytes_consumed;
534 if (num_to_write > mci->max_segment_size)
535 num_to_write = mci->max_segment_size;
537 bool should_compress = false;
538 if (!mci->file_data->headers()->HasHeader("content-encoding")) {
539 if (mci->file_data->headers()->HasHeader("content-type")) {
540 std::string content_type =
541 mci->file_data->headers()->GetHeader("content-type").as_string();
542 if (content_type.find("image") == content_type.npos)
543 should_compress = true;
547 SendDataFrame(mci->stream_id,
548 mci->file_data->body().data() + mci->body_bytes_consumed,
549 num_to_write, 0, should_compress);
550 VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame["
551 << mci->stream_id << "]: " << num_to_write;
552 mci->body_bytes_consumed += num_to_write;
553 mci->bytes_sent += num_to_write;