/src/mozilla-central/netwerk/protocol/http/Http2Push.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set sw=2 ts=8 et tw=80 : */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | // HttpLog.h should generally be included first |
8 | | #include "HttpLog.h" |
9 | | |
10 | | // Log on level :5, instead of default :4. |
11 | | #undef LOG |
12 | | #define LOG(args) LOG5(args) |
13 | | #undef LOG_ENABLED |
14 | | #define LOG_ENABLED() LOG5_ENABLED() |
15 | | |
16 | | #include <algorithm> |
17 | | |
18 | | #include "Http2Push.h" |
19 | | #include "nsHttpChannel.h" |
20 | | #include "nsIHttpPushListener.h" |
21 | | #include "nsString.h" |
22 | | |
23 | | namespace mozilla { |
24 | | namespace net { |
25 | | |
26 | | class CallChannelOnPush final : public Runnable { |
27 | | public: |
28 | | CallChannelOnPush(nsIHttpChannelInternal* associatedChannel, |
29 | | const nsACString& pushedURI, |
30 | | Http2PushedStream* pushStream) |
31 | | : Runnable("net::CallChannelOnPush") |
32 | | , mAssociatedChannel(associatedChannel) |
33 | | , mPushedURI(pushedURI) |
34 | | , mPushedStream(pushStream) |
35 | 0 | { |
36 | 0 | } |
37 | | |
38 | | NS_IMETHOD Run() override |
39 | 0 | { |
40 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
41 | 0 | RefPtr<nsHttpChannel> channel; |
42 | 0 | CallQueryInterface(mAssociatedChannel, channel.StartAssignment()); |
43 | 0 | MOZ_ASSERT(channel); |
44 | 0 | if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) { |
45 | 0 | return NS_OK; |
46 | 0 | } |
47 | 0 | |
48 | 0 | LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this)); |
49 | 0 | mPushedStream->OnPushFailed(); |
50 | 0 | return NS_OK; |
51 | 0 | } |
52 | | |
53 | | private: |
54 | | nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel; |
55 | | const nsCString mPushedURI; |
56 | | Http2PushedStream *mPushedStream; |
57 | | }; |
58 | | |
59 | | ////////////////////////////////////////// |
60 | | // Http2PushedStream |
61 | | ////////////////////////////////////////// |
62 | | |
63 | | Http2PushedStream::Http2PushedStream(Http2PushTransactionBuffer *aTransaction, |
64 | | Http2Session *aSession, |
65 | | Http2Stream *aAssociatedStream, |
66 | | uint32_t aID, |
67 | | uint64_t aCurrentForegroundTabOuterContentWindowId) |
68 | | :Http2Stream(aTransaction, aSession, 0, aCurrentForegroundTabOuterContentWindowId) |
69 | | , mConsumerStream(nullptr) |
70 | | , mAssociatedTransaction(aAssociatedStream->Transaction()) |
71 | | , mBufferedPush(aTransaction) |
72 | | , mStatus(NS_OK) |
73 | | , mPushCompleted(false) |
74 | | , mDeferCleanupOnSuccess(true) |
75 | | , mDeferCleanupOnPush(false) |
76 | | , mOnPushFailed(false) |
77 | 0 | { |
78 | 0 | LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID)); |
79 | 0 | mStreamID = aID; |
80 | 0 | MOZ_ASSERT(!(aID & 1)); // must be even to be a pushed stream |
81 | 0 | mBufferedPush->SetPushStream(this); |
82 | 0 | mRequestContext = aAssociatedStream->RequestContext(); |
83 | 0 | mLastRead = TimeStamp::Now(); |
84 | 0 | SetPriority(aAssociatedStream->Priority() + 1); |
85 | 0 | } |
86 | | |
87 | | bool |
88 | | Http2PushedStream::GetPushComplete() |
89 | 0 | { |
90 | 0 | return mPushCompleted; |
91 | 0 | } |
92 | | |
93 | | nsresult |
94 | | Http2PushedStream::WriteSegments(nsAHttpSegmentWriter *writer, |
95 | | uint32_t count, uint32_t *countWritten) |
96 | 0 | { |
97 | 0 | nsresult rv = Http2Stream::WriteSegments(writer, count, countWritten); |
98 | 0 | if (NS_SUCCEEDED(rv) && *countWritten) { |
99 | 0 | mLastRead = TimeStamp::Now(); |
100 | 0 | } |
101 | 0 |
|
102 | 0 | if (rv == NS_BASE_STREAM_CLOSED) { |
103 | 0 | mPushCompleted = true; |
104 | 0 | rv = NS_OK; // this is what a normal HTTP transaction would do |
105 | 0 | } |
106 | 0 | if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) |
107 | 0 | mStatus = rv; |
108 | 0 | return rv; |
109 | 0 | } |
110 | | |
111 | | bool |
112 | | Http2PushedStream::DeferCleanup(nsresult status) |
113 | 0 | { |
114 | 0 | LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 "\n", this, |
115 | 0 | static_cast<uint32_t>(status))); |
116 | 0 |
|
117 | 0 | if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) { |
118 | 0 | LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer on success\n", this, |
119 | 0 | static_cast<uint32_t>(status))); |
120 | 0 | return true; |
121 | 0 | } |
122 | 0 | if (mDeferCleanupOnPush) { |
123 | 0 | LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer onPush ref\n", this, |
124 | 0 | static_cast<uint32_t>(status))); |
125 | 0 | return true; |
126 | 0 | } |
127 | 0 | if (mConsumerStream) { |
128 | 0 | LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer active consumer\n", this, |
129 | 0 | static_cast<uint32_t>(status))); |
130 | 0 | return true; |
131 | 0 | } |
132 | 0 | LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 " not deferred\n", this, |
133 | 0 | static_cast<uint32_t>(status))); |
134 | 0 | return false; |
135 | 0 | } |
136 | | |
137 | | // return true if channel implements nsIHttpPushListener |
138 | | bool |
139 | | Http2PushedStream::TryOnPush() |
140 | 0 | { |
141 | 0 | nsHttpTransaction *trans = mAssociatedTransaction->QueryHttpTransaction(); |
142 | 0 | if (!trans) { |
143 | 0 | return false; |
144 | 0 | } |
145 | 0 | |
146 | 0 | nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel()); |
147 | 0 | if (!associatedChannel) { |
148 | 0 | return false; |
149 | 0 | } |
150 | 0 | |
151 | 0 | if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) { |
152 | 0 | return false; |
153 | 0 | } |
154 | 0 | |
155 | 0 | mDeferCleanupOnPush = true; |
156 | 0 | nsCString uri = Origin() + Path(); |
157 | 0 | NS_DispatchToMainThread(new CallChannelOnPush(associatedChannel, uri, this)); |
158 | 0 | return true; |
159 | 0 | } |
160 | | |
161 | | // side effect free static method to determine if Http2Stream implements nsIHttpPushListener |
162 | | bool |
163 | | Http2PushedStream::TestOnPush(Http2Stream *stream) |
164 | 0 | { |
165 | 0 | if (!stream) { |
166 | 0 | return false; |
167 | 0 | } |
168 | 0 | nsAHttpTransaction *abstractTransaction = stream->Transaction(); |
169 | 0 | if (!abstractTransaction) { |
170 | 0 | return false; |
171 | 0 | } |
172 | 0 | nsHttpTransaction *trans = abstractTransaction->QueryHttpTransaction(); |
173 | 0 | if (!trans) { |
174 | 0 | return false; |
175 | 0 | } |
176 | 0 | nsCOMPtr<nsIHttpChannelInternal> associatedChannel = do_QueryInterface(trans->HttpChannel()); |
177 | 0 | if (!associatedChannel) { |
178 | 0 | return false; |
179 | 0 | } |
180 | 0 | return (trans->Caps() & NS_HTTP_ONPUSH_LISTENER); |
181 | 0 | } |
182 | | |
183 | | nsresult |
184 | | Http2PushedStream::ReadSegments(nsAHttpSegmentReader *reader, |
185 | | uint32_t, uint32_t *count) |
186 | 0 | { |
187 | 0 | nsresult rv = NS_OK; |
188 | 0 | *count = 0; |
189 | 0 |
|
190 | 0 | mozilla::OriginAttributes originAttributes; |
191 | 0 | switch (mUpstreamState) { |
192 | 0 | case GENERATING_HEADERS: |
193 | 0 | // The request headers for this has been processed, so we need to verify |
194 | 0 | // that :authority, :scheme, and :path MUST be present. :method MUST NOT be |
195 | 0 | // present |
196 | 0 | mSocketTransport->GetOriginAttributes(&originAttributes); |
197 | 0 | CreatePushHashKey(mHeaderScheme, mHeaderHost, originAttributes, |
198 | 0 | mSession->Serial(), mHeaderPath, |
199 | 0 | mOrigin, mHashKey); |
200 | 0 |
|
201 | 0 | LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get())); |
202 | 0 |
|
203 | 0 | // the write side of a pushed transaction just involves manipulating a little state |
204 | 0 | SetSentFin(true); |
205 | 0 | Http2Stream::mRequestHeadersDone = 1; |
206 | 0 | Http2Stream::mOpenGenerated = 1; |
207 | 0 | Http2Stream::ChangeState(UPSTREAM_COMPLETE); |
208 | 0 | break; |
209 | 0 |
|
210 | 0 | case UPSTREAM_COMPLETE: |
211 | 0 | // Let's just clear the stream's transmit buffer by pushing it into |
212 | 0 | // the session. This is probably a window adjustment. |
213 | 0 | LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID)); |
214 | 0 | mSegmentReader = reader; |
215 | 0 | rv = TransmitFrame(nullptr, nullptr, true); |
216 | 0 | mSegmentReader = nullptr; |
217 | 0 | break; |
218 | 0 |
|
219 | 0 | case GENERATING_BODY: |
220 | 0 | case SENDING_BODY: |
221 | 0 | case SENDING_FIN_STREAM: |
222 | 0 | default: |
223 | 0 | break; |
224 | 0 | } |
225 | 0 | |
226 | 0 | return rv; |
227 | 0 | } |
228 | | |
229 | | void |
230 | | Http2PushedStream::AdjustInitialWindow() |
231 | 0 | { |
232 | 0 | LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID)); |
233 | 0 | if (mConsumerStream) { |
234 | 0 | LOG3(("Http2PushStream::AdjustInitialWindow %p 0x%X " |
235 | 0 | "calling super consumer %p 0x%X\n", this, |
236 | 0 | mStreamID, mConsumerStream, mConsumerStream->StreamID())); |
237 | 0 | Http2Stream::AdjustInitialWindow(); |
238 | 0 | // Http2PushedStream::ReadSegments is needed to call TransmitFrame() |
239 | 0 | // and actually get this information into the session bytestream |
240 | 0 | mSession->TransactionHasDataToWrite(this); |
241 | 0 | } |
242 | 0 | // Otherwise, when we get hooked up, the initial window will get bumped |
243 | 0 | // anyway, so we're good to go. |
244 | 0 | } |
245 | | |
246 | | void |
247 | | Http2PushedStream::SetConsumerStream(Http2Stream *consumer) |
248 | 0 | { |
249 | 0 | mConsumerStream = consumer; |
250 | 0 | mDeferCleanupOnPush = false; |
251 | 0 | } |
252 | | |
253 | | bool |
254 | | Http2PushedStream::GetHashKey(nsCString &key) |
255 | 0 | { |
256 | 0 | if (mHashKey.IsEmpty()) |
257 | 0 | return false; |
258 | 0 | |
259 | 0 | key = mHashKey; |
260 | 0 | return true; |
261 | 0 | } |
262 | | |
263 | | void |
264 | | Http2PushedStream::ConnectPushedStream(Http2Stream *stream) |
265 | 0 | { |
266 | 0 | mSession->ConnectPushedStream(stream); |
267 | 0 | } |
268 | | |
269 | | bool |
270 | | Http2PushedStream::IsOrphaned(TimeStamp now) |
271 | 0 | { |
272 | 0 | MOZ_ASSERT(!now.IsNull()); |
273 | 0 |
|
274 | 0 | // if session is not transmitting, and is also not connected to a consumer |
275 | 0 | // stream, and its been like that for too long then it is oprhaned |
276 | 0 |
|
277 | 0 | if (mConsumerStream || mDeferCleanupOnPush) { |
278 | 0 | return false; |
279 | 0 | } |
280 | 0 | |
281 | 0 | if (mOnPushFailed) { |
282 | 0 | return true; |
283 | 0 | } |
284 | 0 | |
285 | 0 | bool rv = ((now - mLastRead).ToSeconds() > 30.0); |
286 | 0 | if (rv) { |
287 | 0 | LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n", |
288 | 0 | mStreamID, (now - mLastRead).ToSeconds())); |
289 | 0 | } |
290 | 0 | return rv; |
291 | 0 | } |
292 | | |
293 | | nsresult |
294 | | Http2PushedStream::GetBufferedData(char *buf, |
295 | | uint32_t count, uint32_t *countWritten) |
296 | 0 | { |
297 | 0 | if (NS_FAILED(mStatus)) |
298 | 0 | return mStatus; |
299 | 0 | |
300 | 0 | nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten); |
301 | 0 | if (NS_FAILED(rv)) |
302 | 0 | return rv; |
303 | 0 | |
304 | 0 | if (!*countWritten) |
305 | 0 | rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK; |
306 | 0 |
|
307 | 0 | return rv; |
308 | 0 | } |
309 | | |
310 | | ////////////////////////////////////////// |
311 | | // Http2PushTransactionBuffer |
312 | | // This is the nsAHttpTransction owned by the stream when the pushed |
313 | | // stream has not yet been matched with a pull request |
314 | | ////////////////////////////////////////// |
315 | | |
316 | | NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer) |
317 | | |
318 | | Http2PushTransactionBuffer::Http2PushTransactionBuffer() |
319 | | : mStatus(NS_OK) |
320 | | , mRequestHead(nullptr) |
321 | | , mPushStream(nullptr) |
322 | | , mIsDone(false) |
323 | | , mBufferedHTTP1Size(kDefaultBufferSize) |
324 | | , mBufferedHTTP1Used(0) |
325 | | , mBufferedHTTP1Consumed(0) |
326 | 0 | { |
327 | 0 | mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size); |
328 | 0 | } |
329 | | |
330 | | Http2PushTransactionBuffer::~Http2PushTransactionBuffer() |
331 | 0 | { |
332 | 0 | delete mRequestHead; |
333 | 0 | } |
334 | | |
335 | | void |
336 | | Http2PushTransactionBuffer::SetConnection(nsAHttpConnection *conn) |
337 | 0 | { |
338 | 0 | } |
339 | | |
340 | | nsAHttpConnection * |
341 | | Http2PushTransactionBuffer::Connection() |
342 | 0 | { |
343 | 0 | return nullptr; |
344 | 0 | } |
345 | | |
346 | | void |
347 | | Http2PushTransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB) |
348 | 0 | { |
349 | 0 | *outCB = nullptr; |
350 | 0 | } |
351 | | |
352 | | void |
353 | | Http2PushTransactionBuffer::OnTransportStatus(nsITransport* transport, |
354 | | nsresult status, int64_t progress) |
355 | 0 | { |
356 | 0 | } |
357 | | |
358 | | nsHttpConnectionInfo * |
359 | | Http2PushTransactionBuffer::ConnectionInfo() |
360 | 0 | { |
361 | 0 | if (!mPushStream) { |
362 | 0 | return nullptr; |
363 | 0 | } |
364 | 0 | if (!mPushStream->Transaction()) { |
365 | 0 | return nullptr; |
366 | 0 | } |
367 | 0 | MOZ_ASSERT(mPushStream->Transaction() != this); |
368 | 0 | return mPushStream->Transaction()->ConnectionInfo(); |
369 | 0 | } |
370 | | |
371 | | bool |
372 | | Http2PushTransactionBuffer::IsDone() |
373 | 0 | { |
374 | 0 | return mIsDone; |
375 | 0 | } |
376 | | |
377 | | nsresult |
378 | | Http2PushTransactionBuffer::Status() |
379 | 0 | { |
380 | 0 | return mStatus; |
381 | 0 | } |
382 | | |
383 | | uint32_t |
384 | | Http2PushTransactionBuffer::Caps() |
385 | 0 | { |
386 | 0 | return 0; |
387 | 0 | } |
388 | | |
389 | | void |
390 | | Http2PushTransactionBuffer::SetDNSWasRefreshed() |
391 | 0 | { |
392 | 0 | } |
393 | | |
394 | | uint64_t |
395 | | Http2PushTransactionBuffer::Available() |
396 | 0 | { |
397 | 0 | return mBufferedHTTP1Used - mBufferedHTTP1Consumed; |
398 | 0 | } |
399 | | |
400 | | nsresult |
401 | | Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader, |
402 | | uint32_t count, uint32_t *countRead) |
403 | 0 | { |
404 | 0 | *countRead = 0; |
405 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
406 | 0 | } |
407 | | |
408 | | nsresult |
409 | | Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer, |
410 | | uint32_t count, uint32_t *countWritten) |
411 | 0 | { |
412 | 0 | if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) { |
413 | 0 | EnsureBuffer(mBufferedHTTP1,mBufferedHTTP1Size + kDefaultBufferSize, |
414 | 0 | mBufferedHTTP1Used, mBufferedHTTP1Size); |
415 | 0 | } |
416 | 0 |
|
417 | 0 | count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used); |
418 | 0 | nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used], |
419 | 0 | count, countWritten); |
420 | 0 | if (NS_SUCCEEDED(rv)) { |
421 | 0 | mBufferedHTTP1Used += *countWritten; |
422 | 0 | } |
423 | 0 | else if (rv == NS_BASE_STREAM_CLOSED) { |
424 | 0 | mIsDone = true; |
425 | 0 | } |
426 | 0 |
|
427 | 0 | if (Available() || mIsDone) { |
428 | 0 | Http2Stream *consumer = mPushStream->GetConsumerStream(); |
429 | 0 |
|
430 | 0 | if (consumer) { |
431 | 0 | LOG3(("Http2PushTransactionBuffer::WriteSegments notifying connection " |
432 | 0 | "consumer data available 0x%X [%" PRIu64 "] done=%d\n", |
433 | 0 | mPushStream->StreamID(), Available(), mIsDone)); |
434 | 0 | mPushStream->ConnectPushedStream(consumer); |
435 | 0 | } |
436 | 0 | } |
437 | 0 |
|
438 | 0 | return rv; |
439 | 0 | } |
440 | | |
441 | | uint32_t |
442 | | Http2PushTransactionBuffer::Http1xTransactionCount() |
443 | 0 | { |
444 | 0 | return 0; |
445 | 0 | } |
446 | | |
447 | | nsHttpRequestHead * |
448 | | Http2PushTransactionBuffer::RequestHead() |
449 | 0 | { |
450 | 0 | if (!mRequestHead) |
451 | 0 | mRequestHead = new nsHttpRequestHead(); |
452 | 0 | return mRequestHead; |
453 | 0 | } |
454 | | |
455 | | nsresult |
456 | | Http2PushTransactionBuffer::TakeSubTransactions( |
457 | | nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) |
458 | 0 | { |
459 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
460 | 0 | } |
461 | | |
462 | | void |
463 | | Http2PushTransactionBuffer::SetProxyConnectFailed() |
464 | 0 | { |
465 | 0 | } |
466 | | |
467 | | void |
468 | | Http2PushTransactionBuffer::Close(nsresult reason) |
469 | 0 | { |
470 | 0 | mStatus = reason; |
471 | 0 | mIsDone = true; |
472 | 0 | } |
473 | | |
474 | | nsresult |
475 | | Http2PushTransactionBuffer::GetBufferedData(char *buf, |
476 | | uint32_t count, |
477 | | uint32_t *countWritten) |
478 | 0 | { |
479 | 0 | *countWritten = std::min(count, static_cast<uint32_t>(Available())); |
480 | 0 | if (*countWritten) { |
481 | 0 | memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten); |
482 | 0 | mBufferedHTTP1Consumed += *countWritten; |
483 | 0 | } |
484 | 0 |
|
485 | 0 | // If all the data has been consumed then reset the buffer |
486 | 0 | if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) { |
487 | 0 | mBufferedHTTP1Consumed = 0; |
488 | 0 | mBufferedHTTP1Used = 0; |
489 | 0 | } |
490 | 0 |
|
491 | 0 | return NS_OK; |
492 | 0 | } |
493 | | |
494 | | } // namespace net |
495 | | } // namespace mozilla |