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

Side by Side Diff: runtime/bin/http_parser.dart

Issue 10068013: Add basic handling to connection upgrade to the HTTP parser (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 8 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/standalone/src/io/HttpParserTest.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/1.0". 7 // Bytes for "HTTP/1.0".
8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; 8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48];
9 // Bytes for "HTTP/1.1". 9 // Bytes for "HTTP/1.1".
10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; 10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49];
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
54 54
55 static final int CHUNK_SIZE_STARTING_CR = 16; 55 static final int CHUNK_SIZE_STARTING_CR = 16;
56 static final int CHUNK_SIZE_STARTING_LF = 17; 56 static final int CHUNK_SIZE_STARTING_LF = 17;
57 static final int CHUNK_SIZE = 18; 57 static final int CHUNK_SIZE = 18;
58 static final int CHUNK_SIZE_EXTENSION = 19; 58 static final int CHUNK_SIZE_EXTENSION = 19;
59 static final int CHUNK_SIZE_ENDING = 20; 59 static final int CHUNK_SIZE_ENDING = 20;
60 static final int CHUNKED_BODY_DONE_CR = 21; 60 static final int CHUNKED_BODY_DONE_CR = 21;
61 static final int CHUNKED_BODY_DONE_LF = 22; 61 static final int CHUNKED_BODY_DONE_LF = 22;
62 static final int BODY = 23; 62 static final int BODY = 23;
63 static final int CLOSED = 24; 63 static final int CLOSED = 24;
64 static final int FAILURE = 25; 64 static final int UPGRADED = 25;
65 static final int FAILURE = 26;
65 66
66 static final int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR; 67 static final int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR;
67 } 68 }
68 69
69 70
70 // States of the HTTP parser state machine. 71 // States of the HTTP parser state machine.
71 class _MessageType { 72 class _MessageType {
72 static final int UNDETERMINED = 0; 73 static final int UNDETERMINED = 0;
73 static final int REQUEST = 1; 74 static final int REQUEST = 1;
74 static final int RESPONSE = 0; 75 static final int RESPONSE = 0;
(...skipping 10 matching lines...) Expand all
85 * [:headerReceived:] 86 * [:headerReceived:]
86 * [:headersComplete:] 87 * [:headersComplete:]
87 * [:dataReceived:] 88 * [:dataReceived:]
88 * [:dataEnd:] 89 * [:dataEnd:]
89 * [:error:] 90 * [:error:]
90 * 91 *
91 * If an HTTP parser error occours it is possible to get an exception 92 * If an HTTP parser error occours it is possible to get an exception
92 * thrown from the [:writeList:] and [:connectionClosed:] methods if 93 * thrown from the [:writeList:] and [:connectionClosed:] methods if
93 * the error callback is not set. 94 * the error callback is not set.
94 * 95 *
95 * For keep-alive connections where the underlying connection stays 96 * The connection upgrades (e.g. switching from HTTP/1.1 to the
96 * open the [:connectionClosed:] method would not be called. 97 * WebSocket protocol) is handled in a special way. If connection
98 * upgrade is specified in the headers, then on the callback to
99 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:]
100 * object will be [:true:] indicating that from now on the protocol is
101 * not HTTP anymore and no more callbacks will happen, that is
102 * [:dataReceived:] and [:dataEnd:] are not called in this case as
103 * there is no more HTTP data. After the upgrade the call to
104 * [:writeList:] causing the upgrade will return with the number of
105 * bytes parsed as HTTP. Any unparsed bytes is part of the protocol
106 * the connection is upgrading to and should be handled according to
107 * that protocol.
97 */ 108 */
98 class _HttpParser { 109 class _HttpParser {
99 _HttpParser() { 110 _HttpParser() {
100 _reset(); 111 _reset();
101 } 112 }
102 113
103 // From RFC 2616. 114 // From RFC 2616.
104 // generic-message = start-line 115 // generic-message = start-line
105 // *(message-header CRLF) 116 // *(message-header CRLF)
106 // CRLF 117 // CRLF
107 // [ message-body ] 118 // [ message-body ]
108 // start-line = Request-Line | Status-Line 119 // start-line = Request-Line | Status-Line
109 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF 120 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
110 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF 121 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
111 // message-header = field-name ":" [ field-value ] 122 // message-header = field-name ":" [ field-value ]
112 int writeList(List<int> buffer, int offset, int count) { 123 int writeList(List<int> buffer, int offset, int count) {
113 int index = offset; 124 int index = offset;
114 int lastIndex = offset + count; 125 int lastIndex = offset + count;
115 try { 126 try {
116 if (_state == _State.CLOSED) { 127 if (_state == _State.CLOSED) {
117 throw new HttpParserException("Data on closed connection"); 128 throw new HttpParserException("Data on closed connection");
118 } 129 }
119 while ((index < lastIndex) && _state != _State.FAILURE) { 130 if (_state == _State.UPGRADED) {
131 throw new HttpParserException("Data on upgraded connection");
132 }
133 while ((index < lastIndex) && _state != _State.FAILURE && _state != _State .UPGRADED) {
120 int byte = buffer[index]; 134 int byte = buffer[index];
121 switch (_state) { 135 switch (_state) {
122 case _State.START: 136 case _State.START:
123 if (byte == _Const.HTTP11[0]) { 137 if (byte == _Const.HTTP11[0]) {
124 // Start parsing HTTP method. 138 // Start parsing HTTP method.
125 _httpVersionIndex = 1; 139 _httpVersionIndex = 1;
126 _state = _State.METHOD_OR_HTTP_VERSION; 140 _state = _State.METHOD_OR_HTTP_VERSION;
127 } else { 141 } else {
128 // Start parsing method. 142 // Start parsing method.
129 if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) { 143 if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) {
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after
293 if (byte == _CharCode.SP || byte == _CharCode.HT) { 307 if (byte == _CharCode.SP || byte == _CharCode.HT) {
294 _state = _State.HEADER_VALUE_START; 308 _state = _State.HEADER_VALUE_START;
295 } else { 309 } else {
296 String headerField = _headerField.toString(); 310 String headerField = _headerField.toString();
297 String headerValue =_headerValue.toString(); 311 String headerValue =_headerValue.toString();
298 // Ignore the Content-Length header if Transfer-Encoding 312 // Ignore the Content-Length header if Transfer-Encoding
299 // is chunked (RFC 2616 section 4.4) 313 // is chunked (RFC 2616 section 4.4)
300 if (headerField == "content-length" && !_chunked) { 314 if (headerField == "content-length" && !_chunked) {
301 _contentLength = Math.parseInt(headerValue); 315 _contentLength = Math.parseInt(headerValue);
302 } else if (headerField == "connection") { 316 } else if (headerField == "connection") {
303 if (headerValue == "keep-alive") { 317 List<String> tokens = _tokenizeFieldValue(headerValue);
304 _keepAlive = true; 318 for (int i = 0; i < tokens.length; i++) {
305 } else if (headerValue == "close") { 319 String token = tokens[i].toLowerCase();
306 _connectionClose = true; 320 if (token == "keep-alive") {
321 _keepAlive = true;
322 } else if (token == "close") {
323 _connectionClose = true;
324 } else if (token == "upgrade") {
325 _connectionUpgrade = true;
326 }
307 } 327 }
308 } else if (headerField == "transfer-encoding" && 328 } else if (headerField == "transfer-encoding" &&
309 headerValue == "chunked") { 329 headerValue == "chunked") {
310 _chunked = true; 330 _chunked = true;
311 _contentLength = -1; 331 _contentLength = -1;
312 } 332 }
313 if (headerReceived != null) { 333 if (headerReceived != null) {
314 headerReceived(headerField, headerValue); 334 headerReceived(headerField, headerValue);
315 } 335 }
316 _headerField.clear(); 336 _headerField.clear();
317 _headerValue.clear(); 337 _headerValue.clear();
318 338
319 if (byte == _CharCode.CR) { 339 if (byte == _CharCode.CR) {
320 _state = _State.HEADER_ENDING; 340 _state = _State.HEADER_ENDING;
321 } else { 341 } else {
322 // Start of new header field. 342 // Start of new header field.
323 _headerField.addCharCode(_toLowerCase(byte)); 343 _headerField.addCharCode(_toLowerCase(byte));
324 _state = _State.HEADER_FIELD; 344 _state = _State.HEADER_FIELD;
325 } 345 }
326 } 346 }
327 break; 347 break;
328 348
329 case _State.HEADER_ENDING: 349 case _State.HEADER_ENDING:
330 _expect(byte, _CharCode.LF); 350 _expect(byte, _CharCode.LF);
331 if (headersComplete != null) headersComplete(); 351 if (_connectionUpgrade) {
332 if (_chunked) { 352 _state = _State.UPGRADED;
333 _state = _State.CHUNK_SIZE; 353 if (headersComplete != null) headersComplete();
334 _remainingContent = 0;
335 } else if (_contentLength == 0 ||
336 (_messageType == _MessageType.REQUEST &&
337 _contentLength == -1) ||
338 (_messageType == _MessageType.RESPONSE &&
339 (_noMessageBody || _responseToMethod == "HEAD"))) {
340 // If there is no message body get ready to process the
341 // next request.
342 _bodyEnd();
343 _reset();
344 } else if (_contentLength > 0) {
345 _remainingContent = _contentLength;
346 _state = _State.BODY;
347 } else { 354 } else {
348 // Neither chunked nor content length. End of body 355 if (headersComplete != null) headersComplete();
349 // indicated by close. 356 if (_chunked) {
350 _state = _State.BODY; 357 _state = _State.CHUNK_SIZE;
358 _remainingContent = 0;
359 } else if (_contentLength == 0 ||
360 (_messageType == _MessageType.REQUEST &&
361 _contentLength == -1) ||
362 (_messageType == _MessageType.RESPONSE &&
363 (_noMessageBody || _responseToMethod == "HEAD"))) {
364 // If there is no message body get ready to process the
365 // next request.
366 _bodyEnd();
367 _reset();
368 } else if (_contentLength > 0) {
369 _remainingContent = _contentLength;
370 _state = _State.BODY;
371 } else {
372 // Neither chunked nor content length. End of body
373 // indicated by close.
374 _state = _State.BODY;
375 }
351 } 376 }
352 break; 377 break;
353 378
354 case _State.CHUNK_SIZE_STARTING_CR: 379 case _State.CHUNK_SIZE_STARTING_CR:
355 _expect(byte, _CharCode.CR); 380 _expect(byte, _CharCode.CR);
356 _state = _State.CHUNK_SIZE_STARTING_LF; 381 _state = _State.CHUNK_SIZE_STARTING_LF;
357 break; 382 break;
358 383
359 case _State.CHUNK_SIZE_STARTING_LF: 384 case _State.CHUNK_SIZE_STARTING_LF:
360 _expect(byte, _CharCode.LF); 385 _expect(byte, _CharCode.LF);
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after
487 error(e); 512 error(e);
488 return; 513 return;
489 } 514 }
490 throw e; 515 throw e;
491 } 516 }
492 } 517 }
493 518
494 int get messageType() => _messageType; 519 int get messageType() => _messageType;
495 int get contentLength() => _contentLength; 520 int get contentLength() => _contentLength;
496 bool get keepAlive() => _keepAlive; 521 bool get keepAlive() => _keepAlive;
522 bool get upgrade() => _connectionUpgrade && _state == _State.UPGRADED;
497 523
498 void set responseToMethod(String method) => _responseToMethod = method; 524 void set responseToMethod(String method) => _responseToMethod = method;
499 525
500 bool get isIdle() => _state == _State.START; 526 bool get isIdle() => _state == _State.START;
501 527
502 void _bodyEnd() { 528 void _bodyEnd() {
503 if (dataEnd != null) { 529 if (dataEnd != null) {
504 dataEnd(_messageType == _MessageType.RESPONSE && _connectionClose); 530 dataEnd(_messageType == _MessageType.RESPONSE && _connectionClose);
505 } 531 }
506 } 532 }
507 533
508 _reset() { 534 _reset() {
509 _state = _State.START; 535 _state = _State.START;
510 _messageType = _MessageType.UNDETERMINED; 536 _messageType = _MessageType.UNDETERMINED;
511 _headerField = new StringBuffer(); 537 _headerField = new StringBuffer();
512 _headerValue = new StringBuffer(); 538 _headerValue = new StringBuffer();
513 _method_or_status_code = new StringBuffer(); 539 _method_or_status_code = new StringBuffer();
514 _uri_or_reason_phrase = new StringBuffer(); 540 _uri_or_reason_phrase = new StringBuffer();
515 541
516 _contentLength = -1; 542 _contentLength = -1;
517 _keepAlive = false; 543 _keepAlive = false;
518 _connectionClose = false; 544 _connectionClose = false;
545 _connectionUpgrade = false;
519 _chunked = false; 546 _chunked = false;
520 547
521 _noMessageBody = false; 548 _noMessageBody = false;
522 _responseToMethod = null; 549 _responseToMethod = null;
523 _remainingContent = null; 550 _remainingContent = null;
524 } 551 }
525 552
526 bool _isTokenChar(int byte) { 553 bool _isTokenChar(int byte) {
527 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; 554 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1;
528 } 555 }
529 556
557 List<String> _tokenizeFieldValue(String headerValue) {
558 List<String> tokens = new List<String>();
559 int start = 0;
560 int index = 0;
561 while (index < headerValue.length) {
562 if (headerValue[index] == ",") {
563 tokens.add(headerValue.substring(start, index));
564 start = index + 1;
565 } else if (headerValue[index] == " " || headerValue[index] == "\t") {
566 start++;
567 }
568 index++;
569 }
570 tokens.add(headerValue.substring(start, index));
571 return tokens;
572 }
573
530 int _toLowerCase(int byte) { 574 int _toLowerCase(int byte) {
531 final int aCode = "A".charCodeAt(0); 575 final int aCode = "A".charCodeAt(0);
532 final int zCode = "Z".charCodeAt(0); 576 final int zCode = "Z".charCodeAt(0);
533 final int delta = "a".charCodeAt(0) - aCode; 577 final int delta = "a".charCodeAt(0) - aCode;
534 return (aCode <= byte && byte <= zCode) ? byte + delta : byte; 578 return (aCode <= byte && byte <= zCode) ? byte + delta : byte;
535 } 579 }
536 580
537 int _expect(int val1, int val2) { 581 int _expect(int val1, int val2) {
538 if (val1 != val2) { 582 if (val1 != val2) {
539 throw new HttpParserException("Failed to parse HTTP"); 583 throw new HttpParserException("Failed to parse HTTP");
(...skipping 16 matching lines...) Expand all
556 int _httpVersionIndex; 600 int _httpVersionIndex;
557 int _messageType; 601 int _messageType;
558 StringBuffer _method_or_status_code; 602 StringBuffer _method_or_status_code;
559 StringBuffer _uri_or_reason_phrase; 603 StringBuffer _uri_or_reason_phrase;
560 StringBuffer _headerField; 604 StringBuffer _headerField;
561 StringBuffer _headerValue; 605 StringBuffer _headerValue;
562 606
563 int _contentLength; 607 int _contentLength;
564 bool _keepAlive; 608 bool _keepAlive;
565 bool _connectionClose; 609 bool _connectionClose;
610 bool _connectionUpgrade;
566 bool _chunked; 611 bool _chunked;
567 612
568 bool _noMessageBody; 613 bool _noMessageBody;
569 String _responseToMethod; // Indicates the method used for the request. 614 String _responseToMethod; // Indicates the method used for the request.
570 int _remainingContent; 615 int _remainingContent;
571 616
572 // Callbacks. 617 // Callbacks.
573 Function requestStart; 618 Function requestStart;
574 Function responseStart; 619 Function responseStart;
575 Function headerReceived; 620 Function headerReceived;
576 Function headersComplete; 621 Function headersComplete;
577 Function dataReceived; 622 Function dataReceived;
578 Function dataEnd; 623 Function dataEnd;
579 Function error; 624 Function error;
580 } 625 }
581 626
582 627
583 class HttpParserException implements Exception { 628 class HttpParserException implements Exception {
584 const HttpParserException([String this.message = ""]); 629 const HttpParserException([String this.message = ""]);
585 String toString() => "HttpParserException: $message"; 630 String toString() => "HttpParserException: $message";
586 final String message; 631 final String message;
587 } 632 }
OLDNEW
« no previous file with comments | « no previous file | tests/standalone/src/io/HttpParserTest.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698