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

Unified Diff: runtime/bin/http_parser.dart

Issue 9834008: Add error handling to the HTTP library (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 9 months 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 side-by-side diff with in-line comments
Download patch
Index: runtime/bin/http_parser.dart
diff --git a/runtime/bin/http_parser.dart b/runtime/bin/http_parser.dart
index 644c0e0cbaa73c9be3c72e25a6b07b3ac0893b62..a349eb5ca7d6b8ca188cebc3cd99be5d72fd395b 100644
--- a/runtime/bin/http_parser.dart
+++ b/runtime/bin/http_parser.dart
@@ -10,6 +10,15 @@ class _Const {
static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49];
static final END_CHUNKED = const [0x30, 13, 10, 13, 10];
+
+ // Bytes for '()<>@,;:\\"/[]?={} \t'.
+ static final SEPARATORS = const [40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47,
+ 91, 93, 63, 61, 123, 125, 32, 9];
+
+ // Bytes for '()<>@,;:\\"/[]?={} \t\r\n'.
+ static final SEPARATORS_AND_CR_LF = const [40, 41, 60, 62, 64, 44, 59, 58, 92,
+ 34, 47, 91, 93, 63, 61, 123, 125,
+ 32, 9, 13, 10];
}
@@ -50,6 +59,7 @@ class _State {
static final int CHUNKED_BODY_DONE_CR = 21;
static final int CHUNKED_BODY_DONE_LF = 22;
static final int BODY = 23;
+ static final int FAILURE = 24;
}
@@ -63,16 +73,23 @@ class _MessageType {
/**
* HTTP parser which parses the HTTP stream as data is supplied
- * through the writeList method. As the data is parsed the events
- * RequestStart
- * ResponseStart
- * UriReceived
- * HeaderReceived
- * HeadersComplete
- * DataReceived
- * DataEnd
- * are generated.
- * Currently only HTTP requests with Content-Length header are supported.
+ * through the [:writeList:] and [:connectionClosed:] methods. As the
+ * data is parsed the following callbacks are called:
+ *
+ * [:requestStart:]
+ * [:responseStart:]
+ * [:headerReceived:]
+ * [:headersComplete:]
+ * [:dataReceived:]
+ * [:dataEnd:]
+ * [:error:]
+ *
+ * If an HTTP parser error occours it is possible to get an exception
+ * thrown from the [:writeList:] and [:connectionClosed:] methods if
+ * the error callback is not set.
+ *
+ * For keep-alive connections where the underlying connection stays
+ * open the [:connectionClosed:] method would not be called.
*/
class _HttpParser {
_HttpParser() {
@@ -91,182 +108,137 @@ class _HttpParser {
int writeList(List<int> buffer, int offset, int count) {
int index = offset;
int lastIndex = offset + count;
- while ((index < lastIndex) && !_failure) {
- int byte = buffer[index];
- switch (_state) {
- case _State.START:
- if (byte == _Const.HTTP11[0]) {
- // Start parsing HTTP method.
- _httpVersionIndex = 1;
- _state = _State.METHOD_OR_HTTP_VERSION;
- } else {
- // Start parsing method.
- _method_or_status_code.addCharCode(byte);
- _state = _State.REQUEST_LINE_METHOD;
- }
- break;
-
- case _State.METHOD_OR_HTTP_VERSION:
- if (_httpVersionIndex < _Const.HTTP11.length &&
- byte == _Const.HTTP11[_httpVersionIndex]) {
- // Continue parsing HTTP version.
- _httpVersionIndex++;
- } else if (_httpVersionIndex == _Const.HTTP11.length &&
- byte == _CharCode.SP) {
- // HTTP version parsed.
- _state = _State.RESPONSE_LINE_STATUS_CODE;
- } else {
- // Did not parse HTTP version. Expect method instead.
- for (int i = 0; i < _httpVersionIndex; i++) {
- _method_or_status_code.addCharCode(_Const.HTTP11[i]);
+ try {
+ while ((index < lastIndex) && _state != _State.FAILURE) {
+ int byte = buffer[index];
+ switch (_state) {
+ case _State.START:
+ if (byte == _Const.HTTP11[0]) {
+ // Start parsing HTTP method.
+ _httpVersionIndex = 1;
+ _state = _State.METHOD_OR_HTTP_VERSION;
+ } else {
+ // Start parsing method.
+ if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) {
+ throw new HttpParserException("Invalid request method");
+ }
+ _method_or_status_code.addCharCode(byte);
+ _state = _State.REQUEST_LINE_METHOD;
}
- _state = _State.REQUEST_LINE_URI;
- }
- break;
-
- case _State.REQUEST_LINE_METHOD:
- if (byte == _CharCode.SP) {
- _state = _State.REQUEST_LINE_URI;
- } else {
- _method_or_status_code.addCharCode(byte);
- }
- break;
-
- case _State.REQUEST_LINE_URI:
- if (byte == _CharCode.SP) {
- _state = _State.REQUEST_LINE_HTTP_VERSION;
- _httpVersionIndex = 0;
- } else {
- _uri_or_reason_phrase.addCharCode(byte);
- }
- break;
-
- case _State.REQUEST_LINE_HTTP_VERSION:
- if (_httpVersionIndex < _Const.HTTP11.length) {
- _expect(byte, _Const.HTTP11[_httpVersionIndex]);
- _httpVersionIndex++;
- } else {
- _expect(byte, _CharCode.CR);
- _state = _State.REQUEST_LINE_ENDING;
- }
- break;
-
- case _State.REQUEST_LINE_ENDING:
- _expect(byte, _CharCode.LF);
- _messageType = _MessageType.REQUEST;
- if (requestStart != null) {
- requestStart(_method_or_status_code.toString(),
- _uri_or_reason_phrase.toString());
- }
- _method_or_status_code.clear();
- _uri_or_reason_phrase.clear();
- _state = _State.HEADER_START;
- break;
-
- case _State.RESPONSE_LINE_STATUS_CODE:
- if (byte == _CharCode.SP) {
- _state = _State.RESPONSE_LINE_REASON_PHRASE;
- } else {
- if (byte < 0x30 && 0x39 < byte) {
- if (error != null) error("Failed to parse HTTP");
- _failure = true;
+ break;
+
+ case _State.METHOD_OR_HTTP_VERSION:
+ if (_httpVersionIndex < _Const.HTTP11.length &&
+ byte == _Const.HTTP11[_httpVersionIndex]) {
+ // Continue parsing HTTP version.
+ _httpVersionIndex++;
+ } else if (_httpVersionIndex == _Const.HTTP11.length &&
+ byte == _CharCode.SP) {
+ // HTTP version parsed.
+ _state = _State.RESPONSE_LINE_STATUS_CODE;
} else {
+ // Did not parse HTTP version. Expect method instead.
+ for (int i = 0; i < _httpVersionIndex; i++) {
+ _method_or_status_code.addCharCode(_Const.HTTP11[i]);
+ }
+ _state = _State.REQUEST_LINE_URI;
+ }
+ break;
+
+ case _State.REQUEST_LINE_METHOD:
+ if (byte == _CharCode.SP) {
+ _state = _State.REQUEST_LINE_URI;
+ } else {
+ if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) {
+ throw new HttpParserException("Invalid request method");
+ }
_method_or_status_code.addCharCode(byte);
}
- }
- break;
-
- case _State.RESPONSE_LINE_REASON_PHRASE:
- if (byte == _CharCode.CR) {
- _state = _State.RESPONSE_LINE_ENDING;
- } else {
- _uri_or_reason_phrase.addCharCode(byte);
- }
- break;
-
- case _State.RESPONSE_LINE_ENDING:
- _expect(byte, _CharCode.LF);
- _messageType == _MessageType.RESPONSE;
- if (responseStart != null) {
+ break;
+
+ case _State.REQUEST_LINE_URI:
+ if (byte == _CharCode.SP) {
+ _state = _State.REQUEST_LINE_HTTP_VERSION;
+ _httpVersionIndex = 0;
+ } else {
+ if (byte == _CharCode.CR || byte == _CharCode.LF) {
+ throw new HttpParserException("Invalid request URI");
+ }
+ _uri_or_reason_phrase.addCharCode(byte);
+ }
+ break;
+
+ case _State.REQUEST_LINE_HTTP_VERSION:
+ if (_httpVersionIndex < _Const.HTTP11.length) {
+ _expect(byte, _Const.HTTP11[_httpVersionIndex]);
+ _httpVersionIndex++;
+ } else {
+ _expect(byte, _CharCode.CR);
+ _state = _State.REQUEST_LINE_ENDING;
+ }
+ break;
+
+ case _State.REQUEST_LINE_ENDING:
+ _expect(byte, _CharCode.LF);
+ _messageType = _MessageType.REQUEST;
+ if (requestStart != null) {
+ requestStart(_method_or_status_code.toString(),
+ _uri_or_reason_phrase.toString());
+ }
+ _method_or_status_code.clear();
+ _uri_or_reason_phrase.clear();
+ _state = _State.HEADER_START;
+ break;
+
+ case _State.RESPONSE_LINE_STATUS_CODE:
+ if (byte == _CharCode.SP) {
+ if (_method_or_status_code.length != 3) {
+ throw new HttpParserException("Invalid response status code");
+ }
+ _state = _State.RESPONSE_LINE_REASON_PHRASE;
+ } else {
+ if (byte < 0x30 && 0x39 < byte) {
+ throw new HttpParserException("Invalid response status code");
+ } else {
+ _method_or_status_code.addCharCode(byte);
+ }
+ }
+ break;
+
+ case _State.RESPONSE_LINE_REASON_PHRASE:
+ if (byte == _CharCode.CR) {
+ if (_uri_or_reason_phrase.length == 0) {
+ throw new HttpParserException("Invalid response reason phrase");
+ }
+ _state = _State.RESPONSE_LINE_ENDING;
+ } else {
+ if (byte == _CharCode.CR || byte == _CharCode.LF) {
+ throw new HttpParserException("Invalid response reason phrase");
+ }
+ _uri_or_reason_phrase.addCharCode(byte);
+ }
+ break;
+
+ case _State.RESPONSE_LINE_ENDING:
+ _expect(byte, _CharCode.LF);
+ _messageType == _MessageType.RESPONSE;
int statusCode = Math.parseInt(_method_or_status_code.toString());
if (statusCode < 100 || statusCode > 599) {
- if (error != null) error("Invalid response status code");
- _failure = true;
+ throw new HttpParserException("Invalid response status code");
} else {
// Check whether this response will never have a body.
_noMessageBody =
statusCode <= 199 || statusCode == 204 || statusCode == 304;
- responseStart(statusCode, _uri_or_reason_phrase.toString());
- }
- }
- _method_or_status_code.clear();
- _uri_or_reason_phrase.clear();
- _state = _State.HEADER_START;
- break;
-
- case _State.HEADER_START:
- if (byte == _CharCode.CR) {
- _state = _State.HEADER_ENDING;
- } else {
- // Start of new header field.
- _headerField.addCharCode(_toLowerCase(byte));
- _state = _State.HEADER_FIELD;
- }
- break;
-
- case _State.HEADER_FIELD:
- if (byte == _CharCode.COLON) {
- _state = _State.HEADER_VALUE_START;
- } else {
- _headerField.addCharCode(_toLowerCase(byte));
- }
- break;
-
- case _State.HEADER_VALUE_START:
- if (byte != _CharCode.SP && byte != _CharCode.HT) {
- // Start of new header value.
- _headerValue.addCharCode(byte);
- _state = _State.HEADER_VALUE;
- }
- break;
-
- case _State.HEADER_VALUE:
- if (byte == _CharCode.CR) {
- _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
- } else {
- _headerValue.addCharCode(byte);
- }
- break;
-
- case _State.HEADER_VALUE_FOLDING_OR_ENDING:
- _expect(byte, _CharCode.LF);
- _state = _State.HEADER_VALUE_FOLD_OR_END;
- break;
-
- case _State.HEADER_VALUE_FOLD_OR_END:
- if (byte == _CharCode.SP || byte == _CharCode.HT) {
- _state = _State.HEADER_VALUE_START;
- } else {
- String headerField = _headerField.toString();
- String headerValue =_headerValue.toString();
- // Ignore the Content-Length header if Transfer-Encoding
- // is chunked (RFC 2616 section 4.4)
- if (headerField == "content-length" && !_chunked) {
- _contentLength = Math.parseInt(headerValue);
- } else if (headerField == "connection" &&
- headerValue == "keep-alive") {
- _keepAlive = true;
- } else if (headerField == "transfer-encoding" &&
- headerValue == "chunked") {
- _chunked = true;
- _contentLength = -1;
}
- if (headerReceived != null) {
- headerReceived(headerField, headerValue);
+ if (responseStart != null) {
+ responseStart(statusCode, _uri_or_reason_phrase.toString());
}
- _headerField.clear();
- _headerValue.clear();
+ _method_or_status_code.clear();
+ _uri_or_reason_phrase.clear();
+ _state = _State.HEADER_START;
+ break;
+ case _State.HEADER_START:
if (byte == _CharCode.CR) {
_state = _State.HEADER_ENDING;
} else {
@@ -274,119 +246,197 @@ class _HttpParser {
_headerField.addCharCode(_toLowerCase(byte));
_state = _State.HEADER_FIELD;
}
- }
- break;
+ break;
+
+ case _State.HEADER_FIELD:
+ if (byte == _CharCode.COLON) {
+ _state = _State.HEADER_VALUE_START;
+ } else {
+ _headerField.addCharCode(_toLowerCase(byte));
+ }
+ break;
+
+ case _State.HEADER_VALUE_START:
+ if (byte != _CharCode.SP && byte != _CharCode.HT) {
+ // Start of new header value.
+ _headerValue.addCharCode(byte);
+ _state = _State.HEADER_VALUE;
+ }
+ break;
+
+ case _State.HEADER_VALUE:
+ if (byte == _CharCode.CR) {
+ _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
+ } else {
+ _headerValue.addCharCode(byte);
+ }
+ break;
+
+ case _State.HEADER_VALUE_FOLDING_OR_ENDING:
+ _expect(byte, _CharCode.LF);
+ _state = _State.HEADER_VALUE_FOLD_OR_END;
+ break;
- case _State.HEADER_ENDING:
- _expect(byte, _CharCode.LF);
- if (headersComplete != null) headersComplete();
+ case _State.HEADER_VALUE_FOLD_OR_END:
+ if (byte == _CharCode.SP || byte == _CharCode.HT) {
+ _state = _State.HEADER_VALUE_START;
+ } else {
+ String headerField = _headerField.toString();
+ String headerValue =_headerValue.toString();
+ // Ignore the Content-Length header if Transfer-Encoding
+ // is chunked (RFC 2616 section 4.4)
+ if (headerField == "content-length" && !_chunked) {
+ _contentLength = Math.parseInt(headerValue);
+ } else if (headerField == "connection" &&
+ headerValue == "keep-alive") {
+ _keepAlive = true;
+ } else if (headerField == "transfer-encoding" &&
+ headerValue == "chunked") {
+ _chunked = true;
+ _contentLength = -1;
+ }
+ if (headerReceived != null) {
+ headerReceived(headerField, headerValue);
+ }
+ _headerField.clear();
+ _headerValue.clear();
+
+ if (byte == _CharCode.CR) {
+ _state = _State.HEADER_ENDING;
+ } else {
+ // Start of new header field.
+ _headerField.addCharCode(_toLowerCase(byte));
+ _state = _State.HEADER_FIELD;
+ }
+ }
+ break;
+
+ case _State.HEADER_ENDING:
+ _expect(byte, _CharCode.LF);
+ if (headersComplete != null) headersComplete();
+
+ if (_chunked) {
+ _state = _State.CHUNK_SIZE;
+ _remainingContent = 0;
+ } else if (_contentLength == 0 ||
+ (_messageType == _MessageType.REQUEST &&
+ _contentLength == -1) ||
+ (_messageType == _MessageType.RESPONSE &&
+ (_noMessageBody || _responseToMethod == "HEAD"))) {
+ // If there is no message body get ready to process the
+ // next request.
+ if (dataEnd != null) dataEnd();
+ _state = _State.START;
+ } else if (_contentLength > 0) {
+ _remainingContent = _contentLength;
+ _state = _State.BODY;
+ } else {
+ // Neither chunked nor content length. End of body
+ // indicated by close.
+ _state = _State.BODY;
+ }
+ break;
+
+ case _State.CHUNK_SIZE_STARTING_CR:
+ _expect(byte, _CharCode.CR);
+ _state = _State.CHUNK_SIZE_STARTING_LF;
+ break;
- if (_chunked) {
+ case _State.CHUNK_SIZE_STARTING_LF:
+ _expect(byte, _CharCode.LF);
_state = _State.CHUNK_SIZE;
- _remainingContent = 0;
- } else if (_contentLength == 0 ||
- (_messageType == _MessageType.REQUEST &&
- _contentLength == -1) ||
- (_messageType == _MessageType.RESPONSE &&
- (_noMessageBody || _responseToMethod == "HEAD"))) {
- // If there is no message body get ready to process the
- // next request.
+ break;
+
+ case _State.CHUNK_SIZE:
+ if (byte == _CharCode.CR) {
+ _state = _State.CHUNK_SIZE_ENDING;
+ } else if (byte == _CharCode.SEMI_COLON) {
+ _state = _State.CHUNK_SIZE_EXTENSION;
+ } else {
+ int value = _expectHexDigit(byte);
+ _remainingContent = _remainingContent * 16 + value;
+ }
+ break;
+
+ case _State.CHUNK_SIZE_EXTENSION:
+ if (byte == _CharCode.CR) {
+ _state = _State.CHUNK_SIZE_ENDING;
+ }
+ break;
+
+ case _State.CHUNK_SIZE_ENDING:
+ _expect(byte, _CharCode.LF);
+ if (_remainingContent > 0) {
+ _state = _State.BODY;
+ } else {
+ _state = _State.CHUNKED_BODY_DONE_CR;
+ }
+ break;
+
+ case _State.CHUNKED_BODY_DONE_CR:
+ _expect(byte, _CharCode.CR);
+ _state = _State.CHUNKED_BODY_DONE_LF;
+ break;
+
+ case _State.CHUNKED_BODY_DONE_LF:
+ _expect(byte, _CharCode.LF);
if (dataEnd != null) dataEnd();
- _state = _State.START;
- } else if (_contentLength > 0) {
- _remainingContent = _contentLength;
- _state = _State.BODY;
- } else {
- // Neither chunked nor content length. End of body
- // indicated by close.
- _state = _State.BODY;
- }
- break;
-
- case _State.CHUNK_SIZE_STARTING_CR:
- _expect(byte, _CharCode.CR);
- _state = _State.CHUNK_SIZE_STARTING_LF;
- break;
-
- case _State.CHUNK_SIZE_STARTING_LF:
- _expect(byte, _CharCode.LF);
- _state = _State.CHUNK_SIZE;
- break;
-
- case _State.CHUNK_SIZE:
- if (byte == _CharCode.CR) {
- _state = _State.CHUNK_SIZE_ENDING;
- } else if (byte == _CharCode.SEMI_COLON) {
- _state = _State.CHUNK_SIZE_EXTENSION;
- } else {
- int value = _expectHexDigit(byte);
- _remainingContent = _remainingContent * 16 + value;
- }
- break;
-
- case _State.CHUNK_SIZE_EXTENSION:
- if (byte == _CharCode.CR) {
- _state = _State.CHUNK_SIZE_ENDING;
- }
- break;
-
- case _State.CHUNK_SIZE_ENDING:
- _expect(byte, _CharCode.LF);
- if (_remainingContent > 0) {
- _state = _State.BODY;
- } else {
- _state = _State.CHUNKED_BODY_DONE_CR;
- }
- break;
-
- case _State.CHUNKED_BODY_DONE_CR:
- _expect(byte, _CharCode.CR);
- _state = _State.CHUNKED_BODY_DONE_LF;
- break;
-
- case _State.CHUNKED_BODY_DONE_LF:
- _expect(byte, _CharCode.LF);
- if (dataEnd != null) dataEnd();
- _reset();
- break;
-
- case _State.BODY:
- // The body is not handled one byte at the time but in blocks.
- int dataAvailable = lastIndex - index;
- ByteArray data;
- if (_remainingContent == null || dataAvailable <= _remainingContent) {
- data = new ByteArray(dataAvailable);
- data.setRange(0, dataAvailable, buffer, index);
- } else {
- data = new ByteArray(_remainingContent);
- data.setRange(0, _remainingContent, buffer, index);
- }
-
- if (dataReceived != null) dataReceived(data);
- if (_remainingContent != null) {
- _remainingContent -= data.length;
- }
- index += data.length;
- if (_remainingContent == 0) {
- if (!_chunked) {
- if (dataEnd != null) dataEnd();
- _reset();
+ _reset();
+ break;
+
+ case _State.BODY:
+ // The body is not handled one byte at the time but in blocks.
+ int dataAvailable = lastIndex - index;
+ ByteArray data;
+ if (_remainingContent == null || dataAvailable <= _remainingContent) {
Mads Ager (google) 2012/03/22 14:30:08 Long line?
Søren Gjesse 2012/03/23 07:34:20 Done.
+ data = new ByteArray(dataAvailable);
+ data.setRange(0, dataAvailable, buffer, index);
} else {
- _state = _State.CHUNK_SIZE_STARTING_CR;
+ data = new ByteArray(_remainingContent);
+ data.setRange(0, _remainingContent, buffer, index);
}
- }
- // Hack - as we always do index++ below.
- index--;
- break;
+ if (dataReceived != null) dataReceived(data);
+ if (_remainingContent != null) {
+ _remainingContent -= data.length;
+ }
+ index += data.length;
+ if (_remainingContent == 0) {
+ if (!_chunked) {
+ if (dataEnd != null) dataEnd();
+ _reset();
+ } else {
+ _state = _State.CHUNK_SIZE_STARTING_CR;
+ }
+ }
- default:
- // Should be unreachable.
- assert(false);
- }
+ // Hack - as we always do index++ below.
+ index--;
+ break;
- // Move to the next byte.
- index++;
+ case _state = _State.FAILURE:
+ // Should be unreachable.
+ assert(false);
+ break;
+
+ default:
+ // Should be unreachable.
+ assert(false);
+ break;
+ }
+
+ // Move to the next byte.
+ index++;
+ }
+ } catch (var e) {
+ // Report the error through the error callback if any. Otherwise
+ // throw the error.
+ if (error != null) {
+ error(e);
+ _state = _State.FAILURE;
+ } else {
+ throw e;
+ }
}
// Return the number of bytes parsed.
@@ -397,8 +447,14 @@ class _HttpParser {
if (!_chunked && _contentLength == -1) {
if (dataEnd != null) dataEnd();
} else {
+ // Report the error through the error callback if any. Otherwise
Mads Ager (google) 2012/03/22 14:30:08 Maybe this should really be our behavior for all o
Søren Gjesse 2012/03/23 07:34:20 That might be a good idea. This can lead to VM ter
+ // throw the error.
+ var e = new HttpParserException(
+ "Connection closed before full body was received");
if (error != null) {
- error("Connection closed before full body was received");
+ error(e);
+ } else {
+ throw e;
}
}
}
@@ -411,7 +467,6 @@ class _HttpParser {
_reset() {
_state = _State.START;
- _failure = false;
_messageType = _MessageType.UNDETERMINED;
_headerField = new StringBuffer();
_headerValue = new StringBuffer();
@@ -436,8 +491,7 @@ class _HttpParser {
int _expect(int val1, int val2) {
if (val1 != val2) {
- if (error != null) error("Failed to parse HTTP");
- _failure = true;
+ throw new HttpParserException("Failed to parse HTTP");
}
}
@@ -449,13 +503,12 @@ class _HttpParser {
} else if (0x61 <= byte && byte <= 0x66) {
return byte - 0x61 + 10; // a - f
} else {
- _failure = true;
+ throw new HttpParserException("Failed to parse HTTP");
return 0;
}
}
int _state;
- bool _failure;
int _httpVersionIndex;
int _messageType;
StringBuffer _method_or_status_code;
@@ -480,3 +533,10 @@ class _HttpParser {
Function dataEnd;
Function error;
}
+
+
+class HttpParserException implements Exception {
+ const HttpParserException([String this.message = ""]);
+ String toString() => "HttpParserException: $message";
+ final String message;
+}

Powered by Google App Engine
This is Rietveld 408576698