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

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

Issue 11337019: Use patching for dart:io. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments 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
(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 class _HttpHeaders implements HttpHeaders {
6 _HttpHeaders() : _headers = new Map<String, List<String>>();
7
8 List<String> operator[](String name) {
9 name = name.toLowerCase();
10 return _headers[name];
11 }
12
13 String value(String name) {
14 name = name.toLowerCase();
15 List<String> values = _headers[name];
16 if (values == null) return null;
17 if (values.length > 1) {
18 throw new HttpException("More than one value for header $name");
19 }
20 return values[0];
21 }
22
23 void add(String name, Object value) {
24 _checkMutable();
25 if (value is List) {
26 for (int i = 0; i < value.length; i++) {
27 _add(name, value[i]);
28 }
29 } else {
30 _add(name, value);
31 }
32 }
33
34 void set(String name, Object value) {
35 name = name.toLowerCase();
36 _checkMutable();
37 removeAll(name);
38 add(name, value);
39 }
40
41 void remove(String name, Object value) {
42 _checkMutable();
43 name = name.toLowerCase();
44 List<String> values = _headers[name];
45 if (values != null) {
46 int index = values.indexOf(value);
47 if (index != -1) {
48 values.removeRange(index, 1);
49 }
50 }
51 }
52
53 void removeAll(String name) {
54 _checkMutable();
55 name = name.toLowerCase();
56 _headers.remove(name);
57 }
58
59 void forEach(void f(String name, List<String> values)) {
60 _headers.forEach(f);
61 }
62
63 void noFolding(String name) {
64 if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>();
65 _noFoldingHeaders.add(name);
66 }
67
68 String get host => _host;
69
70 void set host(String host) {
71 _checkMutable();
72 _host = host;
73 _updateHostHeader();
74 }
75
76 int get port => _port;
77
78 void set port(int port) {
79 _checkMutable();
80 _port = port;
81 _updateHostHeader();
82 }
83
84 Date get ifModifiedSince {
85 List<String> values = _headers["if-modified-since"];
86 if (values != null) {
87 try {
88 return _HttpUtils.parseDate(values[0]);
89 } on Exception catch (e) {
90 return null;
91 }
92 }
93 return null;
94 }
95
96 void set ifModifiedSince(Date ifModifiedSince) {
97 _checkMutable();
98 // Format "ifModifiedSince" header with date in Greenwich Mean Time (GMT).
99 String formatted = _HttpUtils.formatDate(ifModifiedSince.toUtc());
100 _set("if-modified-since", formatted);
101 }
102
103 Date get date {
104 List<String> values = _headers["date"];
105 if (values != null) {
106 try {
107 return _HttpUtils.parseDate(values[0]);
108 } on Exception catch (e) {
109 return null;
110 }
111 }
112 return null;
113 }
114
115 void set date(Date date) {
116 _checkMutable();
117 // Format "Date" header with date in Greenwich Mean Time (GMT).
118 String formatted = _HttpUtils.formatDate(date.toUtc());
119 _set("date", formatted);
120 }
121
122 Date get expires {
123 List<String> values = _headers["expires"];
124 if (values != null) {
125 try {
126 return _HttpUtils.parseDate(values[0]);
127 } on Exception catch (e) {
128 return null;
129 }
130 }
131 return null;
132 }
133
134 void set expires(Date expires) {
135 _checkMutable();
136 // Format "Expires" header with date in Greenwich Mean Time (GMT).
137 String formatted = _HttpUtils.formatDate(expires.toUtc());
138 _set("expires", formatted);
139 }
140
141 ContentType get contentType {
142 var values = _headers["content-type"];
143 if (values != null) {
144 return new ContentType.fromString(values[0]);
145 } else {
146 return new ContentType();
147 }
148 }
149
150 void set contentType(ContentType contentType) {
151 _checkMutable();
152 _set("content-type", contentType.toString());
153 }
154
155 void _add(String name, Object value) {
156 // TODO(sgjesse): Add immutable state throw HttpException is immutable.
157 if (name.toLowerCase() == "date") {
158 if (value is Date) {
159 date = value;
160 } else if (value is String) {
161 _set("date", value);
162 } else {
163 throw new HttpException("Unexpected type for header named $name");
164 }
165 } else if (name.toLowerCase() == "expires") {
166 if (value is Date) {
167 expires = value;
168 } else if (value is String) {
169 _set("expires", value);
170 } else {
171 throw new HttpException("Unexpected type for header named $name");
172 }
173 } else if (name.toLowerCase() == "if-modified-since") {
174 if (value is Date) {
175 ifModifiedSince = value;
176 } else if (value is String) {
177 _set("if-modified-since", value);
178 } else {
179 throw new HttpException("Unexpected type for header named $name");
180 }
181 } else if (name.toLowerCase() == "host") {
182 int pos = value.indexOf(":");
183 if (pos == -1) {
184 _host = value;
185 _port = HttpClient.DEFAULT_HTTP_PORT;
186 } else {
187 if (pos > 0) {
188 _host = value.substring(0, pos);
189 } else {
190 _host = null;
191 }
192 if (pos + 1 == value.length) {
193 _port = HttpClient.DEFAULT_HTTP_PORT;
194 } else {
195 try {
196 _port = parseInt(value.substring(pos + 1));
197 } on FormatException catch (e) {
198 _port = null;
199 }
200 }
201 _set("host", value);
202 }
203 } else if (name.toLowerCase() == "content-type") {
204 _set("content-type", value);
205 } else {
206 name = name.toLowerCase();
207 List<String> values = _headers[name];
208 if (values == null) {
209 values = new List<String>();
210 _headers[name] = values;
211 }
212 if (value is Date) {
213 values.add(_HttpUtils.formatDate(value));
214 } else {
215 values.add(value.toString());
216 }
217 }
218 }
219
220 void _set(String name, String value) {
221 name = name.toLowerCase();
222 List<String> values = new List<String>();
223 _headers[name] = values;
224 values.add(value);
225 }
226
227 _checkMutable() {
228 if (!_mutable) throw new HttpException("HTTP headers are not mutable");
229 }
230
231 _updateHostHeader() {
232 bool defaultPort = _port == null || _port == HttpClient.DEFAULT_HTTP_PORT;
233 String portPart = defaultPort ? "" : ":$_port";
234 _set("host", "$host$portPart");
235 }
236
237 _foldHeader(String name) {
238 if (name == "set-cookie" ||
239 (_noFoldingHeaders != null &&
240 _noFoldingHeaders.indexOf(name) != -1)) {
241 return false;
242 }
243 return true;
244 }
245
246 _write(_HttpConnectionBase connection) {
247 final COLONSP = const [_CharCode.COLON, _CharCode.SP];
248 final COMMASP = const [_CharCode.COMMA, _CharCode.SP];
249 final CRLF = const [_CharCode.CR, _CharCode.LF];
250
251 // Format headers.
252 _headers.forEach((String name, List<String> values) {
253 bool fold = _foldHeader(name);
254 List<int> data;
255 data = name.charCodes;
256 connection._write(data);
257 connection._write(COLONSP);
258 for (int i = 0; i < values.length; i++) {
259 if (i > 0) {
260 if (fold) {
261 connection._write(COMMASP);
262 } else {
263 connection._write(CRLF);
264 data = name.charCodes;
265 connection._write(data);
266 connection._write(COLONSP);
267 }
268 }
269 data = values[i].charCodes;
270 connection._write(data);
271 }
272 connection._write(CRLF);
273 });
274 }
275
276 String toString() {
277 StringBuffer sb = new StringBuffer();
278 _headers.forEach((String name, List<String> values) {
279 sb.add(name);
280 sb.add(": ");
281 bool fold = _foldHeader(name);
282 for (int i = 0; i < values.length; i++) {
283 if (i > 0) {
284 if (fold) {
285 sb.add(", ");
286 } else {
287 sb.add("\n");
288 sb.add(name);
289 sb.add(": ");
290 }
291 }
292 sb.add(values[i]);
293 }
294 sb.add("\n");
295 });
296 return sb.toString();
297 }
298
299 bool _mutable = true; // Are the headers currently mutable?
300 Map<String, List<String>> _headers;
301 List<String> _noFoldingHeaders;
302
303 String _host;
304 int _port;
305 }
306
307
308 class _HeaderValue implements HeaderValue {
309 _HeaderValue([String this.value = ""]);
310
311 _HeaderValue.fromString(String value, {this.parameterSeparator: ";"}) {
312 // Parse the string.
313 _parse(value);
314 }
315
316 Map<String, String> get parameters {
317 if (_parameters == null) _parameters = new Map<String, String>();
318 return _parameters;
319 }
320
321 String toString() {
322 StringBuffer sb = new StringBuffer();
323 sb.add(value);
324 if (parameters != null && parameters.length > 0) {
325 _parameters.forEach((String name, String value) {
326 sb.add("; ");
327 sb.add(name);
328 sb.add("=");
329 sb.add(value);
330 });
331 }
332 return sb.toString();
333 }
334
335 void _parse(String s) {
336 int index = 0;
337
338 bool done() => index == s.length;
339
340 void skipWS() {
341 while (!done()) {
342 if (s[index] != " " && s[index] != "\t") return;
343 index++;
344 }
345 }
346
347 String parseValue() {
348 int start = index;
349 while (!done()) {
350 if (s[index] == " " ||
351 s[index] == "\t" ||
352 s[index] == parameterSeparator) break;
353 index++;
354 }
355 return s.substring(start, index).toLowerCase();
356 }
357
358 void expect(String expected) {
359 if (done() || s[index] != expected) {
360 throw new HttpException("Failed to parse header value");
361 }
362 index++;
363 }
364
365 void maybeExpect(String expected) {
366 if (s[index] == expected) index++;
367 }
368
369 void parseParameters() {
370 _parameters = new Map<String, String>();
371
372 String parseParameterName() {
373 int start = index;
374 while (!done()) {
375 if (s[index] == " " || s[index] == "\t" || s[index] == "=") break;
376 index++;
377 }
378 return s.substring(start, index).toLowerCase();
379 }
380
381 String parseParameterValue() {
382 if (s[index] == "\"") {
383 // Parse quoted value.
384 StringBuffer sb = new StringBuffer();
385 index++;
386 while (!done()) {
387 if (s[index] == "\\") {
388 if (index + 1 == s.length) {
389 throw new HttpException("Failed to parse header value");
390 }
391 index++;
392 } else if (s[index] == "\"") {
393 index++;
394 break;
395 }
396 sb.add(s[index]);
397 index++;
398 }
399 return sb.toString();
400 } else {
401 // Parse non-quoted value.
402 return parseValue();
403 }
404 }
405
406 while (!done()) {
407 skipWS();
408 if (done()) return;
409 String name = parseParameterName();
410 skipWS();
411 expect("=");
412 skipWS();
413 String value = parseParameterValue();
414 _parameters[name] = value;
415 skipWS();
416 if (done()) return;
417 expect(parameterSeparator);
418 }
419 }
420
421 skipWS();
422 value = parseValue();
423 skipWS();
424 if (done()) return;
425 maybeExpect(parameterSeparator);
426 parseParameters();
427 }
428
429 String value;
430 String parameterSeparator;
431 Map<String, String> _parameters;
432 }
433
434
435 class _ContentType extends _HeaderValue implements ContentType {
436 _ContentType(String primaryType, String subType)
437 : _primaryType = primaryType, _subType = subType, super("");
438
439 _ContentType.fromString(String value) : super.fromString(value);
440
441 String get value => "$_primaryType/$_subType";
442
443 void set value(String s) {
444 int index = s.indexOf("/");
445 if (index == -1 || index == (s.length - 1)) {
446 primaryType = s.trim().toLowerCase();
447 subType = "";
448 } else {
449 primaryType = s.substring(0, index).trim().toLowerCase();
450 subType = s.substring(index + 1).trim().toLowerCase();
451 }
452 }
453
454 String get primaryType => _primaryType;
455
456 void set primaryType(String s) {
457 _primaryType = s;
458 }
459
460 String get subType => _subType;
461
462 void set subType(String s) {
463 _subType = s;
464 }
465
466 String get charset => parameters["charset"];
467
468 void set charset(String s) {
469 parameters["charset"] = s;
470 }
471
472 String _primaryType = "";
473 String _subType = "";
474 }
475
476
477 class _Cookie implements Cookie {
478 _Cookie([String this.name, String this.value]);
479
480 _Cookie.fromSetCookieValue(String value) {
481 // Parse the Set-Cookie header value.
482 _parseSetCookieValue(value);
483 }
484
485 // Parse a Set-Cookie header value according to the rules in RFC 6265.
486 void _parseSetCookieValue(String s) {
487 int index = 0;
488
489 bool done() => index == s.length;
490
491 String parseName() {
492 int start = index;
493 while (!done()) {
494 if (s[index] == "=") break;
495 index++;
496 }
497 return s.substring(start, index).trim().toLowerCase();
498 }
499
500 String parseValue() {
501 int start = index;
502 while (!done()) {
503 if (s[index] == ";") break;
504 index++;
505 }
506 return s.substring(start, index).trim().toLowerCase();
507 }
508
509 void expect(String expected) {
510 if (done()) throw new HttpException("Failed to parse header value [$s]");
511 if (s[index] != expected) {
512 throw new HttpException("Failed to parse header value [$s]");
513 }
514 index++;
515 }
516
517 void parseAttributes() {
518 String parseAttributeName() {
519 int start = index;
520 while (!done()) {
521 if (s[index] == "=" || s[index] == ";") break;
522 index++;
523 }
524 return s.substring(start, index).trim().toLowerCase();
525 }
526
527 String parseAttributeValue() {
528 int start = index;
529 while (!done()) {
530 if (s[index] == ";") break;
531 index++;
532 }
533 return s.substring(start, index).trim().toLowerCase();
534 }
535
536 while (!done()) {
537 String name = parseAttributeName();
538 String value = "";
539 if (!done() && s[index] == "=") {
540 index++; // Skip the = character.
541 value = parseAttributeValue();
542 }
543 if (name == "expires") {
544 expires = _HttpUtils.parseCookieDate(value);
545 } else if (name == "max-age") {
546 maxAge = parseInt(value);
547 } else if (name == "domain") {
548 domain = value;
549 } else if (name == "path") {
550 path = value;
551 } else if (name == "httponly") {
552 httpOnly = true;
553 } else if (name == "secure") {
554 secure = true;
555 }
556 if (!done()) index++; // Skip the ; character
557 }
558 }
559
560 name = parseName();
561 if (done() || name.length == 0) {
562 throw new HttpException("Failed to parse header value [$s]");
563 }
564 index++; // Skip the = character.
565 value = parseValue();
566 if (done()) return;
567 index++; // Skip the ; character.
568 parseAttributes();
569 }
570
571 String toString() {
572 StringBuffer sb = new StringBuffer();
573 sb.add(name);
574 sb.add("=");
575 sb.add(value);
576 if (expires != null) {
577 sb.add("; Expires=");
578 sb.add(_HttpUtils.formatDate(expires));
579 }
580 if (maxAge != null) {
581 sb.add("; Max-Age=");
582 sb.add(maxAge);
583 }
584 if (domain != null) {
585 sb.add("; Domain=");
586 sb.add(domain);
587 }
588 if (path != null) {
589 sb.add("; Path=");
590 sb.add(path);
591 }
592 if (secure) sb.add("; Secure");
593 if (httpOnly) sb.add("; HttpOnly");
594 return sb.toString();
595 }
596
597 String name;
598 String value;
599 Date expires;
600 int maxAge;
601 String domain;
602 String path;
603 bool httpOnly = false;
604 bool secure = false;
605 }
606
607
608 class _HttpRequestResponseBase {
609 final int START = 0;
610 final int HEADER_SENT = 1;
611 final int DONE = 2;
612 final int UPGRADED = 3;
613
614 _HttpRequestResponseBase(_HttpConnectionBase this._httpConnection)
615 : _headers = new _HttpHeaders() {
616 _state = START;
617 _headResponse = false;
618 }
619
620 int get contentLength => _contentLength;
621 HttpHeaders get headers => _headers;
622
623 bool get persistentConnection {
624 List<String> connection = headers[HttpHeaders.CONNECTION];
625 if (_protocolVersion == "1.1") {
626 if (connection == null) return true;
627 return !headers[HttpHeaders.CONNECTION].some(
628 (value) => value.toLowerCase() == "close");
629 } else {
630 if (connection == null) return false;
631 return headers[HttpHeaders.CONNECTION].some(
632 (value) => value.toLowerCase() == "keep-alive");
633 }
634 }
635
636 void set persistentConnection(bool persistentConnection) {
637 if (_outputStream != null) throw new HttpException("Header already sent");
638
639 // Determine the value of the "Connection" header.
640 headers.remove(HttpHeaders.CONNECTION, "close");
641 headers.remove(HttpHeaders.CONNECTION, "keep-alive");
642 if (_protocolVersion == "1.1" && !persistentConnection) {
643 headers.add(HttpHeaders.CONNECTION, "close");
644 } else if (_protocolVersion == "1.0" && persistentConnection) {
645 headers.add(HttpHeaders.CONNECTION, "keep-alive");
646 }
647 }
648
649
650 bool _write(List<int> data, bool copyBuffer) {
651 if (_headResponse) return;
652 _ensureHeadersSent();
653 bool allWritten = true;
654 if (data.length > 0) {
655 if (_contentLength < 0) {
656 // Write chunk size if transfer encoding is chunked.
657 _writeHexString(data.length);
658 _writeCRLF();
659 _httpConnection._write(data, copyBuffer);
660 allWritten = _writeCRLF();
661 } else {
662 _updateContentLength(data.length);
663 allWritten = _httpConnection._write(data, copyBuffer);
664 }
665 }
666 return allWritten;
667 }
668
669 bool _writeList(List<int> data, int offset, int count) {
670 if (_headResponse) return;
671 _ensureHeadersSent();
672 bool allWritten = true;
673 if (count > 0) {
674 if (_contentLength < 0) {
675 // Write chunk size if transfer encoding is chunked.
676 _writeHexString(count);
677 _writeCRLF();
678 _httpConnection._writeFrom(data, offset, count);
679 allWritten = _writeCRLF();
680 } else {
681 _updateContentLength(count);
682 allWritten = _httpConnection._writeFrom(data, offset, count);
683 }
684 }
685 return allWritten;
686 }
687
688 bool _writeDone() {
689 bool allWritten = true;
690 if (_contentLength < 0) {
691 // Terminate the content if transfer encoding is chunked.
692 allWritten = _httpConnection._write(_Const.END_CHUNKED);
693 } else {
694 if (!_headResponse && _bodyBytesWritten < _contentLength) {
695 throw new HttpException("Sending less than specified content length");
696 }
697 assert(_headResponse || _bodyBytesWritten == _contentLength);
698 }
699 // If we are done writing the response, and either the client has
700 // closed or the connection is not persistent, we can close. Also
701 // if using HTTP 1.0 and the content length was not known we must
702 // close to indicate end of body.
703 if (!persistentConnection || _httpConnection._closing ||
704 (_protocolVersion == "1.0" && _contentLength < 0)) {
705 _httpConnection._close();
706 }
707 return allWritten;
708 }
709
710 bool _writeHeaders() {
711 _headers._mutable = false;
712 _headers._write(_httpConnection);
713 // Terminate header.
714 return _writeCRLF();
715 }
716
717 bool _writeHexString(int x) {
718 final List<int> hexDigits = [0x30, 0x31, 0x32, 0x33, 0x34,
719 0x35, 0x36, 0x37, 0x38, 0x39,
720 0x41, 0x42, 0x43, 0x44, 0x45, 0x46];
721 List<int> hex = new Uint8List(10);
722 int index = hex.length;
723 while (x > 0) {
724 index--;
725 hex[index] = hexDigits[x % 16];
726 x = x >> 4;
727 }
728 return _httpConnection._writeFrom(hex, index, hex.length - index);
729 }
730
731 bool _writeCRLF() {
732 final CRLF = const [_CharCode.CR, _CharCode.LF];
733 return _httpConnection._write(CRLF);
734 }
735
736 bool _writeSP() {
737 final SP = const [_CharCode.SP];
738 return _httpConnection._write(SP);
739 }
740
741 void _ensureHeadersSent() {
742 // Ensure that headers are written.
743 if (_state == START) {
744 _writeHeader();
745 }
746 }
747
748 void _updateContentLength(int bytes) {
749 if (_bodyBytesWritten + bytes > _contentLength) {
750 throw new HttpException("Writing more than specified content length");
751 }
752 _bodyBytesWritten += bytes;
753 }
754
755 HttpConnectionInfo get connectionInfo => _httpConnection.connectionInfo;
756
757 bool get _done => _state == DONE;
758
759 int _state;
760 bool _headResponse;
761
762 _HttpConnectionBase _httpConnection;
763 _HttpHeaders _headers;
764 List<Cookie> _cookies;
765 String _protocolVersion = "1.1";
766
767 // Length of the content body. If this is set to -1 (default value)
768 // when starting to send data chunked transfer encoding will be
769 // used.
770 int _contentLength = -1;
771 // Number of body bytes written. This is only actual body data not
772 // including headers or chunk information of using chinked transfer
773 // encoding.
774 int _bodyBytesWritten = 0;
775 }
776
777
778 // Parsed HTTP request providing information on the HTTP headers.
779 class _HttpRequest extends _HttpRequestResponseBase implements HttpRequest {
780 _HttpRequest(_HttpConnection connection) : super(connection);
781
782 String get method => _method;
783 String get uri => _uri;
784 String get path => _path;
785 String get queryString => _queryString;
786 Map get queryParameters => _queryParameters;
787
788 List<Cookie> get cookies {
789 if (_cookies != null) return _cookies;
790
791 // Parse a Cookie header value according to the rules in RFC 6265.
792 void _parseCookieString(String s) {
793 int index = 0;
794
795 bool done() => index == s.length;
796
797 void skipWS() {
798 while (!done()) {
799 if (s[index] != " " && s[index] != "\t") return;
800 index++;
801 }
802 }
803
804 String parseName() {
805 int start = index;
806 while (!done()) {
807 if (s[index] == " " || s[index] == "\t" || s[index] == "=") break;
808 index++;
809 }
810 return s.substring(start, index).toLowerCase();
811 }
812
813 String parseValue() {
814 int start = index;
815 while (!done()) {
816 if (s[index] == " " || s[index] == "\t" || s[index] == ";") break;
817 index++;
818 }
819 return s.substring(start, index).toLowerCase();
820 }
821
822 void expect(String expected) {
823 if (done()) {
824 throw new HttpException("Failed to parse header value [$s]");
825 }
826 if (s[index] != expected) {
827 throw new HttpException("Failed to parse header value [$s]");
828 }
829 index++;
830 }
831
832 while (!done()) {
833 skipWS();
834 if (done()) return;
835 String name = parseName();
836 skipWS();
837 expect("=");
838 skipWS();
839 String value = parseValue();
840 _cookies.add(new _Cookie(name, value));
841 skipWS();
842 if (done()) return;
843 expect(";");
844 }
845 }
846
847 _cookies = new List<Cookie>();
848 List<String> headerValues = headers["cookie"];
849 if (headerValues != null) {
850 headerValues.forEach((headerValue) => _parseCookieString(headerValue));
851 }
852 return _cookies;
853 }
854
855 InputStream get inputStream {
856 if (_inputStream == null) {
857 _inputStream = new _HttpInputStream(this);
858 _inputStream._streamMarkedClosed = _dataEndCalled;
859 }
860 return _inputStream;
861 }
862
863 String get protocolVersion => _protocolVersion;
864
865 HttpSession session([init(HttpSession session)]) {
866 if (_session != null) {
867 // It's already mapped, use it.
868 return _session;
869 }
870 // Create session, store it in connection, and return.
871 var sessionManager = _httpConnection._server._sessionManager;
872 return _session = sessionManager.createSession(init);
873 }
874
875 void _onRequestStart(String method, String uri, String version) {
876 _method = method;
877 _uri = uri;
878 _parseRequestUri(uri);
879 }
880
881 void _onHeaderReceived(String name, String value) {
882 _headers.add(name, value);
883 }
884
885 void _onHeadersComplete() {
886 if (_httpConnection._server._sessionManagerInstance != null) {
887 // Map to session if exists.
888 var sessionId = cookies.reduce(null, (last, cookie) {
889 if (last != null) return last;
890 return cookie.name.toUpperCase() == _DART_SESSION_ID ?
891 cookie.value : null;
892 });
893 if (sessionId != null) {
894 var sessionManager = _httpConnection._server._sessionManager;
895 _session = sessionManager.getSession(sessionId);
896 if (_session != null) {
897 _session._markSeen();
898 }
899 }
900 }
901
902 // Get parsed content length.
903 _contentLength = _httpConnection._httpParser.contentLength;
904
905 // Prepare for receiving data.
906 _headers._mutable = false;
907 _buffer = new _BufferList();
908 }
909
910 void _onDataReceived(List<int> data) {
911 _buffer.add(data);
912 if (_inputStream != null) _inputStream._dataReceived();
913 }
914
915 void _onDataEnd() {
916 if (_inputStream != null) _inputStream._closeReceived();
917 _dataEndCalled = true;
918 }
919
920 // Escaped characters in uri are expected to have been parsed.
921 void _parseRequestUri(String uri) {
922 int position;
923 position = uri.indexOf("?", 0);
924 if (position == -1) {
925 _path = _HttpUtils.decodeUrlEncodedString(_uri);
926 _queryString = null;
927 _queryParameters = new Map();
928 } else {
929 _path = _HttpUtils.decodeUrlEncodedString(_uri.substring(0, position));
930 _queryString = _uri.substring(position + 1);
931 _queryParameters = _HttpUtils.splitQueryString(_queryString);
932 }
933 }
934
935 // Delegate functions for the HttpInputStream implementation.
936 int _streamAvailable() {
937 return _buffer.length;
938 }
939
940 List<int> _streamRead(int bytesToRead) {
941 return _buffer.readBytes(bytesToRead);
942 }
943
944 int _streamReadInto(List<int> buffer, int offset, int len) {
945 List<int> data = _buffer.readBytes(len);
946 buffer.setRange(offset, data.length, data);
947 }
948
949 void _streamSetErrorHandler(callback(e)) {
950 _streamErrorHandler = callback;
951 }
952
953 String _method;
954 String _uri;
955 String _path;
956 String _queryString;
957 Map<String, String> _queryParameters;
958 _HttpInputStream _inputStream;
959 _BufferList _buffer;
960 bool _dataEndCalled = false;
961 Function _streamErrorHandler;
962 _HttpSession _session;
963 }
964
965
966 // HTTP response object for sending a HTTP response.
967 class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse {
968 _HttpResponse(_HttpConnection httpConnection)
969 : super(httpConnection),
970 _statusCode = HttpStatus.OK;
971
972 void set contentLength(int contentLength) {
973 if (_state >= HEADER_SENT) throw new HttpException("Header already sent");
974 _contentLength = contentLength;
975 }
976
977 int get statusCode => _statusCode;
978 void set statusCode(int statusCode) {
979 if (_outputStream != null) throw new HttpException("Header already sent");
980 _statusCode = statusCode;
981 }
982
983 String get reasonPhrase => _findReasonPhrase(_statusCode);
984 void set reasonPhrase(String reasonPhrase) {
985 if (_outputStream != null) throw new HttpException("Header already sent");
986 _reasonPhrase = reasonPhrase;
987 }
988
989 List<Cookie> get cookies {
990 if (_cookies == null) _cookies = new List<Cookie>();
991 return _cookies;
992 }
993
994 OutputStream get outputStream {
995 if (_state >= DONE) throw new HttpException("Response closed");
996 if (_outputStream == null) {
997 _outputStream = new _HttpOutputStream(this);
998 }
999 return _outputStream;
1000 }
1001
1002 DetachedSocket detachSocket() {
1003 if (_state >= DONE) throw new HttpException("Response closed");
1004 // Ensure that headers are written.
1005 if (_state == START) {
1006 _writeHeader();
1007 }
1008 _state = UPGRADED;
1009 // Ensure that any trailing data is written.
1010 _writeDone();
1011 // Indicate to the connection that the response handling is done.
1012 return _httpConnection._detachSocket();
1013 }
1014
1015 void _responseEnd() {
1016 _ensureHeadersSent();
1017 _state = DONE;
1018 // Stop tracking no pending write events.
1019 _httpConnection._onNoPendingWrites = null;
1020 // Ensure that any trailing data is written.
1021 _writeDone();
1022 // Indicate to the connection that the response handling is done.
1023 _httpConnection._responseDone();
1024 }
1025
1026 // Delegate functions for the HttpOutputStream implementation.
1027 bool _streamWrite(List<int> buffer, bool copyBuffer) {
1028 if (_done) throw new HttpException("Response closed");
1029 return _write(buffer, copyBuffer);
1030 }
1031
1032 bool _streamWriteFrom(List<int> buffer, int offset, int len) {
1033 if (_done) throw new HttpException("Response closed");
1034 return _writeList(buffer, offset, len);
1035 }
1036
1037 void _streamFlush() {
1038 _httpConnection._flush();
1039 }
1040
1041 void _streamClose() {
1042 _responseEnd();
1043 }
1044
1045 void _streamSetNoPendingWriteHandler(callback()) {
1046 if (_state != DONE) {
1047 _httpConnection._onNoPendingWrites = callback;
1048 }
1049 }
1050
1051 void _streamSetCloseHandler(callback()) {
1052 // TODO(sgjesse): Handle this.
1053 }
1054
1055 void _streamSetErrorHandler(callback(e)) {
1056 _streamErrorHandler = callback;
1057 }
1058
1059 String _findReasonPhrase(int statusCode) {
1060 if (_reasonPhrase != null) {
1061 return _reasonPhrase;
1062 }
1063
1064 switch (statusCode) {
1065 case HttpStatus.CONTINUE: return "Continue";
1066 case HttpStatus.SWITCHING_PROTOCOLS: return "Switching Protocols";
1067 case HttpStatus.OK: return "OK";
1068 case HttpStatus.CREATED: return "Created";
1069 case HttpStatus.ACCEPTED: return "Accepted";
1070 case HttpStatus.NON_AUTHORITATIVE_INFORMATION:
1071 return "Non-Authoritative Information";
1072 case HttpStatus.NO_CONTENT: return "No Content";
1073 case HttpStatus.RESET_CONTENT: return "Reset Content";
1074 case HttpStatus.PARTIAL_CONTENT: return "Partial Content";
1075 case HttpStatus.MULTIPLE_CHOICES: return "Multiple Choices";
1076 case HttpStatus.MOVED_PERMANENTLY: return "Moved Permanently";
1077 case HttpStatus.FOUND: return "Found";
1078 case HttpStatus.SEE_OTHER: return "See Other";
1079 case HttpStatus.NOT_MODIFIED: return "Not Modified";
1080 case HttpStatus.USE_PROXY: return "Use Proxy";
1081 case HttpStatus.TEMPORARY_REDIRECT: return "Temporary Redirect";
1082 case HttpStatus.BAD_REQUEST: return "Bad Request";
1083 case HttpStatus.UNAUTHORIZED: return "Unauthorized";
1084 case HttpStatus.PAYMENT_REQUIRED: return "Payment Required";
1085 case HttpStatus.FORBIDDEN: return "Forbidden";
1086 case HttpStatus.NOT_FOUND: return "Not Found";
1087 case HttpStatus.METHOD_NOT_ALLOWED: return "Method Not Allowed";
1088 case HttpStatus.NOT_ACCEPTABLE: return "Not Acceptable";
1089 case HttpStatus.PROXY_AUTHENTICATION_REQUIRED:
1090 return "Proxy Authentication Required";
1091 case HttpStatus.REQUEST_TIMEOUT: return "Request Time-out";
1092 case HttpStatus.CONFLICT: return "Conflict";
1093 case HttpStatus.GONE: return "Gone";
1094 case HttpStatus.LENGTH_REQUIRED: return "Length Required";
1095 case HttpStatus.PRECONDITION_FAILED: return "Precondition Failed";
1096 case HttpStatus.REQUEST_ENTITY_TOO_LARGE:
1097 return "Request Entity Too Large";
1098 case HttpStatus.REQUEST_URI_TOO_LONG: return "Request-URI Too Large";
1099 case HttpStatus.UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type";
1100 case HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE:
1101 return "Requested range not satisfiable";
1102 case HttpStatus.EXPECTATION_FAILED: return "Expectation Failed";
1103 case HttpStatus.INTERNAL_SERVER_ERROR: return "Internal Server Error";
1104 case HttpStatus.NOT_IMPLEMENTED: return "Not Implemented";
1105 case HttpStatus.BAD_GATEWAY: return "Bad Gateway";
1106 case HttpStatus.SERVICE_UNAVAILABLE: return "Service Unavailable";
1107 case HttpStatus.GATEWAY_TIMEOUT: return "Gateway Time-out";
1108 case HttpStatus.HTTP_VERSION_NOT_SUPPORTED:
1109 return "Http Version not supported";
1110 default: return "Status $statusCode";
1111 }
1112 }
1113
1114 bool _writeHeader() {
1115 List<int> data;
1116
1117 // Write status line.
1118 if (_protocolVersion == "1.1") {
1119 _httpConnection._write(_Const.HTTP11);
1120 } else {
1121 _httpConnection._write(_Const.HTTP10);
1122 }
1123 _writeSP();
1124 data = _statusCode.toString().charCodes;
1125 _httpConnection._write(data);
1126 _writeSP();
1127 data = reasonPhrase.charCodes;
1128 _httpConnection._write(data);
1129 _writeCRLF();
1130
1131 // Determine the value of the "Transfer-Encoding" header based on
1132 // whether the content length is known. HTTP/1.0 does not support
1133 // chunked.
1134 if (_contentLength >= 0) {
1135 _headers.set(HttpHeaders.CONTENT_LENGTH, _contentLength.toString());
1136 } else if (_contentLength < 0 && _protocolVersion == "1.1") {
1137 _headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
1138 }
1139
1140 var session = _httpConnection._request._session;
1141 if (session != null && !session._destroyed) {
1142 // Make sure we only send the current session id.
1143 bool found = false;
1144 for (int i = 0; i < cookies.length; i++) {
1145 if (cookies[i].name.toUpperCase() == _DART_SESSION_ID) {
1146 cookie.value = session.id;
1147 found = true;
1148 break;
1149 }
1150 }
1151 if (!found) {
1152 cookies.add(new Cookie(_DART_SESSION_ID, session.id));
1153 }
1154 }
1155 // Add all the cookies set to the headers.
1156 if (_cookies != null) {
1157 _cookies.forEach((cookie) {
1158 _headers.add("set-cookie", cookie);
1159 });
1160 }
1161
1162 // Write headers.
1163 bool allWritten = _writeHeaders();
1164 _state = HEADER_SENT;
1165 return allWritten;
1166 }
1167
1168 int _statusCode; // Response status code.
1169 String _reasonPhrase; // Response reason phrase.
1170 _HttpOutputStream _outputStream;
1171 Function _streamErrorHandler;
1172 }
1173
1174
1175 class _HttpInputStream extends _BaseDataInputStream implements InputStream {
1176 _HttpInputStream(_HttpRequestResponseBase this._requestOrResponse) {
1177 _checkScheduleCallbacks();
1178 }
1179
1180 int available() {
1181 return _requestOrResponse._streamAvailable();
1182 }
1183
1184 void pipe(OutputStream output, {bool close: true}) {
1185 _pipe(this, output, close: close);
1186 }
1187
1188 List<int> _read(int bytesToRead) {
1189 List<int> result = _requestOrResponse._streamRead(bytesToRead);
1190 _checkScheduleCallbacks();
1191 return result;
1192 }
1193
1194 void set onError(void callback(e)) {
1195 _requestOrResponse._streamSetErrorHandler(callback);
1196 }
1197
1198 int _readInto(List<int> buffer, int offset, int len) {
1199 int result = _requestOrResponse._streamReadInto(buffer, offset, len);
1200 _checkScheduleCallbacks();
1201 return result;
1202 }
1203
1204 void _close() {
1205 // TODO(sgjesse): Handle this.
1206 }
1207
1208 void _dataReceived() {
1209 super._dataReceived();
1210 }
1211
1212 _HttpRequestResponseBase _requestOrResponse;
1213 }
1214
1215
1216 class _HttpOutputStream extends _BaseOutputStream implements OutputStream {
1217 _HttpOutputStream(_HttpRequestResponseBase this._requestOrResponse);
1218
1219 bool write(List<int> buffer, [bool copyBuffer = true]) {
1220 return _requestOrResponse._streamWrite(buffer, copyBuffer);
1221 }
1222
1223 bool writeFrom(List<int> buffer, [int offset = 0, int len]) {
1224 return _requestOrResponse._streamWriteFrom(buffer, offset, len);
1225 }
1226
1227 void flush() {
1228 _requestOrResponse._streamFlush();
1229 }
1230
1231 void close() {
1232 _requestOrResponse._streamClose();
1233 }
1234
1235 bool get closed => _requestOrResponse._done;
1236
1237 void destroy() {
1238 throw "Not implemented";
1239 }
1240
1241 void set onNoPendingWrites(void callback()) {
1242 _requestOrResponse._streamSetNoPendingWriteHandler(callback);
1243 }
1244
1245 void set onClosed(void callback()) {
1246 _requestOrResponse._streamSetCloseHandler(callback);
1247 }
1248
1249 void set onError(void callback(e)) {
1250 _requestOrResponse._streamSetErrorHandler(callback);
1251 }
1252
1253 _HttpRequestResponseBase _requestOrResponse;
1254 }
1255
1256
1257 class _HttpConnectionBase {
1258 _HttpConnectionBase() : _httpParser = new _HttpParser(),
1259 hashCode = _nextHashCode {
1260 _nextHashCode = (_nextHashCode + 1) & 0xFFFFFFF;
1261 }
1262
1263 void _connectionEstablished(Socket socket) {
1264 _socket = socket;
1265 // Register handler for socket events.
1266 _socket.onData = _onData;
1267 _socket.onClosed = _onClosed;
1268 _socket.onError = _onError;
1269 // Ignore errors in the socket output stream as this is getting
1270 // the same errors as the socket itself.
1271 _socket.outputStream.onError = (e) => null;
1272 }
1273
1274 bool _write(List<int> data, [bool copyBuffer = false]) {
1275 if (!_error && !_closing) {
1276 return _socket.outputStream.write(data, copyBuffer);
1277 }
1278 }
1279
1280 bool _writeFrom(List<int> buffer, [int offset, int len]) {
1281 if (!_error && !_closing) {
1282 return _socket.outputStream.writeFrom(buffer, offset, len);
1283 }
1284 }
1285
1286 bool _flush() {
1287 _socket.outputStream.flush();
1288 }
1289
1290 bool _close() {
1291 _closing = true;
1292 _socket.outputStream.close();
1293 }
1294
1295 bool _destroy() {
1296 _closing = true;
1297 _socket.close();
1298 }
1299
1300 void _onData() {
1301 int available = _socket.available();
1302 if (available == 0) {
1303 return;
1304 }
1305
1306 List<int> buffer = new Uint8List(available);
1307 int bytesRead = _socket.readList(buffer, 0, available);
1308 if (bytesRead > 0) {
1309 int parsed = _httpParser.writeList(buffer, 0, bytesRead);
1310 if (!_httpParser.upgrade) {
1311 if (parsed != bytesRead) {
1312 if (_socket != null) {
1313 // TODO(sgjesse): Error handling.
1314 _destroy();
1315 }
1316 }
1317 }
1318 }
1319 }
1320
1321 void _onClosed() {
1322 _closing = true;
1323 _onConnectionClosed(null);
1324 }
1325
1326 void _onError(e) {
1327 // If an error occurs, make sure to close the socket if one is associated.
1328 _error = true;
1329 if (_socket != null) {
1330 _socket.close();
1331 }
1332 _onConnectionClosed(e);
1333 }
1334
1335 DetachedSocket _detachSocket() {
1336 _socket.onData = null;
1337 _socket.onClosed = null;
1338 _socket.onError = null;
1339 _socket.outputStream.onNoPendingWrites = null;
1340 Socket socket = _socket;
1341 _socket = null;
1342 if (onDetach != null) onDetach();
1343 return new _DetachedSocket(socket, _httpParser.unparsedData);
1344 }
1345
1346 HttpConnectionInfo get connectionInfo {
1347 if (_socket == null || _closing || _error) return null;
1348 try {
1349 _HttpConnectionInfo info = new _HttpConnectionInfo();
1350 info.remoteHost = _socket.remoteHost;
1351 info.remotePort = _socket.remotePort;
1352 info.localPort = _socket.port;
1353 return info;
1354 } catch (e) { }
1355 return null;
1356 }
1357
1358 abstract void _onConnectionClosed(e);
1359 abstract void _responseDone();
1360
1361 void set _onNoPendingWrites(void callback()) {
1362 if (!_error) {
1363 _socket.outputStream.onNoPendingWrites = callback;
1364 }
1365 }
1366
1367 Socket _socket;
1368 bool _closing = false; // Is the socket closed by the client?
1369 bool _error = false; // Is the socket closed due to an error?
1370 _HttpParser _httpParser;
1371
1372 Function onDetach;
1373
1374 // Hash code for HTTP connection. Currently this is just a counter.
1375 final int hashCode;
1376 static int _nextHashCode = 0;
1377 }
1378
1379
1380 // HTTP server connection over a socket.
1381 class _HttpConnection extends _HttpConnectionBase {
1382 _HttpConnection(HttpServer this._server) {
1383 // Register HTTP parser callbacks.
1384 _httpParser.requestStart =
1385 (method, uri, version) => _onRequestStart(method, uri, version);
1386 _httpParser.responseStart =
1387 (statusCode, reasonPhrase, version) =>
1388 _onResponseStart(statusCode, reasonPhrase, version);
1389 _httpParser.headerReceived =
1390 (name, value) => _onHeaderReceived(name, value);
1391 _httpParser.headersComplete = () => _onHeadersComplete();
1392 _httpParser.dataReceived = (data) => _onDataReceived(data);
1393 _httpParser.dataEnd = (close) => _onDataEnd(close);
1394 _httpParser.error = (e) => _onError(e);
1395 }
1396
1397 void _onConnectionClosed(e) {
1398 // Don't report errors when HTTP parser is in idle state. Clients
1399 // can close the connection and cause a connection reset by peer
1400 // error which is OK.
1401 if (e != null && onError != null && !_httpParser.isIdle) {
1402 onError(e);
1403 // Propagate the error to the streams.
1404 if (_request != null && _request._streamErrorHandler != null) {
1405 _request._streamErrorHandler(e);
1406 }
1407 if (_response != null && _response._streamErrorHandler != null) {
1408 _response._streamErrorHandler(e);
1409 }
1410 }
1411
1412 // If currently not processing any request close the socket when
1413 // we are done writing the response.
1414 if (_httpParser.isIdle) {
1415 _socket.outputStream.onClosed = () {
1416 _destroy();
1417 if (onClosed != null && e == null) {
1418 // Don't call onClosed if onError has been called.
1419 onClosed();
1420 }
1421 };
1422 // If the client closes and we are done writing the response
1423 // the connection should be closed.
1424 if (_response == null) _close();
1425 return;
1426 }
1427
1428 // Processing a request.
1429 if (e == null) {
1430 // Indicate connection close to the HTTP parser.
1431 _httpParser.connectionClosed();
1432 }
1433 }
1434
1435 void _onRequestStart(String method, String uri, String version) {
1436 // Create new request and response objects for this request.
1437 _request = new _HttpRequest(this);
1438 _response = new _HttpResponse(this);
1439 _request._onRequestStart(method, uri, version);
1440 _request._protocolVersion = version;
1441 _response._protocolVersion = version;
1442 _response._headResponse = method == "HEAD";
1443 }
1444
1445 void _onResponseStart(int statusCode, String reasonPhrase, String version) {
1446 // TODO(sgjesse): Error handling.
1447 }
1448
1449 void _onHeaderReceived(String name, String value) {
1450 _request._onHeaderReceived(name, value);
1451 }
1452
1453 void _onHeadersComplete() {
1454 _request._onHeadersComplete();
1455 _response.persistentConnection = _httpParser.persistentConnection;
1456 if (onRequestReceived != null) {
1457 onRequestReceived(_request, _response);
1458 }
1459 }
1460
1461 void _onDataReceived(List<int> data) {
1462 _request._onDataReceived(data);
1463 }
1464
1465 void _onDataEnd(bool close) {
1466 _request._onDataEnd();
1467 }
1468
1469 void _responseDone() {
1470 // If the connection is closing then close the output stream to
1471 // fully close the socket.
1472 if (_closing) {
1473 _socket.outputStream.onClosed = () {
1474 _socket.close();
1475 };
1476 }
1477 _response = null;
1478 }
1479
1480 HttpServer _server;
1481 HttpRequest _request;
1482 HttpResponse _response;
1483
1484 // Callbacks.
1485 Function onRequestReceived;
1486 Function onClosed;
1487 Function onError;
1488 }
1489
1490
1491 class _RequestHandlerRegistration {
1492 _RequestHandlerRegistration(Function this._matcher, Function this._handler);
1493 Function _matcher;
1494 Function _handler;
1495 }
1496
1497 // HTTP server waiting for socket connections. The connections are
1498 // managed by the server and as requests are received the request.
1499 class _HttpServer implements HttpServer {
1500 _HttpServer() : _connections = new Set<_HttpConnection>(),
1501 _handlers = new List<_RequestHandlerRegistration>();
1502
1503 void listen(String host, int port, {int backlog: 128}) {
1504 listenOn(new ServerSocket(host, port, backlog));
1505 _closeServer = true;
1506 }
1507
1508 void listenOn(ServerSocket serverSocket) {
1509 void onConnection(Socket socket) {
1510 // Accept the client connection.
1511 _HttpConnection connection = new _HttpConnection(this);
1512 connection._connectionEstablished(socket);
1513 _connections.add(connection);
1514 connection.onRequestReceived = _handleRequest;
1515 connection.onClosed = () => _connections.remove(connection);
1516 connection.onDetach = () => _connections.remove(connection);
1517 connection.onError = (e) {
1518 _connections.remove(connection);
1519 if (_onError != null) {
1520 _onError(e);
1521 } else {
1522 throw(e);
1523 }
1524 };
1525 }
1526 serverSocket.onConnection = onConnection;
1527 _server = serverSocket;
1528 _closeServer = false;
1529 }
1530
1531 addRequestHandler(bool matcher(HttpRequest request),
1532 void handler(HttpRequest request, HttpResponse response)) {
1533 _handlers.add(new _RequestHandlerRegistration(matcher, handler));
1534 }
1535
1536 void set defaultRequestHandler(
1537 void handler(HttpRequest request, HttpResponse response)) {
1538 _defaultHandler = handler;
1539 }
1540
1541 void close() {
1542 if (_sessionManagerInstance != null) {
1543 _sessionManagerInstance.close();
1544 _sessionManagerInstance = null;
1545 }
1546 if (_server != null && _closeServer) {
1547 _server.close();
1548 }
1549 _server = null;
1550 for (_HttpConnection connection in _connections) {
1551 connection._destroy();
1552 }
1553 _connections.clear();
1554 }
1555
1556 int get port {
1557 if (_server === null) {
1558 throw new HttpException("The HttpServer is not listening on a port.");
1559 }
1560 return _server.port;
1561 }
1562
1563 void set onError(void callback(e)) {
1564 _onError = callback;
1565 }
1566
1567 int set sessionTimeout(int timeout) {
1568 _sessionManager.sessionTimeout = timeout;
1569 }
1570
1571 void _handleRequest(HttpRequest request, HttpResponse response) {
1572 for (int i = 0; i < _handlers.length; i++) {
1573 if (_handlers[i]._matcher(request)) {
1574 Function handler = _handlers[i]._handler;
1575 try {
1576 handler(request, response);
1577 } catch (e) {
1578 if (_onError != null) {
1579 _onError(e);
1580 } else {
1581 throw e;
1582 }
1583 }
1584 return;
1585 }
1586 }
1587
1588 if (_defaultHandler != null) {
1589 _defaultHandler(request, response);
1590 } else {
1591 response.statusCode = HttpStatus.NOT_FOUND;
1592 response.contentLength = 0;
1593 response.outputStream.close();
1594 }
1595 }
1596
1597 _HttpSessionManager get _sessionManager {
1598 // Lazy init.
1599 if (_sessionManagerInstance == null) {
1600 _sessionManagerInstance = new _HttpSessionManager();
1601 }
1602 return _sessionManagerInstance;
1603 }
1604
1605
1606 ServerSocket _server; // The server listen socket.
1607 bool _closeServer = false;
1608 Set<_HttpConnection> _connections; // Set of currently connected clients.
1609 List<_RequestHandlerRegistration> _handlers;
1610 Object _defaultHandler;
1611 Function _onError;
1612 _HttpSessionManager _sessionManagerInstance;
1613 }
1614
1615
1616 class _HttpClientRequest
1617 extends _HttpRequestResponseBase implements HttpClientRequest {
1618 _HttpClientRequest(String this._method,
1619 Uri this._uri,
1620 _HttpClientConnection connection)
1621 : super(connection) {
1622 _connection = connection;
1623 // Default GET and HEAD requests to have no content.
1624 if (_method == "GET" || _method == "HEAD") {
1625 _contentLength = 0;
1626 }
1627 }
1628
1629 void set contentLength(int contentLength) {
1630 if (_state >= HEADER_SENT) throw new HttpException("Header already sent");
1631 _contentLength = contentLength;
1632 }
1633
1634 List<Cookie> get cookies {
1635 if (_cookies == null) _cookies = new List<Cookie>();
1636 return _cookies;
1637 }
1638
1639 OutputStream get outputStream {
1640 if (_done) throw new HttpException("Request closed");
1641 if (_outputStream == null) {
1642 _outputStream = new _HttpOutputStream(this);
1643 }
1644 return _outputStream;
1645 }
1646
1647 // Delegate functions for the HttpOutputStream implementation.
1648 bool _streamWrite(List<int> buffer, bool copyBuffer) {
1649 if (_done) throw new HttpException("Request closed");
1650 return _write(buffer, copyBuffer);
1651 }
1652
1653 bool _streamWriteFrom(List<int> buffer, int offset, int len) {
1654 if (_done) throw new HttpException("Request closed");
1655 return _writeList(buffer, offset, len);
1656 }
1657
1658 void _streamFlush() {
1659 _httpConnection._flush();
1660 }
1661
1662 void _streamClose() {
1663 _ensureHeadersSent();
1664 _state = DONE;
1665 // Stop tracking no pending write events.
1666 _httpConnection._onNoPendingWrites = null;
1667 // Ensure that any trailing data is written.
1668 _writeDone();
1669 }
1670
1671 void _streamSetNoPendingWriteHandler(callback()) {
1672 if (_state != DONE) {
1673 _httpConnection._onNoPendingWrites = callback;
1674 }
1675 }
1676
1677 void _streamSetCloseHandler(callback()) {
1678 // TODO(sgjesse): Handle this.
1679 }
1680
1681 void _streamSetErrorHandler(callback(e)) {
1682 _streamErrorHandler = callback;
1683 }
1684
1685 void _writeHeader() {
1686 List<int> data;
1687
1688 // Write request line.
1689 data = _method.toString().charCodes;
1690 _httpConnection._write(data);
1691 _writeSP();
1692 // Send the path for direct connections and the whole URL for
1693 // proxy connections.
1694 if (!_connection._usingProxy) {
1695 String path = _uri.path;
1696 if (path.length == 0) path = "/";
1697 if (_uri.query != "") {
1698 if (_uri.fragment != "") {
1699 path = "${path}?${_uri.query}#${_uri.fragment}";
1700 } else {
1701 path = "${path}?${_uri.query}";
1702 }
1703 }
1704 data = path.charCodes;
1705 } else {
1706 data = _uri.toString().charCodes;
1707 }
1708 _httpConnection._write(data);
1709 _writeSP();
1710 _httpConnection._write(_Const.HTTP11);
1711 _writeCRLF();
1712
1713 // Determine the value of the "Transfer-Encoding" header based on
1714 // whether the content length is known. If there is no content
1715 // neither "Content-Length" nor "Transfer-Encoding" is set.
1716 if (_contentLength > 0) {
1717 _headers.set(HttpHeaders.CONTENT_LENGTH, _contentLength.toString());
1718 } else if (_contentLength < 0) {
1719 _headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
1720 }
1721
1722 // Add the cookies to the headers.
1723 if (_cookies != null) {
1724 StringBuffer sb = new StringBuffer();
1725 for (int i = 0; i < _cookies.length; i++) {
1726 if (i > 0) sb.add("; ");
1727 sb.add(_cookies[i].name);
1728 sb.add("=");
1729 sb.add(_cookies[i].value);
1730 }
1731 _headers.add("cookie", sb.toString());
1732 }
1733
1734 // Write headers.
1735 _writeHeaders();
1736 _state = HEADER_SENT;
1737 }
1738
1739 String _method;
1740 Uri _uri;
1741 _HttpClientConnection _connection;
1742 _HttpOutputStream _outputStream;
1743 Function _streamErrorHandler;
1744 }
1745
1746
1747 class _HttpClientResponse
1748 extends _HttpRequestResponseBase implements HttpClientResponse {
1749 _HttpClientResponse(_HttpClientConnection connection)
1750 : super(connection) {
1751 _connection = connection;
1752 }
1753
1754 int get statusCode => _statusCode;
1755 String get reasonPhrase => _reasonPhrase;
1756
1757 bool get isRedirect {
1758 return statusCode == HttpStatus.MOVED_PERMANENTLY ||
1759 statusCode == HttpStatus.FOUND ||
1760 statusCode == HttpStatus.SEE_OTHER ||
1761 statusCode == HttpStatus.TEMPORARY_REDIRECT;
1762 }
1763
1764 List<Cookie> get cookies {
1765 if (_cookies != null) return _cookies;
1766 _cookies = new List<Cookie>();
1767 List<String> values = _headers["set-cookie"];
1768 if (values != null) {
1769 values.forEach((value) {
1770 _cookies.add(new Cookie.fromSetCookieValue(value));
1771 });
1772 }
1773 return _cookies;
1774 }
1775
1776 InputStream get inputStream {
1777 if (_inputStream == null) {
1778 _inputStream = new _HttpInputStream(this);
1779 _inputStream._streamMarkedClosed = _dataEndCalled;
1780 }
1781 return _inputStream;
1782 }
1783
1784 void _onRequestStart(String method, String uri, String version) {
1785 // TODO(sgjesse): Error handling
1786 }
1787
1788 void _onResponseStart(int statusCode, String reasonPhrase, String version) {
1789 _statusCode = statusCode;
1790 _reasonPhrase = reasonPhrase;
1791 }
1792
1793 void _onHeaderReceived(String name, String value) {
1794 _headers.add(name, value);
1795 }
1796
1797 void _handleUnauthorized() {
1798
1799 void retryRequest(_Credentials cr) {
1800 if (cr != null) {
1801 if (cr.scheme == _AuthenticationScheme.DIGEST) {
1802 cr.nonce = header.parameters["nonce"];
1803 cr.algorithm = header.parameters["algorithm"];
1804 cr.qop = header.parameters["qop"];
1805 }
1806 // Drain body and retry.
1807 // TODO(sjgesse): Support digest.
1808 if (cr.scheme == _AuthenticationScheme.BASIC) {
1809 inputStream.onData = inputStream.read;
1810 inputStream.onClosed = _connection.retry;
1811 return;
1812 }
1813 }
1814
1815 // Fall through to here to perform normal response handling if
1816 // there is no sensible authorization handling.
1817 if (_connection._onResponse != null) {
1818 _connection._onResponse(this);
1819 }
1820 }
1821
1822 // Only try to authenticate if there is a challenge in the response.
1823 List<String> challenge = _headers[HttpHeaders.WWW_AUTHENTICATE];
1824 if (challenge != null && challenge.length == 1) {
1825 _HeaderValue header =
1826 new _HeaderValue.fromString(challenge[0], parameterSeparator: ",");
1827 _AuthenticationScheme scheme =
1828 new _AuthenticationScheme.fromString(header.value);
1829 String realm = header.parameters["realm"];
1830
1831 // See if any credentials are available.
1832 _Credentials cr =
1833 _connection._client._findCredentials(
1834 _connection._request._uri, scheme);
1835
1836 // Ask for more credentials if none found or the one found has
1837 // already been used. If it has already been used it must now be
1838 // invalid and is removed.
1839 if (cr == null || cr.used) {
1840 if (cr != null) {
1841 _connection._client._removeCredentials(cr);
1842 }
1843 cr = null;
1844 if (_connection._client._authenticate != null) {
1845 Future authComplete =
1846 _connection._client._authenticate(
1847 _connection._request._uri, scheme.toString(), realm);
1848 authComplete.then((credsAvailable) {
1849 if (credsAvailable) {
1850 cr = _connection._client._findCredentials(
1851 _connection._request._uri, scheme);
1852 retryRequest(cr);
1853 } else {
1854 if (_connection._onResponse != null) {
1855 _connection._onResponse(this);
1856 }
1857 }
1858 });
1859 return;
1860 }
1861 } else {
1862 // If credentials found prepare for retrying the request.
1863 retryRequest(cr);
1864 return;
1865 }
1866 }
1867
1868 // Fall through to here to perform normal response handling if
1869 // there is no sensible authorization handling.
1870 if (_connection._onResponse != null) {
1871 _connection._onResponse(this);
1872 }
1873 }
1874
1875 void _onHeadersComplete() {
1876 // Get parsed content length.
1877 _contentLength = _httpConnection._httpParser.contentLength;
1878
1879 // Prepare for receiving data.
1880 _headers._mutable = false;
1881 _buffer = new _BufferList();
1882
1883 if (isRedirect && _connection.followRedirects) {
1884 if (_connection._redirects == null ||
1885 _connection._redirects.length < _connection.maxRedirects) {
1886 // Check the location header.
1887 List<String> location = headers[HttpHeaders.LOCATION];
1888 if (location == null || location.length > 1) {
1889 throw new RedirectException("Invalid redirect",
1890 _connection._redirects);
1891 }
1892 // Check for redirect loop
1893 if (_connection._redirects != null) {
1894 Uri redirectUrl = new Uri.fromString(location[0]);
1895 for (int i = 0; i < _connection._redirects.length; i++) {
1896 if (_connection._redirects[i].location.toString() ==
1897 redirectUrl.toString()) {
1898 throw new RedirectLoopException(_connection._redirects);
1899 }
1900 }
1901 }
1902 // Drain body and redirect.
1903 inputStream.onData = inputStream.read;
1904 inputStream.onClosed = _connection.redirect;
1905 } else {
1906 throw new RedirectLimitExceededException(_connection._redirects);
1907 }
1908 } else if (statusCode == HttpStatus.UNAUTHORIZED) {
1909 _handleUnauthorized();
1910 } else if (_connection._onResponse != null) {
1911 _connection._onResponse(this);
1912 }
1913 }
1914
1915 void _onDataReceived(List<int> data) {
1916 _buffer.add(data);
1917 if (_inputStream != null) _inputStream._dataReceived();
1918 }
1919
1920 void _onDataEnd() {
1921 _connection._responseDone();
1922 if (_inputStream != null) _inputStream._closeReceived();
1923 _dataEndCalled = true;
1924 }
1925
1926 // Delegate functions for the HttpInputStream implementation.
1927 int _streamAvailable() {
1928 return _buffer.length;
1929 }
1930
1931 List<int> _streamRead(int bytesToRead) {
1932 return _buffer.readBytes(bytesToRead);
1933 }
1934
1935 int _streamReadInto(List<int> buffer, int offset, int len) {
1936 List<int> data = _buffer.readBytes(len);
1937 buffer.setRange(offset, data.length, data);
1938 return data.length;
1939 }
1940
1941 void _streamSetErrorHandler(callback(e)) {
1942 _streamErrorHandler = callback;
1943 }
1944
1945 int _statusCode;
1946 String _reasonPhrase;
1947
1948 _HttpClientConnection _connection;
1949 _HttpInputStream _inputStream;
1950 _BufferList _buffer;
1951 bool _dataEndCalled = false;
1952
1953 Function _streamErrorHandler;
1954 }
1955
1956
1957 class _HttpClientConnection
1958 extends _HttpConnectionBase implements HttpClientConnection {
1959 _HttpClientConnection(_HttpClient this._client);
1960
1961 void _connectionEstablished(_SocketConnection socketConn) {
1962 super._connectionEstablished(socketConn._socket);
1963 _socketConn = socketConn;
1964 // Register HTTP parser callbacks.
1965 _httpParser.requestStart =
1966 (method, uri, version) => _onRequestStart(method, uri, version);
1967 _httpParser.responseStart =
1968 (statusCode, reasonPhrase, version) =>
1969 _onResponseStart(statusCode, reasonPhrase, version);
1970 _httpParser.headerReceived =
1971 (name, value) => _onHeaderReceived(name, value);
1972 _httpParser.headersComplete = () => _onHeadersComplete();
1973 _httpParser.dataReceived = (data) => _onDataReceived(data);
1974 _httpParser.dataEnd = (closed) => _onDataEnd(closed);
1975 _httpParser.error = (e) => _onError(e);
1976 }
1977
1978 void _responseDone() {
1979 if (_closing) {
1980 if (_socket != null) {
1981 _socket.close();
1982 }
1983 } else {
1984 _client._returnSocketConnection(_socketConn);
1985 }
1986 _socket = null;
1987 _socketConn = null;
1988 }
1989
1990 HttpClientRequest open(String method, Uri uri) {
1991 _method = method;
1992 // Tell the HTTP parser the method it is expecting a response to.
1993 _httpParser.responseToMethod = method;
1994 _request = new _HttpClientRequest(method, uri, this);
1995 _response = new _HttpClientResponse(this);
1996 return _request;
1997 }
1998
1999 DetachedSocket detachSocket() {
2000 return _detachSocket();
2001 }
2002
2003 void _onConnectionClosed(e) {
2004 // Socket is closed either due to an error or due to normal socket close.
2005 if (e != null) {
2006 if (_onErrorCallback != null) {
2007 _onErrorCallback(e);
2008 } else {
2009 throw e;
2010 }
2011 }
2012 _closing = true;
2013 if (e != null) {
2014 // Propagate the error to the streams.
2015 if (_response != null && _response._streamErrorHandler != null) {
2016 _response._streamErrorHandler(e);
2017 }
2018 _responseDone();
2019 } else {
2020 // If there was no socket error the socket was closed
2021 // normally. Indicate closing to the HTTP Parser as there might
2022 // still be an HTTP error.
2023 _httpParser.connectionClosed();
2024 }
2025 }
2026
2027 void _onRequestStart(String method, String uri, String version) {
2028 // TODO(sgjesse): Error handling.
2029 }
2030
2031 void _onResponseStart(int statusCode, String reasonPhrase, String version) {
2032 _response._onResponseStart(statusCode, reasonPhrase, version);
2033 }
2034
2035 void _onHeaderReceived(String name, String value) {
2036 _response._onHeaderReceived(name, value);
2037 }
2038
2039 void _onHeadersComplete() {
2040 _response._onHeadersComplete();
2041 }
2042
2043 void _onDataReceived(List<int> data) {
2044 _response._onDataReceived(data);
2045 }
2046
2047 void _onDataEnd(bool close) {
2048 if (close) _closing = true;
2049 _response._onDataEnd();
2050 }
2051
2052 void set onRequest(void handler(HttpClientRequest request)) {
2053 _onRequest = handler;
2054 }
2055
2056 void set onResponse(void handler(HttpClientResponse response)) {
2057 _onResponse = handler;
2058 }
2059
2060 void set onError(void callback(e)) {
2061 _onErrorCallback = callback;
2062 }
2063
2064 void retry() {
2065 if (_socketConn != null) {
2066 throw new HttpException("Cannot retry with body data pending");
2067 }
2068 // Retry the URL using the same connection instance.
2069 _client._openUrl(_method, _request._uri, this);
2070 }
2071
2072 void redirect([String method, Uri url]) {
2073 if (_socketConn != null) {
2074 throw new HttpException("Cannot redirect with body data pending");
2075 }
2076 if (method == null) method = _method;
2077 if (url == null) {
2078 url = new Uri.fromString(_response.headers.value(HttpHeaders.LOCATION));
2079 }
2080 if (_redirects == null) {
2081 _redirects = new List<_RedirectInfo>();
2082 }
2083 _redirects.add(new _RedirectInfo(_response.statusCode, method, url));
2084 _request = null;
2085 _response = null;
2086 // Open redirect URL using the same connection instance.
2087 _client._openUrl(method, url, this);
2088 }
2089
2090 List<RedirectInfo> get redirects => _redirects;
2091
2092 Function _onRequest;
2093 Function _onResponse;
2094 Function _onErrorCallback;
2095
2096 _HttpClient _client;
2097 _SocketConnection _socketConn;
2098 HttpClientRequest _request;
2099 HttpClientResponse _response;
2100 String _method;
2101 bool _usingProxy;
2102
2103 // Redirect handling
2104 bool followRedirects = true;
2105 int maxRedirects = 5;
2106 List<_RedirectInfo> _redirects;
2107
2108 // Callbacks.
2109 var requestReceived;
2110 }
2111
2112
2113 // Class for holding keep-alive sockets in the cache for the HTTP
2114 // client together with the connection information.
2115 class _SocketConnection {
2116 _SocketConnection(String this._host,
2117 int this._port,
2118 Socket this._socket);
2119
2120 void _markReturned() {
2121 _socket.onData = null;
2122 _socket.onClosed = null;
2123 _socket.onError = null;
2124 _returnTime = new Date.now();
2125 }
2126
2127 Duration _idleTime(Date now) => now.difference(_returnTime);
2128
2129 int get hashCode => _socket.hashCode;
2130
2131 String _host;
2132 int _port;
2133 Socket _socket;
2134 Date _returnTime;
2135 }
2136
2137 class _ProxyConfiguration {
2138 static const String PROXY_PREFIX = "PROXY ";
2139 static const String DIRECT_PREFIX = "DIRECT";
2140
2141 _ProxyConfiguration(String configuration) : proxies = new List<_Proxy>() {
2142 if (configuration == null) {
2143 throw new HttpException("Invalid proxy configuration $configuration");
2144 }
2145 List<String> list = configuration.split(";");
2146 list.forEach((String proxy) {
2147 proxy = proxy.trim();
2148 if (!proxy.isEmpty) {
2149 if (proxy.startsWith(PROXY_PREFIX)) {
2150 int colon = proxy.indexOf(":");
2151 if (colon == -1 || colon == 0 || colon == proxy.length - 1) {
2152 throw new HttpException(
2153 "Invalid proxy configuration $configuration");
2154 }
2155 // Skip the "PROXY " prefix.
2156 String host = proxy.substring(PROXY_PREFIX.length, colon).trim();
2157 String portString = proxy.substring(colon + 1).trim();
2158 int port;
2159 try {
2160 port = int.parse(portString);
2161 } on FormatException catch (e) {
2162 throw new HttpException(
2163 "Invalid proxy configuration $configuration, "
2164 "invalid port '$portString'");
2165 }
2166 proxies.add(new _Proxy(host, port));
2167 } else if (proxy.trim() == DIRECT_PREFIX) {
2168 proxies.add(new _Proxy.direct());
2169 } else {
2170 throw new HttpException("Invalid proxy configuration $configuration");
2171 }
2172 }
2173 });
2174 }
2175
2176 const _ProxyConfiguration.direct()
2177 : proxies = const [const _Proxy.direct()];
2178
2179 final List<_Proxy> proxies;
2180 }
2181
2182 class _Proxy {
2183 const _Proxy(this.host, this.port) : isDirect = false;
2184 const _Proxy.direct() : host = null, port = null, isDirect = true;
2185
2186 final String host;
2187 final int port;
2188 final bool isDirect;
2189 }
2190
2191 class _HttpClient implements HttpClient {
2192 static const int DEFAULT_EVICTION_TIMEOUT = 60000;
2193
2194 _HttpClient() : _openSockets = new Map(),
2195 _activeSockets = new Set(),
2196 credentials = new List<_Credentials>(),
2197 _shutdown = false;
2198
2199 HttpClientConnection open(
2200 String method, String host, int port, String path) {
2201 // TODO(sgjesse): The path set here can contain both query and
2202 // fragment. They should be cracked and set correctly.
2203 return _open(method, new Uri.fromComponents(
2204 scheme: "http", domain: host, port: port, path: path));
2205 }
2206
2207 HttpClientConnection _open(String method,
2208 Uri uri,
2209 [_HttpClientConnection connection]) {
2210 if (_shutdown) throw new HttpException("HttpClient shutdown");
2211 if (method == null || uri.domain.isEmpty) {
2212 throw new ArgumentError(null);
2213 }
2214 return _prepareHttpClientConnection(method, uri, connection);
2215 }
2216
2217 HttpClientConnection openUrl(String method, Uri url) {
2218 return _openUrl(method, url);
2219 }
2220
2221 HttpClientConnection _openUrl(String method,
2222 Uri url,
2223 [_HttpClientConnection connection]) {
2224 if (url.scheme != "http") {
2225 throw new HttpException("Unsupported URL scheme ${url.scheme}");
2226 }
2227 return _open(method, url, connection);
2228 }
2229
2230 HttpClientConnection get(String host, int port, String path) {
2231 return open("GET", host, port, path);
2232 }
2233
2234 HttpClientConnection getUrl(Uri url) => _openUrl("GET", url);
2235
2236 HttpClientConnection post(String host, int port, String path) {
2237 return open("POST", host, port, path);
2238 }
2239
2240 HttpClientConnection postUrl(Uri url) => _openUrl("POST", url);
2241
2242 set authenticate(bool f(Uri url, String scheme, String realm)) {
2243 _authenticate = f;
2244 }
2245
2246 void addCredentials(
2247 Uri url, String realm, HttpClientCredentials cr) {
2248 credentials.add(new _Credentials(url, realm, cr));
2249 }
2250
2251 set findProxy(String f(Uri uri)) => _findProxy = f;
2252
2253 void shutdown() {
2254 _openSockets.forEach((String key, Queue<_SocketConnection> connections) {
2255 while (!connections.isEmpty) {
2256 _SocketConnection socketConn = connections.removeFirst();
2257 socketConn._socket.close();
2258 }
2259 });
2260 _activeSockets.forEach((_SocketConnection socketConn) {
2261 socketConn._socket.close();
2262 });
2263 if (_evictionTimer != null) _cancelEvictionTimer();
2264 _shutdown = true;
2265 }
2266
2267 void _cancelEvictionTimer() {
2268 _evictionTimer.cancel();
2269 _evictionTimer = null;
2270 }
2271
2272 String _connectionKey(String host, int port) {
2273 return "$host:$port";
2274 }
2275
2276 HttpClientConnection _prepareHttpClientConnection(
2277 String method,
2278 Uri url,
2279 [_HttpClientConnection connection]) {
2280
2281 void _establishConnection(String host,
2282 int port,
2283 _ProxyConfiguration proxyConfiguration,
2284 int proxyIndex) {
2285
2286 void _connectionOpened(_SocketConnection socketConn,
2287 _HttpClientConnection connection,
2288 bool usingProxy) {
2289 connection._usingProxy = usingProxy;
2290 connection._connectionEstablished(socketConn);
2291 HttpClientRequest request = connection.open(method, url);
2292 request.headers.host = host;
2293 request.headers.port = port;
2294 if (url.userInfo != null && !url.userInfo.isEmpty) {
2295 // If the URL contains user information use that for basic
2296 // authorization
2297 _UTF8Encoder encoder = new _UTF8Encoder();
2298 String auth =
2299 CryptoUtils.bytesToBase64(encoder.encodeString(url.userInfo));
2300 request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth");
2301 } else {
2302 // Look for credentials.
2303 _Credentials cr = _findCredentials(url);
2304 if (cr != null) {
2305 cr.authorize(request);
2306 }
2307 }
2308 if (connection._onRequest != null) {
2309 connection._onRequest(request);
2310 } else {
2311 request.outputStream.close();
2312 }
2313 }
2314
2315 assert(proxyIndex < proxyConfiguration.proxies.length);
2316
2317 // Determine the actual host to connect to.
2318 String connectHost;
2319 int connectPort;
2320 _Proxy proxy = proxyConfiguration.proxies[proxyIndex];
2321 if (proxy.isDirect) {
2322 connectHost = host;
2323 connectPort = port;
2324 } else {
2325 connectHost = proxy.host;
2326 connectPort = proxy.port;
2327 }
2328
2329 // If there are active connections for this key get the first one
2330 // otherwise create a new one.
2331 String key = _connectionKey(connectHost, connectPort);
2332 Queue socketConnections = _openSockets[key];
2333 if (socketConnections == null || socketConnections.isEmpty) {
2334 Socket socket = new Socket(connectHost, connectPort);
2335 // Until the connection is established handle connection errors
2336 // here as the HttpClientConnection object is not yet associated
2337 // with the socket.
2338 socket.onError = (e) {
2339 proxyIndex++;
2340 if (proxyIndex < proxyConfiguration.proxies.length) {
2341 // Try the next proxy in the list.
2342 _establishConnection(host, port, proxyConfiguration, proxyIndex);
2343 } else {
2344 // Report the error through the HttpClientConnection object to
2345 // the client.
2346 connection._onError(e);
2347 }
2348 };
2349 socket.onConnect = () {
2350 // When the connection is established, clear the error
2351 // callback as it will now be handled by the
2352 // HttpClientConnection object which will be associated with
2353 // the connected socket.
2354 socket.onError = null;
2355 _SocketConnection socketConn =
2356 new _SocketConnection(connectHost, connectPort, socket);
2357 _activeSockets.add(socketConn);
2358 _connectionOpened(socketConn, connection, !proxy.isDirect);
2359 };
2360 } else {
2361 _SocketConnection socketConn = socketConnections.removeFirst();
2362 _activeSockets.add(socketConn);
2363 new Timer(0, (ignored) =>
2364 _connectionOpened(socketConn, connection, !proxy.isDirect));
2365
2366 // Get rid of eviction timer if there are no more active connections.
2367 if (socketConnections.isEmpty) _openSockets.remove(key);
2368 if (_openSockets.isEmpty) _cancelEvictionTimer();
2369 }
2370 }
2371
2372 // Find the TCP host and port.
2373 String host = url.domain;
2374 int port = url.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : url.port;
2375
2376 // Create a new connection object if we are not re-using an existing one.
2377 if (connection == null) {
2378 connection = new _HttpClientConnection(this);
2379 }
2380 connection.onDetach = () => _activeSockets.remove(connection._socketConn);
2381
2382 // Check to see if a proxy server should be used for this connection.
2383 _ProxyConfiguration proxyConfiguration = const _ProxyConfiguration.direct();
2384 if (_findProxy != null) {
2385 // TODO(sgjesse): Keep a map of these as normally only a few
2386 // configuration strings will be used.
2387 proxyConfiguration = new _ProxyConfiguration(_findProxy(url));
2388 }
2389
2390 // Establish the connection starting with the first proxy configured.
2391 _establishConnection(host, port, proxyConfiguration, 0);
2392
2393 return connection;
2394 }
2395
2396 void _returnSocketConnection(_SocketConnection socketConn) {
2397 // Mark socket as returned to unregister from the old connection.
2398 socketConn._markReturned();
2399
2400 // If the HTTP client is beeing shutdown don't return the connection.
2401 if (_shutdown) {
2402 socketConn._socket.close();
2403 return;
2404 };
2405
2406 String key = _connectionKey(socketConn._host, socketConn._port);
2407
2408 // Get or create the connection list for this key.
2409 Queue sockets = _openSockets[key];
2410 if (sockets == null) {
2411 sockets = new Queue();
2412 _openSockets[key] = sockets;
2413 }
2414
2415 // If there is currently no eviction timer start one.
2416 if (_evictionTimer == null) {
2417 void _handleEviction(Timer timer) {
2418 Date now = new Date.now();
2419 List<String> emptyKeys = new List<String>();
2420 _openSockets.forEach(
2421 void _(String key, Queue<_SocketConnection> connections) {
2422 // As returned connections are added at the head of the
2423 // list remove from the tail.
2424 while (!connections.isEmpty) {
2425 _SocketConnection socketConn = connections.last;
2426 if (socketConn._idleTime(now).inMilliseconds >
2427 DEFAULT_EVICTION_TIMEOUT) {
2428 connections.removeLast();
2429 socketConn._socket.close();
2430 if (connections.isEmpty) emptyKeys.add(key);
2431 } else {
2432 break;
2433 }
2434 }
2435 });
2436
2437 // Remove the keys for which here are no more open connections.
2438 emptyKeys.forEach((String key) => _openSockets.remove(key));
2439
2440 // If all connections where evicted cancel the eviction timer.
2441 if (_openSockets.isEmpty) _cancelEvictionTimer();
2442 }
2443 _evictionTimer = new Timer.repeating(10000, _handleEviction);
2444 }
2445
2446 // Return connection.
2447 _activeSockets.remove(socketConn);
2448 sockets.addFirst(socketConn);
2449 }
2450
2451 _Credentials _findCredentials(Uri url, [_AuthenticationScheme scheme]) {
2452 // Look for credentials.
2453 _Credentials cr =
2454 credentials.reduce(null, (_Credentials prev, _Credentials value) {
2455 if (value.applies(url, scheme)) {
2456 if (prev == null) return value;
2457 return value.uri.path.length > prev.uri.path.length ? value : prev;
2458 } else {
2459 return prev;
2460 }
2461 });
2462 return cr;
2463 }
2464
2465 void _removeCredentials(_Credentials cr) {
2466 int index = credentials.indexOf(cr);
2467 if (index != -1) {
2468 credentials.removeAt(index);
2469 }
2470 }
2471
2472 Function _onOpen;
2473 Map<String, Queue<_SocketConnection>> _openSockets;
2474 Set<_SocketConnection> _activeSockets;
2475 List<_Credentials> credentials;
2476 Timer _evictionTimer;
2477 Function _findProxy;
2478 Function _authenticate;
2479 bool _shutdown; // Has this HTTP client been shutdown?
2480 }
2481
2482
2483 class _HttpConnectionInfo implements HttpConnectionInfo {
2484 String remoteHost;
2485 int remotePort;
2486 int localPort;
2487 }
2488
2489
2490 class _DetachedSocket implements DetachedSocket {
2491 _DetachedSocket(this._socket, this._unparsedData);
2492 Socket get socket => _socket;
2493 List<int> get unparsedData => _unparsedData;
2494 Socket _socket;
2495 List<int> _unparsedData;
2496 }
2497
2498
2499 class _AuthenticationScheme {
2500 static const UNKNOWN = const _AuthenticationScheme(-1);
2501 static const BASIC = const _AuthenticationScheme(0);
2502 static const DIGEST = const _AuthenticationScheme(1);
2503
2504 const _AuthenticationScheme(this._scheme);
2505
2506 factory _AuthenticationScheme.fromString(String scheme) {
2507 if (scheme.toLowerCase() == "basic") return BASIC;
2508 if (scheme.toLowerCase() == "digest") return DIGEST;
2509 return UNKNOWN;
2510 }
2511
2512 String toString() {
2513 if (this == BASIC) return "Basic";
2514 if (this == DIGEST) return "Digest";
2515 return "Unknown";
2516 }
2517
2518 final int _scheme;
2519 }
2520
2521
2522 class _Credentials {
2523 _Credentials(this.uri, this.realm, this.credentials);
2524
2525 _AuthenticationScheme get scheme => credentials.scheme;
2526
2527 bool applies(Uri uri, _AuthenticationScheme scheme) {
2528 if (scheme != null && credentials.scheme != scheme) return false;
2529 if (uri.domain != this.uri.domain) return false;
2530 int thisPort =
2531 this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port;
2532 int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port;
2533 if (otherPort != thisPort) return false;
2534 return uri.path.startsWith(this.uri.path);
2535 }
2536
2537 void authorize(HttpClientRequest request) {
2538 credentials.authorize(this, request);
2539 used = true;
2540 }
2541
2542 bool used = false;
2543 Uri uri;
2544 String realm;
2545 HttpClientCredentials credentials;
2546
2547 // Digest specific fields.
2548 String nonce;
2549 String algorithm;
2550 String qop;
2551 }
2552
2553
2554 class _HttpClientCredentials implements HttpClientCredentials {
2555 abstract _AuthenticationScheme get scheme;
2556 abstract void authorize(HttpClientRequest request);
2557 }
2558
2559
2560 class _HttpClientBasicCredentials implements HttpClientBasicCredentials {
2561 _HttpClientBasicCredentials(this.username,
2562 this.password);
2563
2564 _AuthenticationScheme get scheme => _AuthenticationScheme.BASIC;
2565
2566 void authorize(_Credentials _, HttpClientRequest request) {
2567 // There is no mentioning of username/password encoding in RFC
2568 // 2617. However there is an open draft for adding an additional
2569 // accept-charset parameter to the WWW-Authenticate and
2570 // Proxy-Authenticate headers, see
2571 // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
2572 // now always use UTF-8 encoding.
2573 _UTF8Encoder encoder = new _UTF8Encoder();
2574 String auth =
2575 CryptoUtils.bytesToBase64(encoder.encodeString(
2576 "$username:$password"));
2577 request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth");
2578 }
2579
2580 String username;
2581 String password;
2582 }
2583
2584
2585 class _HttpClientDigestCredentials implements HttpClientDigestCredentials {
2586 _HttpClientDigestCredentials(this.username,
2587 this.password);
2588
2589 _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST;
2590
2591 void authorize(_Credentials credentials, HttpClientRequest request) {
2592 // TODO(sgjesse): Implement!!!
2593 throw new UnsupportedOperationException();
2594 }
2595
2596 String username;
2597 String password;
2598 }
2599
2600
2601
2602 class _RedirectInfo implements RedirectInfo {
2603 const _RedirectInfo(int this.statusCode,
2604 String this.method,
2605 Uri this.location);
2606 final int statusCode;
2607 final String method;
2608 final Uri location;
2609 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698