Coverage Report

Created: 2018-09-25 14:53

/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