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

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: 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
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 = 443 // Make sure that readUnparsedData will return the correct data.
442 buffer.getRange(index + 1, count - (index + 1 - offset)); 444 _index++;
Mads Ager (google) 2012/11/07 13:02:51 The reason for incrementing here and then avoiding
Søren Gjesse 2012/11/07 13:31:14 Yes, but thats not good. Changed the handling of _
443 if (headersComplete != null) headersComplete(); 445 if (headersComplete != null) headersComplete();
444 } else { 446 } else {
445 if (headersComplete != null) headersComplete(); 447 if (headersComplete != null) headersComplete();
446 if (_chunked) { 448 if (_chunked) {
447 _state = _State.CHUNK_SIZE; 449 _state = _State.CHUNK_SIZE;
448 _remainingContent = 0; 450 _remainingContent = 0;
449 } else if (_contentLength == 0 || 451 } else if (_contentLength == 0 ||
450 (_messageType == _MessageType.RESPONSE && 452 (_messageType == _MessageType.RESPONSE &&
451 (_noMessageBody || _responseToMethod == "HEAD"))) { 453 (_noMessageBody || _responseToMethod == "HEAD"))) {
452 // If there is no message body get ready to process the 454 // 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; 508 break;
507 509
508 case _State.CHUNKED_BODY_DONE_LF: 510 case _State.CHUNKED_BODY_DONE_LF:
509 _expect(byte, _CharCode.LF); 511 _expect(byte, _CharCode.LF);
510 _bodyEnd(); 512 _bodyEnd();
511 _reset(); 513 _reset();
512 break; 514 break;
513 515
514 case _State.BODY: 516 case _State.BODY:
515 // The body is not handled one byte at a time but in blocks. 517 // The body is not handled one byte at a time but in blocks.
516 int dataAvailable = lastIndex - index; 518 int dataAvailable = _lastIndex - _index;
517 List<int> data; 519 List<int> data;
518 if (_remainingContent == null || 520 if (_remainingContent == null ||
519 dataAvailable <= _remainingContent) { 521 dataAvailable <= _remainingContent) {
520 data = new Uint8List(dataAvailable); 522 data = new Uint8List(dataAvailable);
521 data.setRange(0, dataAvailable, buffer, index); 523 data.setRange(0, dataAvailable, _buffer, _index);
522 } else { 524 } else {
523 data = new Uint8List(_remainingContent); 525 data = new Uint8List(_remainingContent);
524 data.setRange(0, _remainingContent, buffer, index); 526 data.setRange(0, _remainingContent, _buffer, _index);
525 } 527 }
526 528
527 if (dataReceived != null) dataReceived(data); 529 if (dataReceived != null) dataReceived(data);
528 if (_remainingContent != null) { 530 if (_remainingContent != null) {
529 _remainingContent -= data.length; 531 _remainingContent -= data.length;
530 } 532 }
531 index += data.length; 533 _index += data.length;
532 if (_remainingContent == 0) { 534 if (_remainingContent == 0) {
533 if (!_chunked) { 535 if (!_chunked) {
534 _bodyEnd(); 536 _bodyEnd();
535 _reset(); 537 _reset();
536 } else { 538 } else {
537 _state = _State.CHUNK_SIZE_STARTING_CR; 539 _state = _State.CHUNK_SIZE_STARTING_CR;
538 } 540 }
539 } 541 }
540 542
541 // Hack - as we always do index++ below. 543 // Hack - as we always do _index++ below.
542 index--; 544 _index--;
543 break; 545 break;
544 546
545 case _State.FAILURE: 547 case _State.FAILURE:
546 // Should be unreachable. 548 // Should be unreachable.
547 assert(false); 549 assert(false);
548 break; 550 break;
549 551
550 default: 552 default:
551 // Should be unreachable. 553 // Should be unreachable.
552 assert(false); 554 assert(false);
553 break; 555 break;
554 } 556 }
555 557
556 // Move to the next byte. 558 // Move to the next byte.
557 index++; 559 if (_state != _State.UPGRADED) _index++;
558 } 560 }
559 } catch (e) { 561 } catch (e) {
560 // Report the error through the error callback if any. Otherwise 562 // Report the error through the error callback if any. Otherwise
561 // throw the error. 563 // throw the error.
562 if (error != null) { 564 if (error != null) {
563 error(e); 565 error(e);
564 _state = _State.FAILURE; 566 _state = _State.FAILURE;
565 } else { 567 } else {
566 throw e; 568 throw e;
567 } 569 }
568 } 570 }
569 571
570 // Return the number of bytes parsed. 572 // If all data is parsed there is not need to hold on to the buffer.
Mads Ager (google) 2012/11/07 13:02:51 there is not need -> there is no need
Søren Gjesse 2012/11/07 13:31:14 Done.
571 return index - offset; 573 if (_index == _lastIndex) _releaseBuffer();
574 }
575
576 void writeList(List<int> buffer, int offset, int count) {
577 assert(_buffer == null);
578 _buffer = buffer;
579 _index = offset;
580 _lastIndex = offset + count;
581 _parse();
572 } 582 }
573 583
574 void connectionClosed() { 584 void connectionClosed() {
575 if (_state < _State.FIRST_BODY_STATE) { 585 if (_state < _State.FIRST_BODY_STATE) {
576 _state = _State.FAILURE; 586 _state = _State.FAILURE;
577 // Report the error through the error callback if any. Otherwise 587 // Report the error through the error callback if any. Otherwise
578 // throw the error. 588 // throw the error.
579 var e = new HttpParserException( 589 var e = new HttpParserException(
580 "Connection closed before full header was received"); 590 "Connection closed before full header was received");
581 if (error != null) { 591 if (error != null) {
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
616 626
617 int get messageType => _messageType; 627 int get messageType => _messageType;
618 int get contentLength => _contentLength; 628 int get contentLength => _contentLength;
619 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; 629 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED;
620 bool get persistentConnection => _persistentConnection; 630 bool get persistentConnection => _persistentConnection;
621 631
622 void set responseToMethod(String method) { _responseToMethod = method; } 632 void set responseToMethod(String method) { _responseToMethod = method; }
623 633
624 bool get isIdle => _state == _State.START; 634 bool get isIdle => _state == _State.START;
625 635
626 List<int> get unparsedData => _unparsedData; 636 List<int> readUnparsedData() {
637 if (_buffer == null) return [];
638 if (_index == _lastIndex) return [];
639 var result = _buffer.getRange(_index, _lastIndex - _index);
640 _releaseBuffer();
641 return result;
642 }
627 643
628 void _bodyEnd() { 644 void _bodyEnd() {
629 if (dataEnd != null) { 645 if (dataEnd != null) {
630 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); 646 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection);
631 } 647 }
632 } 648 }
633 649
634 _reset() { 650 _reset() {
635 _state = _State.START; 651 _state = _State.START;
636 _messageType = _MessageType.UNDETERMINED; 652 _messageType = _MessageType.UNDETERMINED;
637 _headerField = new StringBuffer(); 653 _headerField = new StringBuffer();
638 _headerValue = new StringBuffer(); 654 _headerValue = new StringBuffer();
639 _method_or_status_code = new StringBuffer(); 655 _method_or_status_code = new StringBuffer();
640 _uri_or_reason_phrase = new StringBuffer(); 656 _uri_or_reason_phrase = new StringBuffer();
641 657
642 _httpVersion = _HttpVersion.UNDETERMINED; 658 _httpVersion = _HttpVersion.UNDETERMINED;
643 _contentLength = -1; 659 _contentLength = -1;
644 _persistentConnection = false; 660 _persistentConnection = false;
645 _connectionUpgrade = false; 661 _connectionUpgrade = false;
646 _chunked = false; 662 _chunked = false;
647 663
648 _noMessageBody = false; 664 _noMessageBody = false;
649 _responseToMethod = null; 665 _responseToMethod = null;
650 _remainingContent = null; 666 _remainingContent = null;
651 } 667 }
652 668
669 _releaseBuffer() {
670 _buffer = null;
671 _index = null;
672 _lastIndex = null;
673 }
674
653 bool _isTokenChar(int byte) { 675 bool _isTokenChar(int byte) {
654 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; 676 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1;
655 } 677 }
656 678
657 List<String> _tokenizeFieldValue(String headerValue) { 679 List<String> _tokenizeFieldValue(String headerValue) {
658 List<String> tokens = new List<String>(); 680 List<String> tokens = new List<String>();
659 int start = 0; 681 int start = 0;
660 int index = 0; 682 int index = 0;
661 while (index < headerValue.length) { 683 while (index < headerValue.length) {
662 if (headerValue[index] == ",") { 684 if (headerValue[index] == ",") {
(...skipping 26 matching lines...) Expand all
689 return byte - 0x30; // 0 - 9 711 return byte - 0x30; // 0 - 9
690 } else if (0x41 <= byte && byte <= 0x46) { 712 } else if (0x41 <= byte && byte <= 0x46) {
691 return byte - 0x41 + 10; // A - F 713 return byte - 0x41 + 10; // A - F
692 } else if (0x61 <= byte && byte <= 0x66) { 714 } else if (0x61 <= byte && byte <= 0x66) {
693 return byte - 0x61 + 10; // a - f 715 return byte - 0x61 + 10; // a - f
694 } else { 716 } else {
695 throw new HttpParserException("Failed to parse HTTP"); 717 throw new HttpParserException("Failed to parse HTTP");
696 } 718 }
697 } 719 }
698 720
721 // The data currently parsed.
Mads Ager (google) 2012/11/07 13:02:51 The data that is currently being parsed?
Søren Gjesse 2012/11/07 13:31:14 Done.
722 List<int> _buffer;
723 int _index;
724 int _lastIndex;
725
699 int _state; 726 int _state;
700 int _httpVersionIndex; 727 int _httpVersionIndex;
701 int _messageType; 728 int _messageType;
702 StringBuffer _method_or_status_code; 729 StringBuffer _method_or_status_code;
703 StringBuffer _uri_or_reason_phrase; 730 StringBuffer _uri_or_reason_phrase;
704 StringBuffer _headerField; 731 StringBuffer _headerField;
705 StringBuffer _headerValue; 732 StringBuffer _headerValue;
706 733
707 int _httpVersion; 734 int _httpVersion;
708 int _contentLength; 735 int _contentLength;
709 bool _persistentConnection; 736 bool _persistentConnection;
710 bool _connectionUpgrade; 737 bool _connectionUpgrade;
711 bool _chunked; 738 bool _chunked;
712 739
713 bool _noMessageBody; 740 bool _noMessageBody;
714 String _responseToMethod; // Indicates the method used for the request. 741 String _responseToMethod; // Indicates the method used for the request.
715 int _remainingContent; 742 int _remainingContent;
716 743
717 List<int> _unparsedData; // Unparsed data after connection upgrade.
718 // Callbacks. 744 // Callbacks.
719 Function requestStart; 745 Function requestStart;
720 Function responseStart; 746 Function responseStart;
721 Function headerReceived; 747 Function headerReceived;
722 Function headersComplete; 748 Function headersComplete;
723 Function dataReceived; 749 Function dataReceived;
724 Function dataEnd; 750 Function dataEnd;
725 Function error; 751 Function error;
726 } 752 }
727 753
728 754
729 class HttpParserException implements Exception { 755 class HttpParserException implements Exception {
730 const HttpParserException([String this.message = ""]); 756 const HttpParserException([String this.message = ""]);
731 String toString() => "HttpParserException: $message"; 757 String toString() => "HttpParserException: $message";
732 final String message; 758 final String message;
733 } 759 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698