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