1 // Copyright 2013 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 "content/browser/renderer_host/websocket_dispatcher_host.h"
10 #include "base/callback.h"
11 #include "base/logging.h"
12 #include "base/numerics/safe_conversions.h"
13 #include "base/rand_util.h"
14 #include "base/stl_util.h"
15 #include "content/browser/child_process_security_policy_impl.h"
16 #include "content/browser/renderer_host/websocket_host.h"
17 #include "content/common/websocket_messages.h"
23 // Many methods defined in this file return a WebSocketHostState enum
24 // value. Make WebSocketHostState visible at file scope so it doesn't have to be
25 // fully-qualified every time.
26 typedef WebSocketDispatcherHost::WebSocketHostState WebSocketHostState
;
28 // Max number of pending connections per WebSocketDispatcherHost
29 // used for per-renderer WebSocket throttling.
30 const int kMaxPendingWebSocketConnections
= 255;
34 WebSocketDispatcherHost::WebSocketDispatcherHost(
36 const GetRequestContextCallback
& get_context_callback
)
37 : BrowserMessageFilter(WebSocketMsgStart
),
38 process_id_(process_id
),
39 get_context_callback_(get_context_callback
),
40 websocket_host_factory_(
41 base::Bind(&WebSocketDispatcherHost::CreateWebSocketHost
,
42 base::Unretained(this))),
43 num_pending_connections_(0),
44 num_current_succeeded_connections_(0),
45 num_previous_succeeded_connections_(0),
46 num_current_failed_connections_(0),
47 num_previous_failed_connections_(0) {}
49 WebSocketDispatcherHost::WebSocketDispatcherHost(
51 const GetRequestContextCallback
& get_context_callback
,
52 const WebSocketHostFactory
& websocket_host_factory
)
53 : BrowserMessageFilter(WebSocketMsgStart
),
54 process_id_(process_id
),
55 get_context_callback_(get_context_callback
),
56 websocket_host_factory_(websocket_host_factory
),
57 num_pending_connections_(0),
58 num_current_succeeded_connections_(0),
59 num_previous_succeeded_connections_(0),
60 num_current_failed_connections_(0),
61 num_previous_failed_connections_(0) {}
63 WebSocketHost
* WebSocketDispatcherHost::CreateWebSocketHost(
65 base::TimeDelta delay
) {
66 return new WebSocketHost(
67 routing_id
, this, get_context_callback_
.Run(), delay
);
70 bool WebSocketDispatcherHost::OnMessageReceived(const IPC::Message
& message
) {
71 switch (message
.type()) {
72 case WebSocketHostMsg_AddChannelRequest::ID
:
73 case WebSocketMsg_SendFrame::ID
:
74 case WebSocketMsg_FlowControl::ID
:
75 case WebSocketMsg_DropChannel::ID
:
79 // Every message that has not been handled by a previous filter passes
80 // through here, so it is good to pass them on as efficiently as possible.
84 int routing_id
= message
.routing_id();
85 WebSocketHost
* host
= GetHost(routing_id
);
86 if (message
.type() == WebSocketHostMsg_AddChannelRequest::ID
) {
88 DVLOG(1) << "routing_id=" << routing_id
<< " already in use.";
89 // The websocket multiplexing spec says to should drop the physical
90 // connection in this case, but there isn't a real physical connection
91 // to the renderer, and killing the renderer for this would seem to be a
92 // little extreme. So for now just ignore the bogus request.
93 return true; // We handled the message (by ignoring it).
95 if (num_pending_connections_
>= kMaxPendingWebSocketConnections
) {
96 if(!Send(new WebSocketMsg_NotifyFailure(routing_id
,
97 "Error in connection establishment: net::ERR_INSUFFICIENT_RESOURCES"
99 DVLOG(1) << "Sending of message type "
100 << "WebSocketMsg_NotifyFailure failed.";
104 host
= websocket_host_factory_
.Run(routing_id
, CalculateDelay());
105 hosts_
.insert(WebSocketHostTable::value_type(routing_id
, host
));
106 ++num_pending_connections_
;
107 if (!throttling_period_timer_
.IsRunning())
108 throttling_period_timer_
.Start(
110 base::TimeDelta::FromMinutes(2),
112 &WebSocketDispatcherHost::ThrottlingPeriodTimerCallback
);
115 DVLOG(1) << "Received invalid routing ID " << routing_id
116 << " from renderer.";
117 return true; // We handled the message (by ignoring it).
119 return host
->OnMessageReceived(message
);
122 bool WebSocketDispatcherHost::CanReadRawCookies() const {
123 ChildProcessSecurityPolicyImpl
* policy
=
124 ChildProcessSecurityPolicyImpl::GetInstance();
125 return policy
->CanReadRawCookies(process_id_
);
128 WebSocketHost
* WebSocketDispatcherHost::GetHost(int routing_id
) const {
129 WebSocketHostTable::const_iterator it
= hosts_
.find(routing_id
);
130 return it
== hosts_
.end() ? NULL
: it
->second
;
133 WebSocketHostState
WebSocketDispatcherHost::SendOrDrop(IPC::Message
* message
) {
134 const uint32 message_type
= message
->type();
135 const int32 message_routing_id
= message
->routing_id();
136 if (!Send(message
)) {
138 DVLOG(1) << "Sending of message type " << message_type
139 << " failed. Dropping channel.";
140 DeleteWebSocketHost(message_routing_id
);
141 return WEBSOCKET_HOST_DELETED
;
143 return WEBSOCKET_HOST_ALIVE
;
146 WebSocketHostState
WebSocketDispatcherHost::SendAddChannelResponse(
148 const std::string
& selected_protocol
,
149 const std::string
& extensions
) {
150 // Update throttling counters (success).
151 WebSocketHost
* host
= GetHost(routing_id
);
153 host
->OnHandshakeSucceeded();
154 --num_pending_connections_
;
155 DCHECK_GE(num_pending_connections_
, 0);
156 ++num_current_succeeded_connections_
;
158 return SendOrDrop(new WebSocketMsg_AddChannelResponse(
159 routing_id
, selected_protocol
, extensions
));
162 WebSocketHostState
WebSocketDispatcherHost::SendFrame(
165 WebSocketMessageType type
,
166 const std::vector
<char>& data
) {
167 return SendOrDrop(new WebSocketMsg_SendFrame(routing_id
, fin
, type
, data
));
170 WebSocketHostState
WebSocketDispatcherHost::SendFlowControl(int routing_id
,
172 return SendOrDrop(new WebSocketMsg_FlowControl(routing_id
, quota
));
175 WebSocketHostState
WebSocketDispatcherHost::NotifyClosingHandshake(
177 return SendOrDrop(new WebSocketMsg_NotifyClosing(routing_id
));
180 WebSocketHostState
WebSocketDispatcherHost::NotifyStartOpeningHandshake(
181 int routing_id
, const WebSocketHandshakeRequest
& request
) {
182 return SendOrDrop(new WebSocketMsg_NotifyStartOpeningHandshake(
183 routing_id
, request
));
186 WebSocketHostState
WebSocketDispatcherHost::NotifyFinishOpeningHandshake(
187 int routing_id
, const WebSocketHandshakeResponse
& response
) {
188 return SendOrDrop(new WebSocketMsg_NotifyFinishOpeningHandshake(
189 routing_id
, response
));
192 WebSocketHostState
WebSocketDispatcherHost::NotifyFailure(
194 const std::string
& message
) {
195 if (SendOrDrop(new WebSocketMsg_NotifyFailure(
196 routing_id
, message
)) == WEBSOCKET_HOST_DELETED
) {
197 return WEBSOCKET_HOST_DELETED
;
199 DeleteWebSocketHost(routing_id
);
200 return WEBSOCKET_HOST_DELETED
;
203 WebSocketHostState
WebSocketDispatcherHost::DoDropChannel(
207 const std::string
& reason
) {
209 new WebSocketMsg_DropChannel(routing_id
, was_clean
, code
, reason
)) ==
210 WEBSOCKET_HOST_DELETED
)
211 return WEBSOCKET_HOST_DELETED
;
212 DeleteWebSocketHost(routing_id
);
213 return WEBSOCKET_HOST_DELETED
;
216 WebSocketDispatcherHost::~WebSocketDispatcherHost() {
217 std::vector
<WebSocketHost
*> hosts
;
218 for (base::hash_map
<int, WebSocketHost
*>::const_iterator i
= hosts_
.begin();
219 i
!= hosts_
.end(); ++i
) {
220 // In order to avoid changing the container while iterating, we copy
222 hosts
.push_back(i
->second
);
225 for (size_t i
= 0; i
< hosts
.size(); ++i
) {
226 // Note that some calls to GoAway could fail. In that case hosts[i] will be
227 // deleted and removed from |hosts_| in |DoDropChannel|.
232 STLDeleteContainerPairSecondPointers(hosts_
.begin(), hosts_
.end());
235 void WebSocketDispatcherHost::DeleteWebSocketHost(int routing_id
) {
236 WebSocketHostTable::iterator it
= hosts_
.find(routing_id
);
237 DCHECK(it
!= hosts_
.end());
239 if (!it
->second
->handshake_succeeded()) {
240 // Update throttling counters (failure).
241 --num_pending_connections_
;
242 DCHECK_GE(num_pending_connections_
, 0);
243 ++num_current_failed_connections_
;
249 DCHECK_LE(base::checked_cast
<size_t>(num_pending_connections_
),
253 int64_t WebSocketDispatcherHost::num_failed_connections() const {
254 return num_previous_failed_connections_
+
255 num_current_failed_connections_
;
258 int64_t WebSocketDispatcherHost::num_succeeded_connections() const {
259 return num_previous_succeeded_connections_
+
260 num_current_succeeded_connections_
;
263 // Calculate delay as described in
264 // the per-renderer WebSocket throttling design doc:
265 // https://docs.google.com/document/d/1aw2oN5PKfk-1gLnBrlv1OwLA8K3-ykM2ckwX2lubTg4/edit?usp=sharing
266 base::TimeDelta
WebSocketDispatcherHost::CalculateDelay() const {
267 int64_t f
= num_failed_connections();
268 int64_t s
= num_succeeded_connections();
269 int p
= num_pending_connections();
270 return base::TimeDelta::FromMilliseconds(
271 base::RandInt(1000, 5000) *
272 (1 << std::min(p
+ f
/ (s
+ 1), INT64_C(16))) / 65536);
275 void WebSocketDispatcherHost::ThrottlingPeriodTimerCallback() {
276 num_previous_failed_connections_
= num_current_failed_connections_
;
277 num_current_failed_connections_
= 0;
279 num_previous_succeeded_connections_
= num_current_succeeded_connections_
;
280 num_current_succeeded_connections_
= 0;
282 if (num_pending_connections_
== 0 &&
283 num_previous_failed_connections_
== 0 &&
284 num_previous_succeeded_connections_
== 0) {
285 throttling_period_timer_
.Stop();
289 } // namespace content