• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayIsArray,
5  Boolean,
6  ObjectAssign,
7  ObjectCreate,
8  ObjectKeys,
9  ObjectPrototypeHasOwnProperty,
10  Proxy,
11  ReflectGetPrototypeOf,
12  Symbol,
13} = primordials;
14
15const assert = require('internal/assert');
16const Stream = require('stream');
17const { Readable } = Stream;
18const {
19  constants: {
20    HTTP2_HEADER_AUTHORITY,
21    HTTP2_HEADER_CONNECTION,
22    HTTP2_HEADER_METHOD,
23    HTTP2_HEADER_PATH,
24    HTTP2_HEADER_SCHEME,
25    HTTP2_HEADER_STATUS,
26
27    HTTP_STATUS_CONTINUE,
28    HTTP_STATUS_EXPECTATION_FAILED,
29    HTTP_STATUS_METHOD_NOT_ALLOWED,
30    HTTP_STATUS_OK
31  }
32} = internalBinding('http2');
33const {
34  codes: {
35    ERR_HTTP2_HEADERS_SENT,
36    ERR_HTTP2_INFO_STATUS_NOT_ALLOWED,
37    ERR_HTTP2_INVALID_HEADER_VALUE,
38    ERR_HTTP2_INVALID_STREAM,
39    ERR_HTTP2_NO_SOCKET_MANIPULATION,
40    ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED,
41    ERR_HTTP2_STATUS_INVALID,
42    ERR_INVALID_ARG_VALUE,
43    ERR_INVALID_CALLBACK,
44    ERR_INVALID_HTTP_TOKEN,
45    ERR_STREAM_WRITE_AFTER_END
46  },
47  hideStackFrames
48} = require('internal/errors');
49const { validateString } = require('internal/validators');
50const { kSocket, kRequest, kProxySocket } = require('internal/http2/util');
51
52const kBeginSend = Symbol('begin-send');
53const kState = Symbol('state');
54const kStream = Symbol('stream');
55const kResponse = Symbol('response');
56const kHeaders = Symbol('headers');
57const kRawHeaders = Symbol('rawHeaders');
58const kTrailers = Symbol('trailers');
59const kRawTrailers = Symbol('rawTrailers');
60const kSetHeader = Symbol('setHeader');
61const kAborted = Symbol('aborted');
62
63let statusMessageWarned = false;
64let statusConnectionHeaderWarned = false;
65
66// Defines and implements an API compatibility layer on top of the core
67// HTTP/2 implementation, intended to provide an interface that is as
68// close as possible to the current require('http') API
69
70const assertValidHeader = hideStackFrames((name, value) => {
71  if (name === '' || typeof name !== 'string') {
72    throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
73  }
74  if (isPseudoHeader(name)) {
75    throw new ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED();
76  }
77  if (value === undefined || value === null) {
78    throw new ERR_HTTP2_INVALID_HEADER_VALUE(value, name);
79  }
80  if (!isConnectionHeaderAllowed(name, value)) {
81    connectionHeaderMessageWarn();
82  }
83});
84
85function isPseudoHeader(name) {
86  switch (name) {
87    case HTTP2_HEADER_STATUS:    // :status
88    case HTTP2_HEADER_METHOD:    // :method
89    case HTTP2_HEADER_PATH:      // :path
90    case HTTP2_HEADER_AUTHORITY: // :authority
91    case HTTP2_HEADER_SCHEME:    // :scheme
92      return true;
93    default:
94      return false;
95  }
96}
97
98function statusMessageWarn() {
99  if (statusMessageWarned === false) {
100    process.emitWarning(
101      'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)',
102      'UnsupportedWarning'
103    );
104    statusMessageWarned = true;
105  }
106}
107
108function isConnectionHeaderAllowed(name, value) {
109  return name !== HTTP2_HEADER_CONNECTION ||
110         value === 'trailers';
111}
112
113function connectionHeaderMessageWarn() {
114  if (statusConnectionHeaderWarned === false) {
115    process.emitWarning(
116      'The provided connection header is not valid, ' +
117      'the value will be dropped from the header and ' +
118      'will never be in use.',
119      'UnsupportedWarning'
120    );
121    statusConnectionHeaderWarned = true;
122  }
123}
124
125function onStreamData(chunk) {
126  const request = this[kRequest];
127  if (request !== undefined && !request.push(chunk))
128    this.pause();
129}
130
131function onStreamTrailers(trailers, flags, rawTrailers) {
132  const request = this[kRequest];
133  if (request !== undefined) {
134    ObjectAssign(request[kTrailers], trailers);
135    request[kRawTrailers].push(...rawTrailers);
136  }
137}
138
139function onStreamEnd() {
140  // Cause the request stream to end as well.
141  const request = this[kRequest];
142  if (request !== undefined)
143    this[kRequest].push(null);
144}
145
146function onStreamError(error) {
147  // This is purposefully left blank
148  //
149  // errors in compatibility mode are
150  // not forwarded to the request
151  // and response objects.
152}
153
154function onRequestPause() {
155  this[kStream].pause();
156}
157
158function onRequestResume() {
159  this[kStream].resume();
160}
161
162function onStreamDrain() {
163  const response = this[kResponse];
164  if (response !== undefined)
165    response.emit('drain');
166}
167
168function onStreamAbortedRequest() {
169  const request = this[kRequest];
170  if (request !== undefined && request[kState].closed === false) {
171    request[kAborted] = true;
172    request.emit('aborted');
173  }
174}
175
176function onStreamAbortedResponse() {
177  // non-op for now
178}
179
180function resumeStream(stream) {
181  stream.resume();
182}
183
184const proxySocketHandler = {
185  has(stream, prop) {
186    const ref = stream.session !== undefined ? stream.session[kSocket] : stream;
187    return (prop in stream) || (prop in ref);
188  },
189
190  get(stream, prop) {
191    switch (prop) {
192      case 'on':
193      case 'once':
194      case 'end':
195      case 'emit':
196      case 'destroy':
197        return stream[prop].bind(stream);
198      case 'writable':
199      case 'destroyed':
200        return stream[prop];
201      case 'readable':
202        if (stream.destroyed)
203          return false;
204        const request = stream[kRequest];
205        return request ? request.readable : stream.readable;
206      case 'setTimeout':
207        const session = stream.session;
208        if (session !== undefined)
209          return session.setTimeout.bind(session);
210        return stream.setTimeout.bind(stream);
211      case 'write':
212      case 'read':
213      case 'pause':
214      case 'resume':
215        throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
216      default:
217        const ref = stream.session !== undefined ?
218          stream.session[kSocket] : stream;
219        const value = ref[prop];
220        return typeof value === 'function' ? value.bind(ref) : value;
221    }
222  },
223  getPrototypeOf(stream) {
224    if (stream.session !== undefined)
225      return ReflectGetPrototypeOf(stream.session[kSocket]);
226    return ReflectGetPrototypeOf(stream);
227  },
228  set(stream, prop, value) {
229    switch (prop) {
230      case 'writable':
231      case 'readable':
232      case 'destroyed':
233      case 'on':
234      case 'once':
235      case 'end':
236      case 'emit':
237      case 'destroy':
238        stream[prop] = value;
239        return true;
240      case 'setTimeout':
241        const session = stream.session;
242        if (session !== undefined)
243          session.setTimeout = value;
244        else
245          stream.setTimeout = value;
246        return true;
247      case 'write':
248      case 'read':
249      case 'pause':
250      case 'resume':
251        throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
252      default:
253        const ref = stream.session !== undefined ?
254          stream.session[kSocket] : stream;
255        ref[prop] = value;
256        return true;
257    }
258  }
259};
260
261function onStreamCloseRequest() {
262  const req = this[kRequest];
263
264  if (req === undefined)
265    return;
266
267  const state = req[kState];
268  state.closed = true;
269
270  req.push(null);
271  // If the user didn't interact with incoming data and didn't pipe it,
272  // dump it for compatibility with http1
273  if (!state.didRead && !req._readableState.resumeScheduled)
274    req.resume();
275
276  this[kProxySocket] = null;
277  this[kRequest] = undefined;
278
279  req.emit('close');
280}
281
282function onStreamTimeout(kind) {
283  return function onStreamTimeout() {
284    const obj = this[kind];
285    obj.emit('timeout');
286  };
287}
288
289class Http2ServerRequest extends Readable {
290  constructor(stream, headers, options, rawHeaders) {
291    super({ autoDestroy: false, ...options });
292    this[kState] = {
293      closed: false,
294      didRead: false,
295    };
296    // Headers in HTTP/1 are not initialized using Object.create(null) which,
297    // although preferable, would simply break too much code. Ergo header
298    // initialization using Object.create(null) in HTTP/2 is intentional.
299    this[kHeaders] = headers;
300    this[kRawHeaders] = rawHeaders;
301    this[kTrailers] = {};
302    this[kRawTrailers] = [];
303    this[kStream] = stream;
304    this[kAborted] = false;
305    stream[kProxySocket] = null;
306    stream[kRequest] = this;
307
308    // Pause the stream..
309    stream.on('trailers', onStreamTrailers);
310    stream.on('end', onStreamEnd);
311    stream.on('error', onStreamError);
312    stream.on('aborted', onStreamAbortedRequest);
313    stream.on('close', onStreamCloseRequest);
314    stream.on('timeout', onStreamTimeout(kRequest));
315    this.on('pause', onRequestPause);
316    this.on('resume', onRequestResume);
317  }
318
319  get aborted() {
320    return this[kAborted];
321  }
322
323  get complete() {
324    return this[kAborted] ||
325           this.readableEnded ||
326           this[kState].closed ||
327           this[kStream].destroyed;
328  }
329
330  get stream() {
331    return this[kStream];
332  }
333
334  get headers() {
335    return this[kHeaders];
336  }
337
338  get rawHeaders() {
339    return this[kRawHeaders];
340  }
341
342  get trailers() {
343    return this[kTrailers];
344  }
345
346  get rawTrailers() {
347    return this[kRawTrailers];
348  }
349
350  get httpVersionMajor() {
351    return 2;
352  }
353
354  get httpVersionMinor() {
355    return 0;
356  }
357
358  get httpVersion() {
359    return '2.0';
360  }
361
362  get socket() {
363    const stream = this[kStream];
364    const proxySocket = stream[kProxySocket];
365    if (proxySocket === null)
366      return stream[kProxySocket] = new Proxy(stream, proxySocketHandler);
367    return proxySocket;
368  }
369
370  get connection() {
371    return this.socket;
372  }
373
374  _read(nread) {
375    const state = this[kState];
376    assert(!state.closed);
377    if (!state.didRead) {
378      state.didRead = true;
379      this[kStream].on('data', onStreamData);
380    } else {
381      process.nextTick(resumeStream, this[kStream]);
382    }
383  }
384
385  get method() {
386    return this[kHeaders][HTTP2_HEADER_METHOD];
387  }
388
389  set method(method) {
390    validateString(method, 'method');
391    if (method.trim() === '')
392      throw new ERR_INVALID_ARG_VALUE('method', method);
393
394    this[kHeaders][HTTP2_HEADER_METHOD] = method;
395  }
396
397  get authority() {
398    return this[kHeaders][HTTP2_HEADER_AUTHORITY];
399  }
400
401  get scheme() {
402    return this[kHeaders][HTTP2_HEADER_SCHEME];
403  }
404
405  get url() {
406    return this[kHeaders][HTTP2_HEADER_PATH];
407  }
408
409  set url(url) {
410    this[kHeaders][HTTP2_HEADER_PATH] = url;
411  }
412
413  setTimeout(msecs, callback) {
414    if (!this[kState].closed)
415      this[kStream].setTimeout(msecs, callback);
416    return this;
417  }
418}
419
420function onStreamTrailersReady() {
421  this.sendTrailers(this[kResponse][kTrailers]);
422}
423
424function onStreamCloseResponse() {
425  const res = this[kResponse];
426
427  if (res === undefined)
428    return;
429
430  const state = res[kState];
431
432  if (this.headRequest !== state.headRequest)
433    return;
434
435  state.closed = true;
436
437  this[kProxySocket] = null;
438
439  this.removeListener('wantTrailers', onStreamTrailersReady);
440  this[kResponse] = undefined;
441
442  res.emit('finish');
443  res.emit('close');
444}
445
446class Http2ServerResponse extends Stream {
447  constructor(stream, options) {
448    super(options);
449    this[kState] = {
450      closed: false,
451      ending: false,
452      destroyed: false,
453      headRequest: false,
454      sendDate: true,
455      statusCode: HTTP_STATUS_OK,
456    };
457    this[kHeaders] = ObjectCreate(null);
458    this[kTrailers] = ObjectCreate(null);
459    this[kStream] = stream;
460    stream[kProxySocket] = null;
461    stream[kResponse] = this;
462    this.writable = true;
463    stream.on('drain', onStreamDrain);
464    stream.on('aborted', onStreamAbortedResponse);
465    stream.on('close', onStreamCloseResponse);
466    stream.on('wantTrailers', onStreamTrailersReady);
467    stream.on('timeout', onStreamTimeout(kResponse));
468  }
469
470  // User land modules such as finalhandler just check truthiness of this
471  // but if someone is actually trying to use this for more than that
472  // then we simply can't support such use cases
473  get _header() {
474    return this.headersSent;
475  }
476
477  get writableEnded() {
478    const state = this[kState];
479    return state.ending;
480  }
481
482  get finished() {
483    const state = this[kState];
484    return state.ending;
485  }
486
487  get socket() {
488    // This is compatible with http1 which removes socket reference
489    // only from ServerResponse but not IncomingMessage
490    if (this[kState].closed)
491      return undefined;
492
493    const stream = this[kStream];
494    const proxySocket = stream[kProxySocket];
495    if (proxySocket === null)
496      return stream[kProxySocket] = new Proxy(stream, proxySocketHandler);
497    return proxySocket;
498  }
499
500  get connection() {
501    return this.socket;
502  }
503
504  get stream() {
505    return this[kStream];
506  }
507
508  get headersSent() {
509    return this[kStream].headersSent;
510  }
511
512  get sendDate() {
513    return this[kState].sendDate;
514  }
515
516  set sendDate(bool) {
517    this[kState].sendDate = Boolean(bool);
518  }
519
520  get statusCode() {
521    return this[kState].statusCode;
522  }
523
524  get writableCorked() {
525    return this[kStream].writableCorked;
526  }
527
528  get writableHighWaterMark() {
529    return this[kStream].writableHighWaterMark;
530  }
531
532  get writableFinished() {
533    return this[kStream].writableFinished;
534  }
535
536  get writableLength() {
537    return this[kStream].writableLength;
538  }
539
540  set statusCode(code) {
541    code |= 0;
542    if (code >= 100 && code < 200)
543      throw new ERR_HTTP2_INFO_STATUS_NOT_ALLOWED();
544    if (code < 100 || code > 599)
545      throw new ERR_HTTP2_STATUS_INVALID(code);
546    this[kState].statusCode = code;
547  }
548
549  setTrailer(name, value) {
550    validateString(name, 'name');
551    name = name.trim().toLowerCase();
552    assertValidHeader(name, value);
553    this[kTrailers][name] = value;
554  }
555
556  addTrailers(headers) {
557    const keys = ObjectKeys(headers);
558    let key = '';
559    for (let i = 0; i < keys.length; i++) {
560      key = keys[i];
561      this.setTrailer(key, headers[key]);
562    }
563  }
564
565  getHeader(name) {
566    validateString(name, 'name');
567    name = name.trim().toLowerCase();
568    return this[kHeaders][name];
569  }
570
571  getHeaderNames() {
572    return ObjectKeys(this[kHeaders]);
573  }
574
575  getHeaders() {
576    const headers = ObjectCreate(null);
577    return ObjectAssign(headers, this[kHeaders]);
578  }
579
580  hasHeader(name) {
581    validateString(name, 'name');
582    name = name.trim().toLowerCase();
583    return ObjectPrototypeHasOwnProperty(this[kHeaders], name);
584  }
585
586  removeHeader(name) {
587    validateString(name, 'name');
588    if (this[kStream].headersSent)
589      throw new ERR_HTTP2_HEADERS_SENT();
590
591    name = name.trim().toLowerCase();
592
593    if (name === 'date') {
594      this[kState].sendDate = false;
595
596      return;
597    }
598
599    delete this[kHeaders][name];
600  }
601
602  setHeader(name, value) {
603    validateString(name, 'name');
604    if (this[kStream].headersSent)
605      throw new ERR_HTTP2_HEADERS_SENT();
606
607    this[kSetHeader](name, value);
608  }
609
610  [kSetHeader](name, value) {
611    name = name.trim().toLowerCase();
612    assertValidHeader(name, value);
613
614    if (!isConnectionHeaderAllowed(name, value)) {
615      return;
616    }
617
618    this[kHeaders][name] = value;
619  }
620
621  get statusMessage() {
622    statusMessageWarn();
623
624    return '';
625  }
626
627  set statusMessage(msg) {
628    statusMessageWarn();
629  }
630
631  flushHeaders() {
632    const state = this[kState];
633    if (!state.closed && !this[kStream].headersSent)
634      this.writeHead(state.statusCode);
635  }
636
637  writeHead(statusCode, statusMessage, headers) {
638    const state = this[kState];
639
640    if (state.closed || this.stream.destroyed)
641      return this;
642    if (this[kStream].headersSent)
643      throw new ERR_HTTP2_HEADERS_SENT();
644
645    if (typeof statusMessage === 'string')
646      statusMessageWarn();
647
648    if (headers === undefined && typeof statusMessage === 'object')
649      headers = statusMessage;
650
651    let i;
652    if (ArrayIsArray(headers)) {
653      for (i = 0; i < headers.length; i++) {
654        const header = headers[i];
655        this[kSetHeader](header[0], header[1]);
656      }
657    } else if (typeof headers === 'object') {
658      const keys = ObjectKeys(headers);
659      let key = '';
660      for (i = 0; i < keys.length; i++) {
661        key = keys[i];
662        this[kSetHeader](key, headers[key]);
663      }
664    }
665
666    state.statusCode = statusCode;
667    this[kBeginSend]();
668
669    return this;
670  }
671
672  cork() {
673    this[kStream].cork();
674  }
675
676  uncork() {
677    this[kStream].uncork();
678  }
679
680  write(chunk, encoding, cb) {
681    const state = this[kState];
682
683    if (typeof encoding === 'function') {
684      cb = encoding;
685      encoding = 'utf8';
686    }
687
688    let err;
689    if (state.ending) {
690      err = new ERR_STREAM_WRITE_AFTER_END();
691    } else if (state.closed) {
692      err = new ERR_HTTP2_INVALID_STREAM();
693    } else if (state.destroyed) {
694      return false;
695    }
696
697    if (err) {
698      if (typeof cb === 'function')
699        process.nextTick(cb, err);
700      this.destroy(err);
701      return false;
702    }
703
704    const stream = this[kStream];
705    if (!stream.headersSent)
706      this.writeHead(state.statusCode);
707    return stream.write(chunk, encoding, cb);
708  }
709
710  end(chunk, encoding, cb) {
711    const stream = this[kStream];
712    const state = this[kState];
713
714    if (typeof chunk === 'function') {
715      cb = chunk;
716      chunk = null;
717    } else if (typeof encoding === 'function') {
718      cb = encoding;
719      encoding = 'utf8';
720    }
721
722    if ((state.closed || state.ending) &&
723        state.headRequest === stream.headRequest) {
724      if (typeof cb === 'function') {
725        process.nextTick(cb);
726      }
727      return this;
728    }
729
730    if (chunk !== null && chunk !== undefined)
731      this.write(chunk, encoding);
732
733    state.headRequest = stream.headRequest;
734    state.ending = true;
735
736    if (typeof cb === 'function') {
737      if (stream.writableEnded)
738        this.once('finish', cb);
739      else
740        stream.once('finish', cb);
741    }
742
743    if (!stream.headersSent)
744      this.writeHead(this[kState].statusCode);
745
746    if (this[kState].closed || stream.destroyed)
747      onStreamCloseResponse.call(stream);
748    else
749      stream.end();
750
751    return this;
752  }
753
754  destroy(err) {
755    if (this[kState].destroyed)
756      return;
757
758    this[kState].destroyed = true;
759    this[kStream].destroy(err);
760  }
761
762  setTimeout(msecs, callback) {
763    if (this[kState].closed)
764      return;
765    this[kStream].setTimeout(msecs, callback);
766  }
767
768  createPushResponse(headers, callback) {
769    if (typeof callback !== 'function')
770      throw new ERR_INVALID_CALLBACK(callback);
771    if (this[kState].closed) {
772      process.nextTick(callback, new ERR_HTTP2_INVALID_STREAM());
773      return;
774    }
775    this[kStream].pushStream(headers, {}, (err, stream, headers, options) => {
776      if (err) {
777        callback(err);
778        return;
779      }
780      callback(null, new Http2ServerResponse(stream));
781    });
782  }
783
784  [kBeginSend]() {
785    const state = this[kState];
786    const headers = this[kHeaders];
787    headers[HTTP2_HEADER_STATUS] = state.statusCode;
788    const options = {
789      endStream: state.ending,
790      waitForTrailers: true,
791      sendDate: state.sendDate
792    };
793    this[kStream].respond(headers, options);
794  }
795
796  // TODO doesn't support callbacks
797  writeContinue() {
798    const stream = this[kStream];
799    if (stream.headersSent || this[kState].closed)
800      return false;
801    stream.additionalHeaders({
802      [HTTP2_HEADER_STATUS]: HTTP_STATUS_CONTINUE
803    });
804    return true;
805  }
806}
807
808function onServerStream(ServerRequest, ServerResponse,
809                        stream, headers, flags, rawHeaders) {
810  const server = this;
811  const request = new ServerRequest(stream, headers, undefined, rawHeaders);
812  const response = new ServerResponse(stream);
813
814  // Check for the CONNECT method
815  const method = headers[HTTP2_HEADER_METHOD];
816  if (method === 'CONNECT') {
817    if (!server.emit('connect', request, response)) {
818      response.statusCode = HTTP_STATUS_METHOD_NOT_ALLOWED;
819      response.end();
820    }
821    return;
822  }
823
824  // Check for Expectations
825  if (headers.expect !== undefined) {
826    if (headers.expect === '100-continue') {
827      if (server.listenerCount('checkContinue')) {
828        server.emit('checkContinue', request, response);
829      } else {
830        response.writeContinue();
831        server.emit('request', request, response);
832      }
833    } else if (server.listenerCount('checkExpectation')) {
834      server.emit('checkExpectation', request, response);
835    } else {
836      response.statusCode = HTTP_STATUS_EXPECTATION_FAILED;
837      response.end();
838    }
839    return;
840  }
841
842  server.emit('request', request, response);
843}
844
845module.exports = {
846  onServerStream,
847  Http2ServerRequest,
848  Http2ServerResponse,
849};
850