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 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
109 * thrown from the [:writeList:] and [:connectionClosed:] methods if | 109 * thrown from the [:writeList:] and [:connectionClosed:] methods if |
110 * the error callback is not set. | 110 * the error callback is not set. |
111 * | 111 * |
112 * The connection upgrades (e.g. switching from HTTP/1.1 to the | 112 * The connection upgrades (e.g. switching from HTTP/1.1 to the |
113 * WebSocket protocol) is handled in a special way. If connection | 113 * WebSocket protocol) is handled in a special way. If connection |
114 * upgrade is specified in the headers, then on the callback to | 114 * upgrade is specified in the headers, then on the callback to |
115 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] | 115 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] |
116 * object will be [:true:] indicating that from now on the protocol is | 116 * object will be [:true:] indicating that from now on the protocol is |
117 * not HTTP anymore and no more callbacks will happen, that is | 117 * not HTTP anymore and no more callbacks will happen, that is |
118 * [:dataReceived:] and [:dataEnd:] are not called in this case as | 118 * [:dataReceived:] and [:dataEnd:] are not called in this case as |
119 * there is no more HTTP data. After the upgrade the call to | 119 * there is no more HTTP data. After the upgrade the method |
120 * [:writeList:] causing the upgrade will return with the number of | 120 * [:readUnparsedData:] can be used to read any remaining bytes in the |
121 * bytes parsed as HTTP. Any unparsed bytes is part of the protocol | 121 * HTTP parser which are part of the protocol the connection is |
122 * the connection is upgrading to and should be handled according to | 122 * upgrading to. These bytes cannot be processed by the HTTP parser |
123 * that protocol. | 123 * and should be handled according to whatever protocol is being |
124 * upgraded to. | |
124 */ | 125 */ |
125 class _HttpParser { | 126 class _HttpParser { |
126 _HttpParser() { | 127 _HttpParser() { |
127 _reset(); | 128 _reset(); |
128 } | 129 } |
129 | 130 |
130 // From RFC 2616. | 131 // From RFC 2616. |
131 // generic-message = start-line | 132 // generic-message = start-line |
132 // *(message-header CRLF) | 133 // *(message-header CRLF) |
133 // CRLF | 134 // CRLF |
134 // [ message-body ] | 135 // [ message-body ] |
135 // start-line = Request-Line | Status-Line | 136 // start-line = Request-Line | Status-Line |
136 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | 137 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF |
137 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | 138 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
138 // message-header = field-name ":" [ field-value ] | 139 // message-header = field-name ":" [ field-value ] |
139 int writeList(List<int> buffer, int offset, int count) { | 140 void _parse() { |
140 int index = offset; | |
141 int lastIndex = offset + count; | |
142 try { | 141 try { |
143 if (_state == _State.CLOSED) { | 142 if (_state == _State.CLOSED) { |
144 throw new HttpParserException("Data on closed connection"); | 143 throw new HttpParserException("Data on closed connection"); |
145 } | 144 } |
146 if (_state == _State.UPGRADED) { | 145 if (_state == _State.UPGRADED) { |
147 throw new HttpParserException("Data on upgraded connection"); | 146 throw new HttpParserException("Data on upgraded connection"); |
148 } | 147 } |
149 if (_state == _State.FAILURE) { | 148 if (_state == _State.FAILURE) { |
150 throw new HttpParserException("Data on failed connection"); | 149 throw new HttpParserException("Data on failed connection"); |
151 } | 150 } |
152 while ((index < lastIndex) && | 151 while (_buffer != null && |
152 _index < _lastIndex && | |
153 _state != _State.FAILURE && | 153 _state != _State.FAILURE && |
154 _state != _State.UPGRADED) { | 154 _state != _State.UPGRADED) { |
155 int byte = buffer[index]; | 155 int byte = _buffer[_index++]; |
156 switch (_state) { | 156 switch (_state) { |
157 case _State.START: | 157 case _State.START: |
158 if (byte == _Const.HTTP[0]) { | 158 if (byte == _Const.HTTP[0]) { |
159 // Start parsing method or HTTP version. | 159 // Start parsing method or HTTP version. |
160 _httpVersionIndex = 1; | 160 _httpVersionIndex = 1; |
161 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; | 161 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; |
162 } else { | 162 } else { |
163 // Start parsing method. | 163 // Start parsing method. |
164 if (!_isTokenChar(byte)) { | 164 if (!_isTokenChar(byte)) { |
165 throw new HttpParserException("Invalid request method"); | 165 throw new HttpParserException("Invalid request method"); |
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
318 _messageType == _MessageType.RESPONSE; | 318 _messageType == _MessageType.RESPONSE; |
319 int statusCode = parseInt(_method_or_status_code.toString()); | 319 int statusCode = parseInt(_method_or_status_code.toString()); |
320 if (statusCode < 100 || statusCode > 599) { | 320 if (statusCode < 100 || statusCode > 599) { |
321 throw new HttpParserException("Invalid response status code"); | 321 throw new HttpParserException("Invalid response status code"); |
322 } else { | 322 } else { |
323 // Check whether this response will never have a body. | 323 // Check whether this response will never have a body. |
324 _noMessageBody = | 324 _noMessageBody = |
325 statusCode <= 199 || statusCode == 204 || statusCode == 304; | 325 statusCode <= 199 || statusCode == 204 || statusCode == 304; |
326 } | 326 } |
327 if (responseStart != null) { | 327 if (responseStart != null) { |
328 responseStart(statusCode, _uri_or_reason_phrase.toString(), versio n); | 328 responseStart(statusCode, |
329 _uri_or_reason_phrase.toString(), | |
330 version); | |
329 } | 331 } |
330 _method_or_status_code.clear(); | 332 _method_or_status_code.clear(); |
331 _uri_or_reason_phrase.clear(); | 333 _uri_or_reason_phrase.clear(); |
332 _state = _State.HEADER_START; | 334 _state = _State.HEADER_START; |
333 break; | 335 break; |
334 | 336 |
335 case _State.HEADER_START: | 337 case _State.HEADER_START: |
336 if (byte == _CharCode.CR) { | 338 if (byte == _CharCode.CR) { |
337 _state = _State.HEADER_ENDING; | 339 _state = _State.HEADER_ENDING; |
338 } else { | 340 } else { |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
431 // If a request message has neither Content-Length nor | 433 // If a request message has neither Content-Length nor |
432 // Transfer-Encoding the message must not have a body (RFC | 434 // Transfer-Encoding the message must not have a body (RFC |
433 // 2616 section 4.3). | 435 // 2616 section 4.3). |
434 if (_messageType == _MessageType.REQUEST && | 436 if (_messageType == _MessageType.REQUEST && |
435 _contentLength < 0 && | 437 _contentLength < 0 && |
436 _chunked == false) { | 438 _chunked == false) { |
437 _contentLength = 0; | 439 _contentLength = 0; |
438 } | 440 } |
439 if (_connectionUpgrade) { | 441 if (_connectionUpgrade) { |
440 _state = _State.UPGRADED; | 442 _state = _State.UPGRADED; |
441 _unparsedData = | |
442 buffer.getRange(index + 1, count - (index + 1 - offset)); | |
443 if (headersComplete != null) headersComplete(); | 443 if (headersComplete != null) headersComplete(); |
444 } else { | 444 } else { |
445 if (headersComplete != null) headersComplete(); | 445 if (headersComplete != null) headersComplete(); |
446 if (_chunked) { | 446 if (_chunked) { |
447 _state = _State.CHUNK_SIZE; | 447 _state = _State.CHUNK_SIZE; |
448 _remainingContent = 0; | 448 _remainingContent = 0; |
449 } else if (_contentLength == 0 || | 449 } else if (_contentLength == 0 || |
450 (_messageType == _MessageType.RESPONSE && | 450 (_messageType == _MessageType.RESPONSE && |
451 (_noMessageBody || _responseToMethod == "HEAD"))) { | 451 (_noMessageBody || _responseToMethod == "HEAD"))) { |
452 // If there is no message body get ready to process the | 452 // If there is no message body get ready to process the |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
506 break; | 506 break; |
507 | 507 |
508 case _State.CHUNKED_BODY_DONE_LF: | 508 case _State.CHUNKED_BODY_DONE_LF: |
509 _expect(byte, _CharCode.LF); | 509 _expect(byte, _CharCode.LF); |
510 _bodyEnd(); | 510 _bodyEnd(); |
511 _reset(); | 511 _reset(); |
512 break; | 512 break; |
513 | 513 |
514 case _State.BODY: | 514 case _State.BODY: |
515 // The body is not handled one byte at a time but in blocks. | 515 // The body is not handled one byte at a time but in blocks. |
516 int dataAvailable = lastIndex - index; | 516 _index--; |
517 int dataAvailable = _lastIndex - _index; | |
517 List<int> data; | 518 List<int> data; |
518 if (_remainingContent == null || | 519 if (_remainingContent == null || |
519 dataAvailable <= _remainingContent) { | 520 dataAvailable <= _remainingContent) { |
520 data = new Uint8List(dataAvailable); | 521 data = new Uint8List(dataAvailable); |
521 data.setRange(0, dataAvailable, buffer, index); | 522 data.setRange(0, dataAvailable, _buffer, _index); |
522 } else { | 523 } else { |
523 data = new Uint8List(_remainingContent); | 524 data = new Uint8List(_remainingContent); |
524 data.setRange(0, _remainingContent, buffer, index); | 525 data.setRange(0, _remainingContent, _buffer, _index); |
525 } | 526 } |
526 | 527 |
527 if (dataReceived != null) dataReceived(data); | 528 if (dataReceived != null) dataReceived(data); |
528 if (_remainingContent != null) { | 529 if (_remainingContent != null) { |
529 _remainingContent -= data.length; | 530 _remainingContent -= data.length; |
530 } | 531 } |
531 index += data.length; | 532 _index += data.length; |
532 if (_remainingContent == 0) { | 533 if (_remainingContent == 0) { |
533 if (!_chunked) { | 534 if (!_chunked) { |
534 _bodyEnd(); | 535 _bodyEnd(); |
535 _reset(); | 536 _reset(); |
536 } else { | 537 } else { |
537 _state = _State.CHUNK_SIZE_STARTING_CR; | 538 _state = _State.CHUNK_SIZE_STARTING_CR; |
538 } | 539 } |
539 } | 540 } |
540 | |
541 // Hack - as we always do index++ below. | |
542 index--; | |
543 break; | 541 break; |
544 | 542 |
545 case _State.FAILURE: | 543 case _State.FAILURE: |
546 // Should be unreachable. | 544 // Should be unreachable. |
547 assert(false); | 545 assert(false); |
548 break; | 546 break; |
549 | 547 |
550 default: | 548 default: |
551 // Should be unreachable. | 549 // Should be unreachable. |
552 assert(false); | 550 assert(false); |
553 break; | 551 break; |
554 } | 552 } |
555 | |
556 // Move to the next byte. | |
557 index++; | |
558 } | 553 } |
559 } catch (e) { | 554 } catch (e) { |
560 // Report the error through the error callback if any. Otherwise | 555 // Report the error through the error callback if any. Otherwise |
561 // throw the error. | 556 // throw the error. |
562 if (error != null) { | 557 if (error != null) { |
563 error(e); | 558 error(e); |
564 _state = _State.FAILURE; | 559 _state = _State.FAILURE; |
565 } else { | 560 } else { |
566 throw e; | 561 throw e; |
567 } | 562 } |
568 } | 563 } |
569 | 564 |
570 // Return the number of bytes parsed. | 565 // If all data is parsed there is no need to hold on to the buffer. |
571 return index - offset; | 566 if (_index == _lastIndex) _releaseBuffer(); |
Mads Ager (google)
2012/11/07 13:36:43
Can we make this stronger? Maybe add an assert tha
Søren Gjesse
2012/11/07 13:58:34
Changed to release buffer except when connection i
| |
567 } | |
568 | |
569 void writeList(List<int> buffer, int offset, int count) { | |
570 assert(_buffer == null); | |
571 _buffer = buffer; | |
572 _index = offset; | |
573 _lastIndex = offset + count; | |
574 _parse(); | |
572 } | 575 } |
573 | 576 |
574 void connectionClosed() { | 577 void connectionClosed() { |
575 if (_state < _State.FIRST_BODY_STATE) { | 578 if (_state < _State.FIRST_BODY_STATE) { |
576 _state = _State.FAILURE; | 579 _state = _State.FAILURE; |
577 // Report the error through the error callback if any. Otherwise | 580 // Report the error through the error callback if any. Otherwise |
578 // throw the error. | 581 // throw the error. |
579 var e = new HttpParserException( | 582 var e = new HttpParserException( |
580 "Connection closed before full header was received"); | 583 "Connection closed before full header was received"); |
581 if (error != null) { | 584 if (error != null) { |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
616 | 619 |
617 int get messageType => _messageType; | 620 int get messageType => _messageType; |
618 int get contentLength => _contentLength; | 621 int get contentLength => _contentLength; |
619 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; | 622 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; |
620 bool get persistentConnection => _persistentConnection; | 623 bool get persistentConnection => _persistentConnection; |
621 | 624 |
622 void set responseToMethod(String method) { _responseToMethod = method; } | 625 void set responseToMethod(String method) { _responseToMethod = method; } |
623 | 626 |
624 bool get isIdle => _state == _State.START; | 627 bool get isIdle => _state == _State.START; |
625 | 628 |
626 List<int> get unparsedData => _unparsedData; | 629 List<int> readUnparsedData() { |
630 if (_buffer == null) return []; | |
631 if (_index == _lastIndex) return []; | |
632 var result = _buffer.getRange(_index, _lastIndex - _index); | |
633 _releaseBuffer(); | |
634 return result; | |
635 } | |
627 | 636 |
628 void _bodyEnd() { | 637 void _bodyEnd() { |
629 if (dataEnd != null) { | 638 if (dataEnd != null) { |
630 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); | 639 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); |
631 } | 640 } |
632 } | 641 } |
633 | 642 |
634 _reset() { | 643 _reset() { |
635 _state = _State.START; | 644 _state = _State.START; |
636 _messageType = _MessageType.UNDETERMINED; | 645 _messageType = _MessageType.UNDETERMINED; |
637 _headerField = new StringBuffer(); | 646 _headerField = new StringBuffer(); |
638 _headerValue = new StringBuffer(); | 647 _headerValue = new StringBuffer(); |
639 _method_or_status_code = new StringBuffer(); | 648 _method_or_status_code = new StringBuffer(); |
640 _uri_or_reason_phrase = new StringBuffer(); | 649 _uri_or_reason_phrase = new StringBuffer(); |
641 | 650 |
642 _httpVersion = _HttpVersion.UNDETERMINED; | 651 _httpVersion = _HttpVersion.UNDETERMINED; |
643 _contentLength = -1; | 652 _contentLength = -1; |
644 _persistentConnection = false; | 653 _persistentConnection = false; |
645 _connectionUpgrade = false; | 654 _connectionUpgrade = false; |
646 _chunked = false; | 655 _chunked = false; |
647 | 656 |
648 _noMessageBody = false; | 657 _noMessageBody = false; |
649 _responseToMethod = null; | 658 _responseToMethod = null; |
650 _remainingContent = null; | 659 _remainingContent = null; |
651 } | 660 } |
652 | 661 |
662 _releaseBuffer() { | |
663 _buffer = null; | |
664 _index = null; | |
665 _lastIndex = null; | |
666 } | |
667 | |
653 bool _isTokenChar(int byte) { | 668 bool _isTokenChar(int byte) { |
654 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | 669 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; |
655 } | 670 } |
656 | 671 |
657 List<String> _tokenizeFieldValue(String headerValue) { | 672 List<String> _tokenizeFieldValue(String headerValue) { |
658 List<String> tokens = new List<String>(); | 673 List<String> tokens = new List<String>(); |
659 int start = 0; | 674 int start = 0; |
660 int index = 0; | 675 int index = 0; |
661 while (index < headerValue.length) { | 676 while (index < headerValue.length) { |
662 if (headerValue[index] == ",") { | 677 if (headerValue[index] == ",") { |
(...skipping 26 matching lines...) Expand all Loading... | |
689 return byte - 0x30; // 0 - 9 | 704 return byte - 0x30; // 0 - 9 |
690 } else if (0x41 <= byte && byte <= 0x46) { | 705 } else if (0x41 <= byte && byte <= 0x46) { |
691 return byte - 0x41 + 10; // A - F | 706 return byte - 0x41 + 10; // A - F |
692 } else if (0x61 <= byte && byte <= 0x66) { | 707 } else if (0x61 <= byte && byte <= 0x66) { |
693 return byte - 0x61 + 10; // a - f | 708 return byte - 0x61 + 10; // a - f |
694 } else { | 709 } else { |
695 throw new HttpParserException("Failed to parse HTTP"); | 710 throw new HttpParserException("Failed to parse HTTP"); |
696 } | 711 } |
697 } | 712 } |
698 | 713 |
714 // The data that is currently being parsed. | |
715 List<int> _buffer; | |
716 int _index; | |
717 int _lastIndex; | |
718 | |
699 int _state; | 719 int _state; |
700 int _httpVersionIndex; | 720 int _httpVersionIndex; |
701 int _messageType; | 721 int _messageType; |
702 StringBuffer _method_or_status_code; | 722 StringBuffer _method_or_status_code; |
703 StringBuffer _uri_or_reason_phrase; | 723 StringBuffer _uri_or_reason_phrase; |
704 StringBuffer _headerField; | 724 StringBuffer _headerField; |
705 StringBuffer _headerValue; | 725 StringBuffer _headerValue; |
706 | 726 |
707 int _httpVersion; | 727 int _httpVersion; |
708 int _contentLength; | 728 int _contentLength; |
709 bool _persistentConnection; | 729 bool _persistentConnection; |
710 bool _connectionUpgrade; | 730 bool _connectionUpgrade; |
711 bool _chunked; | 731 bool _chunked; |
712 | 732 |
713 bool _noMessageBody; | 733 bool _noMessageBody; |
714 String _responseToMethod; // Indicates the method used for the request. | 734 String _responseToMethod; // Indicates the method used for the request. |
715 int _remainingContent; | 735 int _remainingContent; |
716 | 736 |
717 List<int> _unparsedData; // Unparsed data after connection upgrade. | |
718 // Callbacks. | 737 // Callbacks. |
719 Function requestStart; | 738 Function requestStart; |
720 Function responseStart; | 739 Function responseStart; |
721 Function headerReceived; | 740 Function headerReceived; |
722 Function headersComplete; | 741 Function headersComplete; |
723 Function dataReceived; | 742 Function dataReceived; |
724 Function dataEnd; | 743 Function dataEnd; |
725 Function error; | 744 Function error; |
726 } | 745 } |
727 | 746 |
728 | 747 |
729 class HttpParserException implements Exception { | 748 class HttpParserException implements Exception { |
730 const HttpParserException([String this.message = ""]); | 749 const HttpParserException([String this.message = ""]); |
731 String toString() => "HttpParserException: $message"; | 750 String toString() => "HttpParserException: $message"; |
732 final String message; | 751 final String message; |
733 } | 752 } |
OLD | NEW |