Node.js  v8.x
Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine
node_http2_core-inl.h
Go to the documentation of this file.
1 #ifndef SRC_NODE_HTTP2_CORE_INL_H_
2 #define SRC_NODE_HTTP2_CORE_INL_H_
3 
4 #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5 
6 #include "node_http2_core.h"
7 #include "node_internals.h" // arraysize
8 #include "freelist.h"
9 
10 namespace node {
11 namespace http2 {
12 
13 #define FREELIST_MAX 1024
14 
15 #define LINKED_LIST_ADD(list, item) \
16  do { \
17  if (list ## _tail_ == nullptr) { \
18  list ## _head_ = item; \
19  list ## _tail_ = item; \
20  } else { \
21  list ## _tail_->next = item; \
22  list ## _tail_ = item; \
23  } \
24  } while (0);
25 
26 extern Freelist<nghttp2_data_chunk_t, FREELIST_MAX>
28 
29 extern Freelist<Nghttp2Stream, FREELIST_MAX> stream_free_list;
30 
31 extern Freelist<nghttp2_header_list, FREELIST_MAX> header_free_list;
32 
33 extern Freelist<nghttp2_data_chunks_t, FREELIST_MAX>
35 
36 #ifdef NODE_DEBUG_HTTP2
37 inline int Nghttp2Session::OnNghttpError(nghttp2_session* session,
38  const char* message,
39  size_t len,
40  void* user_data) {
41  Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
42  DEBUG_HTTP2("Nghttp2Session %s: Error '%.*s'\n",
43  handle->TypeName(handle->type()), len, message);
44  return 0;
45 }
46 #endif
47 
48 // nghttp2 calls this at the beginning a new HEADERS or PUSH_PROMISE frame.
49 // We use it to ensure that an Nghttp2Stream instance is allocated to store
50 // the state.
51 inline int Nghttp2Session::OnBeginHeadersCallback(nghttp2_session* session,
52  const nghttp2_frame* frame,
53  void* user_data) {
54  Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
55  int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
56  frame->push_promise.promised_stream_id :
57  frame->hd.stream_id;
58  DEBUG_HTTP2("Nghttp2Session %s: beginning headers for stream %d\n",
59  handle->TypeName(handle->type()), id);
60 
61  Nghttp2Stream* stream = handle->FindStream(id);
62  if (stream == nullptr) {
63  Nghttp2Stream::Init(id, handle, frame->headers.cat);
64  } else {
65  stream->StartHeaders(frame->headers.cat);
66  }
67  return 0;
68 }
69 
70 // nghttp2 calls this once for every header name-value pair in a HEADERS
71 // or PUSH_PROMISE block. CONTINUATION frames are handled automatically
72 // and transparently so we do not need to worry about those at all.
73 inline int Nghttp2Session::OnHeaderCallback(nghttp2_session* session,
74  const nghttp2_frame* frame,
75  nghttp2_rcbuf *name,
76  nghttp2_rcbuf *value,
77  uint8_t flags,
78  void* user_data) {
79  Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
80  int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
81  frame->push_promise.promised_stream_id :
82  frame->hd.stream_id;
83  Nghttp2Stream* stream = handle->FindStream(id);
84  nghttp2_header_list* header = header_free_list.pop();
85  header->name = name;
86  header->value = value;
87  nghttp2_rcbuf_incref(name);
88  nghttp2_rcbuf_incref(value);
89  LINKED_LIST_ADD(stream->current_headers, header);
90  return 0;
91 }
92 
93 
94 // When nghttp2 has completely processed a frame, it calls OnFrameReceive.
95 // It is our responsibility to delegate out from there. We can ignore most
96 // control frames since nghttp2 will handle those for us.
97 inline int Nghttp2Session::OnFrameReceive(nghttp2_session* session,
98  const nghttp2_frame* frame,
99  void* user_data) {
100  Nghttp2Session* handle = static_cast<Nghttp2Session*>(user_data);
101  DEBUG_HTTP2("Nghttp2Session %s: complete frame received: type: %d\n",
102  handle->TypeName(handle->type()), frame->hd.type);
103  bool ack;
104  switch (frame->hd.type) {
105  case NGHTTP2_DATA:
106  handle->HandleDataFrame(frame);
107  break;
108  case NGHTTP2_PUSH_PROMISE:
109  case NGHTTP2_HEADERS:
110  handle->HandleHeadersFrame(frame);
111  break;
112  case NGHTTP2_SETTINGS:
113  ack = (frame->hd.flags & NGHTTP2_FLAG_ACK) == NGHTTP2_FLAG_ACK;
114  handle->OnSettings(ack);
115  break;
116  case NGHTTP2_PRIORITY:
117  handle->HandlePriorityFrame(frame);
118  break;
119  case NGHTTP2_GOAWAY:
120  handle->HandleGoawayFrame(frame);
121  break;
122  default:
123  break;
124  }
125  return 0;
126 }
127 
128 inline int Nghttp2Session::OnFrameNotSent(nghttp2_session *session,
129  const nghttp2_frame *frame,
130  int error_code,
131  void *user_data) {
132  Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
133  DEBUG_HTTP2("Nghttp2Session %s: frame type %d was not sent, code: %d\n",
134  handle->TypeName(handle->type()), frame->hd.type, error_code);
135  // Do not report if the frame was not sent due to the session closing
136  if (error_code != NGHTTP2_ERR_SESSION_CLOSING &&
137  error_code != NGHTTP2_ERR_STREAM_CLOSED &&
138  error_code != NGHTTP2_ERR_STREAM_CLOSING)
139  handle->OnFrameError(frame->hd.stream_id, frame->hd.type, error_code);
140  return 0;
141 }
142 
143 inline int Nghttp2Session::OnInvalidHeader(nghttp2_session* session,
144  const nghttp2_frame* frame,
145  nghttp2_rcbuf* name,
146  nghttp2_rcbuf* value,
147  uint8_t flags,
148  void* user_data) {
149  // Ignore invalid header fields by default.
150  return 0;
151 }
152 
153 // Called when nghttp2 closes a stream, either in response to an RST_STREAM
154 // frame or the stream closing naturally on it's own
155 inline int Nghttp2Session::OnStreamClose(nghttp2_session *session,
156  int32_t id,
157  uint32_t code,
158  void *user_data) {
159  Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
160  DEBUG_HTTP2("Nghttp2Session %s: stream %d closed, code: %d\n",
161  handle->TypeName(handle->type()), id, code);
162  Nghttp2Stream *stream = handle->FindStream(id);
163  // Intentionally ignore the callback if the stream does not exist
164  if (stream != nullptr)
165  stream->Close(code);
166  return 0;
167 }
168 
169 // Called by nghttp2 to collect the data while a file response is sent.
170 // The buf is the DATA frame buffer that needs to be filled with at most
171 // length bytes. flags is used to control what nghttp2 does next.
172 inline ssize_t Nghttp2Session::OnStreamReadFD(nghttp2_session *session,
173  int32_t id,
174  uint8_t *buf,
175  size_t length,
176  uint32_t *flags,
177  nghttp2_data_source *source,
178  void *user_data) {
179  Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
180  DEBUG_HTTP2("Nghttp2Session %s: reading outbound file data for stream %d\n",
181  handle->TypeName(handle->type()), id);
182  Nghttp2Stream *stream = handle->FindStream(id);
183 
184  int fd = source->fd;
185  int64_t offset = stream->fd_offset_;
186  ssize_t numchars = 0;
187 
188  if (stream->fd_length_ >= 0 &&
189  stream->fd_length_ < static_cast<int64_t>(length))
190  length = stream->fd_length_;
191 
192  uv_buf_t data;
193  data.base = reinterpret_cast<char *>(buf);
194  data.len = length;
195 
196  uv_fs_t read_req;
197 
198  if (length > 0) {
199  numchars = uv_fs_read(handle->loop_,
200  &read_req,
201  fd, &data, 1,
202  offset, nullptr);
203  uv_fs_req_cleanup(&read_req);
204  }
205 
206  // Close the stream with an error if reading fails
207  if (numchars < 0)
208  return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
209 
210  // Update the read offset for the next read
211  stream->fd_offset_ += numchars;
212  stream->fd_length_ -= numchars;
213 
214  // if numchars < length, assume that we are done.
215  if (static_cast<size_t>(numchars) < length || length <= 0) {
216  DEBUG_HTTP2("Nghttp2Session %s: no more data for stream %d\n",
217  handle->TypeName(handle->type()), id);
218  *flags |= NGHTTP2_DATA_FLAG_EOF;
219  GetTrailers(session, handle, stream, flags);
220  }
221 
222  return numchars;
223 }
224 
225 // Called by nghttp2 to collect the data to pack within a DATA frame.
226 // The buf is the DATA frame buffer that needs to be filled with at most
227 // length bytes. flags is used to control what nghttp2 does next.
228 inline ssize_t Nghttp2Session::OnStreamRead(nghttp2_session *session,
229  int32_t id,
230  uint8_t *buf,
231  size_t length,
232  uint32_t *flags,
233  nghttp2_data_source *source,
234  void *user_data) {
235  Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
236  DEBUG_HTTP2("Nghttp2Session %s: reading outbound data for stream %d\n",
237  handle->TypeName(handle->type()), id);
238  Nghttp2Stream *stream = handle->FindStream(id);
239  size_t remaining = length;
240  size_t offset = 0;
241 
242  // While there is data in the queue, copy data into buf until it is full.
243  // There may be data left over, which will be sent the next time nghttp
244  // calls this callback.
245  while (stream->queue_head_ != nullptr) {
246  DEBUG_HTTP2("Nghttp2Session %s: processing outbound data chunk\n",
247  handle->TypeName(handle->type()));
248  nghttp2_stream_write_queue *head = stream->queue_head_;
249  while (stream->queue_head_index_ < head->nbufs) {
250  if (remaining == 0)
251  goto end;
252 
253  unsigned int n = stream->queue_head_index_;
254  // len is the number of bytes in head->bufs[n] that are yet to be written
255  size_t len = head->bufs[n].len - stream->queue_head_offset_;
256  size_t bytes_to_write = len < remaining ? len : remaining;
257  memcpy(buf + offset,
258  head->bufs[n].base + stream->queue_head_offset_,
259  bytes_to_write);
260  offset += bytes_to_write;
261  remaining -= bytes_to_write;
262  if (bytes_to_write < len) {
263  stream->queue_head_offset_ += bytes_to_write;
264  } else {
265  stream->queue_head_index_++;
266  stream->queue_head_offset_ = 0;
267  }
268  }
269  stream->queue_head_offset_ = 0;
270  stream->queue_head_index_ = 0;
271  stream->queue_head_ = head->next;
272  head->cb(head->req, 0);
273  delete head;
274  }
275  stream->queue_tail_ = nullptr;
276 
277 end:
278  // If we are no longer writable and there is no more data in the queue,
279  // then we need to set the NGHTTP2_DATA_FLAG_EOF flag.
280  // If we are still writable but there is not yet any data to send, set the
281  // NGHTTP2_ERR_DEFERRED flag. This will put the stream into a pending state
282  // that will wait for data to become available.
283  // If neither of these flags are set, then nghttp2 will call this callback
284  // again to get the data for the next DATA frame.
285  int writable = stream->queue_head_ != nullptr || stream->IsWritable();
286  if (offset == 0 && writable && stream->queue_head_ == nullptr) {
287  DEBUG_HTTP2("Nghttp2Session %s: deferring stream %d\n",
288  handle->TypeName(handle->type()), id);
289  return NGHTTP2_ERR_DEFERRED;
290  }
291  if (!writable) {
292  DEBUG_HTTP2("Nghttp2Session %s: no more data for stream %d\n",
293  handle->TypeName(handle->type()), id);
294  *flags |= NGHTTP2_DATA_FLAG_EOF;
295 
296  GetTrailers(session, handle, stream, flags);
297  }
298  CHECK(offset <= length);
299  return offset;
300 }
301 
302 // Called by nghttp2 when it needs to determine how much padding to apply
303 // to a DATA or HEADERS frame
304 inline ssize_t Nghttp2Session::OnSelectPadding(nghttp2_session *session,
305  const nghttp2_frame *frame,
306  size_t maxPayloadLen,
307  void *user_data) {
308  Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
309  CHECK(handle->HasGetPaddingCallback());
310  ssize_t padding = handle->GetPadding(frame->hd.length, maxPayloadLen);
311  DEBUG_HTTP2("Nghttp2Session %s: using padding, size: %d\n",
312  handle->TypeName(handle->type()), padding);
313  return padding;
314 }
315 
316 // Called by nghttp2 multiple times while processing a DATA frame
317 inline int Nghttp2Session::OnDataChunkReceived(nghttp2_session *session,
318  uint8_t flags,
319  int32_t id,
320  const uint8_t *data,
321  size_t len,
322  void *user_data) {
323  Nghttp2Session *handle = static_cast<Nghttp2Session *>(user_data);
324  DEBUG_HTTP2("Nghttp2Session %s: buffering data chunk for stream %d, size: "
325  "%d, flags: %d\n", handle->TypeName(handle->type()),
326  id, len, flags);
327  Nghttp2Stream *stream = handle->FindStream(id);
328  nghttp2_data_chunk_t *chunk = data_chunk_free_list.pop();
329  chunk->buf = uv_buf_init(new char[len], len);
330  memcpy(chunk->buf.base, data, len);
331  if (stream->data_chunks_tail_ == nullptr) {
332  stream->data_chunks_head_ =
333  stream->data_chunks_tail_ = chunk;
334  } else {
335  stream->data_chunks_tail_->next = chunk;
336  stream->data_chunks_tail_ = chunk;
337  }
338  return 0;
339 }
340 
341 inline void Nghttp2Session::GetTrailers(nghttp2_session *session,
342  Nghttp2Session *handle,
343  Nghttp2Stream *stream,
344  uint32_t *flags) {
345  if (stream->GetTrailers()) {
346  // Only when we are done sending the last chunk of data do we check for
347  // any trailing headers that are to be sent. This is the only opportunity
348  // we have to make this check. If there are trailers, then the
349  // NGHTTP2_DATA_FLAG_NO_END_STREAM flag must be set.
350  SubmitTrailers submit_trailers{handle, stream, flags};
351  handle->OnTrailers(stream, submit_trailers);
352  }
353 }
354 
355 inline void Nghttp2Session::SubmitTrailers::Submit(nghttp2_nv *trailers,
356  size_t length) const {
357  if (length == 0)
358  return;
359  DEBUG_HTTP2("Nghttp2Session %s: sending trailers for stream %d, "
360  "count: %d\n", handle_->TypeName(handle_->type()),
361  stream_->id(), length);
362  *flags_ |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
363  nghttp2_submit_trailer(handle_->session_,
364  stream_->id(),
365  trailers,
366  length);
367 }
368 
369 // See: https://nghttp2.org/documentation/nghttp2_submit_shutdown_notice.html
370 inline void Nghttp2Session::SubmitShutdownNotice() {
371  DEBUG_HTTP2("Nghttp2Session %s: submitting shutdown notice\n",
372  TypeName(type()));
373  nghttp2_submit_shutdown_notice(session_);
374 }
375 
376 // Sends a SETTINGS frame on the current session
377 // Note that this *should* send a SETTINGS frame even if niv == 0 and there
378 // are no settings entries to send.
379 inline int Nghttp2Session::SubmitSettings(const nghttp2_settings_entry iv[],
380  size_t niv) {
381  DEBUG_HTTP2("Nghttp2Session %s: submitting settings, count: %d\n",
382  TypeName(type()), niv);
383  return nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv, niv);
384 }
385 
386 // Returns the Nghttp2Stream associated with the given id, or nullptr if none
387 inline Nghttp2Stream* Nghttp2Session::FindStream(int32_t id) {
388  auto s = streams_.find(id);
389  if (s != streams_.end()) {
390  DEBUG_HTTP2("Nghttp2Session %s: stream %d found\n",
391  TypeName(type()), id);
392  return s->second;
393  } else {
394  DEBUG_HTTP2("Nghttp2Session %s: stream %d not found\n",
395  TypeName(type()), id);
396  return nullptr;
397  }
398 }
399 
400 // Flushes any received queued chunks of data out to the JS layer
401 inline void Nghttp2Stream::FlushDataChunks(bool done) {
402  while (data_chunks_head_ != nullptr) {
403  DEBUG_HTTP2("Nghttp2Stream %d: emitting data chunk\n", id_);
404  nghttp2_data_chunk_t* item = data_chunks_head_;
405  data_chunks_head_ = item->next;
406  // item will be passed to the Buffer instance and freed on gc
407  session_->OnDataChunk(this, item);
408  }
409  data_chunks_tail_ = nullptr;
410  if (done)
411  session_->OnDataChunk(this, nullptr);
412 }
413 
414 // Passes all of the the chunks for a data frame out to the JS layer
415 // The chunks are collected as the frame is being processed and sent out
416 // to the JS side only when the frame is fully processed.
417 inline void Nghttp2Session::HandleDataFrame(const nghttp2_frame* frame) {
418  int32_t id = frame->hd.stream_id;
419  DEBUG_HTTP2("Nghttp2Session %s: handling data frame for stream %d\n",
420  TypeName(type()), id);
421  Nghttp2Stream* stream = this->FindStream(id);
422  // If the stream does not exist, something really bad happened
423  CHECK_NE(stream, nullptr);
424  bool done = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) ==
425  NGHTTP2_FLAG_END_STREAM;
426  stream->FlushDataChunks(done);
427 }
428 
429 // Passes all of the collected headers for a HEADERS frame out to the JS layer.
430 // The headers are collected as the frame is being processed and sent out
431 // to the JS side only when the frame is fully processed.
432 inline void Nghttp2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
433  int32_t id = (frame->hd.type == NGHTTP2_PUSH_PROMISE) ?
434  frame->push_promise.promised_stream_id : frame->hd.stream_id;
435  DEBUG_HTTP2("Nghttp2Session %s: handling headers frame for stream %d\n",
436  TypeName(type()), id);
437  Nghttp2Stream* stream = FindStream(id);
438  // If the stream does not exist, something really bad happened
439  CHECK_NE(stream, nullptr);
440  OnHeaders(stream,
441  stream->headers(),
442  stream->headers_category(),
443  frame->hd.flags);
444  stream->FreeHeaders();
445 }
446 
447 // Notifies the JS layer that a PRIORITY frame has been received
448 inline void Nghttp2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
449  nghttp2_priority priority_frame = frame->priority;
450  int32_t id = frame->hd.stream_id;
451  DEBUG_HTTP2("Nghttp2Session %s: handling priority frame for stream %d\n",
452  TypeName(type()), id);
453  // Ignore the priority frame if stream ID is <= 0
454  // This actually should never happen because nghttp2 should treat this as
455  // an error condition that terminates the session.
456  if (id > 0) {
457  nghttp2_priority_spec spec = priority_frame.pri_spec;
458  OnPriority(id, spec.stream_id, spec.weight, spec.exclusive);
459  }
460 }
461 
462 // Notifies the JS layer that a GOAWAY frame has been received
463 inline void Nghttp2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
464  nghttp2_goaway goaway_frame = frame->goaway;
465  DEBUG_HTTP2("Nghttp2Session %s: handling goaway frame\n", TypeName(type()));
466 
467  OnGoAway(goaway_frame.last_stream_id,
468  goaway_frame.error_code,
469  goaway_frame.opaque_data,
470  goaway_frame.opaque_data_len);
471 }
472 
473 // Prompts nghttp2 to flush the queue of pending data frames
474 inline void Nghttp2Session::SendPendingData() {
475  DEBUG_HTTP2("Nghttp2Session %s: Sending pending data\n", TypeName(type()));
476  // Do not attempt to send data on the socket if the destroying flag has
477  // been set. That means everything is shutting down and the socket
478  // will not be usable.
479  if (IsDestroying())
480  return;
481  const uint8_t* data;
482  ssize_t len = 0;
483  size_t ncopy = 0;
484  uv_buf_t buf;
485  AllocateSend(SEND_BUFFER_RECOMMENDED_SIZE, &buf);
486  while (nghttp2_session_want_write(session_)) {
487  len = nghttp2_session_mem_send(session_, &data);
488  CHECK_GE(len, 0); // If this is less than zero, we're out of memory
489  // While len is greater than 0, send a chunk
490  while (len > 0) {
491  ncopy = len;
492  if (ncopy > buf.len)
493  ncopy = buf.len;
494  memcpy(buf.base, data, ncopy);
495  Send(&buf, ncopy);
496  len -= ncopy;
497  CHECK_GE(len, 0); // This should never be less than zero
498  }
499  }
500 }
501 
502 // Initialize the Nghttp2Session handle by creating and
503 // assigning the Nghttp2Session instance and associated
504 // uv_loop_t.
505 inline int Nghttp2Session::Init(uv_loop_t* loop,
506  const nghttp2_session_type type,
507  nghttp2_option* options,
508  nghttp2_mem* mem) {
509  DEBUG_HTTP2("Nghttp2Session %s: initializing session\n",
510  TypeName(type));
511  loop_ = loop;
512  session_type_ = type;
513  destroying_ = false;
514  int ret = 0;
515 
516  nghttp2_session_callbacks* callbacks
517  = callback_struct_saved[HasGetPaddingCallback() ? 1 : 0].callbacks;
518 
519  nghttp2_option* opts;
520  if (options != nullptr) {
521  opts = options;
522  } else {
523  nghttp2_option_new(&opts);
524  }
525 
526  switch (type) {
527  case NGHTTP2_SESSION_SERVER:
528  ret = nghttp2_session_server_new3(&session_,
529  callbacks,
530  this,
531  opts,
532  mem);
533  break;
534  case NGHTTP2_SESSION_CLIENT:
535  ret = nghttp2_session_client_new3(&session_,
536  callbacks,
537  this,
538  opts,
539  mem);
540  break;
541  }
542  if (opts != options) {
543  nghttp2_option_del(opts);
544  }
545 
546  // For every node::Http2Session instance, there is a uv_prep_t handle
547  // whose callback is triggered on every tick of the event loop. When
548  // run, nghttp2 is prompted to send any queued data it may have stored.
549  uv_prepare_init(loop_, &prep_);
550  uv_prepare_start(&prep_, [](uv_prepare_t* t) {
551  Nghttp2Session* session = ContainerOf(&Nghttp2Session::prep_, t);
552  session->SendPendingData();
553  });
554 // uv_unref(reinterpret_cast<uv_handle_t*>(&prep_));
555  return ret;
556 }
557 
558 inline void Nghttp2Session::MarkDestroying() {
559  destroying_ = true;
560 }
561 
562 inline int Nghttp2Session::Free() {
563  CHECK(session_ != nullptr);
564  DEBUG_HTTP2("Nghttp2Session %s: freeing session\n", TypeName(type()));
565  // Stop the loop
566  uv_prepare_stop(&prep_);
567  auto PrepClose = [](uv_handle_t* handle) {
568  Nghttp2Session* session =
569  ContainerOf(&Nghttp2Session::prep_,
570  reinterpret_cast<uv_prepare_t*>(handle));
571  session->OnFreeSession();
572  };
573  uv_close(reinterpret_cast<uv_handle_t*>(&prep_), PrepClose);
574  nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
575  nghttp2_session_del(session_);
576  session_ = nullptr;
577  loop_ = nullptr;
578  DEBUG_HTTP2("Nghttp2Session %s: session freed\n", TypeName(type()));
579  return 1;
580 }
581 
582 // Write data received from the socket to the underlying nghttp2_session.
583 inline ssize_t Nghttp2Session::Write(const uv_buf_t* bufs, unsigned int nbufs) {
584  size_t total = 0;
585  for (unsigned int n = 0; n < nbufs; n++) {
586  ssize_t ret =
587  nghttp2_session_mem_recv(session_,
588  reinterpret_cast<uint8_t*>(bufs[n].base),
589  bufs[n].len);
590  if (ret < 0) {
591  return ret;
592  } else {
593  total += ret;
594  }
595  }
596  SendPendingData();
597  return total;
598 }
599 
600 inline void Nghttp2Session::AddStream(Nghttp2Stream* stream) {
601  streams_[stream->id()] = stream;
602 }
603 
604 // Removes a stream instance from this session
605 inline void Nghttp2Session::RemoveStream(int32_t id) {
606  streams_.erase(id);
607 }
608 
609 // Implementation for Nghttp2Stream functions
610 
611 inline Nghttp2Stream* Nghttp2Stream::Init(
612  int32_t id,
613  Nghttp2Session* session,
614  nghttp2_headers_category category,
615  bool getTrailers) {
616  DEBUG_HTTP2("Nghttp2Stream %d: initializing stream\n", id);
617  Nghttp2Stream* stream = stream_free_list.pop();
618  stream->ResetState(id, session, category, getTrailers);
619  session->AddStream(stream);
620  return stream;
621 }
622 
623 
624 // Resets the state of the stream instance to defaults
625 inline void Nghttp2Stream::ResetState(
626  int32_t id,
627  Nghttp2Session* session,
628  nghttp2_headers_category category,
629  bool getTrailers) {
630  DEBUG_HTTP2("Nghttp2Stream %d: resetting stream state\n", id);
631  session_ = session;
632  queue_head_ = nullptr;
633  queue_tail_ = nullptr;
634  data_chunks_head_ = nullptr;
635  data_chunks_tail_ = nullptr;
636  current_headers_head_ = nullptr;
637  current_headers_tail_ = nullptr;
638  current_headers_category_ = category;
639  flags_ = NGHTTP2_STREAM_FLAG_NONE;
640  id_ = id;
641  code_ = NGHTTP2_NO_ERROR;
642  prev_local_window_size_ = 65535;
643  queue_head_index_ = 0;
644  queue_head_offset_ = 0;
645  getTrailers_ = getTrailers;
646 }
647 
648 
649 inline void Nghttp2Stream::Destroy() {
650  DEBUG_HTTP2("Nghttp2Stream %d: destroying stream\n", id_);
651  // Do nothing if this stream instance is already destroyed
652  if (IsDestroyed())
653  return;
654  flags_ |= NGHTTP2_STREAM_FLAG_DESTROYED;
655  Nghttp2Session* session = this->session_;
656 
657  if (session != nullptr) {
658  // Remove this stream from the associated session
659  session_->RemoveStream(this->id());
660  session_ = nullptr;
661  }
662 
663  // Free any remaining incoming data chunks.
664  while (data_chunks_head_ != nullptr) {
665  nghttp2_data_chunk_t* chunk = data_chunks_head_;
666  data_chunks_head_ = chunk->next;
667  delete[] chunk->buf.base;
668  data_chunk_free_list.push(chunk);
669  }
670  data_chunks_tail_ = nullptr;
671 
672  // Free any remaining outgoing data chunks.
673  while (queue_head_ != nullptr) {
674  nghttp2_stream_write_queue* head = queue_head_;
675  queue_head_ = head->next;
676  head->cb(head->req, UV_ECANCELED);
677  delete head;
678  }
679  queue_tail_ = nullptr;
680 
681  // Free any remaining headers
682  FreeHeaders();
683 
684  // Return this stream instance to the freelist
685  stream_free_list.push(this);
686 }
687 
688 inline void Nghttp2Stream::FreeHeaders() {
689  DEBUG_HTTP2("Nghttp2Stream %d: freeing headers\n", id_);
690  while (current_headers_head_ != nullptr) {
691  DEBUG_HTTP2("Nghttp2Stream %d: freeing header item\n", id_);
692  nghttp2_header_list* item = current_headers_head_;
693  current_headers_head_ = item->next;
694  header_free_list.push(item);
695  }
696  current_headers_tail_ = nullptr;
697 }
698 
699 // Submit informational headers for a stream.
700 inline int Nghttp2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) {
701  DEBUG_HTTP2("Nghttp2Stream %d: sending informational headers, count: %d\n",
702  id_, len);
703  CHECK_GT(len, 0);
704  return nghttp2_submit_headers(session_->session(),
705  NGHTTP2_FLAG_NONE,
706  id_, nullptr,
707  nva, len, nullptr);
708 }
709 
710 inline int Nghttp2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
711  bool silent) {
712  DEBUG_HTTP2("Nghttp2Stream %d: sending priority spec\n", id_);
713  return silent ?
714  nghttp2_session_change_stream_priority(session_->session(),
715  id_, prispec) :
716  nghttp2_submit_priority(session_->session(),
717  NGHTTP2_FLAG_NONE,
718  id_, prispec);
719 }
720 
721 // Submit an RST_STREAM frame
722 inline int Nghttp2Stream::SubmitRstStream(const uint32_t code) {
723  DEBUG_HTTP2("Nghttp2Stream %d: sending rst-stream, code: %d\n", id_, code);
724  session_->SendPendingData();
725  return nghttp2_submit_rst_stream(session_->session(),
726  NGHTTP2_FLAG_NONE,
727  id_,
728  code);
729 }
730 
731 // Submit a push promise.
732 inline int32_t Nghttp2Stream::SubmitPushPromise(
733  nghttp2_nv* nva,
734  size_t len,
735  Nghttp2Stream** assigned,
736  bool emptyPayload) {
737  CHECK_GT(len, 0);
738  DEBUG_HTTP2("Nghttp2Stream %d: sending push promise\n", id_);
739  int32_t ret = nghttp2_submit_push_promise(session_->session(),
740  NGHTTP2_FLAG_NONE,
741  id_, nva, len,
742  nullptr);
743  if (ret > 0) {
744  auto stream = Nghttp2Stream::Init(ret, session_);
745  if (emptyPayload) stream->Shutdown();
746  if (assigned != nullptr) *assigned = stream;
747  }
748  return ret;
749 }
750 
751 // Initiate a response. If the nghttp2_stream is still writable by
752 // the time this is called, then an nghttp2_data_provider will be
753 // initialized, causing at least one (possibly empty) data frame to
754 // be sent.
755 inline int Nghttp2Stream::SubmitResponse(nghttp2_nv* nva,
756  size_t len,
757  bool emptyPayload,
758  bool getTrailers) {
759  CHECK_GT(len, 0);
760  DEBUG_HTTP2("Nghttp2Stream %d: submitting response\n", id_);
761  getTrailers_ = getTrailers;
762  nghttp2_data_provider* provider = nullptr;
763  nghttp2_data_provider prov;
764  prov.source.ptr = this;
765  prov.read_callback = Nghttp2Session::OnStreamRead;
766  if (!emptyPayload && IsWritable())
767  provider = &prov;
768 
769  return nghttp2_submit_response(session_->session(), id_,
770  nva, len, provider);
771 }
772 
773 // Initiate a response that contains data read from a file descriptor.
774 inline int Nghttp2Stream::SubmitFile(int fd,
775  nghttp2_nv* nva, size_t len,
776  int64_t offset,
777  int64_t length,
778  bool getTrailers) {
779  CHECK_GT(len, 0);
780  CHECK_GT(fd, 0);
781  DEBUG_HTTP2("Nghttp2Stream %d: submitting file\n", id_);
782  getTrailers_ = getTrailers;
783  nghttp2_data_provider prov;
784  prov.source.ptr = this;
785  prov.source.fd = fd;
786  prov.read_callback = Nghttp2Session::OnStreamReadFD;
787 
788  if (offset > 0) fd_offset_ = offset;
789  if (length > -1) fd_length_ = length;
790 
791  return nghttp2_submit_response(session_->session(), id_,
792  nva, len, &prov);
793 }
794 
795 // Initiate a request. If writable is true (the default), then
796 // an nghttp2_data_provider will be initialized, causing at
797 // least one (possibly empty) data frame to to be sent.
798 inline int32_t Nghttp2Session::SubmitRequest(
799  nghttp2_priority_spec* prispec,
800  nghttp2_nv* nva,
801  size_t len,
802  Nghttp2Stream** assigned,
803  bool emptyPayload,
804  bool getTrailers) {
805  CHECK_GT(len, 0);
806  DEBUG_HTTP2("Nghttp2Session: submitting request\n");
807  nghttp2_data_provider* provider = nullptr;
808  nghttp2_data_provider prov;
809  prov.source.ptr = this;
810  prov.read_callback = OnStreamRead;
811  if (!emptyPayload)
812  provider = &prov;
813  int32_t ret = nghttp2_submit_request(session_,
814  prispec, nva, len,
815  provider, nullptr);
816  // Assign the Nghttp2Stream handle
817  if (ret > 0) {
818  Nghttp2Stream* stream = Nghttp2Stream::Init(ret, this,
819  NGHTTP2_HCAT_HEADERS,
820  getTrailers);
821  if (emptyPayload) stream->Shutdown();
822  if (assigned != nullptr) *assigned = stream;
823  }
824  return ret;
825 }
826 
827 // Queue the given set of uv_but_t handles for writing to an
828 // nghttp2_stream. The callback will be invoked once the chunks
829 // of data have been flushed to the underlying nghttp2_session.
830 // Note that this does *not* mean that the data has been flushed
831 // to the socket yet.
832 inline int Nghttp2Stream::Write(nghttp2_stream_write_t* req,
833  const uv_buf_t bufs[],
834  unsigned int nbufs,
835  nghttp2_stream_write_cb cb) {
836  if (!IsWritable()) {
837  if (cb != nullptr)
838  cb(req, UV_EOF);
839  return 0;
840  }
841  DEBUG_HTTP2("Nghttp2Stream %d: queuing buffers to send, count: %d\n",
842  id_, nbufs);
843  nghttp2_stream_write_queue* item = new nghttp2_stream_write_queue;
844  item->cb = cb;
845  item->req = req;
846  item->nbufs = nbufs;
847  item->bufs.AllocateSufficientStorage(nbufs);
848  req->handle = this;
849  req->item = item;
850  memcpy(*(item->bufs), bufs, nbufs * sizeof(*bufs));
851 
852  if (queue_head_ == nullptr) {
853  queue_head_ = item;
854  queue_tail_ = item;
855  } else {
856  queue_tail_->next = item;
857  queue_tail_ = item;
858  }
859  nghttp2_session_resume_data(session_->session(), id_);
860  return 0;
861 }
862 
863 inline void Nghttp2Stream::ReadStart() {
864  // Has no effect if IsReading() is true.
865  if (IsReading())
866  return;
867  DEBUG_HTTP2("Nghttp2Stream %d: start reading\n", id_);
868  if (IsPaused()) {
869  // If handle->reading is less than zero, read_start had never previously
870  // been called. If handle->reading is zero, reading had started and read
871  // stop had been previously called, meaning that the flow control window
872  // has been explicitly set to zero. Reset the flow control window now to
873  // restart the flow of data.
874  nghttp2_session_set_local_window_size(session_->session(),
875  NGHTTP2_FLAG_NONE,
876  id_,
877  prev_local_window_size_);
878  }
879  flags_ |= NGHTTP2_STREAM_FLAG_READ_START;
880  flags_ &= ~NGHTTP2_STREAM_FLAG_READ_PAUSED;
881 
882  // Flush any queued data chunks immediately out to the JS layer
883  FlushDataChunks();
884 }
885 
886 inline void Nghttp2Stream::ReadStop() {
887  DEBUG_HTTP2("Nghttp2Stream %d: stop reading\n", id_);
888  // Has no effect if IsReading() is false, which will happen if we either
889  // have not started reading yet at all (NGHTTP2_STREAM_FLAG_READ_START is not
890  // set) or if we're already paused (NGHTTP2_STREAM_FLAG_READ_PAUSED is set.
891  if (!IsReading())
892  return;
893  flags_ |= NGHTTP2_STREAM_FLAG_READ_PAUSED;
894 
895  // When not reading, explicitly set the local window size to 0 so that
896  // the peer does not keep sending data that has to be buffered
897  int32_t ret =
898  nghttp2_session_get_stream_local_window_size(session_->session(), id_);
899  if (ret >= 0)
900  prev_local_window_size_ = ret;
901  nghttp2_session_set_local_window_size(session_->session(),
902  NGHTTP2_FLAG_NONE,
903  id_, 0);
904 }
905 
906 nghttp2_data_chunks_t::~nghttp2_data_chunks_t() {
907  for (unsigned int n = 0; n < nbufs; n++) {
908  free(buf[n].base);
909  }
910 }
911 
912 Nghttp2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
913  nghttp2_session_callbacks_new(&callbacks);
914  nghttp2_session_callbacks_set_on_begin_headers_callback(
915  callbacks, OnBeginHeadersCallback);
916  nghttp2_session_callbacks_set_on_header_callback2(
917  callbacks, OnHeaderCallback);
918  nghttp2_session_callbacks_set_on_frame_recv_callback(
919  callbacks, OnFrameReceive);
920  nghttp2_session_callbacks_set_on_stream_close_callback(
921  callbacks, OnStreamClose);
922  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
923  callbacks, OnDataChunkReceived);
924  nghttp2_session_callbacks_set_on_frame_not_send_callback(
925  callbacks, OnFrameNotSent);
926  nghttp2_session_callbacks_set_on_invalid_header_callback2(
927  callbacks, OnInvalidHeader);
928 
929 #ifdef NODE_DEBUG_HTTP2
930  nghttp2_session_callbacks_set_error_callback(
931  callbacks, OnNghttpError);
932 #endif
933 
934  if (kHasGetPaddingCallback) {
935  nghttp2_session_callbacks_set_select_padding_callback(
936  callbacks, OnSelectPadding);
937  }
938 }
939 
940 Nghttp2Session::Callbacks::~Callbacks() {
941  nghttp2_session_callbacks_del(callbacks);
942 }
943 
944 Nghttp2Session::SubmitTrailers::SubmitTrailers(
945  Nghttp2Session* handle,
946  Nghttp2Stream* stream,
947  uint32_t* flags)
948  : handle_(handle), stream_(stream), flags_(flags) { }
949 
950 } // namespace http2
951 } // namespace node
952 
953 #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
954 
955 #endif // SRC_NODE_HTTP2_CORE_INL_H_
Freelist< Nghttp2Stream, FREELIST_MAX > stream_free_list
Definition: node_http2.cc:81
unsigned char * buf
Definition: cares_wrap.cc:483
int len
Definition: cares_wrap.cc:485
std::string source
Definition: module_wrap.cc:306
union node::cares_wrap::@8::CaresAsyncData::@0 data
this done
Definition: v8ustack.d:366
Freelist< nghttp2_data_chunks_t, FREELIST_MAX > data_chunks_free_list
Definition: node_http2.cc:86
dtrace s
Definition: v8ustack.d:615
dtrace t
Definition: v8ustack.d:582
uv_fs_t req
Definition: node_file.cc:374
dtrace n
Definition: v8ustack.d:531
Freelist< nghttp2_header_list, FREELIST_MAX > header_free_list
Definition: node_http2.cc:83
void Init(int *argc, const char **argv, int *exec_argc, const char ***exec_argv)
Definition: node.cc:4351
Freelist< nghttp2_data_chunk_t, FREELIST_MAX > data_chunk_free_list
Definition: node_http2.cc:79