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