| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -821,6 +821,16 @@ that. | |||
| 821 | 821 | ||
| 822 | 822 | Occurs with multiple attempts to shutdown an HTTP/2 session. | |
| 823 | 823 | ||
| 824 | + <a id="ERR_HTTP2_ALTSVC_INVALID_ORIGIN"></a> | ||
| 825 | + ### ERR_HTTP2_ALTSVC_INVALID_ORIGIN | ||
| 826 | + | ||
| 827 | + HTTP/2 ALTSVC frames require a valid origin. | ||
| 828 | + | ||
| 829 | + <a id="ERR_HTTP2_ALTSVC_LENGTH"></a> | ||
| 830 | + ### ERR_HTTP2_ALTSVC_LENGTH | ||
| 831 | + | ||
| 832 | + HTTP/2 ALTSVC frames are limited to a maximum of 16,382 payload bytes. | ||
| 833 | + | ||
| 824 | 834 | <a id="ERR_HTTP2_CONNECT_AUTHORITY"></a> | |
| 825 | 835 | ### ERR_HTTP2_CONNECT_AUTHORITY | |
| 826 | 836 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -558,11 +558,97 @@ added: REPLACEME | |||
| 558 | 558 | Calls [`unref()`][`net.Socket.prototype.unref`] on this `Http2Session` | |
| 559 | 559 | instance's underlying [`net.Socket`]. | |
| 560 | 560 | ||
| 561 | + ### Class: ServerHttp2Session | ||
| 562 | + <!-- YAML | ||
| 563 | + added: v8.4.0 | ||
| 564 | + --> | ||
| 565 | + | ||
| 566 | + #### serverhttp2session.altsvc(alt, originOrStream) | ||
| 567 | + <!-- YAML | ||
| 568 | + added: REPLACEME | ||
| 569 | + --> | ||
| 570 | + | ||
| 571 | + * `alt` {string} A description of the alternative service configuration as | ||
| 572 | + defined by [RFC 7838][]. | ||
| 573 | + * `originOrStream` {string|number} Either a URL string specifying the origin or | ||
| 574 | + the numeric identifier of an active `Http2Stream`. | ||
| 575 | + | ||
| 576 | + Submits an `ALTSVC` frame (as defined by [RFC 7838][]) to the connected client. | ||
| 577 | + | ||
| 578 | + ```js | ||
| 579 | + const http2 = require('http2'); | ||
| 580 | + | ||
| 581 | + const server = http2.createServer(); | ||
| 582 | + server.on('session', (session) => { | ||
| 583 | + // Set altsvc for origin https://example.org:80 | ||
| 584 | + session.altsvc('h2=":8000"', 'https://example.org:80'); | ||
| 585 | + }); | ||
| 586 | + | ||
| 587 | + server.on('stream', (stream) => { | ||
| 588 | + // Set altsvc for a specific stream | ||
| 589 | + stream.session.altsvc('h2=":8000"', stream.id); | ||
| 590 | + }); | ||
| 591 | + ``` | ||
| 592 | + | ||
| 593 | + Sending an `ALTSVC` frame with a specific stream ID indicates that the alternate | ||
| 594 | + service is associated with the origin of the given `Http2Stream`. | ||
| 595 | + | ||
| 596 | + The `alt` and origin string *must* contain only ASCII bytes and are | ||
| 597 | + strictly interpreted as a sequence of ASCII bytes. The special value `'clear'` | ||
| 598 | + may be passed to clear any previously set alternative service for a given | ||
| 599 | + domain. | ||
| 600 | + | ||
| 601 | + When a string is passed for the `originOrStream` argument, it will be parsed as | ||
| 602 | + a URL and the origin will be derived. For insetance, the origin for the | ||
| 603 | + HTTP URL `'https://example.org/foo/bar'` is the ASCII string | ||
| 604 | + `'https://example.org'`. An error will be thrown if either the given string | ||
| 605 | + cannot be parsed as a URL or if a valid origin cannot be derived. | ||
| 606 | + | ||
| 607 | + #### Specifying alternative services | ||
| 608 | + | ||
| 609 | + The format of the `alt` parameter is strictly defined by [RFC 7838][] as an | ||
| 610 | + ASCII string containing a comma-delimited list of "alternative" protocols | ||
| 611 | + associated with a specific host and port. | ||
| 612 | + | ||
| 613 | + For example, the value `'h2="example.org:81"'` indicates that the HTTP/2 | ||
| 614 | + protocol is available on the host `'example.org'` on TCP/IP port 81. The | ||
| 615 | + host and port *must* be contained within the quote (`"`) characters. | ||
| 616 | + | ||
| 617 | + Multiple alternatives may be specified, for instance: `'h2="example.org:81", | ||
| 618 | + h2=":82"'` | ||
| 619 | + | ||
| 620 | + The protocol identifier (`'h2'` in the examples) may be any valid | ||
| 621 | + [ALPN Protocol ID][]. | ||
| 622 | + | ||
| 623 | + The syntax of these values is not validated by the Node.js implementation and | ||
| 624 | + are passed through as provided by the user or received from the peer. | ||
| 625 | + | ||
| 561 | 626 | ### Class: ClientHttp2Session | |
| 562 | 627 | <!-- YAML | |
| 563 | 628 | added: v8.4.0 | |
| 564 | 629 | --> | |
| 565 | 630 | ||
| 631 | + #### Event: 'altsvc' | ||
| 632 | + <!-- YAML | ||
| 633 | + added: REPLACEME | ||
| 634 | + --> | ||
| 635 | + | ||
| 636 | + The `'altsvc'` event is emitted whenever an `ALTSVC` frame is received by | ||
| 637 | + the client. The event is emitted with the `ALTSVC` value, origin, and stream | ||
| 638 | + ID, if any. If no `origin` is provided in the `ALTSVC` frame, `origin` will | ||
| 639 | + be an empty string. | ||
| 640 | + | ||
| 641 | + ```js | ||
| 642 | + const http2 = require('http2'); | ||
| 643 | + const client = http2.connect('https://example.org'); | ||
| 644 | + | ||
| 645 | + client.on('altsvc', (alt, origin, stream) => { | ||
| 646 | + console.log(alt); | ||
| 647 | + console.log(origin); | ||
| 648 | + console.log(stream); | ||
| 649 | + }); | ||
| 650 | + ``` | ||
| 651 | + | ||
| 566 | 652 | #### clienthttp2session.request(headers[, options]) | |
| 567 | 653 | <!-- YAML | |
| 568 | 654 | added: v8.4.0 | |
@@ -2850,6 +2936,7 @@ following additional properties: | |||
| 2850 | 2936 | ||
| 2851 | 2937 | ||
| 2852 | 2938 | [ALPN negotiation]: #http2_alpn_negotiation | |
| 2939 | + [ALPN Protocol ID]: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids | ||
| 2853 | 2940 | [Compatibility API]: #http2_compatibility_api | |
| 2854 | 2941 | [HTTP/1]: http.html | |
| 2855 | 2942 | [HTTP/2]: https://tools.ietf.org/html/rfc7540 | |
@@ -2858,6 +2945,7 @@ following additional properties: | |||
| 2858 | 2945 | [Http2Session and Sockets]: #http2_http2session_and_sockets | |
| 2859 | 2946 | [Performance Observer]: perf_hooks.html | |
| 2860 | 2947 | [Readable Stream]: stream.html#stream_class_stream_readable | |
| 2948 | + [RFC 7838]: https://tools.ietf.org/html/rfc7838 | ||
| 2861 | 2949 | [Settings Object]: #http2_settings_object | |
| 2862 | 2950 | [Using options.selectPadding]: #http2_using_options_selectpadding | |
| 2863 | 2951 | [Writable Stream]: stream.html#stream_writable_streams | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -304,6 +304,10 @@ E('ERR_FS_INVALID_SYMLINK_TYPE', | |||
| 304 | 304 | 'Symlink type must be one of "dir", "file", or "junction". Received "%s"'); | |
| 305 | 305 | E('ERR_HTTP2_ALREADY_SHUTDOWN', | |
| 306 | 306 | 'Http2Session is already shutdown or destroyed'); | |
| 307 | + E('ERR_HTTP2_ALTSVC_INVALID_ORIGIN', | ||
| 308 | + 'HTTP/2 ALTSVC frames require a valid origin'); | ||
| 309 | + E('ERR_HTTP2_ALTSVC_LENGTH', | ||
| 310 | + 'HTTP/2 ALTSVC frames are limited to 16382 bytes'); | ||
| 307 | 311 | E('ERR_HTTP2_CONNECT_AUTHORITY', | |
| 308 | 312 | ':authority header is required for CONNECT requests'); | |
| 309 | 313 | E('ERR_HTTP2_CONNECT_PATH', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,9 @@ const kMaxFrameSize = (2 ** 24) - 1; | |||
| 32 | 32 | const kMaxInt = (2 ** 32) - 1; | |
| 33 | 33 | const kMaxStreams = (2 ** 31) - 1; | |
| 34 | 34 | ||
| 35 | + // eslint-disable-next-line no-control-regex | ||
| 36 | + const kQuotedString = /^[\x09\x20-\x5b\x5d-\x7e\x80-\xff]*$/; | ||
| 37 | + | ||
| 35 | 38 | const { | |
| 36 | 39 | assertIsObject, | |
| 37 | 40 | assertValidPseudoHeaderResponse, | |
@@ -364,6 +367,16 @@ function onFrameError(id, type, code) { | |||
| 364 | 367 | process.nextTick(emit, emitter, 'frameError', type, code, id); | |
| 365 | 368 | } | |
| 366 | 369 | ||
| 370 | + function onAltSvc(stream, origin, alt) { | ||
| 371 | + const session = this[kOwner]; | ||
| 372 | + if (session.destroyed) | ||
| 373 | + return; | ||
| 374 | + debug(`Http2Session ${sessionName(session[kType])}: altsvc received: ` + | ||
| 375 | + `stream: ${stream}, origin: ${origin}, alt: ${alt}`); | ||
| 376 | + session[kUpdateTimer](); | ||
| 377 | + process.nextTick(emit, session, 'altsvc', alt, origin, stream); | ||
| 378 | + } | ||
| 379 | + | ||
| 367 | 380 | // Receiving a GOAWAY frame from the connected peer is a signal that no | |
| 368 | 381 | // new streams should be created. If the code === NGHTTP2_NO_ERROR, we | |
| 369 | 382 | // are going to send our close, but allow existing frames to close | |
@@ -706,6 +719,7 @@ function setupHandle(socket, type, options) { | |||
| 706 | 719 | handle.onheaders = onSessionHeaders; | |
| 707 | 720 | handle.onframeerror = onFrameError; | |
| 708 | 721 | handle.ongoawaydata = onGoawayData; | |
| 722 | + handle.onaltsvc = onAltSvc; | ||
| 709 | 723 | ||
| 710 | 724 | if (typeof options.selectPadding === 'function') | |
| 711 | 725 | handle.ongetpadding = onSelectPadding(options.selectPadding); | |
@@ -1154,6 +1168,42 @@ class ServerHttp2Session extends Http2Session { | |||
| 1154 | 1168 | get server() { | |
| 1155 | 1169 | return this[kServer]; | |
| 1156 | 1170 | } | |
| 1171 | + | ||
| 1172 | + // Submits an altsvc frame to be sent to the client. `stream` is a | ||
| 1173 | + // numeric Stream ID. origin is a URL string that will be used to get | ||
| 1174 | + // the origin. alt is a string containing the altsvc details. No fancy | ||
| 1175 | + // API is provided for that. | ||
| 1176 | + altsvc(alt, originOrStream) { | ||
| 1177 | + if (this.destroyed) | ||
| 1178 | + throw new errors.Error('ERR_HTTP2_INVALID_SESSION'); | ||
| 1179 | + | ||
| 1180 | + let stream = 0; | ||
| 1181 | + let origin; | ||
| 1182 | + | ||
| 1183 | + if (typeof originOrStream === 'string') { | ||
| 1184 | + origin = (new URL(originOrStream)).origin; | ||
| 1185 | + if (origin.length === 0 || origin === 'null') | ||
| 1186 | + throw new errors.TypeError('ERR_HTTP2_ALTSVC_INVALID_ORIGIN'); | ||
| 1187 | + } else if (typeof originOrStream === 'number') { | ||
| 1188 | + if (originOrStream >>> 0 !== originOrStream || originOrStream === 0) | ||
| 1189 | + throw new errors.RangeError('ERR_OUT_OF_RANGE', 'originOrStream'); | ||
| 1190 | + stream = originOrStream; | ||
| 1191 | + } else if (originOrStream !== undefined) { | ||
| 1192 | + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'originOrStream', | ||
| 1193 | + ['string', 'number']); | ||
| 1194 | + } | ||
| 1195 | + | ||
| 1196 | + if (typeof alt !== 'string') | ||
| 1197 | + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'alt', 'string'); | ||
| 1198 | + if (!kQuotedString.test(alt)) | ||
| 1199 | + throw new errors.TypeError('ERR_INVALID_CHAR', 'alt'); | ||
| 1200 | + | ||
| 1201 | + // Max length permitted for ALTSVC | ||
| 1202 | + if ((alt.length + (origin !== undefined ? origin.length : 0)) > 16382) | ||
| 1203 | + throw new errors.TypeError('ERR_HTTP2_ALTSVC_LENGTH'); | ||
| 1204 | + | ||
| 1205 | + this[kHandle].altsvc(stream, origin || '', alt); | ||
| 1206 | + } | ||
| 1157 | 1207 | } | |
| 1158 | 1208 | ||
| 1159 | 1209 | // ClientHttp2Session instances have to wait for the socket to connect after | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -179,6 +179,7 @@ class ModuleWrap; | |||
| 179 | 179 | V(netmask_string, "netmask") \ | |
| 180 | 180 | V(nsname_string, "nsname") \ | |
| 181 | 181 | V(ocsp_request_string, "OCSPRequest") \ | |
| 182 | + V(onaltsvc_string, "onaltsvc") \ | ||
| 182 | 183 | V(onchange_string, "onchange") \ | |
| 183 | 184 | V(onclienthello_string, "onclienthello") \ | |
| 184 | 185 | V(oncomplete_string, "oncomplete") \ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -103,6 +103,11 @@ Http2Options::Http2Options(Environment* env) { | |||
| 103 | 103 | // are required to buffer. | |
| 104 | 104 | nghttp2_option_set_no_auto_window_update(options_, 1); | |
| 105 | 105 | ||
| 106 | + // Enable built in support for ALTSVC frames. Once we add support for | ||
| 107 | + // other non-built in extension frames, this will need to be handled | ||
| 108 | + // a bit differently. For now, let's let nghttp2 take care of it. | ||
| 109 | + nghttp2_option_set_builtin_recv_extension_type(options_, NGHTTP2_ALTSVC); | ||
| 110 | + | ||
| 106 | 111 | AliasedBuffer<uint32_t, v8::Uint32Array>& buffer = | |
| 107 | 112 | env->http2_state()->options_buffer; | |
| 108 | 113 | uint32_t flags = buffer[IDX_OPTIONS_FLAGS]; | |
@@ -830,6 +835,10 @@ inline int Http2Session::OnFrameReceive(nghttp2_session* handle, | |||
| 830 | 835 | break; | |
| 831 | 836 | case NGHTTP2_PING: | |
| 832 | 837 | session->HandlePingFrame(frame); | |
| 838 | + break; | ||
| 839 | + case NGHTTP2_ALTSVC: | ||
| 840 | + session->HandleAltSvcFrame(frame); | ||
| 841 | + break; | ||
| 833 | 842 | default: | |
| 834 | 843 | break; | |
| 835 | 844 | } | |
@@ -1168,6 +1177,34 @@ inline void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) { | |||
| 1168 | 1177 | MakeCallback(env()->ongoawaydata_string(), arraysize(argv), argv); | |
| 1169 | 1178 | } | |
| 1170 | 1179 | ||
| 1180 | + // Called by OnFrameReceived when a complete ALTSVC frame has been received. | ||
| 1181 | + inline void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) { | ||
| 1182 | + Isolate* isolate = env()->isolate(); | ||
| 1183 | + HandleScope scope(isolate); | ||
| 1184 | + Local<Context> context = env()->context(); | ||
| 1185 | + Context::Scope context_scope(context); | ||
| 1186 | + | ||
| 1187 | + int32_t id = GetFrameID(frame); | ||
| 1188 | + | ||
| 1189 | + nghttp2_extension ext = frame->ext; | ||
| 1190 | + nghttp2_ext_altsvc* altsvc = static_cast<nghttp2_ext_altsvc*>(ext.payload); | ||
| 1191 | + DEBUG_HTTP2SESSION(this, "handling altsvc frame"); | ||
| 1192 | + | ||
| 1193 | + Local<Value> argv[3] = { | ||
| 1194 | + Integer::New(isolate, id), | ||
| 1195 | + String::NewFromOneByte(isolate, | ||
| 1196 | + altsvc->origin, | ||
| 1197 | + v8::NewStringType::kNormal, | ||
| 1198 | + altsvc->origin_len).ToLocalChecked(), | ||
| 1199 | + String::NewFromOneByte(isolate, | ||
| 1200 | + altsvc->field_value, | ||
| 1201 | + v8::NewStringType::kNormal, | ||
| 1202 | + altsvc->field_value_len).ToLocalChecked(), | ||
| 1203 | + }; | ||
| 1204 | + | ||
| 1205 | + MakeCallback(env()->onaltsvc_string(), arraysize(argv), argv); | ||
| 1206 | + } | ||
| 1207 | + | ||
| 1171 | 1208 | // Called by OnFrameReceived when a complete PING frame has been received. | |
| 1172 | 1209 | inline void Http2Session::HandlePingFrame(const nghttp2_frame* frame) { | |
| 1173 | 1210 | bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK; | |
@@ -2477,6 +2514,44 @@ void Http2Stream::RefreshState(const FunctionCallbackInfo<Value>& args) { | |||
| 2477 | 2514 | } | |
| 2478 | 2515 | } | |
| 2479 | 2516 | ||
| 2517 | + void Http2Session::AltSvc(int32_t id, | ||
| 2518 | + uint8_t* origin, | ||
| 2519 | + size_t origin_len, | ||
| 2520 | + uint8_t* value, | ||
| 2521 | + size_t value_len) { | ||
| 2522 | + Http2Scope h2scope(this); | ||
| 2523 | + CHECK_EQ(nghttp2_submit_altsvc(session_, NGHTTP2_FLAG_NONE, id, | ||
| 2524 | + origin, origin_len, value, value_len), 0); | ||
| 2525 | + } | ||
| 2526 | + | ||
| 2527 | + // Submits an AltSvc frame to the sent to the connected peer. | ||
| 2528 | + void Http2Session::AltSvc(const FunctionCallbackInfo<Value>& args) { | ||
| 2529 | + Environment* env = Environment::GetCurrent(args); | ||
| 2530 | + Http2Session* session; | ||
| 2531 | + ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); | ||
| 2532 | + | ||
| 2533 | + int32_t id = args[0]->Int32Value(env->context()).ToChecked(); | ||
| 2534 | + | ||
| 2535 | + // origin and value are both required to be ASCII, handle them as such. | ||
| 2536 | + Local<String> origin_str = args[1]->ToString(env->context()).ToLocalChecked(); | ||
| 2537 | + Local<String> value_str = args[2]->ToString(env->context()).ToLocalChecked(); | ||
| 2538 | + | ||
| 2539 | + size_t origin_len = origin_str->Length(); | ||
| 2540 | + size_t value_len = value_str->Length(); | ||
| 2541 | + | ||
| 2542 | + CHECK_LE(origin_len + value_len, 16382); // Max permitted for ALTSVC | ||
| 2543 | + // Verify that origin len != 0 if stream id == 0, or | ||
| 2544 | + // that origin len == 0 if stream id != 0 | ||
| 2545 | + CHECK((origin_len != 0 && id == 0) || (origin_len == 0 && id != 0)); | ||
| 2546 | + | ||
| 2547 | + MaybeStackBuffer<uint8_t> origin(origin_len); | ||
| 2548 | + MaybeStackBuffer<uint8_t> value(value_len); | ||
| 2549 | + origin_str->WriteOneByte(*origin); | ||
| 2550 | + value_str->WriteOneByte(*value); | ||
| 2551 | + | ||
| 2552 | + session->AltSvc(id, *origin, origin_len, *value, value_len); | ||
| 2553 | + } | ||
| 2554 | + | ||
| 2480 | 2555 | // Submits a PING frame to be sent to the connected peer. | |
| 2481 | 2556 | void Http2Session::Ping(const FunctionCallbackInfo<Value>& args) { | |
| 2482 | 2557 | Environment* env = Environment::GetCurrent(args); | |
@@ -2694,6 +2769,7 @@ void Initialize(Local<Object> target, | |||
| 2694 | 2769 | session->SetClassName(http2SessionClassName); | |
| 2695 | 2770 | session->InstanceTemplate()->SetInternalFieldCount(1); | |
| 2696 | 2771 | AsyncWrap::AddWrapMethods(env, session); | |
| 2772 | + env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc); | ||
| 2697 | 2773 | env->SetProtoMethod(session, "ping", Http2Session::Ping); | |
| 2698 | 2774 | env->SetProtoMethod(session, "consume", Http2Session::Consume); | |
| 2699 | 2775 | env->SetProtoMethod(session, "destroy", Http2Session::Destroy); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -800,6 +800,11 @@ class Http2Session : public AsyncWrap { | |||
| 800 | 800 | void Consume(Local<External> external); | |
| 801 | 801 | void Unconsume(); | |
| 802 | 802 | void Goaway(uint32_t code, int32_t lastStreamID, uint8_t* data, size_t len); | |
| 803 | + void AltSvc(int32_t id, | ||
| 804 | + uint8_t* origin, | ||
| 805 | + size_t origin_len, | ||
| 806 | + uint8_t* value, | ||
| 807 | + size_t value_len); | ||
| 803 | 808 | ||
| 804 | 809 | bool Ping(v8::Local<v8::Function> function); | |
| 805 | 810 | ||
@@ -877,6 +882,7 @@ class Http2Session : public AsyncWrap { | |||
| 877 | 882 | static void UpdateChunksSent(const FunctionCallbackInfo<Value>& args); | |
| 878 | 883 | static void RefreshState(const FunctionCallbackInfo<Value>& args); | |
| 879 | 884 | static void Ping(const FunctionCallbackInfo<Value>& args); | |
| 885 | + static void AltSvc(const FunctionCallbackInfo<Value>& args); | ||
| 880 | 886 | ||
| 881 | 887 | template <get_setting fn> | |
| 882 | 888 | static void RefreshSettings(const FunctionCallbackInfo<Value>& args); | |
@@ -921,6 +927,7 @@ class Http2Session : public AsyncWrap { | |||
| 921 | 927 | inline void HandlePriorityFrame(const nghttp2_frame* frame); | |
| 922 | 928 | inline void HandleSettingsFrame(const nghttp2_frame* frame); | |
| 923 | 929 | inline void HandlePingFrame(const nghttp2_frame* frame); | |
| 930 | + inline void HandleAltSvcFrame(const nghttp2_frame* frame); | ||
| 924 | 931 | ||
| 925 | 932 | // nghttp2 callbacks | |
| 926 | 933 | static inline int OnBeginHeadersCallback( | |
| Back | FazBrowse Home | New Git URL |
0 commit comments