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