Home | History | Annotate | Download | only in http
      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/http/http_stream_factory_impl.h"
      6 
      7 #include <string>
      8 
      9 #include "base/stl_util.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "net/base/net_log.h"
     12 #include "net/base/net_util.h"
     13 #include "net/http/http_network_session.h"
     14 #include "net/http/http_pipelined_connection.h"
     15 #include "net/http/http_pipelined_host.h"
     16 #include "net/http/http_pipelined_stream.h"
     17 #include "net/http/http_server_properties.h"
     18 #include "net/http/http_stream_factory_impl_job.h"
     19 #include "net/http/http_stream_factory_impl_request.h"
     20 #include "net/spdy/spdy_http_stream.h"
     21 #include "url/gurl.h"
     22 
     23 namespace net {
     24 
     25 namespace {
     26 
     27 const PortAlternateProtocolPair kNoAlternateProtocol = {
     28   0,  UNINITIALIZED_ALTERNATE_PROTOCOL
     29 };
     30 
     31 GURL UpgradeUrlToHttps(const GURL& original_url, int port) {
     32   GURL::Replacements replacements;
     33   // new_sheme and new_port need to be in scope here because GURL::Replacements
     34   // references the memory contained by them directly.
     35   const std::string new_scheme = "https";
     36   const std::string new_port = base::IntToString(port);
     37   replacements.SetSchemeStr(new_scheme);
     38   replacements.SetPortStr(new_port);
     39   return original_url.ReplaceComponents(replacements);
     40 }
     41 
     42 }  // namespace
     43 
     44 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session,
     45                                              bool for_websockets)
     46     : session_(session),
     47       http_pipelined_host_pool_(this, NULL,
     48                                 session_->http_server_properties(),
     49                                 session_->force_http_pipelining()),
     50       for_websockets_(for_websockets) {}
     51 
     52 HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
     53   DCHECK(request_map_.empty());
     54   DCHECK(spdy_session_request_map_.empty());
     55   DCHECK(http_pipelining_request_map_.empty());
     56 
     57   std::set<const Job*> tmp_job_set;
     58   tmp_job_set.swap(orphaned_job_set_);
     59   STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
     60   DCHECK(orphaned_job_set_.empty());
     61 
     62   tmp_job_set.clear();
     63   tmp_job_set.swap(preconnect_job_set_);
     64   STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
     65   DCHECK(preconnect_job_set_.empty());
     66 }
     67 
     68 HttpStreamRequest* HttpStreamFactoryImpl::RequestStream(
     69     const HttpRequestInfo& request_info,
     70     RequestPriority priority,
     71     const SSLConfig& server_ssl_config,
     72     const SSLConfig& proxy_ssl_config,
     73     HttpStreamRequest::Delegate* delegate,
     74     const BoundNetLog& net_log) {
     75   DCHECK(!for_websockets_);
     76   return RequestStreamInternal(request_info,
     77                                priority,
     78                                server_ssl_config,
     79                                proxy_ssl_config,
     80                                delegate,
     81                                NULL,
     82                                net_log);
     83 }
     84 
     85 HttpStreamRequest* HttpStreamFactoryImpl::RequestWebSocketHandshakeStream(
     86     const HttpRequestInfo& request_info,
     87     RequestPriority priority,
     88     const SSLConfig& server_ssl_config,
     89     const SSLConfig& proxy_ssl_config,
     90     HttpStreamRequest::Delegate* delegate,
     91     WebSocketHandshakeStreamBase::CreateHelper* create_helper,
     92     const BoundNetLog& net_log) {
     93   DCHECK(for_websockets_);
     94   DCHECK(create_helper);
     95   return RequestStreamInternal(request_info,
     96                                priority,
     97                                server_ssl_config,
     98                                proxy_ssl_config,
     99                                delegate,
    100                                create_helper,
    101                                net_log);
    102 }
    103 
    104 HttpStreamRequest* HttpStreamFactoryImpl::RequestStreamInternal(
    105     const HttpRequestInfo& request_info,
    106     RequestPriority priority,
    107     const SSLConfig& server_ssl_config,
    108     const SSLConfig& proxy_ssl_config,
    109     HttpStreamRequest::Delegate* delegate,
    110     WebSocketHandshakeStreamBase::CreateHelper*
    111         websocket_handshake_stream_create_helper,
    112     const BoundNetLog& net_log) {
    113   Request* request = new Request(request_info.url,
    114                                  this,
    115                                  delegate,
    116                                  websocket_handshake_stream_create_helper,
    117                                  net_log);
    118 
    119   GURL alternate_url;
    120   PortAlternateProtocolPair alternate =
    121       GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
    122   Job* alternate_job = NULL;
    123   if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
    124     // Never share connection with other jobs for FTP requests.
    125     DCHECK(!request_info.url.SchemeIs("ftp"));
    126 
    127     HttpRequestInfo alternate_request_info = request_info;
    128     alternate_request_info.url = alternate_url;
    129     alternate_job =
    130         new Job(this, session_, alternate_request_info, priority,
    131                 server_ssl_config, proxy_ssl_config, net_log.net_log());
    132     request->AttachJob(alternate_job);
    133     alternate_job->MarkAsAlternate(request_info.url, alternate);
    134   }
    135 
    136   Job* job = new Job(this, session_, request_info, priority,
    137                      server_ssl_config, proxy_ssl_config, net_log.net_log());
    138   request->AttachJob(job);
    139   if (alternate_job) {
    140     // Never share connection with other jobs for FTP requests.
    141     DCHECK(!request_info.url.SchemeIs("ftp"));
    142 
    143     job->WaitFor(alternate_job);
    144     // Make sure to wait until we call WaitFor(), before starting
    145     // |alternate_job|, otherwise |alternate_job| will not notify |job|
    146     // appropriately.
    147     alternate_job->Start(request);
    148   }
    149   // Even if |alternate_job| has already finished, it won't have notified the
    150   // request yet, since we defer that to the next iteration of the MessageLoop,
    151   // so starting |job| is always safe.
    152   job->Start(request);
    153   return request;
    154 }
    155 
    156 void HttpStreamFactoryImpl::PreconnectStreams(
    157     int num_streams,
    158     const HttpRequestInfo& request_info,
    159     RequestPriority priority,
    160     const SSLConfig& server_ssl_config,
    161     const SSLConfig& proxy_ssl_config) {
    162   DCHECK(!for_websockets_);
    163   GURL alternate_url;
    164   PortAlternateProtocolPair alternate =
    165       GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
    166   Job* job = NULL;
    167   if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
    168     HttpRequestInfo alternate_request_info = request_info;
    169     alternate_request_info.url = alternate_url;
    170     job = new Job(this, session_, alternate_request_info, priority,
    171                   server_ssl_config, proxy_ssl_config, session_->net_log());
    172     job->MarkAsAlternate(request_info.url, alternate);
    173   } else {
    174     job = new Job(this, session_, request_info, priority,
    175                   server_ssl_config, proxy_ssl_config, session_->net_log());
    176   }
    177   preconnect_job_set_.insert(job);
    178   job->Preconnect(num_streams);
    179 }
    180 
    181 base::Value* HttpStreamFactoryImpl::PipelineInfoToValue() const {
    182   return http_pipelined_host_pool_.PipelineInfoToValue();
    183 }
    184 
    185 const HostMappingRules* HttpStreamFactoryImpl::GetHostMappingRules() const {
    186   return session_->params().host_mapping_rules;
    187 }
    188 
    189 PortAlternateProtocolPair HttpStreamFactoryImpl::GetAlternateProtocolRequestFor(
    190     const GURL& original_url,
    191     GURL* alternate_url) const {
    192   if (!use_alternate_protocols())
    193     return kNoAlternateProtocol;
    194 
    195   if (original_url.SchemeIs("ftp"))
    196     return kNoAlternateProtocol;
    197 
    198   HostPortPair origin = HostPortPair(original_url.HostNoBrackets(),
    199                                      original_url.EffectiveIntPort());
    200 
    201   const HttpServerProperties& http_server_properties =
    202       *session_->http_server_properties();
    203   if (!http_server_properties.HasAlternateProtocol(origin))
    204     return kNoAlternateProtocol;
    205 
    206   PortAlternateProtocolPair alternate =
    207       http_server_properties.GetAlternateProtocol(origin);
    208   if (alternate.protocol == ALTERNATE_PROTOCOL_BROKEN)
    209     return kNoAlternateProtocol;
    210 
    211   if (!IsAlternateProtocolValid(alternate.protocol)) {
    212     NOTREACHED();
    213     return kNoAlternateProtocol;
    214   }
    215 
    216   // Some shared unix systems may have user home directories (like
    217   // http://foo.com/~mike) which allow users to emit headers.  This is a bad
    218   // idea already, but with Alternate-Protocol, it provides the ability for a
    219   // single user on a multi-user system to hijack the alternate protocol.
    220   // These systems also enforce ports <1024 as restricted ports.  So don't
    221   // allow protocol upgrades to user-controllable ports.
    222   const int kUnrestrictedPort = 1024;
    223   if (!session_->params().enable_user_alternate_protocol_ports &&
    224       (alternate.port >= kUnrestrictedPort &&
    225        origin.port() < kUnrestrictedPort))
    226     return kNoAlternateProtocol;
    227 
    228   origin.set_port(alternate.port);
    229   if (alternate.protocol >= NPN_SPDY_MINIMUM_VERSION &&
    230       alternate.protocol <= NPN_SPDY_MAXIMUM_VERSION) {
    231     if (!spdy_enabled())
    232       return kNoAlternateProtocol;
    233 
    234     if (HttpStreamFactory::HasSpdyExclusion(origin))
    235       return kNoAlternateProtocol;
    236 
    237     *alternate_url = UpgradeUrlToHttps(original_url, alternate.port);
    238   } else {
    239     DCHECK_EQ(QUIC, alternate.protocol);
    240     if (!session_->params().enable_quic ||
    241         !(original_url.SchemeIs("http") ||
    242           session_->params().enable_quic_https)) {
    243         return kNoAlternateProtocol;
    244     }
    245     // TODO(rch):  Figure out how to make QUIC iteract with PAC
    246     // scripts.  By not re-writing the URL, we will query the PAC script
    247     // for the proxy to use to reach the original URL via TCP.  But
    248     // the alternate request will be going via UDP to a different port.
    249     *alternate_url = original_url;
    250   }
    251   return alternate;
    252 }
    253 
    254 void HttpStreamFactoryImpl::OrphanJob(Job* job, const Request* request) {
    255   DCHECK(ContainsKey(request_map_, job));
    256   DCHECK_EQ(request_map_[job], request);
    257   DCHECK(!ContainsKey(orphaned_job_set_, job));
    258 
    259   request_map_.erase(job);
    260 
    261   orphaned_job_set_.insert(job);
    262   job->Orphan(request);
    263 }
    264 
    265 void HttpStreamFactoryImpl::OnNewSpdySessionReady(
    266     const base::WeakPtr<SpdySession>& spdy_session,
    267     bool direct,
    268     const SSLConfig& used_ssl_config,
    269     const ProxyInfo& used_proxy_info,
    270     bool was_npn_negotiated,
    271     NextProto protocol_negotiated,
    272     bool using_spdy,
    273     const BoundNetLog& net_log) {
    274   while (true) {
    275     if (!spdy_session)
    276       break;
    277     const SpdySessionKey& spdy_session_key = spdy_session->spdy_session_key();
    278     // Each iteration may empty out the RequestSet for |spdy_session_key| in
    279     // |spdy_session_request_map_|. So each time, check for RequestSet and use
    280     // the first one.
    281     //
    282     // TODO(willchan): If it's important, switch RequestSet out for a FIFO
    283     // queue (Order by priority first, then FIFO within same priority). Unclear
    284     // that it matters here.
    285     if (!ContainsKey(spdy_session_request_map_, spdy_session_key))
    286       break;
    287     Request* request = *spdy_session_request_map_[spdy_session_key].begin();
    288     request->Complete(was_npn_negotiated,
    289                       protocol_negotiated,
    290                       using_spdy,
    291                       net_log);
    292     if (for_websockets_) {
    293       WebSocketHandshakeStreamBase::CreateHelper* create_helper =
    294           request->websocket_handshake_stream_create_helper();
    295       DCHECK(create_helper);
    296       bool use_relative_url = direct || request->url().SchemeIs("wss");
    297       request->OnWebSocketHandshakeStreamReady(
    298           NULL,
    299           used_ssl_config,
    300           used_proxy_info,
    301           create_helper->CreateSpdyStream(spdy_session, use_relative_url));
    302     } else {
    303       bool use_relative_url = direct || request->url().SchemeIs("https");
    304       request->OnStreamReady(
    305           NULL,
    306           used_ssl_config,
    307           used_proxy_info,
    308           new SpdyHttpStream(spdy_session, use_relative_url));
    309     }
    310   }
    311   // TODO(mbelshe): Alert other valid requests.
    312 }
    313 
    314 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) {
    315   orphaned_job_set_.erase(job);
    316   delete job;
    317 }
    318 
    319 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) {
    320   preconnect_job_set_.erase(job);
    321   delete job;
    322   OnPreconnectsCompleteInternal();
    323 }
    324 
    325 void HttpStreamFactoryImpl::OnHttpPipelinedHostHasAdditionalCapacity(
    326     HttpPipelinedHost* host) {
    327   while (ContainsKey(http_pipelining_request_map_, host->GetKey())) {
    328     HttpPipelinedStream* stream =
    329         http_pipelined_host_pool_.CreateStreamOnExistingPipeline(
    330             host->GetKey());
    331     if (!stream) {
    332       break;
    333     }
    334 
    335     Request* request = *http_pipelining_request_map_[host->GetKey()].begin();
    336     request->Complete(stream->was_npn_negotiated(),
    337                       stream->protocol_negotiated(),
    338                       false,  // not using_spdy
    339                       stream->net_log());
    340     request->OnStreamReady(NULL,
    341                            stream->used_ssl_config(),
    342                            stream->used_proxy_info(),
    343                            stream);
    344   }
    345 }
    346 
    347 void HttpStreamFactoryImpl::AbortPipelinedRequestsWithKey(
    348     const Job* job, const HttpPipelinedHost::Key& key, int status,
    349     const SSLConfig& used_ssl_config) {
    350   RequestVector requests_to_fail = http_pipelining_request_map_[key];
    351   for (RequestVector::const_iterator it = requests_to_fail.begin();
    352        it != requests_to_fail.end(); ++it) {
    353     Request* request = *it;
    354     if (request == request_map_[job]) {
    355       continue;
    356     }
    357     request->OnStreamFailed(NULL, status, used_ssl_config);
    358   }
    359 }
    360 
    361 }  // namespace net
    362