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