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