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

Side by Side Diff: samples/chat/http_impl.dart

Issue 9488008: Include HTTP library in the standalone VM (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Minor update 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « samples/chat/http.dart ('k') | samples/tests/samples/src/chat/ChatServerTest.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 // Global constants.
6 class _Const {
7 // Bytes for "HTTP/1.0".
8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48];
9 // Bytes for "HTTP/1.1".
10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49];
11
12 static final END_CHUNKED = const [0x30, 13, 10, 13, 10];
13 }
14
15 // Frequently used character codes.
16 class _CharCode {
17 static final int HT = 9;
18 static final int LF = 10;
19 static final int CR = 13;
20 static final int SP = 32;
21 static final int COLON = 58;
22 }
23
24
25 // States of the HTTP parser state machine.
26 class _State {
27 static final int START = 0;
28 static final int METHOD_OR_HTTP_VERSION = 1;
29 static final int REQUEST_LINE_METHOD = 2;
30 static final int REQUEST_LINE_URI = 3;
31 static final int REQUEST_LINE_HTTP_VERSION = 4;
32 static final int REQUEST_LINE_ENDING = 5;
33 static final int RESPONSE_LINE_STATUS_CODE = 6;
34 static final int RESPONSE_LINE_REASON_PHRASE = 7;
35 static final int RESPONSE_LINE_ENDING = 8;
36 static final int HEADER_START = 9;
37 static final int HEADER_FIELD = 10;
38 static final int HEADER_VALUE_START = 11;
39 static final int HEADER_VALUE = 12;
40 static final int HEADER_VALUE_FOLDING_OR_ENDING = 13;
41 static final int HEADER_VALUE_FOLD_OR_END = 14;
42 static final int HEADER_ENDING = 15;
43 static final int CHUNK_SIZE_STARTING_CR = 16;
44 static final int CHUNK_SIZE_STARTING_LF = 17;
45 static final int CHUNK_SIZE = 18;
46 static final int CHUNK_SIZE_ENDING = 19;
47 static final int CHUNKED_BODY_DONE_CR = 20;
48 static final int CHUNKED_BODY_DONE_LF = 21;
49 static final int BODY = 22;
50 }
51
52
53 /**
54 * HTTP parser which parses the HTTP stream as data is supplied
55 * through the writeList method. As the data is parsed the events
56 * RequestStart
57 * UriReceived
58 * HeaderReceived
59 * HeadersComplete
60 * DataReceived
61 * DataEnd
62 * are generated.
63 * Currently only HTTP requests with Content-Length header are supported.
64 */
65 class HttpParser {
66 HttpParser()
67 : _state = _State.START,
68 _failure = false,
69 _headerField = new StringBuffer(),
70 _headerValue = new StringBuffer(),
71 _method_or_status_code = new StringBuffer(),
72 _uri_or_reason_phrase = new StringBuffer();
73
74 // From RFC 2616.
75 // generic-message = start-line
76 // *(message-header CRLF)
77 // CRLF
78 // [ message-body ]
79 // start-line = Request-Line | Status-Line
80 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
81 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
82 // message-header = field-name ":" [ field-value ]
83 int writeList(List<int> buffer, int offset, int count) {
84 int index = offset;
85 int lastIndex = offset + count;
86 while ((index < lastIndex) && !_failure) {
87 int byte = buffer[index];
88 switch (_state) {
89 case _State.START:
90 _contentLength = 0;
91 _keepAlive = false;
92 _chunked = false;
93
94 if (byte == _Const.HTTP11[0]) {
95 // Start parsing HTTP method.
96 _httpVersionIndex = 1;
97 _state = _State.METHOD_OR_HTTP_VERSION;
98 } else {
99 // Start parsing method.
100 _method_or_status_code.addCharCode(byte);
101 _state = _State.REQUEST_LINE_METHOD;
102 }
103 break;
104
105 case _State.METHOD_OR_HTTP_VERSION:
106 if (_httpVersionIndex < _Const.HTTP11.length &&
107 byte == _Const.HTTP11[_httpVersionIndex]) {
108 // Continue parsing HTTP version.
109 _httpVersionIndex++;
110 } else if (_httpVersionIndex == _Const.HTTP11.length &&
111 byte == _CharCode.SP) {
112 // HTTP version parsed.
113 _state = _State.RESPONSE_LINE_STATUS_CODE;
114 } else {
115 // Did not parse HTTP version. Expect method instead.
116 for (int i = 0; i < _httpVersionIndex; i++) {
117 _method_or_status_code.addCharCode(_Const.HTTP11[i]);
118 }
119 _state = _State.REQUEST_LINE_URI;
120 }
121 break;
122
123 case _State.REQUEST_LINE_METHOD:
124 if (byte == _CharCode.SP) {
125 _state = _State.REQUEST_LINE_URI;
126 } else {
127 _method_or_status_code.addCharCode(byte);
128 }
129 break;
130
131 case _State.REQUEST_LINE_URI:
132 if (byte == _CharCode.SP) {
133 _state = _State.REQUEST_LINE_HTTP_VERSION;
134 _httpVersionIndex = 0;
135 } else {
136 _uri_or_reason_phrase.addCharCode(byte);
137 }
138 break;
139
140 case _State.REQUEST_LINE_HTTP_VERSION:
141 if (_httpVersionIndex < _Const.HTTP11.length) {
142 _expect(byte, _Const.HTTP11[_httpVersionIndex]);
143 _httpVersionIndex++;
144 } else {
145 _expect(byte, _CharCode.CR);
146 _state = _State.REQUEST_LINE_ENDING;
147 }
148 break;
149
150 case _State.REQUEST_LINE_ENDING:
151 _expect(byte, _CharCode.LF);
152 if (requestStart != null) {
153 requestStart(_method_or_status_code.toString(),
154 _uri_or_reason_phrase.toString());
155 }
156 _method_or_status_code.clear();
157 _uri_or_reason_phrase.clear();
158 _state = _State.HEADER_START;
159 break;
160
161 case _State.RESPONSE_LINE_STATUS_CODE:
162 if (byte == _CharCode.SP) {
163 _state = _State.RESPONSE_LINE_REASON_PHRASE;
164 } else {
165 if (byte < 0x30 && 0x39 < byte) {
166 _failure = true;
167 } else {
168 _method_or_status_code.addCharCode(byte);
169 }
170 }
171 break;
172
173 case _State.RESPONSE_LINE_REASON_PHRASE:
174 if (byte == _CharCode.CR) {
175 _state = _State.RESPONSE_LINE_ENDING;
176 } else {
177 _uri_or_reason_phrase.addCharCode(byte);
178 }
179 break;
180
181 case _State.RESPONSE_LINE_ENDING:
182 _expect(byte, _CharCode.LF);
183 // TODO(sgjesse): Check for valid status code.
184 if (responseStart != null) {
185 responseStart(Math.parseInt(_method_or_status_code.toString()),
186 _uri_or_reason_phrase.toString());
187 }
188 _method_or_status_code.clear();
189 _uri_or_reason_phrase.clear();
190 _state = _State.HEADER_START;
191 break;
192
193 case _State.HEADER_START:
194 if (byte == _CharCode.CR) {
195 _state = _State.HEADER_ENDING;
196 } else {
197 // Start of new header field.
198 _headerField.addCharCode(_toLowerCase(byte));
199 _state = _State.HEADER_FIELD;
200 }
201 break;
202
203 case _State.HEADER_FIELD:
204 if (byte == _CharCode.COLON) {
205 _state = _State.HEADER_VALUE_START;
206 } else {
207 _headerField.addCharCode(_toLowerCase(byte));
208 }
209 break;
210
211 case _State.HEADER_VALUE_START:
212 if (byte != _CharCode.SP && byte != _CharCode.HT) {
213 // Start of new header value.
214 _headerValue.addCharCode(byte);
215 _state = _State.HEADER_VALUE;
216 }
217 break;
218
219 case _State.HEADER_VALUE:
220 if (byte == _CharCode.CR) {
221 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
222 } else {
223 _headerValue.addCharCode(byte);
224 }
225 break;
226
227 case _State.HEADER_VALUE_FOLDING_OR_ENDING:
228 _expect(byte, _CharCode.LF);
229 _state = _State.HEADER_VALUE_FOLD_OR_END;
230 break;
231
232 case _State.HEADER_VALUE_FOLD_OR_END:
233 if (byte == _CharCode.SP || byte == _CharCode.HT) {
234 _state = _State.HEADER_VALUE_START;
235 } else {
236 String headerField = _headerField.toString();
237 String headerValue =_headerValue.toString();
238 // Ignore the Content-Length header if Transfer-Encoding
239 // is chunked (RFC 2616 section 4.4)
240 if (headerField == "content-length" && !_chunked) {
241 _contentLength = Math.parseInt(headerValue);
242 } else if (headerField == "connection" &&
243 headerValue == "keep-alive") {
244 _keepAlive = true;
245 } else if (headerField == "transfer-encoding" &&
246 headerValue == "chunked") {
247 _chunked = true;
248 _contentLength = -1;
249 }
250 if (headerReceived != null) {
251 headerReceived(headerField, headerValue);
252 }
253 _headerField.clear();
254 _headerValue.clear();
255
256 if (byte == _CharCode.CR) {
257 _state = _State.HEADER_ENDING;
258 } else {
259 // Start of new header field.
260 _headerField.addCharCode(_toLowerCase(byte));
261 _state = _State.HEADER_FIELD;
262 }
263 }
264 break;
265
266 case _State.HEADER_ENDING:
267 _expect(byte, _CharCode.LF);
268 if (headersComplete != null) headersComplete();
269
270 // If there is no data get ready to process the next request.
271 if (_chunked) {
272 _state = _State.CHUNK_SIZE;
273 _remainingContent = 0;
274 } else if (_contentLength == 0) {
275 if (dataEnd != null) dataEnd();
276 _state = _State.START;
277 } else if (_contentLength > 0) {
278 _remainingContent = _contentLength;
279 _state = _State.BODY;
280 } else {
281 // TODO(sgjesse): Error handling.
282 }
283 break;
284
285 case _State.CHUNK_SIZE_STARTING_CR:
286 _expect(byte, _CharCode.CR);
287 _state = _State.CHUNK_SIZE_STARTING_LF;
288 break;
289
290 case _State.CHUNK_SIZE_STARTING_LF:
291 _expect(byte, _CharCode.LF);
292 _state = _State.CHUNK_SIZE;
293 break;
294
295 case _State.CHUNK_SIZE:
296 if (byte == _CharCode.CR) {
297 _state = _State.CHUNK_SIZE_ENDING;
298 } else {
299 int value = _expectHexDigit(byte);
300 _remainingContent = _remainingContent * 16 + value;
301 }
302 break;
303
304 case _State.CHUNK_SIZE_ENDING:
305 _expect(byte, _CharCode.LF);
306 if (_remainingContent > 0) {
307 _state = _State.BODY;
308 } else {
309 _state = _State.CHUNKED_BODY_DONE_CR;
310 }
311 break;
312
313 case _State.CHUNKED_BODY_DONE_CR:
314 _expect(byte, _CharCode.CR);
315 _state = _State.CHUNKED_BODY_DONE_LF;
316 break;
317
318 case _State.CHUNKED_BODY_DONE_LF:
319 _expect(byte, _CharCode.LF);
320 if (dataEnd != null) dataEnd();
321 _state = _State.START;
322 break;
323
324 case _State.BODY:
325 // The body is not handled one byte at the time but in blocks.
326 int dataAvailable = lastIndex - index;
327 ByteArray data;
328 if (dataAvailable <= _remainingContent) {
329 data = new ByteArray(dataAvailable);
330 data.setRange(0, dataAvailable, buffer, index);
331 } else {
332 data = new ByteArray(_remainingContent);
333 data.setRange(0, _remainingContent, buffer, index);
334 }
335
336 if (dataReceived != null) dataReceived(data);
337 _remainingContent -= data.length;
338 index += data.length;
339 if (_remainingContent == 0) {
340 if (!_chunked) {
341 if (dataEnd != null) dataEnd();
342 _state = _State.START;
343 } else {
344 _state = _State.CHUNK_SIZE_STARTING_CR;
345 }
346 }
347
348 // Hack - as we always do index++ below.
349 index--;
350 break;
351
352 default:
353 // Should be unreachable.
354 assert(false);
355 }
356
357 // Move to the next byte.
358 index++;
359 }
360
361 // Return the number of bytes parsed.
362 return index - offset;
363 }
364
365 int get contentLength() => _contentLength;
366 bool get keepAlive() => _keepAlive;
367
368 int _toLowerCase(int byte) {
369 final int aCode = "A".charCodeAt(0);
370 final int zCode = "Z".charCodeAt(0);
371 final int delta = "a".charCodeAt(0) - aCode;
372 return (aCode <= byte && byte <= zCode) ? byte + delta : byte;
373 }
374
375 int _expect(int val1, int val2) {
376 if (val1 != val2) {
377 _failure = true;
378 }
379 }
380
381 int _expectHexDigit(int byte) {
382 if (0x30 <= byte && byte <= 0x39) {
383 return byte - 0x30; // 0 - 9
384 } else if (0x41 <= byte && byte <= 0x46) {
385 return byte - 0x41 + 10; // A - F
386 } else if (0x61 <= byte && byte <= 0x66) {
387 return byte - 0x61 + 10; // a - f
388 } else {
389 _failure = true;
390 return 0;
391 }
392 }
393
394 int _state;
395 bool _failure;
396 int _httpVersionIndex;
397 StringBuffer _method_or_status_code;
398 StringBuffer _uri_or_reason_phrase;
399 StringBuffer _headerField;
400 StringBuffer _headerValue;
401
402 int _contentLength;
403 bool _keepAlive;
404 bool _chunked;
405
406 int _remainingContent;
407
408 // Callbacks.
409 Function requestStart;
410 Function responseStart;
411 Function headerReceived;
412 Function headersComplete;
413 Function dataReceived;
414 Function dataEnd;
415 }
416
417
418 // Utility class for encoding a string into UTF-8 byte stream.
419 class _UTF8Encoder {
420 static List<int> encodeString(String string) {
421 int size = _encodingSize(string);
422 ByteArray result = new ByteArray(size);
423 _encodeString(string, result);
424 return result;
425 }
426
427 static int _encodingSize(String string) => _encodeString(string, null);
428
429 static int _encodeString(String string, List<int> buffer) {
430 int pos = 0;
431 int length = string.length;
432 for (int i = 0; i < length; i++) {
433 int additionalBytes;
434 int charCode = string.charCodeAt(i);
435 if (charCode <= 0x007F) {
436 additionalBytes = 0;
437 if (buffer != null) buffer[pos] = charCode;
438 } else if (charCode <= 0x07FF) {
439 // 110xxxxx (xxxxx is top 5 bits).
440 if (buffer != null) buffer[pos] = ((charCode >> 6) & 0x1F) | 0xC0;
441 additionalBytes = 1;
442 } else if (charCode <= 0xFFFF) {
443 // 1110xxxx (xxxx is top 4 bits)
444 if (buffer != null) buffer[pos] = ((charCode >> 12) & 0x0F)| 0xE0;
445 additionalBytes = 2;
446 } else {
447 // 11110xxx (xxx is top 3 bits)
448 if (buffer != null) buffer[pos] = ((charCode >> 18) & 0x07) | 0xF0;
449 additionalBytes = 3;
450 }
451 pos++;
452 if (buffer != null) {
453 for (int i = additionalBytes; i > 0; i--) {
454 // 10xxxxxx (xxxxxx is next 6 bits from the top).
455 buffer[pos++] = ((charCode >> (6 * (i - 1))) & 0x3F) | 0x80;
456 }
457 } else {
458 pos += additionalBytes;
459 }
460 }
461 return pos;
462 }
463 }
464
465
466 class _HttpRequestResponseBase {
467 _HttpRequestResponseBase(_HttpConnectionBase this._httpConnection)
468 : _contentLength = -1,
469 _keepAlive = false,
470 _headers = new Map();
471
472 int get contentLength() => _contentLength;
473 bool get keepAlive() => _keepAlive;
474
475 void _setHeader(String name, String value) {
476 _headers[name] = value;
477 }
478
479 bool _write(List<int> data, bool copyBuffer) {
480 bool allWritten = true;
481 if (data.length > 0) {
482 if (_contentLength < 0) {
483 // Write chunk size if transfer encoding is chunked.
484 _writeHexString(data.length);
485 _writeCRLF();
486 _httpConnection.outputStream.write(data, copyBuffer);
487 allWritten = _writeCRLF();
488 } else {
489 allWritten = _httpConnection.outputStream.write(data, copyBuffer);
490 }
491 }
492 return allWritten;
493 }
494
495 bool _writeList(List<int> data, int offset, int count) {
496 bool allWritten = true;
497 if (count > 0) {
498 if (_contentLength < 0) {
499 // Write chunk size if transfer encoding is chunked.
500 _writeHexString(count);
501 _writeCRLF();
502 _httpConnection.outputStream.writeFrom(data, offset, count);
503 allWritten = _writeCRLF();
504 } else {
505 allWritten = _httpConnection.outputStream.writeFrom(data, offset, count) ;
506 }
507 }
508 return allWritten;
509 }
510
511 bool _writeString(String string) {
512 bool allWritten = true;
513 if (string.length > 0) {
514 // Encode as UTF-8 and write data.
515 List<int> data = _UTF8Encoder.encodeString(string);
516 allWritten = _writeList(data, 0, data.length);
517 }
518 return allWritten;
519 }
520
521 bool _writeDone() {
522 bool allWritten = true;
523 if (_contentLength < 0) {
524 // Terminate the content if transfer encoding is chunked.
525 allWritten = _httpConnection.outputStream.write(_Const.END_CHUNKED);
526 }
527 return allWritten;
528 }
529
530 bool _writeHeaders() {
531 List<int> data;
532
533 // Format headers.
534 _headers.forEach((String name, String value) {
535 data = name.charCodes();
536 _httpConnection.outputStream.write(data);
537 data = ": ".charCodes();
538 _httpConnection.outputStream.write(data);
539 data = value.charCodes();
540 _httpConnection.outputStream.write(data);
541 _writeCRLF();
542 });
543 // Terminate header.
544 return _writeCRLF();
545 }
546
547 bool _writeHexString(int x) {
548 final List<int> hexDigits = [0x30, 0x31, 0x32, 0x33, 0x34,
549 0x35, 0x36, 0x37, 0x38, 0x39,
550 0x41, 0x42, 0x43, 0x44, 0x45, 0x46];
551 ByteArray hex = new ByteArray(10);
552 int index = hex.length;
553 while (x > 0) {
554 index--;
555 hex[index] = hexDigits[x % 16];
556 x = x >> 4;
557 }
558 return _httpConnection.outputStream.writeFrom(hex, index, hex.length - index );
559 }
560
561 bool _writeCRLF() {
562 final CRLF = const [_CharCode.CR, _CharCode.LF];
563 return _httpConnection.outputStream.write(CRLF);
564 }
565
566 bool _writeSP() {
567 final SP = const [_CharCode.SP];
568 return _httpConnection.outputStream.write(SP);
569 }
570
571 _HttpConnectionBase _httpConnection;
572 Map<String, String> _headers;
573
574 // Length of the content body. If this is set to -1 (default value)
575 // when starting to send data chunked transfer encoding will be
576 // used.
577 int _contentLength;
578 bool _keepAlive;
579 }
580
581
582 // Parsed HTTP request providing information on the HTTP headers.
583 class _HttpRequest extends _HttpRequestResponseBase implements HttpRequest {
584 _HttpRequest(_HttpConnection connection) : super(connection);
585
586 String get method() => _method;
587 String get uri() => _uri;
588 String get path() => _path;
589 Map get headers() => _headers;
590 String get queryString() => _queryString;
591 Map get queryParameters() => _queryParameters;
592
593 InputStream get inputStream() {
594 if (_inputStream == null) {
595 _inputStream = new _HttpInputStream(this);
596 }
597 return _inputStream;
598 }
599
600 void _requestStartHandler(String method, String uri) {
601 _method = method;
602 _uri = uri;
603 _parseRequestUri(uri);
604 }
605
606 void _headerReceivedHandler(String name, String value) {
607 _setHeader(name, value);
608 }
609
610 void _headersCompleteHandler() {
611 // Prepare for receiving data.
612 _buffer = new _BufferList();
613 }
614
615 void _dataReceivedHandler(List<int> data) {
616 _buffer.add(data);
617 if (_inputStream != null) _inputStream._dataReceived();
618 }
619
620 void _dataEndHandler() {
621 if (_inputStream != null) _inputStream._closeReceived();
622 }
623
624 // Escaped characters in uri are expected to have been parsed.
625 void _parseRequestUri(String uri) {
626 int position;
627 position = uri.indexOf("?", 0);
628 if (position == -1) {
629 _path = HttpUtil.decodeUrlEncodedString(_uri);
630 _queryString = null;
631 _queryParameters = new Map();
632 } else {
633 _path = HttpUtil.decodeUrlEncodedString(_uri.substring(0, position));
634 _queryString = _uri.substring(position + 1);
635 _queryParameters = HttpUtil.splitQueryString(_queryString);
636 }
637 }
638
639 // Delegate functions for the HttpInputStream implementation.
640 int _streamAvailable() {
641 return _buffer.length;
642 }
643
644 List<int> _streamRead(int bytesToRead) {
645 return _buffer.readBytes(bytesToRead);
646 }
647
648 int _streamReadInto(List<int> buffer, int offset, int len) {
649 List<int> data = _buffer.readBytes(len);
650 buffer.setRange(offset, data.length, data);
651 }
652
653 String _method;
654 String _uri;
655 String _path;
656 String _queryString;
657 Map<String, String> _queryParameters;
658 _HttpInputStream _inputStream;
659 _BufferList _buffer;
660 }
661
662
663 // HTTP response object for sending a HTTP response.
664 class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse {
665 static final int START = 0;
666 static final int HEADERS_SENT = 1;
667 static final int DONE = 2;
668
669 _HttpResponse(_HttpConnection httpConnection)
670 : super(httpConnection),
671 statusCode = HttpStatus.OK,
672 _state = START;
673
674 void set contentLength(int contentLength) {
675 if (_outputStream != null) return new HttpException("Header already sent");
676 _contentLength = contentLength;
677 }
678 void set keepAlive(bool keepAlive) {
679 if (_outputStream != null) return new HttpException("Header already sent");
680 _keepAlive = keepAlive;
681 }
682
683 // Set a header on the response. NOTE: If the same header is set
684 // more than once only the last one will be part of the response.
685 void setHeader(String name, String value) {
686 if (_outputStream != null) return new HttpException("Header already sent");
687 _setHeader(name, value);
688 }
689
690 OutputStream get outputStream() {
691 if (_state == DONE) throw new HttpException("Response closed");
692 if (_outputStream == null) {
693 // Ensure that headers are written.
694 if (_state == START) {
695 _writeHeader();
696 }
697 _outputStream = new _HttpOutputStream(this);
698 }
699 return _outputStream;
700 }
701
702 bool writeString(String string) {
703 // Invoke the output stream getter to make sure the header is sent.
704 outputStream;
705 return _writeString(string);
706 }
707
708 // Delegate functions for the HttpOutputStream implementation.
709 bool _streamWrite(List<int> buffer, bool copyBuffer) {
710 return _write(buffer, copyBuffer);
711 }
712
713 bool _streamWriteFrom(List<int> buffer, int offset, int len) {
714 return _writeList(buffer, offset, len);
715 }
716
717 void _streamClose() {
718 _state = DONE;
719 // Stop tracking no pending write events.
720 _httpConnection.outputStream.noPendingWriteHandler = null;
721 // Ensure that any trailing data is written.
722 _writeDone();
723 // If the connection is closing then close the output stream to
724 // fully close the socket.
725 if (_httpConnection._closing) {
726 _httpConnection.outputStream.close();
727 }
728 }
729
730 void _streamSetNoPendingWriteHandler(callback()) {
731 if (_state != DONE) {
732 _httpConnection.outputStream.noPendingWriteHandler = callback;
733 }
734 }
735
736 void _streamSetCloseHandler(callback()) {
737 // TODO(sgjesse): Handle this.
738 }
739
740 void _streamSetErrorHandler(callback()) {
741 // TODO(sgjesse): Handle this.
742 }
743
744 String _findReasonPhrase(int statusCode) {
745 if (reasonPhrase != null) {
746 return reasonPhrase;
747 }
748
749 switch (statusCode) {
750 case HttpStatus.CONTINUE: return "Continue";
751 case HttpStatus.SWITCHING_PROTOCOLS: return "Switching Protocols";
752 case HttpStatus.OK: return "OK";
753 case HttpStatus.CREATED: return "Created";
754 case HttpStatus.ACCEPTED: return "Accepted";
755 case HttpStatus.NON_AUTHORITATIVE_INFORMATION:
756 return "Non-Authoritative Information";
757 case HttpStatus.NO_CONTENT: return "No Content";
758 case HttpStatus.RESET_CONTENT: return "Reset Content";
759 case HttpStatus.PARTIAL_CONTENT: return "Partial Content";
760 case HttpStatus.MULTIPLE_CHOICES: return "Multiple Choices";
761 case HttpStatus.MOVED_PERMANENTLY: return "Moved Permanently";
762 case HttpStatus.FOUND: return "Found";
763 case HttpStatus.SEE_OTHER: return "See Other";
764 case HttpStatus.NOT_MODIFIED: return "Not Modified";
765 case HttpStatus.USE_PROXY: return "Use Proxy";
766 case HttpStatus.TEMPORARY_REDIRECT: return "Temporary Redirect";
767 case HttpStatus.BAD_REQUEST: return "Bad Request";
768 case HttpStatus.UNAUTHORIZED: return "Unauthorized";
769 case HttpStatus.PAYMENT_REQUIRED: return "Payment Required";
770 case HttpStatus.FORBIDDEN: return "Forbidden";
771 case HttpStatus.NOT_FOUND: return "Not Found";
772 case HttpStatus.METHOD_NOT_ALLOWED: return "Method Not Allowed";
773 case HttpStatus.NOT_ACCEPTABLE: return "Not Acceptable";
774 case HttpStatus.PROXY_AUTHENTICATION_REQUIRED:
775 return "Proxy Authentication Required";
776 case HttpStatus.REQUEST_TIMEOUT: return "Request Time-out";
777 case HttpStatus.CONFLICT: return "Conflict";
778 case HttpStatus.GONE: return "Gone";
779 case HttpStatus.LENGTH_REQUIRED: return "Length Required";
780 case HttpStatus.PRECONDITION_FAILED: return "Precondition Failed";
781 case HttpStatus.REQUEST_ENTITY_TOO_LARGE:
782 return "Request Entity Too Large";
783 case HttpStatus.REQUEST_URI_TOO_LONG: return "Request-URI Too Large";
784 case HttpStatus.UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type";
785 case HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE:
786 return "Requested range not satisfiable";
787 case HttpStatus.EXPECTATION_FAILED: return "Expectation Failed";
788 case HttpStatus.INTERNAL_SERVER_ERROR: return "Internal Server Error";
789 case HttpStatus.NOT_IMPLEMENTED: return "Not Implemented";
790 case HttpStatus.BAD_GATEWAY: return "Bad Gateway";
791 case HttpStatus.SERVICE_UNAVAILABLE: return "Service Unavailable";
792 case HttpStatus.GATEWAY_TIMEOUT: return "Gateway Time-out";
793 case HttpStatus.HTTP_VERSION_NOT_SUPPORTED:
794 return "Http Version not supported";
795 default: return "Status " + statusCode.toString();
796 }
797 }
798
799 bool _writeHeader() {
800 List<int> data;
801 OutputStream stream = _httpConnection.outputStream;
802
803 // Write status line.
804 stream.write(_Const.HTTP11);
805 _writeSP();
806 data = statusCode.toString().charCodes();
807 stream.write(data);
808 _writeSP();
809 data = _findReasonPhrase(statusCode).charCodes();
810 stream.write(data);
811 _writeCRLF();
812
813 // Determine the value of the "Connection" header
814 // based on the keep alive state.
815 setHeader("Connection", keepAlive ? "keep-alive" : "close");
816 // Determine the value of the "Transfer-Encoding" header based on
817 // whether the content length is known.
818 if (_contentLength >= 0) {
819 setHeader("Content-Length", _contentLength.toString());
820 } else {
821 setHeader("Transfer-Encoding", "chunked");
822 }
823
824 // Write headers.
825 bool allWritten = _writeHeaders();
826 _state = HEADERS_SENT;
827 return allWritten;
828 }
829
830 // Response status code.
831 int statusCode;
832 String reasonPhrase;
833 _HttpOutputStream _outputStream;
834 int _state;
835 }
836
837
838 class _HttpInputStream extends _BaseDataInputStream implements InputStream {
839 _HttpInputStream(_HttpRequestResponseBase this._requestOrResponse) {
840 _checkScheduleCallbacks();
841 }
842
843 int available() {
844 return _requestOrResponse._streamAvailable();
845 }
846
847 void pipe(OutputStream output, [bool close = true]) {
848 _pipe(this, output, close: close);
849 }
850
851 List<int> _read(int bytesToRead) {
852 List<int> result = _requestOrResponse._streamRead(bytesToRead);
853 _checkScheduleCallbacks();
854 return result;
855 }
856
857 int _readInto(List<int> buffer, int offset, int len) {
858 int result = _requestOrResponse._streamReadInto(buffer, offset, len);
859 _checkScheduleCallbacks();
860 return result;
861 }
862
863 void _close() {
864 // TODO(sgjesse): Handle this.
865 }
866
867 void _dataReceived() {
868 super._dataReceived();
869 }
870
871 _HttpRequestResponseBase _requestOrResponse;
872 }
873
874
875 class _HttpOutputStream implements OutputStream {
876 _HttpOutputStream(_HttpRequestResponseBase this._requestOrResponse);
877
878 bool write(List<int> buffer, [bool copyBuffer = true]) {
879 return _requestOrResponse._streamWrite(buffer, copyBuffer);
880 }
881
882 bool writeFrom(List<int> buffer, [int offset = 0, int len]) {
883 return _requestOrResponse._streamWriteFrom(buffer, offset, len);
884 }
885
886 void close() {
887 _requestOrResponse._streamClose();
888 }
889
890 void destroy() {
891 throw "Not implemented";
892 }
893
894 void set noPendingWriteHandler(void callback()) {
895 _requestOrResponse._streamSetNoPendingWriteHandler(callback);
896 }
897
898 void set closeHandler(void callback()) {
899 _requestOrResponse._streamSetCloseHandler(callback);
900 }
901
902 void set errorHandler(void callback()) {
903 _requestOrResponse._streamSetErrorHandler(callback);
904 }
905
906 _HttpRequestResponseBase _requestOrResponse;
907 }
908
909
910 class _HttpConnectionBase {
911 _HttpConnectionBase() : _sendBuffers = new Queue(),
912 _httpParser = new HttpParser();
913
914 void _connectionEstablished(Socket socket) {
915 _socket = socket;
916 // Register handler for socket events.
917 _socket.dataHandler = _dataHandler;
918 _socket.closeHandler = _closeHandler;
919 _socket.errorHandler = _errorHandler;
920 }
921
922 OutputStream get outputStream() {
923 return _socket.outputStream;
924 }
925
926 void _dataHandler() {
927 int available = _socket.available();
928 if (available == 0) {
929 return;
930 }
931
932 ByteArray buffer = new ByteArray(available);
933 int bytesRead = _socket.readList(buffer, 0, available);
934 if (bytesRead > 0) {
935 int parsed = _httpParser.writeList(buffer, 0, bytesRead);
936 if (parsed != bytesRead) {
937 print("Failed to parse HTTP data $parsed $bytesRead");
938 _socket.close();
939 }
940 }
941 }
942
943 void _closeHandler() {
944 // Client closed socket for writing. Socket should still be open
945 // for writing the response.
946 _closing = true;
947 if (_disconnectHandlerCallback != null) _disconnectHandlerCallback();
948 }
949
950 void _errorHandler() {
951 // If an error occours, treat the socket as closed.
952 _closeHandler();
953 if (_errorHandlerCallback != null) {
954 _errorHandlerCallback("Connection closed while sending data to client.");
955 }
956 }
957
958 void set disconnectHandler(void callback()) {
959 _disconnectHandlerCallback = callback;
960 }
961
962 void set errorHandler(void callback(String errorMessage)) {
963 _errorHandlerCallback = callback;
964 }
965
966 Socket _socket;
967 bool _closing = false; // Is the socket closed by the client?
968 HttpParser _httpParser;
969
970 Queue _sendBuffers;
971
972 Function _disconnectHandlerCallback;
973 Function _errorHandlerCallback;
974 }
975
976
977 // HTTP server connection over a socket.
978 class _HttpConnection extends _HttpConnectionBase {
979 _HttpConnection() {
980 // Register HTTP parser callbacks.
981 _httpParser.requestStart =
982 (method, uri) => _requestStartHandler(method, uri);
983 _httpParser.responseStart =
984 (statusCode, reasonPhrase) =>
985 _responseStartHandler(statusCode, reasonPhrase);
986 _httpParser.headerReceived =
987 (name, value) => _headerReceivedHandler(name, value);
988 _httpParser.headersComplete = () => _headersCompleteHandler();
989 _httpParser.dataReceived = (data) => _dataReceivedHandler(data);
990 _httpParser.dataEnd = () => _dataEndHandler();
991 }
992
993 void _requestStartHandler(String method, String uri) {
994 // Create new request and response objects for this request.
995 _request = new _HttpRequest(this);
996 _response = new _HttpResponse(this);
997 _request._requestStartHandler(method, uri);
998 }
999
1000 void _responseStartHandler(int statusCode, String reasonPhrase) {
1001 // TODO(sgjesse): Error handling.
1002 }
1003
1004 void _headerReceivedHandler(String name, String value) {
1005 _request._headerReceivedHandler(name, value);
1006 }
1007
1008 void _headersCompleteHandler() {
1009 _request._headersCompleteHandler();
1010 _response.keepAlive = _httpParser.keepAlive;
1011 if (requestReceived != null) {
1012 requestReceived(_request, _response);
1013 }
1014 }
1015
1016 void _dataReceivedHandler(List<int> data) {
1017 _request._dataReceivedHandler(data);
1018 }
1019
1020 void _dataEndHandler() {
1021 _request._dataEndHandler();
1022 }
1023
1024 HttpRequest _request;
1025 HttpResponse _response;
1026
1027 // Callbacks.
1028 var requestReceived;
1029 }
1030
1031
1032 // HTTP server waiting for socket connections. The connections are
1033 // managed by the server and as requests are received the request.
1034 class _HttpServer implements HttpServer {
1035 _HttpServer () : _debugTrace = false;
1036
1037 void listen(String host, int port, [int backlog = 5]) {
1038
1039 void connectionHandler(Socket socket) {
1040 // Accept the client connection.
1041 _HttpConnection connection = new _HttpConnection();
1042 connection._connectionEstablished(socket);
1043 connection.requestReceived = _requestHandler;
1044 _connections.add(connection);
1045 if (_debugTrace) {
1046 print("New connection (total ${_connections.length} connections)");
1047 }
1048 void disconnectHandler() {
1049 for (int i = 0; i < _connections.length; i++) {
1050 if (_connections[i] == connection) {
1051 _connections.removeRange(i, 1);
1052 break;
1053 }
1054 }
1055 if (_debugTrace) {
1056 print("Closed connection (total ${_connections.length} connections)");
1057 }
1058 }
1059 connection.disconnectHandler = disconnectHandler;
1060 void errorHandler(String errorMessage) {
1061 if (_errorHandler != null) _errorHandler(errorMessage);
1062 }
1063 connection.errorHandler = errorHandler;
1064 }
1065
1066 // TODO(ajohnsen): Use Set once Socket is Hashable.
1067 _connections = new List<_HttpConnection>();
1068 _server = new ServerSocket(host, port, backlog);
1069 _server.connectionHandler = connectionHandler;
1070 }
1071
1072 void close() => _server.close();
1073 int get port() => _server.port;
1074
1075 void set errorHandler(void handler(String errorMessage)) {
1076 _errorHandler = handler;
1077 }
1078
1079 void set requestHandler(void handler(HttpRequest, HttpResponse)) {
1080 _requestHandler = handler;
1081 }
1082
1083 ServerSocket _server; // The server listen socket.
1084 List<_HttpConnection> _connections; // List of currently connected clients.
1085 Function _requestHandler;
1086 Function _errorHandler;
1087 bool _debugTrace;
1088 }
1089
1090
1091 class _HttpClientRequest
1092 extends _HttpRequestResponseBase implements HttpClientRequest {
1093 static final int START = 0;
1094 static final int HEADERS_SENT = 1;
1095 static final int DONE = 2;
1096
1097 _HttpClientRequest(String this._method,
1098 String this._uri,
1099 _HttpClientConnection connection)
1100 : super(connection),
1101 _state = START {
1102 _connection = connection;
1103 // Default GET requests to have no content.
1104 if (_method == "GET") {
1105 _contentLength = 0;
1106 }
1107 }
1108
1109 void set contentLength(int contentLength) => _contentLength = contentLength;
1110 void set keepAlive(bool keepAlive) => _keepAlive = keepAlive;
1111 int get statusCode() { return _statusCode; }
1112 String get reasonPhrase() { return _reasonPhrase; }
1113
1114 void setHeader(String name, String value) {
1115 _setHeader(name, value);
1116 }
1117
1118 bool writeString(String string) {
1119 outputStream;
1120 return _writeString(string);
1121 }
1122
1123 OutputStream get outputStream() {
1124 if (_state == DONE) throw new HttpException("Request closed");
1125 if (_outputStream == null) {
1126 // Ensure that headers are written.
1127 if (_state == START) {
1128 _writeHeader();
1129 }
1130 _outputStream = new _HttpOutputStream(this);
1131 }
1132 return _outputStream;
1133 }
1134
1135 // Delegate functions for the HttpOutputStream implementation.
1136 bool _streamWrite(List<int> buffer, bool copyBuffer) {
1137 return _write(buffer, copyBuffer);
1138 }
1139
1140 bool _streamWriteFrom(List<int> buffer, int offset, int len) {
1141 return _writeList(buffer, offset, len);
1142 }
1143
1144 void _streamClose() {
1145 _state = DONE;
1146 // Stop tracking no pending write events.
1147 _httpConnection.outputStream.noPendingWriteHandler = null;
1148 // Ensure that any trailing data is written.
1149 _writeDone();
1150 // If the connection is closing then close the output stream to
1151 // fully close the socket.
1152 if (_httpConnection._closing) {
1153 _httpConnection.outputStream.close();
1154 }
1155 }
1156
1157 void _streamSetNoPendingWriteHandler(callback()) {
1158 if (_state != DONE) {
1159 _httpConnection.outputStream.noPendingWriteHandler = callback;
1160 }
1161 }
1162
1163 void _streamSetCloseHandler(callback()) {
1164 // TODO(sgjesse): Handle this.
1165 }
1166
1167 void _streamSetErrorHandler(callback()) {
1168 // TODO(sgjesse): Handle this.
1169 }
1170
1171 void _writeHeader() {
1172 List<int> data;
1173 OutputStream stream = _httpConnection.outputStream;
1174
1175 // Write request line.
1176 data = _method.toString().charCodes();
1177 stream.write(data);
1178 _writeSP();
1179 data = _uri.toString().charCodes();
1180 stream.write(data);
1181 _writeSP();
1182 stream.write(_Const.HTTP11);
1183 _writeCRLF();
1184
1185 // Determine the value of the "Connection" header
1186 // based on the keep alive state.
1187 setHeader("Connection", keepAlive ? "keep-alive" : "close");
1188 // Determine the value of the "Transfer-Encoding" header based on
1189 // whether the content length is known.
1190 if (_contentLength >= 0) {
1191 setHeader("Content-Length", _contentLength.toString());
1192 } else {
1193 setHeader("Transfer-Encoding", "chunked");
1194 }
1195
1196 // Write headers.
1197 _writeHeaders();
1198 _state = HEADERS_SENT;
1199 }
1200
1201 String _method;
1202 String _uri;
1203 _HttpClientConnection _connection;
1204 _HttpOutputStream _outputStream;
1205 int _state;
1206 }
1207
1208
1209 class _HttpClientResponse
1210 extends _HttpRequestResponseBase implements HttpClientResponse {
1211 _HttpClientResponse(_HttpClientConnection connection)
1212 : super(connection) {
1213 _connection = connection;
1214 }
1215
1216 int get statusCode() { return _statusCode; }
1217 int get reasonPhrase() { return _reasonPhrase; }
1218 Map get headers() => _headers;
1219
1220 InputStream get inputStream() {
1221 if (_inputStream == null) {
1222 _inputStream = new _HttpInputStream(this);
1223 }
1224 return _inputStream;
1225 }
1226
1227 void _requestStartHandler(String method, String uri) {
1228 // TODO(sgjesse): Error handling
1229 }
1230
1231 void _responseStartHandler(int statusCode, String reasonPhrase) {
1232 _statusCode = statusCode;
1233 _reasonPhrase = reasonPhrase;
1234 }
1235
1236 void _headerReceivedHandler(String name, String value) {
1237 _setHeader(name, value);
1238 }
1239
1240 void _headersCompleteHandler() {
1241 _buffer = new _BufferList();
1242 if (_connection._responseHandler != null) {
1243 _connection._responseHandler(this);
1244 }
1245 }
1246
1247 void _dataReceivedHandler(List<int> data) {
1248 _buffer.add(data);
1249 if (_inputStream != null) _inputStream._dataReceived();
1250 }
1251
1252 void _dataEndHandler() {
1253 if (_inputStream != null) _inputStream._closeReceived();
1254 }
1255
1256 // Delegate functions for the HttpInputStream implementation.
1257 int _streamAvailable() {
1258 return _buffer.length;
1259 }
1260
1261 List<int> _streamRead(int bytesToRead) {
1262 return _buffer.readBytes(bytesToRead);
1263 }
1264
1265 int _streamReadInto(List<int> buffer, int offset, int len) {
1266 List<int> data = _buffer.readBytes(len);
1267 buffer.setRange(offset, data.length, data);
1268 return data.length;
1269 }
1270
1271 int _statusCode;
1272 String _reasonPhrase;
1273
1274 _HttpClientConnection _connection;
1275 _HttpInputStream _inputStream;
1276 _BufferList _buffer;
1277 }
1278
1279
1280 class _HttpClientConnection
1281 extends _HttpConnectionBase implements HttpClientConnection {
1282 _HttpClientConnection(_HttpClient this._client);
1283
1284 void _connectionEstablished(_SocketConnection socketConn) {
1285 super._connectionEstablished(socketConn._socket);
1286 _socketConn = socketConn;
1287 // Register HTTP parser callbacks.
1288 _httpParser.requestStart =
1289 (method, uri) => _requestStartHandler(method, uri);
1290 _httpParser.responseStart =
1291 (statusCode, reasonPhrase) =>
1292 _responseStartHandler(statusCode, reasonPhrase);
1293 _httpParser.headerReceived =
1294 (name, value) => _headerReceivedHandler(name, value);
1295 _httpParser.headersComplete = () => _headersCompleteHandler();
1296 _httpParser.dataReceived = (data) => _dataReceivedHandler(data);
1297 _httpParser.dataEnd = () => _dataEndHandler();
1298 }
1299
1300 HttpClientRequest open(String method, String uri) {
1301 _request = new _HttpClientRequest(method, uri, this);
1302 _request.keepAlive = true;
1303 _response = new _HttpClientResponse(this);
1304 return _request;
1305 }
1306
1307 void _requestStartHandler(String method, String uri) {
1308 // TODO(sgjesse): Error handling.
1309 }
1310
1311 void _responseStartHandler(int statusCode, String reasonPhrase) {
1312 _response._responseStartHandler(statusCode, reasonPhrase);
1313 }
1314
1315 void _headerReceivedHandler(String name, String value) {
1316 _response._headerReceivedHandler(name, value);
1317 }
1318
1319 void _headersCompleteHandler() {
1320 _response._headersCompleteHandler();
1321 }
1322
1323 void _dataReceivedHandler(List<int> data) {
1324 _response._dataReceivedHandler(data);
1325 }
1326
1327 void _dataEndHandler() {
1328 if (_response.headers["connection"] == "close") {
1329 _socket.close();
1330 } else {
1331 _client._returnSocketConnection(_socketConn);
1332 _socket = null;
1333 _socketConn = null;
1334 }
1335 _response._dataEndHandler();
1336 }
1337
1338 void set requestHandler(void handler(HttpClientRequest request)) {
1339 _requestHandler = handler;
1340 }
1341
1342 void set responseHandler(void handler(HttpClientResponse response)) {
1343 _responseHandler = handler;
1344 }
1345
1346 Function _requestHandler;
1347 Function _responseHandler;
1348
1349 _HttpClient _client;
1350 _SocketConnection _socketConn;
1351 HttpClientRequest _request;
1352 HttpClientResponse _response;
1353
1354 // Callbacks.
1355 var requestReceived;
1356
1357 }
1358
1359
1360 // Class for holding keep-alive sockets in the cache for the HTTP
1361 // client together with the connection information.
1362 class _SocketConnection {
1363 _SocketConnection(String this._host,
1364 int this._port,
1365 Socket this._socket);
1366
1367 void _markReturned() {
1368 _socket.dataHandler = null;
1369 _socket.closeHandler = null;
1370 _socket.errorHandler = null;
1371 _returnTime = new Date.now();
1372 }
1373
1374 Duration _idleTime(Date now) => now.difference(_returnTime);
1375
1376 String _host;
1377 int _port;
1378 Socket _socket;
1379 Date _returnTime;
1380 }
1381
1382
1383 class _HttpClient implements HttpClient {
1384 static final int DEFAULT_EVICTION_TIMEOUT = 60000;
1385
1386 _HttpClient() : _openSockets = new Map(), _shutdown = false;
1387
1388 HttpClientConnection open(
1389 String method, String host, int port, String path) {
1390 if (_shutdown) throw new HttpException("HttpClient shutdown");
1391 return _prepareHttpClientConnection(host, port, method, path);
1392 }
1393
1394 HttpClientConnection get(String host, int port, String path) {
1395 return open("GET", host, port, path);
1396 }
1397
1398 HttpClientConnection post(String host, int port, String path) {
1399 return open("POST", host, port, path);
1400 }
1401
1402 void shutdown() {
1403 _openSockets.forEach(
1404 void _(String key, Queue<_SocketConnection> connections) {
1405 while (!connections.isEmpty()) {
1406 var socketConn = connections.removeFirst();
1407 socketConn._socket.close();
1408 }
1409 });
1410 if (_evictionTimer != null) {
1411 _evictionTimer.cancel();
1412 }
1413 _shutdown = true;
1414 }
1415
1416 String _connectionKey(String host, int port) {
1417 return "$host:$port";
1418 }
1419
1420 HttpClientConnection _prepareHttpClientConnection(
1421 String host, int port, String method, String path) {
1422
1423 void _connectionOpened(_SocketConnection socketConn,
1424 _HttpClientConnection connection) {
1425 connection._connectionEstablished(socketConn);
1426 HttpClientRequest request = connection.open(method, path);
1427 if (connection._requestHandler != null) {
1428 connection._requestHandler(request);
1429 } else {
1430 request.outputStream.close();
1431 }
1432 }
1433
1434 _HttpClientConnection connection = new _HttpClientConnection(this);
1435
1436 // If there are active connections for this key get the first one
1437 // otherwise create a new one.
1438 Queue socketConnections = _openSockets[_connectionKey(host, port)];
1439 if (socketConnections == null || socketConnections.isEmpty()) {
1440 Socket socket = new Socket(host, port);
1441 socket.connectHandler = () {
1442 socket.errorHandler = null;
1443 _SocketConnection socketConn =
1444 new _SocketConnection(host, port, socket);
1445 _connectionOpened(socketConn, connection);
1446 };
1447 socket.errorHandler = () {
1448 if (_errorHandler !== null) {
1449 _errorHandler(HttpStatus.NETWORK_CONNECT_TIMEOUT_ERROR);
1450 }
1451 };
1452 } else {
1453 _SocketConnection socketConn = socketConnections.removeFirst();
1454 new Timer((ignored) => _connectionOpened(socketConn, connection), 0);
1455
1456 // Get rid of eviction timer if there are no more active connections.
1457 if (socketConnections.isEmpty()) {
1458 _evictionTimer.cancel();
1459 _evictionTimer = null;
1460 }
1461 }
1462
1463 return connection;
1464 }
1465
1466 void _returnSocketConnection(_SocketConnection socketConn) {
1467 // If the HTTP client is beeing shutdown don't return the connection.
1468 if (_shutdown) {
1469 socketConn._socket.close();
1470 return;
1471 };
1472
1473 String key = _connectionKey(socketConn._host, socketConn._port);
1474
1475 // Get or create the connection list for this key.
1476 Queue sockets = _openSockets[key];
1477 if (sockets == null) {
1478 sockets = new Queue();
1479 _openSockets[key] = sockets;
1480 }
1481
1482 // If there is currently no eviction timer start one.
1483 if (_evictionTimer == null) {
1484 void _handleEviction(Timer timer) {
1485 Date now = new Date.now();
1486 _openSockets.forEach(
1487 void _(String key, Queue<_SocketConnection> connections) {
1488 // As returned connections are added at the head of the
1489 // list remove from the tail.
1490 while (!connections.isEmpty()) {
1491 _SocketConnection socketConn = connections.last();
1492 if (socketConn._idleTime(now).inMilliseconds >
1493 DEFAULT_EVICTION_TIMEOUT) {
1494 connections.removeLast();
1495 } else {
1496 break;
1497 }
1498 }
1499 });
1500 }
1501 _evictionTimer = new Timer.repeating(_handleEviction, 10000);
1502 }
1503
1504 // Return connection.
1505 sockets.addFirst(socketConn);
1506 socketConn._markReturned();
1507 }
1508
1509 void set errorHandler(void callback(int status)) {
1510 _errorHandler = callback;
1511 }
1512
1513 Function _openHandler;
1514 Function _errorHandler;
1515 Map<String, Queue<_SocketConnection>> _openSockets;
1516 Timer _evictionTimer;
1517 bool _shutdown; // Has this HTTP client been shutdown?
1518 }
1519
1520
1521 class HttpUtil {
1522 static String decodeUrlEncodedString(String urlEncoded) {
1523 void invalidEscape() {
1524 // TODO(sgjesse): Handle the error.
1525 print("Invalid escape code.");
1526 }
1527
1528 StringBuffer result = new StringBuffer();
1529 for (int ii = 0; urlEncoded.length > ii; ++ii) {
1530 if ('+' == urlEncoded[ii]) {
1531 result.add(' ');
1532 } else if ('%' == urlEncoded[ii] &&
1533 urlEncoded.length - 2 > ii) {
1534 try {
1535 int charCode =
1536 Math.parseInt('0x' + urlEncoded.substring(ii + 1, ii + 3));
1537 if (charCode <= 0x7f) {
1538 result.add(new String.fromCharCodes([charCode]));
1539 ii += 2;
1540 } else {
1541 invalidEscape();
1542 return '';
1543 }
1544 } catch (BadNumberFormatException ignored) {
1545 invalidEscape();
1546 return '';
1547 }
1548 } else {
1549 result.add(urlEncoded[ii]);
1550 }
1551 }
1552 return result.toString();
1553 }
1554
1555 static Map<String, String> splitQueryString(String queryString) {
1556 Map<String, String> result = new Map<String, String>();
1557 int currentPosition = 0;
1558 while (currentPosition < queryString.length) {
1559 int position = queryString.indexOf("=", currentPosition);
1560 if (position == -1) {
1561 break;
1562 }
1563 String name = queryString.substring(currentPosition, position);
1564 currentPosition = position + 1;
1565 position = queryString.indexOf("&", currentPosition);
1566 String value;
1567 if (position == -1) {
1568 value = queryString.substring(currentPosition);
1569 currentPosition = queryString.length;
1570 } else {
1571 value = queryString.substring(currentPosition, position);
1572 currentPosition = position + 1;
1573 }
1574 result[HttpUtil.decodeUrlEncodedString(name)] =
1575 HttpUtil.decodeUrlEncodedString(value);
1576 }
1577 return result;
1578 }
1579 }
OLDNEW
« no previous file with comments | « samples/chat/http.dart ('k') | samples/tests/samples/src/chat/ChatServerTest.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698