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