Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: sdk/lib/io/http_parser.dart

Issue 11359085: Refactor HTTP parser to hold current buffer (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed review commetns Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/io/http_impl.dart ('k') | tests/standalone/io/http_parser_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « sdk/lib/io/http_impl.dart ('k') | tests/standalone/io/http_parser_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698