OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 // Global constants. | 5 // Global constants. |
6 class _Const { | 6 class _Const { |
7 // Bytes for "HTTP". | 7 // Bytes for "HTTP". |
8 static const HTTP = const [72, 84, 84, 80]; | 8 static const HTTP = const [72, 84, 84, 80]; |
9 // Bytes for "HTTP/1.". | 9 // Bytes for "HTTP/1.". |
10 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; | 10 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 } | 92 } |
93 | 93 |
94 | 94 |
95 /** | 95 /** |
96 * HTTP parser which parses the HTTP stream as data is supplied | 96 * HTTP parser which parses the HTTP stream as data is supplied |
97 * through the [:writeList:] and [:connectionClosed:] methods. As the | 97 * through the [:writeList:] and [:connectionClosed:] methods. As the |
98 * data is parsed the following callbacks are called: | 98 * data is parsed the following callbacks are called: |
99 * | 99 * |
100 * [:requestStart:] | 100 * [:requestStart:] |
101 * [:responseStart:] | 101 * [:responseStart:] |
102 * [:headerReceived:] | |
103 * [:headersComplete:] | |
104 * [:dataReceived:] | 102 * [:dataReceived:] |
105 * [:dataEnd:] | 103 * [:dataEnd:] |
106 * [:closed:] | 104 * [:closed:] |
107 * [:error:] | 105 * [:error:] |
108 * | 106 * |
109 * If an HTTP parser error occours it is possible to get an exception | 107 * If an HTTP parser error occours it is possible to get an exception |
110 * thrown from the [:writeList:] and [:connectionClosed:] methods if | 108 * thrown from the [:writeList:] and [:connectionClosed:] methods if |
111 * the error callback is not set. | 109 * the error callback is not set. |
112 * | 110 * |
113 * The connection upgrades (e.g. switching from HTTP/1.1 to the | 111 * The connection upgrades (e.g. switching from HTTP/1.1 to the |
114 * WebSocket protocol) is handled in a special way. If connection | 112 * WebSocket protocol) is handled in a special way. If connection |
115 * upgrade is specified in the headers, then on the callback to | 113 * upgrade is specified in the headers, then on the callback to |
116 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] | 114 * [:responseStart:] the [:upgrade:] property on the [:HttpParser:] |
117 * object will be [:true:] indicating that from now on the protocol is | 115 * object will be [:true:] indicating that from now on the protocol is |
118 * not HTTP anymore and no more callbacks will happen, that is | 116 * not HTTP anymore and no more callbacks will happen, that is |
119 * [:dataReceived:] and [:dataEnd:] are not called in this case as | 117 * [:dataReceived:] and [:dataEnd:] are not called in this case as |
120 * there is no more HTTP data. After the upgrade the method | 118 * there is no more HTTP data. After the upgrade the method |
121 * [:readUnparsedData:] can be used to read any remaining bytes in the | 119 * [:readUnparsedData:] can be used to read any remaining bytes in the |
122 * HTTP parser which are part of the protocol the connection is | 120 * HTTP parser which are part of the protocol the connection is |
123 * upgrading to. These bytes cannot be processed by the HTTP parser | 121 * upgrading to. These bytes cannot be processed by the HTTP parser |
124 * and should be handled according to whatever protocol is being | 122 * and should be handled according to whatever protocol is being |
125 * upgraded to. | 123 * upgraded to. |
126 */ | 124 */ |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
282 } | 280 } |
283 } else { | 281 } else { |
284 _expect(byte, _CharCode.CR); | 282 _expect(byte, _CharCode.CR); |
285 _state = _State.REQUEST_LINE_ENDING; | 283 _state = _State.REQUEST_LINE_ENDING; |
286 } | 284 } |
287 break; | 285 break; |
288 | 286 |
289 case _State.REQUEST_LINE_ENDING: | 287 case _State.REQUEST_LINE_ENDING: |
290 _expect(byte, _CharCode.LF); | 288 _expect(byte, _CharCode.LF); |
291 _messageType = _MessageType.REQUEST; | 289 _messageType = _MessageType.REQUEST; |
292 requestStart(new String.fromCharCodes(_method_or_status_code), | |
293 new String.fromCharCodes(_uri_or_reason_phrase), | |
294 version); | |
295 _method_or_status_code.clear(); | |
296 _uri_or_reason_phrase.clear(); | |
297 _state = _State.HEADER_START; | 290 _state = _State.HEADER_START; |
298 break; | 291 break; |
299 | 292 |
300 case _State.RESPONSE_LINE_STATUS_CODE: | 293 case _State.RESPONSE_LINE_STATUS_CODE: |
301 if (byte == _CharCode.SP) { | 294 if (byte == _CharCode.SP) { |
302 if (_method_or_status_code.length != 3) { | 295 if (_method_or_status_code.length != 3) { |
303 throw new HttpParserException("Invalid response status code"); | 296 throw new HttpParserException("Invalid response status code"); |
304 } | 297 } |
305 _state = _State.RESPONSE_LINE_REASON_PHRASE; | 298 _state = _State.RESPONSE_LINE_REASON_PHRASE; |
306 } else { | 299 } else { |
(...skipping 15 matching lines...) Expand all Loading... |
322 if (byte == _CharCode.CR || byte == _CharCode.LF) { | 315 if (byte == _CharCode.CR || byte == _CharCode.LF) { |
323 throw new HttpParserException("Invalid response reason phrase"); | 316 throw new HttpParserException("Invalid response reason phrase"); |
324 } | 317 } |
325 _uri_or_reason_phrase.add(byte); | 318 _uri_or_reason_phrase.add(byte); |
326 } | 319 } |
327 break; | 320 break; |
328 | 321 |
329 case _State.RESPONSE_LINE_ENDING: | 322 case _State.RESPONSE_LINE_ENDING: |
330 _expect(byte, _CharCode.LF); | 323 _expect(byte, _CharCode.LF); |
331 _messageType == _MessageType.RESPONSE; | 324 _messageType == _MessageType.RESPONSE; |
332 int statusCode = parseInt(new String.fromCharCodes(_method_or_status
_code)); | 325 _statusCode = parseInt(new String.fromCharCodes(_method_or_status_c
ode)); |
333 if (statusCode < 100 || statusCode > 599) { | 326 if (_statusCode < 100 || _statusCode > 599) { |
334 throw new HttpParserException("Invalid response status code"); | 327 throw new HttpParserException("Invalid response status code"); |
335 } else { | 328 } else { |
336 // Check whether this response will never have a body. | 329 // Check whether this response will never have a body. |
337 _noMessageBody = | 330 _noMessageBody = |
338 statusCode <= 199 || statusCode == 204 || statusCode == 304; | 331 _statusCode <= 199 || _statusCode == 204 || _statusCode == 304
; |
339 } | 332 } |
340 responseStart(statusCode, | |
341 new String.fromCharCodes(_uri_or_reason_phrase), | |
342 version); | |
343 _method_or_status_code.clear(); | |
344 _uri_or_reason_phrase.clear(); | |
345 _state = _State.HEADER_START; | 333 _state = _State.HEADER_START; |
346 break; | 334 break; |
347 | 335 |
348 case _State.HEADER_START: | 336 case _State.HEADER_START: |
349 if (byte == _CharCode.CR) { | 337 if (byte == _CharCode.CR) { |
350 _state = _State.HEADER_ENDING; | 338 _state = _State.HEADER_ENDING; |
351 } else { | 339 } else { |
352 // Start of new header field. | 340 // Start of new header field. |
353 _headerField.add(_toLowerCase(byte)); | 341 _headerField.add(_toLowerCase(byte)); |
354 _state = _State.HEADER_FIELD; | 342 _state = _State.HEADER_FIELD; |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 List<String> tokens = _tokenizeFieldValue(headerValue); | 392 List<String> tokens = _tokenizeFieldValue(headerValue); |
405 for (int i = 0; i < tokens.length; i++) { | 393 for (int i = 0; i < tokens.length; i++) { |
406 String token = tokens[i].toLowerCase(); | 394 String token = tokens[i].toLowerCase(); |
407 if (token == "keep-alive") { | 395 if (token == "keep-alive") { |
408 _persistentConnection = true; | 396 _persistentConnection = true; |
409 } else if (token == "close") { | 397 } else if (token == "close") { |
410 _persistentConnection = false; | 398 _persistentConnection = false; |
411 } else if (token == "upgrade") { | 399 } else if (token == "upgrade") { |
412 _connectionUpgrade = true; | 400 _connectionUpgrade = true; |
413 } | 401 } |
414 headerReceived(headerField, token); | 402 _headers.add(headerField, token); |
| 403 |
415 } | 404 } |
416 reportHeader = false; | 405 reportHeader = false; |
417 } else if (headerField == "transfer-encoding" && | 406 } else if (headerField == "transfer-encoding" && |
418 headerValue.toLowerCase() == "chunked") { | 407 headerValue.toLowerCase() == "chunked") { |
419 // Ignore the Content-Length header if Transfer-Encoding | 408 // Ignore the Content-Length header if Transfer-Encoding |
420 // is chunked (RFC 2616 section 4.4) | 409 // is chunked (RFC 2616 section 4.4) |
421 _chunked = true; | 410 _chunked = true; |
422 _contentLength = -1; | 411 _contentLength = -1; |
423 } | 412 } |
424 if (reportHeader) { | 413 if (reportHeader) { |
425 headerReceived(headerField, headerValue); | 414 _headers.add(headerField, headerValue); |
426 } | 415 } |
427 _headerField.clear(); | 416 _headerField.clear(); |
428 _headerValue.clear(); | 417 _headerValue.clear(); |
429 | 418 |
430 if (byte == _CharCode.CR) { | 419 if (byte == _CharCode.CR) { |
431 _state = _State.HEADER_ENDING; | 420 _state = _State.HEADER_ENDING; |
432 } else { | 421 } else { |
433 // Start of new header field. | 422 // Start of new header field. |
434 _headerField.add(_toLowerCase(byte)); | 423 _headerField.add(_toLowerCase(byte)); |
435 _state = _State.HEADER_FIELD; | 424 _state = _State.HEADER_FIELD; |
436 } | 425 } |
437 } | 426 } |
438 break; | 427 break; |
439 | 428 |
440 case _State.HEADER_ENDING: | 429 case _State.HEADER_ENDING: |
441 _expect(byte, _CharCode.LF); | 430 _expect(byte, _CharCode.LF); |
442 // If a request message has neither Content-Length nor | 431 // If a request message has neither Content-Length nor |
443 // Transfer-Encoding the message must not have a body (RFC | 432 // Transfer-Encoding the message must not have a body (RFC |
444 // 2616 section 4.3). | 433 // 2616 section 4.3). |
445 if (_messageType == _MessageType.REQUEST && | 434 if (_messageType == _MessageType.REQUEST && |
446 _contentLength < 0 && | 435 _contentLength < 0 && |
447 _chunked == false) { | 436 _chunked == false) { |
448 _contentLength = 0; | 437 _contentLength = 0; |
449 } | 438 } |
450 if (_connectionUpgrade) { | 439 if (_connectionUpgrade) { |
451 _state = _State.UPGRADED; | 440 _state = _State.UPGRADED; |
452 headersComplete(); | 441 } |
| 442 if (_requestParser) { |
| 443 requestStart(new String.fromCharCodes(_method_or_status_code), |
| 444 new String.fromCharCodes(_uri_or_reason_phrase), |
| 445 version, |
| 446 _headers); |
453 } else { | 447 } else { |
454 headersComplete(); | 448 responseStart(_statusCode, |
| 449 new String.fromCharCodes(_uri_or_reason_phrase), |
| 450 version, |
| 451 _headers); |
| 452 } |
| 453 _method_or_status_code.clear(); |
| 454 _uri_or_reason_phrase.clear(); |
| 455 if (!_connectionUpgrade) { |
| 456 _method_or_status_code.clear(); |
| 457 _uri_or_reason_phrase.clear(); |
455 if (_chunked) { | 458 if (_chunked) { |
456 _state = _State.CHUNK_SIZE; | 459 _state = _State.CHUNK_SIZE; |
457 _remainingContent = 0; | 460 _remainingContent = 0; |
458 } else if (_contentLength == 0 || | 461 } else if (_contentLength == 0 || |
459 (_messageType == _MessageType.RESPONSE && | 462 (_messageType == _MessageType.RESPONSE && |
460 (_noMessageBody || _responseToMethod == "HEAD"))) { | 463 (_noMessageBody || _responseToMethod == "HEAD"))) { |
461 // If there is no message body get ready to process the | 464 // If there is no message body get ready to process the |
462 // next request. | 465 // next request. |
463 _bodyEnd(); | 466 _bodyEnd(); |
464 _reset(); | 467 _reset(); |
(...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
665 | 668 |
666 _httpVersion = _HttpVersion.UNDETERMINED; | 669 _httpVersion = _HttpVersion.UNDETERMINED; |
667 _contentLength = -1; | 670 _contentLength = -1; |
668 _persistentConnection = false; | 671 _persistentConnection = false; |
669 _connectionUpgrade = false; | 672 _connectionUpgrade = false; |
670 _chunked = false; | 673 _chunked = false; |
671 | 674 |
672 _noMessageBody = false; | 675 _noMessageBody = false; |
673 _responseToMethod = null; | 676 _responseToMethod = null; |
674 _remainingContent = null; | 677 _remainingContent = null; |
| 678 |
| 679 _headers = new _HttpHeaders(); |
675 } | 680 } |
676 | 681 |
677 _releaseBuffer() { | 682 _releaseBuffer() { |
678 _buffer = null; | 683 _buffer = null; |
679 _index = null; | 684 _index = null; |
680 _lastIndex = null; | 685 _lastIndex = null; |
681 } | 686 } |
682 | 687 |
683 bool _isTokenChar(int byte) { | 688 bool _isTokenChar(int byte) { |
684 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | 689 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
728 | 733 |
729 // The data that is currently being parsed. | 734 // The data that is currently being parsed. |
730 List<int> _buffer; | 735 List<int> _buffer; |
731 int _index; | 736 int _index; |
732 int _lastIndex; | 737 int _lastIndex; |
733 | 738 |
734 bool _requestParser; | 739 bool _requestParser; |
735 int _state; | 740 int _state; |
736 int _httpVersionIndex; | 741 int _httpVersionIndex; |
737 int _messageType; | 742 int _messageType; |
| 743 int _statusCode; |
738 List _method_or_status_code; | 744 List _method_or_status_code; |
739 List _uri_or_reason_phrase; | 745 List _uri_or_reason_phrase; |
740 List _headerField; | 746 List _headerField; |
741 List _headerValue; | 747 List _headerValue; |
742 | 748 |
743 int _httpVersion; | 749 int _httpVersion; |
744 int _contentLength; | 750 int _contentLength; |
745 bool _persistentConnection; | 751 bool _persistentConnection; |
746 bool _connectionUpgrade; | 752 bool _connectionUpgrade; |
747 bool _chunked; | 753 bool _chunked; |
748 | 754 |
749 bool _noMessageBody; | 755 bool _noMessageBody; |
750 String _responseToMethod; // Indicates the method used for the request. | 756 String _responseToMethod; // Indicates the method used for the request. |
751 int _remainingContent; | 757 int _remainingContent; |
752 | 758 |
| 759 _HttpHeaders _headers = new _HttpHeaders(); |
| 760 |
753 // Callbacks. | 761 // Callbacks. |
754 Function requestStart; | 762 Function requestStart; |
755 Function responseStart; | 763 Function responseStart; |
756 Function headerReceived; | |
757 Function headersComplete; | |
758 Function dataReceived; | 764 Function dataReceived; |
759 Function dataEnd; | 765 Function dataEnd; |
760 Function error; | 766 Function error; |
761 Function closed; | 767 Function closed; |
762 } | 768 } |
763 | 769 |
764 | 770 |
765 class HttpParserException implements Exception { | 771 class HttpParserException implements Exception { |
766 const HttpParserException([String this.message = ""]); | 772 const HttpParserException([String this.message = ""]); |
767 String toString() => "HttpParserException: $message"; | 773 String toString() => "HttpParserException: $message"; |
768 final String message; | 774 final String message; |
769 } | 775 } |
OLD | NEW |