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