|
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 "Http2Compression.h" |
|
19 #include "Http2Session.h" |
|
20 #include "Http2Stream.h" |
|
21 #include "Http2Push.h" |
|
22 |
|
23 #include "mozilla/Telemetry.h" |
|
24 #include "nsAlgorithm.h" |
|
25 #include "nsHttp.h" |
|
26 #include "nsHttpHandler.h" |
|
27 #include "nsHttpRequestHead.h" |
|
28 #include "nsISocketTransport.h" |
|
29 #include "prnetdb.h" |
|
30 |
|
31 #ifdef DEBUG |
|
32 // defined by the socket transport service while active |
|
33 extern PRThread *gSocketThread; |
|
34 #endif |
|
35 |
|
36 namespace mozilla { |
|
37 namespace net { |
|
38 |
|
39 Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, |
|
40 Http2Session *session, |
|
41 int32_t priority) |
|
42 : mStreamID(0), |
|
43 mSession(session), |
|
44 mUpstreamState(GENERATING_HEADERS), |
|
45 mState(IDLE), |
|
46 mAllHeadersSent(0), |
|
47 mAllHeadersReceived(0), |
|
48 mTransaction(httpTransaction), |
|
49 mSocketTransport(session->SocketTransport()), |
|
50 mSegmentReader(nullptr), |
|
51 mSegmentWriter(nullptr), |
|
52 mChunkSize(session->SendingChunkSize()), |
|
53 mRequestBlockedOnRead(0), |
|
54 mRecvdFin(0), |
|
55 mRecvdReset(0), |
|
56 mSentReset(0), |
|
57 mCountAsActive(0), |
|
58 mSentFin(0), |
|
59 mSentWaitingFor(0), |
|
60 mSetTCPSocketBuffer(0), |
|
61 mTxInlineFrameSize(Http2Session::kDefaultBufferSize), |
|
62 mTxInlineFrameUsed(0), |
|
63 mTxStreamFrameSize(0), |
|
64 mRequestBodyLenRemaining(0), |
|
65 mLocalUnacked(0), |
|
66 mBlockedOnRwin(false), |
|
67 mTotalSent(0), |
|
68 mTotalRead(0), |
|
69 mPushSource(nullptr) |
|
70 { |
|
71 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
72 |
|
73 LOG3(("Http2Stream::Http2Stream %p", this)); |
|
74 |
|
75 mServerReceiveWindow = session->GetServerInitialStreamWindow(); |
|
76 mClientReceiveWindow = session->PushAllowance(); |
|
77 |
|
78 mTxInlineFrame = new uint8_t[mTxInlineFrameSize]; |
|
79 |
|
80 PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST <= kNormalPriority); |
|
81 |
|
82 // values of priority closer to 0 are higher priority for both |
|
83 // mPriority and the priority argument. They are relative but not |
|
84 // proportional. |
|
85 int32_t httpPriority; |
|
86 if (priority >= nsISupportsPriority::PRIORITY_LOWEST) { |
|
87 httpPriority = kWorstPriority; |
|
88 } else if (priority <= nsISupportsPriority::PRIORITY_HIGHEST) { |
|
89 httpPriority = kBestPriority; |
|
90 } else { |
|
91 httpPriority = kNormalPriority + priority; |
|
92 } |
|
93 MOZ_ASSERT(httpPriority >= 0); |
|
94 mPriority = static_cast<uint32_t>(httpPriority); |
|
95 } |
|
96 |
|
97 Http2Stream::~Http2Stream() |
|
98 { |
|
99 mStreamID = Http2Session::kDeadStreamID; |
|
100 } |
|
101 |
|
102 // ReadSegments() is used to write data down the socket. Generally, HTTP |
|
103 // request data is pulled from the approriate transaction and |
|
104 // converted to HTTP/2 data. Sometimes control data like a window-update is |
|
105 // generated instead. |
|
106 |
|
107 nsresult |
|
108 Http2Stream::ReadSegments(nsAHttpSegmentReader *reader, |
|
109 uint32_t count, |
|
110 uint32_t *countRead) |
|
111 { |
|
112 LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x", |
|
113 this, reader, count, mUpstreamState)); |
|
114 |
|
115 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
116 |
|
117 nsresult rv = NS_ERROR_UNEXPECTED; |
|
118 mRequestBlockedOnRead = 0; |
|
119 |
|
120 if (mRecvdFin || mRecvdReset) { |
|
121 // Don't transmit any request frames if the peer cannot respond |
|
122 LOG3(("Http2Stream %p ReadSegments request stream aborted due to" |
|
123 " response side closure\n", this)); |
|
124 return NS_ERROR_ABORT; |
|
125 } |
|
126 |
|
127 // avoid runt chunks if possible by anticipating |
|
128 // full data frames |
|
129 if (count > (mChunkSize + 8)) { |
|
130 uint32_t numchunks = count / (mChunkSize + 8); |
|
131 count = numchunks * (mChunkSize + 8); |
|
132 } |
|
133 |
|
134 switch (mUpstreamState) { |
|
135 case GENERATING_HEADERS: |
|
136 case GENERATING_BODY: |
|
137 case SENDING_BODY: |
|
138 // Call into the HTTP Transaction to generate the HTTP request |
|
139 // stream. That stream will show up in OnReadSegment(). |
|
140 mSegmentReader = reader; |
|
141 rv = mTransaction->ReadSegments(this, count, countRead); |
|
142 mSegmentReader = nullptr; |
|
143 |
|
144 // Check to see if the transaction's request could be written out now. |
|
145 // If not, mark the stream for callback when writing can proceed. |
|
146 if (NS_SUCCEEDED(rv) && |
|
147 mUpstreamState == GENERATING_HEADERS && |
|
148 !mAllHeadersSent) |
|
149 mSession->TransactionHasDataToWrite(this); |
|
150 |
|
151 // mTxinlineFrameUsed represents any queued un-sent frame. It might |
|
152 // be 0 if there is no such frame, which is not a gurantee that we |
|
153 // don't have more request body to send - just that any data that was |
|
154 // sent comprised a complete HTTP/2 frame. Likewise, a non 0 value is |
|
155 // a queued, but complete, http/2 frame length. |
|
156 |
|
157 // Mark that we are blocked on read if the http transaction needs to |
|
158 // provide more of the request message body and there is nothing queued |
|
159 // for writing |
|
160 if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed) |
|
161 mRequestBlockedOnRead = 1; |
|
162 |
|
163 // If the sending flow control window is open (!mBlockedOnRwin) then |
|
164 // continue sending the request |
|
165 if (!mBlockedOnRwin && |
|
166 !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { |
|
167 LOG3(("Http2Stream::ReadSegments %p 0x%X: Sending request data complete, " |
|
168 "mUpstreamState=%x",this, mStreamID, mUpstreamState)); |
|
169 if (mSentFin) { |
|
170 ChangeState(UPSTREAM_COMPLETE); |
|
171 } else { |
|
172 GenerateDataFrameHeader(0, true); |
|
173 ChangeState(SENDING_FIN_STREAM); |
|
174 mSession->TransactionHasDataToWrite(this); |
|
175 rv = NS_BASE_STREAM_WOULD_BLOCK; |
|
176 } |
|
177 } |
|
178 break; |
|
179 |
|
180 case SENDING_FIN_STREAM: |
|
181 // We were trying to send the FIN-STREAM but were blocked from |
|
182 // sending it out - try again. |
|
183 if (!mSentFin) { |
|
184 mSegmentReader = reader; |
|
185 rv = TransmitFrame(nullptr, nullptr, false); |
|
186 mSegmentReader = nullptr; |
|
187 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, |
|
188 "Transmit Frame should be all or nothing"); |
|
189 if (NS_SUCCEEDED(rv)) |
|
190 ChangeState(UPSTREAM_COMPLETE); |
|
191 } else { |
|
192 rv = NS_OK; |
|
193 mTxInlineFrameUsed = 0; // cancel fin data packet |
|
194 ChangeState(UPSTREAM_COMPLETE); |
|
195 } |
|
196 |
|
197 *countRead = 0; |
|
198 |
|
199 // don't change OK to WOULD BLOCK. we are really done sending if OK |
|
200 break; |
|
201 |
|
202 case UPSTREAM_COMPLETE: |
|
203 *countRead = 0; |
|
204 rv = NS_OK; |
|
205 break; |
|
206 |
|
207 default: |
|
208 MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state"); |
|
209 break; |
|
210 } |
|
211 |
|
212 return rv; |
|
213 } |
|
214 |
|
215 // WriteSegments() is used to read data off the socket. Generally this is |
|
216 // just a call through to the associate nsHttpTransaciton for this stream |
|
217 // for the remaining data bytes indicated by the current DATA frame. |
|
218 |
|
219 nsresult |
|
220 Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer, |
|
221 uint32_t count, |
|
222 uint32_t *countWritten) |
|
223 { |
|
224 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
225 MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); |
|
226 |
|
227 LOG3(("Http2Stream::WriteSegments %p count=%d state=%x", |
|
228 this, count, mUpstreamState)); |
|
229 |
|
230 mSegmentWriter = writer; |
|
231 nsresult rv = mTransaction->WriteSegments(this, count, countWritten); |
|
232 mSegmentWriter = nullptr; |
|
233 |
|
234 return rv; |
|
235 } |
|
236 |
|
237 void |
|
238 Http2Stream::CreatePushHashKey(const nsCString &scheme, |
|
239 const nsCString &hostHeader, |
|
240 uint64_t serial, |
|
241 const nsCSubstring &pathInfo, |
|
242 nsCString &outOrigin, |
|
243 nsCString &outKey) |
|
244 { |
|
245 outOrigin = scheme; |
|
246 outOrigin.Append(NS_LITERAL_CSTRING("://")); |
|
247 outOrigin.Append(hostHeader); |
|
248 |
|
249 outKey = outOrigin; |
|
250 outKey.Append(NS_LITERAL_CSTRING("/[http2.")); |
|
251 outKey.AppendInt(serial); |
|
252 outKey.Append(NS_LITERAL_CSTRING("]")); |
|
253 outKey.Append(pathInfo); |
|
254 } |
|
255 |
|
256 nsresult |
|
257 Http2Stream::ParseHttpRequestHeaders(const char *buf, |
|
258 uint32_t avail, |
|
259 uint32_t *countUsed) |
|
260 { |
|
261 // Returns NS_OK even if the headers are incomplete |
|
262 // set mAllHeadersSent flag if they are complete |
|
263 |
|
264 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
265 MOZ_ASSERT(mUpstreamState == GENERATING_HEADERS); |
|
266 |
|
267 LOG3(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x", |
|
268 this, avail, mUpstreamState)); |
|
269 |
|
270 mFlatHttpRequestHeaders.Append(buf, avail); |
|
271 |
|
272 // We can use the simple double crlf because firefox is the |
|
273 // only client we are parsing |
|
274 int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); |
|
275 |
|
276 if (endHeader == kNotFound) { |
|
277 // We don't have all the headers yet |
|
278 LOG3(("Http2Stream::ParseHttpRequestHeaders %p " |
|
279 "Need more header bytes. Len = %d", |
|
280 this, mFlatHttpRequestHeaders.Length())); |
|
281 *countUsed = avail; |
|
282 return NS_OK; |
|
283 } |
|
284 |
|
285 // We have recvd all the headers, trim the local |
|
286 // buffer of the final empty line, and set countUsed to reflect |
|
287 // the whole header has been consumed. |
|
288 uint32_t oldLen = mFlatHttpRequestHeaders.Length(); |
|
289 mFlatHttpRequestHeaders.SetLength(endHeader + 2); |
|
290 *countUsed = avail - (oldLen - endHeader) + 4; |
|
291 mAllHeadersSent = 1; |
|
292 |
|
293 nsCString hostHeader; |
|
294 nsCString hashkey; |
|
295 mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); |
|
296 |
|
297 CreatePushHashKey(NS_LITERAL_CSTRING("https"), |
|
298 hostHeader, mSession->Serial(), |
|
299 mTransaction->RequestHead()->RequestURI(), |
|
300 mOrigin, hashkey); |
|
301 |
|
302 // check the push cache for GET |
|
303 if (mTransaction->RequestHead()->IsGet()) { |
|
304 // from :scheme, :authority, :path |
|
305 nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); |
|
306 SpdyPushCache *cache = nullptr; |
|
307 if (loadGroupCI) |
|
308 loadGroupCI->GetSpdyPushCache(&cache); |
|
309 |
|
310 Http2PushedStream *pushedStream = nullptr; |
|
311 // we remove the pushedstream from the push cache so that |
|
312 // it will not be used for another GET. This does not destroy the |
|
313 // stream itself - that is done when the transactionhash is done with it. |
|
314 if (cache) |
|
315 pushedStream = cache->RemovePushedStreamHttp2(hashkey); |
|
316 |
|
317 LOG3(("Pushed Stream Lookup " |
|
318 "session=%p key=%s loadgroupci=%p cache=%p hit=%p\n", |
|
319 mSession, hashkey.get(), loadGroupCI, cache, pushedStream)); |
|
320 |
|
321 if (pushedStream) { |
|
322 LOG3(("Pushed Stream Match located id=0x%X key=%s\n", |
|
323 pushedStream->StreamID(), hashkey.get())); |
|
324 pushedStream->SetConsumerStream(this); |
|
325 mPushSource = pushedStream; |
|
326 SetSentFin(true); |
|
327 AdjustPushedPriority(); |
|
328 |
|
329 // This stream has been activated (and thus counts against the concurrency |
|
330 // limit intentionally), but will not be registered via |
|
331 // RegisterStreamID (below) because of the push match. |
|
332 // Release that semaphore count immediately (instead of waiting for |
|
333 // cleanup stream) so we can initiate more pull streams. |
|
334 mSession->MaybeDecrementConcurrent(this); |
|
335 |
|
336 // There is probably pushed data buffered so trigger a read manually |
|
337 // as we can't rely on future network events to do it |
|
338 mSession->ConnectPushedStream(this); |
|
339 return NS_OK; |
|
340 } |
|
341 } |
|
342 |
|
343 // It is now OK to assign a streamID that we are assured will |
|
344 // be monotonically increasing amongst new streams on this |
|
345 // session |
|
346 mStreamID = mSession->RegisterStreamID(this); |
|
347 MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd"); |
|
348 LOG3(("Stream ID 0x%X [session=%p] for URI %s\n", |
|
349 mStreamID, mSession, |
|
350 nsCString(mTransaction->RequestHead()->RequestURI()).get())); |
|
351 |
|
352 if (mStreamID >= 0x80000000) { |
|
353 // streamID must fit in 31 bits. Evading This is theoretically possible |
|
354 // because stream ID assignment is asynchronous to stream creation |
|
355 // because of the protocol requirement that the new stream ID |
|
356 // be monotonically increasing. In reality this is really not possible |
|
357 // because new streams stop being added to a session with millions of |
|
358 // IDs still available and no race condition is going to bridge that gap; |
|
359 // so we can be comfortable on just erroring out for correctness in that |
|
360 // case. |
|
361 LOG3(("Stream assigned out of range ID: 0x%X", mStreamID)); |
|
362 return NS_ERROR_UNEXPECTED; |
|
363 } |
|
364 |
|
365 // Now we need to convert the flat http headers into a set |
|
366 // of HTTP/2 headers by writing to mTxInlineFrame{sz} |
|
367 |
|
368 nsCString compressedData; |
|
369 mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, |
|
370 mTransaction->RequestHead()->Method(), |
|
371 mTransaction->RequestHead()->RequestURI(), |
|
372 hostHeader, |
|
373 NS_LITERAL_CSTRING("https"), |
|
374 compressedData); |
|
375 |
|
376 // Determine whether to put the fin bit on the header frame or whether |
|
377 // to wait for a data packet to put it on. |
|
378 uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY; |
|
379 |
|
380 if (mTransaction->RequestHead()->IsGet() || |
|
381 mTransaction->RequestHead()->IsConnect() || |
|
382 mTransaction->RequestHead()->IsHead()) { |
|
383 // for GET, CONNECT, and HEAD place the fin bit right on the |
|
384 // header packet |
|
385 |
|
386 SetSentFin(true); |
|
387 firstFrameFlags |= Http2Session::kFlag_END_STREAM; |
|
388 } else if (mTransaction->RequestHead()->IsPost() || |
|
389 mTransaction->RequestHead()->IsPut() || |
|
390 mTransaction->RequestHead()->IsOptions()) { |
|
391 // place fin in a data frame even for 0 length messages for iterop |
|
392 } else if (!mRequestBodyLenRemaining) { |
|
393 // for other HTTP extension methods, rely on the content-length |
|
394 // to determine whether or not to put fin on headers |
|
395 SetSentFin(true); |
|
396 firstFrameFlags |= Http2Session::kFlag_END_STREAM; |
|
397 } |
|
398 |
|
399 // split this one HEADERS frame up into N HEADERS + CONTINUATION frames if it exceeds the |
|
400 // 2^14-1 limit for 1 frame. Do it by inserting header size gaps in the existing |
|
401 // frame for the new headers and for the first one a priority field. There is |
|
402 // no question this is ugly, but a 16KB HEADERS frame should be a long |
|
403 // tail event, so this is really just for correctness and a nop in the base case. |
|
404 // |
|
405 |
|
406 MOZ_ASSERT(!mTxInlineFrameUsed); |
|
407 |
|
408 uint32_t dataLength = compressedData.Length(); |
|
409 uint32_t maxFrameData = Http2Session::kMaxFrameData - 4; // 4 byes for priority |
|
410 uint32_t numFrames = 1; |
|
411 |
|
412 if (dataLength > maxFrameData) { |
|
413 numFrames += ((dataLength - maxFrameData) + Http2Session::kMaxFrameData - 1) / |
|
414 Http2Session::kMaxFrameData; |
|
415 MOZ_ASSERT (numFrames > 1); |
|
416 } |
|
417 |
|
418 // note that we could still have 1 frame for 0 bytes of data. that's ok. |
|
419 |
|
420 uint32_t messageSize = dataLength; |
|
421 messageSize += 12; // header frame overhead |
|
422 messageSize += (numFrames - 1) * 8; // continuation frames overhead |
|
423 |
|
424 Http2Session::EnsureBuffer(mTxInlineFrame, |
|
425 dataLength + messageSize, |
|
426 mTxInlineFrameUsed, |
|
427 mTxInlineFrameSize); |
|
428 |
|
429 mTxInlineFrameUsed += messageSize; |
|
430 LOG3(("%p Generating %d bytes of HEADERS for stream 0x%X at priority %u frames %u\n", |
|
431 this, mTxInlineFrameUsed, mStreamID, mPriority, numFrames)); |
|
432 |
|
433 uint32_t outputOffset = 0; |
|
434 uint32_t compressedDataOffset = 0; |
|
435 for (uint32_t idx = 0; idx < numFrames; ++idx) { |
|
436 uint32_t flags, frameLen; |
|
437 bool lastFrame = (idx == numFrames - 1); |
|
438 |
|
439 flags = 0; |
|
440 frameLen = maxFrameData; |
|
441 if (!idx) { |
|
442 flags |= firstFrameFlags; |
|
443 // Only the first frame needs the 4-byte offset |
|
444 maxFrameData = Http2Session::kMaxFrameData; |
|
445 } |
|
446 if (lastFrame) { |
|
447 frameLen = dataLength; |
|
448 flags |= Http2Session::kFlag_END_HEADERS; |
|
449 } |
|
450 dataLength -= frameLen; |
|
451 |
|
452 mSession->CreateFrameHeader( |
|
453 mTxInlineFrame.get() + outputOffset, |
|
454 frameLen + (idx ? 0 : 4), |
|
455 (idx) ? Http2Session::FRAME_TYPE_CONTINUATION : Http2Session::FRAME_TYPE_HEADERS, |
|
456 flags, mStreamID); |
|
457 outputOffset += 8; |
|
458 |
|
459 if (!idx) { |
|
460 uint32_t priority = PR_htonl(mPriority); |
|
461 memcpy (mTxInlineFrame.get() + outputOffset, &priority, 4); |
|
462 outputOffset += 4; |
|
463 } |
|
464 |
|
465 memcpy(mTxInlineFrame.get() + outputOffset, |
|
466 compressedData.BeginReading() + compressedDataOffset, frameLen); |
|
467 compressedDataOffset += frameLen; |
|
468 outputOffset += frameLen; |
|
469 } |
|
470 |
|
471 Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, compressedData.Length()); |
|
472 |
|
473 // The size of the input headers is approximate |
|
474 uint32_t ratio = |
|
475 compressedData.Length() * 100 / |
|
476 (11 + mTransaction->RequestHead()->RequestURI().Length() + |
|
477 mFlatHttpRequestHeaders.Length()); |
|
478 |
|
479 const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); |
|
480 int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); |
|
481 while (true) { |
|
482 int32_t startIndex = crlfIndex + 2; |
|
483 |
|
484 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); |
|
485 if (crlfIndex == -1) |
|
486 break; |
|
487 |
|
488 int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, |
|
489 crlfIndex - startIndex); |
|
490 if (colonIndex == -1) |
|
491 break; |
|
492 |
|
493 nsDependentCSubstring name = Substring(beginBuffer + startIndex, |
|
494 beginBuffer + colonIndex); |
|
495 // all header names are lower case in spdy |
|
496 ToLowerCase(name); |
|
497 |
|
498 if (name.Equals("content-length")) { |
|
499 nsCString *val = new nsCString(); |
|
500 int32_t valueIndex = colonIndex + 1; |
|
501 while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') |
|
502 ++valueIndex; |
|
503 |
|
504 nsDependentCSubstring v = Substring(beginBuffer + valueIndex, |
|
505 beginBuffer + crlfIndex); |
|
506 val->Append(v); |
|
507 |
|
508 int64_t len; |
|
509 if (nsHttp::ParseInt64(val->get(), nullptr, &len)) |
|
510 mRequestBodyLenRemaining = len; |
|
511 break; |
|
512 } |
|
513 } |
|
514 |
|
515 mFlatHttpRequestHeaders.Truncate(); |
|
516 Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); |
|
517 return NS_OK; |
|
518 } |
|
519 |
|
520 void |
|
521 Http2Stream::AdjustInitialWindow() |
|
522 { |
|
523 // The default initial_window is sized for pushed streams. When we |
|
524 // generate a client pulled stream we want to disable flow control for |
|
525 // the stream with a window update. Do the same for pushed streams |
|
526 // when they connect to a pull. |
|
527 |
|
528 // >0 even numbered IDs are pushed streams. |
|
529 // odd numbered IDs are pulled streams. |
|
530 // 0 is the sink for a pushed stream. |
|
531 Http2Stream *stream = this; |
|
532 if (!mStreamID) { |
|
533 MOZ_ASSERT(mPushSource); |
|
534 if (!mPushSource) |
|
535 return; |
|
536 stream = mPushSource; |
|
537 MOZ_ASSERT(stream->mStreamID); |
|
538 MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream |
|
539 |
|
540 // If the pushed stream has recvd a FIN, there is no reason to update |
|
541 // the window |
|
542 if (stream->RecvdFin() || stream->RecvdReset()) |
|
543 return; |
|
544 } |
|
545 |
|
546 uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; |
|
547 Http2Session::EnsureBuffer(mTxInlineFrame, |
|
548 mTxInlineFrameUsed + 12, |
|
549 mTxInlineFrameUsed, |
|
550 mTxInlineFrameSize); |
|
551 mTxInlineFrameUsed += 12; |
|
552 |
|
553 mSession->CreateFrameHeader(packet, 4, |
|
554 Http2Session::FRAME_TYPE_WINDOW_UPDATE, |
|
555 0, stream->mStreamID); |
|
556 |
|
557 MOZ_ASSERT(mClientReceiveWindow <= ASpdySession::kInitialRwin); |
|
558 uint32_t bump = ASpdySession::kInitialRwin - mClientReceiveWindow; |
|
559 mClientReceiveWindow += bump; |
|
560 bump = PR_htonl(bump); |
|
561 memcpy(packet + 8, &bump, 4); |
|
562 LOG3(("AdjustInitialwindow increased flow control window %p 0x%X\n", |
|
563 this, stream->mStreamID)); |
|
564 } |
|
565 |
|
566 void |
|
567 Http2Stream::AdjustPushedPriority() |
|
568 { |
|
569 // >0 even numbered IDs are pushed streams. odd numbered IDs are pulled streams. |
|
570 // 0 is the sink for a pushed stream. |
|
571 |
|
572 if (mStreamID || !mPushSource) |
|
573 return; |
|
574 |
|
575 MOZ_ASSERT(mPushSource->mStreamID && !(mPushSource->mStreamID & 1)); |
|
576 |
|
577 // If the pushed stream has recvd a FIN, there is no reason to update |
|
578 // the window |
|
579 if (mPushSource->RecvdFin() || mPushSource->RecvdReset()) |
|
580 return; |
|
581 |
|
582 uint8_t *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; |
|
583 Http2Session::EnsureBuffer(mTxInlineFrame, |
|
584 mTxInlineFrameUsed + 12, |
|
585 mTxInlineFrameUsed, |
|
586 mTxInlineFrameSize); |
|
587 mTxInlineFrameUsed += 12; |
|
588 |
|
589 mSession->CreateFrameHeader(packet, 4, |
|
590 Http2Session::FRAME_TYPE_PRIORITY, 0, |
|
591 mPushSource->mStreamID); |
|
592 |
|
593 uint32_t newPriority = PR_htonl(mPriority); |
|
594 mPushSource->SetPriority(newPriority); |
|
595 memcpy(packet + 8, &newPriority, 4); |
|
596 |
|
597 LOG3(("AdjustPushedPriority %p id 0x%X to %X\n", this, mPushSource->mStreamID, |
|
598 newPriority)); |
|
599 } |
|
600 |
|
601 void |
|
602 Http2Stream::UpdateTransportReadEvents(uint32_t count) |
|
603 { |
|
604 mTotalRead += count; |
|
605 mTransaction->OnTransportStatus(mSocketTransport, |
|
606 NS_NET_STATUS_RECEIVING_FROM, |
|
607 mTotalRead); |
|
608 } |
|
609 |
|
610 void |
|
611 Http2Stream::UpdateTransportSendEvents(uint32_t count) |
|
612 { |
|
613 mTotalSent += count; |
|
614 |
|
615 // normally on non-windows platform we use TCP autotuning for |
|
616 // the socket buffers, and this works well (managing enough |
|
617 // buffers for BDP while conserving memory) for HTTP even when |
|
618 // it creates really deep queues. However this 'buffer bloat' is |
|
619 // a problem for http/2 because it ruins the low latency properties |
|
620 // necessary for PING and cancel to work meaningfully. |
|
621 // |
|
622 // If this stream represents a large upload, disable autotuning for |
|
623 // the session and cap the send buffers by default at 128KB. |
|
624 // (10Mbit/sec @ 100ms) |
|
625 // |
|
626 uint32_t bufferSize = gHttpHandler->SpdySendBufferSize(); |
|
627 if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) { |
|
628 mSetTCPSocketBuffer = 1; |
|
629 mSocketTransport->SetSendBufferSize(bufferSize); |
|
630 } |
|
631 |
|
632 if (mUpstreamState != SENDING_FIN_STREAM) |
|
633 mTransaction->OnTransportStatus(mSocketTransport, |
|
634 NS_NET_STATUS_SENDING_TO, |
|
635 mTotalSent); |
|
636 |
|
637 if (!mSentWaitingFor && !mRequestBodyLenRemaining) { |
|
638 mSentWaitingFor = 1; |
|
639 mTransaction->OnTransportStatus(mSocketTransport, |
|
640 NS_NET_STATUS_WAITING_FOR, |
|
641 0); |
|
642 } |
|
643 } |
|
644 |
|
645 nsresult |
|
646 Http2Stream::TransmitFrame(const char *buf, |
|
647 uint32_t *countUsed, |
|
648 bool forceCommitment) |
|
649 { |
|
650 // If TransmitFrame returns SUCCESS than all the data is sent (or at least |
|
651 // buffered at the session level), if it returns WOULD_BLOCK then none of |
|
652 // the data is sent. |
|
653 |
|
654 // You can call this function with no data and no out parameter in order to |
|
655 // flush internal buffers that were previously blocked on writing. You can |
|
656 // of course feed new data to it as well. |
|
657 |
|
658 LOG3(("Http2Stream::TransmitFrame %p inline=%d stream=%d", |
|
659 this, mTxInlineFrameUsed, mTxStreamFrameSize)); |
|
660 if (countUsed) |
|
661 *countUsed = 0; |
|
662 |
|
663 if (!mTxInlineFrameUsed) { |
|
664 MOZ_ASSERT(!buf); |
|
665 return NS_OK; |
|
666 } |
|
667 |
|
668 MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit"); |
|
669 MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader"); |
|
670 MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed), |
|
671 "TransmitFrame arguments inconsistent"); |
|
672 |
|
673 uint32_t transmittedCount; |
|
674 nsresult rv; |
|
675 |
|
676 // In the (relatively common) event that we have a small amount of data |
|
677 // split between the inlineframe and the streamframe, then move the stream |
|
678 // data into the inlineframe via copy in order to coalesce into one write. |
|
679 // Given the interaction with ssl this is worth the small copy cost. |
|
680 if (mTxStreamFrameSize && mTxInlineFrameUsed && |
|
681 mTxStreamFrameSize < Http2Session::kDefaultBufferSize && |
|
682 mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) { |
|
683 LOG3(("Coalesce Transmit")); |
|
684 memcpy (mTxInlineFrame + mTxInlineFrameUsed, |
|
685 buf, mTxStreamFrameSize); |
|
686 if (countUsed) |
|
687 *countUsed += mTxStreamFrameSize; |
|
688 mTxInlineFrameUsed += mTxStreamFrameSize; |
|
689 mTxStreamFrameSize = 0; |
|
690 } |
|
691 |
|
692 rv = |
|
693 mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed, |
|
694 forceCommitment); |
|
695 |
|
696 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { |
|
697 MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK"); |
|
698 mSession->TransactionHasDataToWrite(this); |
|
699 } |
|
700 if (NS_FAILED(rv)) // this will include WOULD_BLOCK |
|
701 return rv; |
|
702 |
|
703 // This function calls mSegmentReader->OnReadSegment to report the actual http/2 |
|
704 // bytes through to the session object and then the HttpConnection which calls |
|
705 // the socket write function. It will accept all of the inline and stream |
|
706 // data because of the above 'commitment' even if it has to buffer |
|
707 |
|
708 rv = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()), |
|
709 mTxInlineFrameUsed, |
|
710 &transmittedCount); |
|
711 LOG3(("Http2Stream::TransmitFrame for inline BufferOutput session=%p " |
|
712 "stream=%p result %x len=%d", |
|
713 mSession, this, rv, transmittedCount)); |
|
714 |
|
715 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, |
|
716 "inconsistent inline commitment result"); |
|
717 |
|
718 if (NS_FAILED(rv)) |
|
719 return rv; |
|
720 |
|
721 MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed, |
|
722 "inconsistent inline commitment count"); |
|
723 |
|
724 Http2Session::LogIO(mSession, this, "Writing from Inline Buffer", |
|
725 reinterpret_cast<char*>(mTxInlineFrame.get()), |
|
726 transmittedCount); |
|
727 |
|
728 if (mTxStreamFrameSize) { |
|
729 if (!buf) { |
|
730 // this cannot happen |
|
731 MOZ_ASSERT(false, "Stream transmit with null buf argument to " |
|
732 "TransmitFrame()"); |
|
733 LOG3(("Stream transmit with null buf argument to TransmitFrame()\n")); |
|
734 return NS_ERROR_UNEXPECTED; |
|
735 } |
|
736 |
|
737 // If there is already data buffered, just add to that to form |
|
738 // a single TLS Application Data Record - otherwise skip the memcpy |
|
739 if (mSession->AmountOfOutputBuffered()) { |
|
740 rv = mSession->BufferOutput(buf, mTxStreamFrameSize, |
|
741 &transmittedCount); |
|
742 } else { |
|
743 rv = mSession->OnReadSegment(buf, mTxStreamFrameSize, |
|
744 &transmittedCount); |
|
745 } |
|
746 |
|
747 LOG3(("Http2Stream::TransmitFrame for regular session=%p " |
|
748 "stream=%p result %x len=%d", |
|
749 mSession, this, rv, transmittedCount)); |
|
750 |
|
751 MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, |
|
752 "inconsistent stream commitment result"); |
|
753 |
|
754 if (NS_FAILED(rv)) |
|
755 return rv; |
|
756 |
|
757 MOZ_ASSERT(transmittedCount == mTxStreamFrameSize, |
|
758 "inconsistent stream commitment count"); |
|
759 |
|
760 Http2Session::LogIO(mSession, this, "Writing from Transaction Buffer", |
|
761 buf, transmittedCount); |
|
762 |
|
763 *countUsed += mTxStreamFrameSize; |
|
764 } |
|
765 |
|
766 mSession->FlushOutputQueue(); |
|
767 |
|
768 // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 |
|
769 UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); |
|
770 |
|
771 mTxInlineFrameUsed = 0; |
|
772 mTxStreamFrameSize = 0; |
|
773 |
|
774 return NS_OK; |
|
775 } |
|
776 |
|
777 void |
|
778 Http2Stream::ChangeState(enum upstreamStateType newState) |
|
779 { |
|
780 LOG3(("Http2Stream::ChangeState() %p from %X to %X", |
|
781 this, mUpstreamState, newState)); |
|
782 mUpstreamState = newState; |
|
783 } |
|
784 |
|
785 void |
|
786 Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) |
|
787 { |
|
788 LOG3(("Http2Stream::GenerateDataFrameHeader %p len=%d last=%d", |
|
789 this, dataLength, lastFrame)); |
|
790 |
|
791 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
792 MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty"); |
|
793 MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty"); |
|
794 |
|
795 uint8_t frameFlags = 0; |
|
796 if (lastFrame) { |
|
797 frameFlags |= Http2Session::kFlag_END_STREAM; |
|
798 if (dataLength) |
|
799 SetSentFin(true); |
|
800 } |
|
801 |
|
802 mSession->CreateFrameHeader(mTxInlineFrame.get(), |
|
803 dataLength, |
|
804 Http2Session::FRAME_TYPE_DATA, |
|
805 frameFlags, mStreamID); |
|
806 |
|
807 mTxInlineFrameUsed = 8; |
|
808 mTxStreamFrameSize = dataLength; |
|
809 } |
|
810 |
|
811 // ConvertHeaders is used to convert the response headers |
|
812 // into HTTP/1 format and report some telemetry |
|
813 nsresult |
|
814 Http2Stream::ConvertResponseHeaders(Http2Decompressor *decompressor, |
|
815 nsACString &aHeadersIn, |
|
816 nsACString &aHeadersOut) |
|
817 { |
|
818 aHeadersOut.Truncate(); |
|
819 aHeadersOut.SetCapacity(aHeadersIn.Length() + 512); |
|
820 |
|
821 nsresult rv = |
|
822 decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()), |
|
823 aHeadersIn.Length(), |
|
824 aHeadersOut); |
|
825 if (NS_FAILED(rv)) { |
|
826 LOG3(("Http2Stream::ConvertHeaders %p decode Error\n", this)); |
|
827 return NS_ERROR_ILLEGAL_VALUE; |
|
828 } |
|
829 |
|
830 nsAutoCString status; |
|
831 decompressor->GetStatus(status); |
|
832 if (status.IsEmpty()) { |
|
833 LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this)); |
|
834 return NS_ERROR_ILLEGAL_VALUE; |
|
835 } |
|
836 |
|
837 if (aHeadersIn.Length() && aHeadersOut.Length()) { |
|
838 Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length()); |
|
839 uint32_t ratio = |
|
840 aHeadersIn.Length() * 100 / aHeadersOut.Length(); |
|
841 Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); |
|
842 } |
|
843 |
|
844 aHeadersIn.Truncate(); |
|
845 aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n")); |
|
846 LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading())); |
|
847 return NS_OK; |
|
848 } |
|
849 |
|
850 // ConvertHeaders is used to convert the response headers |
|
851 // into HTTP/1 format and report some telemetry |
|
852 nsresult |
|
853 Http2Stream::ConvertPushHeaders(Http2Decompressor *decompressor, |
|
854 nsACString &aHeadersIn, |
|
855 nsACString &aHeadersOut) |
|
856 { |
|
857 aHeadersOut.Truncate(); |
|
858 nsresult rv = |
|
859 decompressor->DecodeHeaderBlock(reinterpret_cast<const uint8_t *>(aHeadersIn.BeginReading()), |
|
860 aHeadersIn.Length(), |
|
861 aHeadersOut); |
|
862 if (NS_FAILED(rv)) { |
|
863 LOG3(("Http2Stream::ConvertPushHeaders %p Error\n", this)); |
|
864 return NS_ERROR_ILLEGAL_VALUE; |
|
865 } |
|
866 |
|
867 nsCString method; |
|
868 decompressor->GetHost(mHeaderHost); |
|
869 decompressor->GetScheme(mHeaderScheme); |
|
870 decompressor->GetPath(mHeaderPath); |
|
871 |
|
872 if (mHeaderHost.IsEmpty() || mHeaderScheme.IsEmpty() || mHeaderPath.IsEmpty()) { |
|
873 LOG3(("Http2Stream::ConvertPushHeaders %p Error - missing required " |
|
874 "host=%s scheme=%s path=%s\n", this, mHeaderHost.get(), mHeaderScheme.get(), |
|
875 mHeaderPath.get())); |
|
876 return NS_ERROR_ILLEGAL_VALUE; |
|
877 } |
|
878 |
|
879 decompressor->GetMethod(method); |
|
880 if (!method.Equals(NS_LITERAL_CSTRING("GET"))) { |
|
881 LOG3(("Http2Stream::ConvertPushHeaders %p Error - method not supported: %s\n", |
|
882 this, method.get())); |
|
883 return NS_ERROR_NOT_IMPLEMENTED; |
|
884 } |
|
885 |
|
886 aHeadersIn.Truncate(); |
|
887 LOG (("decoded push headers are:\n%s", aHeadersOut.BeginReading())); |
|
888 return NS_OK; |
|
889 } |
|
890 |
|
891 void |
|
892 Http2Stream::Close(nsresult reason) |
|
893 { |
|
894 mTransaction->Close(reason); |
|
895 } |
|
896 |
|
897 bool |
|
898 Http2Stream::AllowFlowControlledWrite() |
|
899 { |
|
900 return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0); |
|
901 } |
|
902 |
|
903 void |
|
904 Http2Stream::UpdateServerReceiveWindow(int32_t delta) |
|
905 { |
|
906 mServerReceiveWindow += delta; |
|
907 |
|
908 if (mBlockedOnRwin && AllowFlowControlledWrite()) { |
|
909 LOG3(("Http2Stream::UpdateServerReceived UnPause %p 0x%X " |
|
910 "Open stream window\n", this, mStreamID)); |
|
911 mSession->TransactionHasDataToWrite(this); } |
|
912 } |
|
913 |
|
914 void |
|
915 Http2Stream::SetPriority(uint32_t newVal) |
|
916 { |
|
917 mPriority = std::min(newVal, 0x7fffffffU); |
|
918 } |
|
919 |
|
920 void |
|
921 Http2Stream::SetRecvdFin(bool aStatus) |
|
922 { |
|
923 mRecvdFin = aStatus ? 1 : 0; |
|
924 if (!aStatus) |
|
925 return; |
|
926 |
|
927 if (mState == OPEN || mState == RESERVED_BY_REMOTE) { |
|
928 mState = CLOSED_BY_REMOTE; |
|
929 } else if (mState == CLOSED_BY_LOCAL) { |
|
930 mState = CLOSED; |
|
931 } |
|
932 } |
|
933 |
|
934 void |
|
935 Http2Stream::SetSentFin(bool aStatus) |
|
936 { |
|
937 mSentFin = aStatus ? 1 : 0; |
|
938 if (!aStatus) |
|
939 return; |
|
940 |
|
941 if (mState == OPEN || mState == RESERVED_BY_REMOTE) { |
|
942 mState = CLOSED_BY_LOCAL; |
|
943 } else if (mState == CLOSED_BY_REMOTE) { |
|
944 mState = CLOSED; |
|
945 } |
|
946 } |
|
947 |
|
948 void |
|
949 Http2Stream::SetRecvdReset(bool aStatus) |
|
950 { |
|
951 mRecvdReset = aStatus ? 1 : 0; |
|
952 if (!aStatus) |
|
953 return; |
|
954 mState = CLOSED; |
|
955 } |
|
956 |
|
957 void |
|
958 Http2Stream::SetSentReset(bool aStatus) |
|
959 { |
|
960 mSentReset = aStatus ? 1 : 0; |
|
961 if (!aStatus) |
|
962 return; |
|
963 mState = CLOSED; |
|
964 } |
|
965 |
|
966 //----------------------------------------------------------------------------- |
|
967 // nsAHttpSegmentReader |
|
968 //----------------------------------------------------------------------------- |
|
969 |
|
970 nsresult |
|
971 Http2Stream::OnReadSegment(const char *buf, |
|
972 uint32_t count, |
|
973 uint32_t *countRead) |
|
974 { |
|
975 LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x", |
|
976 this, count, mUpstreamState)); |
|
977 |
|
978 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
979 MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader"); |
|
980 |
|
981 nsresult rv = NS_ERROR_UNEXPECTED; |
|
982 uint32_t dataLength; |
|
983 |
|
984 switch (mUpstreamState) { |
|
985 case GENERATING_HEADERS: |
|
986 // The buffer is the HTTP request stream, including at least part of the |
|
987 // HTTP request header. This state's job is to build a HEADERS frame |
|
988 // from the header information. count is the number of http bytes available |
|
989 // (which may include more than the header), and in countRead we return |
|
990 // the number of those bytes that we consume (i.e. the portion that are |
|
991 // header bytes) |
|
992 |
|
993 rv = ParseHttpRequestHeaders(buf, count, countRead); |
|
994 if (NS_FAILED(rv)) |
|
995 return rv; |
|
996 LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d", |
|
997 this, *countRead, count, mAllHeadersSent)); |
|
998 if (mAllHeadersSent) { |
|
999 SetHTTPState(OPEN); |
|
1000 AdjustInitialWindow(); |
|
1001 // This version of TransmitFrame cannot block |
|
1002 rv = TransmitFrame(nullptr, nullptr, true); |
|
1003 ChangeState(GENERATING_BODY); |
|
1004 break; |
|
1005 } |
|
1006 MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data"); |
|
1007 break; |
|
1008 |
|
1009 case GENERATING_BODY: |
|
1010 // if there is session flow control and either the stream window is active and |
|
1011 // exhaused or the session window is exhausted then suspend |
|
1012 if (!AllowFlowControlledWrite()) { |
|
1013 *countRead = 0; |
|
1014 LOG3(("Http2Stream this=%p, id 0x%X request body suspended because " |
|
1015 "remote window is stream=%ld session=%ld.\n", this, mStreamID, |
|
1016 mServerReceiveWindow, mSession->ServerSessionWindow())); |
|
1017 mBlockedOnRwin = true; |
|
1018 return NS_BASE_STREAM_WOULD_BLOCK; |
|
1019 } |
|
1020 mBlockedOnRwin = false; |
|
1021 |
|
1022 // The chunk is the smallest of: availableData, configured chunkSize, |
|
1023 // stream window, session window, or 14 bit framing limit. |
|
1024 // Its amazing we send anything at all. |
|
1025 dataLength = std::min(count, mChunkSize); |
|
1026 |
|
1027 if (dataLength > Http2Session::kMaxFrameData) |
|
1028 dataLength = Http2Session::kMaxFrameData; |
|
1029 |
|
1030 if (dataLength > mSession->ServerSessionWindow()) |
|
1031 dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow()); |
|
1032 |
|
1033 if (dataLength > mServerReceiveWindow) |
|
1034 dataLength = static_cast<uint32_t>(mServerReceiveWindow); |
|
1035 |
|
1036 LOG3(("Http2Stream this=%p id 0x%X send calculation " |
|
1037 "avail=%d chunksize=%d stream window=%d session window=%d " |
|
1038 "max frame=%d USING=%d\n", this, mStreamID, |
|
1039 count, mChunkSize, mServerReceiveWindow, mSession->ServerSessionWindow(), |
|
1040 Http2Session::kMaxFrameData, dataLength)); |
|
1041 |
|
1042 mSession->DecrementServerSessionWindow(dataLength); |
|
1043 mServerReceiveWindow -= dataLength; |
|
1044 |
|
1045 LOG3(("Http2Stream %p id %x request len remaining %d, " |
|
1046 "count avail %d, chunk used %d", |
|
1047 this, mStreamID, mRequestBodyLenRemaining, count, dataLength)); |
|
1048 if (dataLength > mRequestBodyLenRemaining) |
|
1049 return NS_ERROR_UNEXPECTED; |
|
1050 mRequestBodyLenRemaining -= dataLength; |
|
1051 GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining); |
|
1052 ChangeState(SENDING_BODY); |
|
1053 // NO BREAK |
|
1054 |
|
1055 case SENDING_BODY: |
|
1056 MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b"); |
|
1057 rv = TransmitFrame(buf, countRead, false); |
|
1058 MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed, |
|
1059 "Transmit Frame should be all or nothing"); |
|
1060 |
|
1061 LOG3(("TransmitFrame() rv=%x returning %d data bytes. " |
|
1062 "Header is %d Body is %d.", |
|
1063 rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize)); |
|
1064 |
|
1065 // normalize a partial write with a WOULD_BLOCK into just a partial write |
|
1066 // as some code will take WOULD_BLOCK to mean an error with nothing |
|
1067 // written (e.g. nsHttpTransaction::ReadRequestSegment() |
|
1068 if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) |
|
1069 rv = NS_OK; |
|
1070 |
|
1071 // If that frame was all sent, look for another one |
|
1072 if (!mTxInlineFrameUsed) |
|
1073 ChangeState(GENERATING_BODY); |
|
1074 break; |
|
1075 |
|
1076 case SENDING_FIN_STREAM: |
|
1077 MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment"); |
|
1078 break; |
|
1079 |
|
1080 default: |
|
1081 MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state"); |
|
1082 break; |
|
1083 } |
|
1084 |
|
1085 return rv; |
|
1086 } |
|
1087 |
|
1088 //----------------------------------------------------------------------------- |
|
1089 // nsAHttpSegmentWriter |
|
1090 //----------------------------------------------------------------------------- |
|
1091 |
|
1092 nsresult |
|
1093 Http2Stream::OnWriteSegment(char *buf, |
|
1094 uint32_t count, |
|
1095 uint32_t *countWritten) |
|
1096 { |
|
1097 LOG3(("Http2Stream::OnWriteSegment %p count=%d state=%x 0x%X\n", |
|
1098 this, count, mUpstreamState, mStreamID)); |
|
1099 |
|
1100 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); |
|
1101 MOZ_ASSERT(mSegmentWriter); |
|
1102 |
|
1103 if (!mPushSource) |
|
1104 return mSegmentWriter->OnWriteSegment(buf, count, countWritten); |
|
1105 |
|
1106 nsresult rv; |
|
1107 rv = mPushSource->GetBufferedData(buf, count, countWritten); |
|
1108 if (NS_FAILED(rv)) |
|
1109 return rv; |
|
1110 |
|
1111 mSession->ConnectPushedStream(this); |
|
1112 return NS_OK; |
|
1113 } |
|
1114 |
|
1115 } // namespace mozilla::net |
|
1116 } // namespace mozilla |
|
1117 |