Index: runtime/bin/http_impl.dart |
diff --git a/runtime/bin/http_impl.dart b/runtime/bin/http_impl.dart |
deleted file mode 100644 |
index da64e52f5467d561e84aa70400904e60e638a497..0000000000000000000000000000000000000000 |
--- a/runtime/bin/http_impl.dart |
+++ /dev/null |
@@ -1,2609 +0,0 @@ |
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-class _HttpHeaders implements HttpHeaders { |
- _HttpHeaders() : _headers = new Map<String, List<String>>(); |
- |
- List<String> operator[](String name) { |
- name = name.toLowerCase(); |
- return _headers[name]; |
- } |
- |
- String value(String name) { |
- name = name.toLowerCase(); |
- List<String> values = _headers[name]; |
- if (values == null) return null; |
- if (values.length > 1) { |
- throw new HttpException("More than one value for header $name"); |
- } |
- return values[0]; |
- } |
- |
- void add(String name, Object value) { |
- _checkMutable(); |
- if (value is List) { |
- for (int i = 0; i < value.length; i++) { |
- _add(name, value[i]); |
- } |
- } else { |
- _add(name, value); |
- } |
- } |
- |
- void set(String name, Object value) { |
- name = name.toLowerCase(); |
- _checkMutable(); |
- removeAll(name); |
- add(name, value); |
- } |
- |
- void remove(String name, Object value) { |
- _checkMutable(); |
- name = name.toLowerCase(); |
- List<String> values = _headers[name]; |
- if (values != null) { |
- int index = values.indexOf(value); |
- if (index != -1) { |
- values.removeRange(index, 1); |
- } |
- } |
- } |
- |
- void removeAll(String name) { |
- _checkMutable(); |
- name = name.toLowerCase(); |
- _headers.remove(name); |
- } |
- |
- void forEach(void f(String name, List<String> values)) { |
- _headers.forEach(f); |
- } |
- |
- void noFolding(String name) { |
- if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>(); |
- _noFoldingHeaders.add(name); |
- } |
- |
- String get host => _host; |
- |
- void set host(String host) { |
- _checkMutable(); |
- _host = host; |
- _updateHostHeader(); |
- } |
- |
- int get port => _port; |
- |
- void set port(int port) { |
- _checkMutable(); |
- _port = port; |
- _updateHostHeader(); |
- } |
- |
- Date get ifModifiedSince { |
- List<String> values = _headers["if-modified-since"]; |
- if (values != null) { |
- try { |
- return _HttpUtils.parseDate(values[0]); |
- } on Exception catch (e) { |
- return null; |
- } |
- } |
- return null; |
- } |
- |
- void set ifModifiedSince(Date ifModifiedSince) { |
- _checkMutable(); |
- // Format "ifModifiedSince" header with date in Greenwich Mean Time (GMT). |
- String formatted = _HttpUtils.formatDate(ifModifiedSince.toUtc()); |
- _set("if-modified-since", formatted); |
- } |
- |
- Date get date { |
- List<String> values = _headers["date"]; |
- if (values != null) { |
- try { |
- return _HttpUtils.parseDate(values[0]); |
- } on Exception catch (e) { |
- return null; |
- } |
- } |
- return null; |
- } |
- |
- void set date(Date date) { |
- _checkMutable(); |
- // Format "Date" header with date in Greenwich Mean Time (GMT). |
- String formatted = _HttpUtils.formatDate(date.toUtc()); |
- _set("date", formatted); |
- } |
- |
- Date get expires { |
- List<String> values = _headers["expires"]; |
- if (values != null) { |
- try { |
- return _HttpUtils.parseDate(values[0]); |
- } on Exception catch (e) { |
- return null; |
- } |
- } |
- return null; |
- } |
- |
- void set expires(Date expires) { |
- _checkMutable(); |
- // Format "Expires" header with date in Greenwich Mean Time (GMT). |
- String formatted = _HttpUtils.formatDate(expires.toUtc()); |
- _set("expires", formatted); |
- } |
- |
- ContentType get contentType { |
- var values = _headers["content-type"]; |
- if (values != null) { |
- return new ContentType.fromString(values[0]); |
- } else { |
- return new ContentType(); |
- } |
- } |
- |
- void set contentType(ContentType contentType) { |
- _checkMutable(); |
- _set("content-type", contentType.toString()); |
- } |
- |
- void _add(String name, Object value) { |
- // TODO(sgjesse): Add immutable state throw HttpException is immutable. |
- if (name.toLowerCase() == "date") { |
- if (value is Date) { |
- date = value; |
- } else if (value is String) { |
- _set("date", value); |
- } else { |
- throw new HttpException("Unexpected type for header named $name"); |
- } |
- } else if (name.toLowerCase() == "expires") { |
- if (value is Date) { |
- expires = value; |
- } else if (value is String) { |
- _set("expires", value); |
- } else { |
- throw new HttpException("Unexpected type for header named $name"); |
- } |
- } else if (name.toLowerCase() == "if-modified-since") { |
- if (value is Date) { |
- ifModifiedSince = value; |
- } else if (value is String) { |
- _set("if-modified-since", value); |
- } else { |
- throw new HttpException("Unexpected type for header named $name"); |
- } |
- } else if (name.toLowerCase() == "host") { |
- int pos = value.indexOf(":"); |
- if (pos == -1) { |
- _host = value; |
- _port = HttpClient.DEFAULT_HTTP_PORT; |
- } else { |
- if (pos > 0) { |
- _host = value.substring(0, pos); |
- } else { |
- _host = null; |
- } |
- if (pos + 1 == value.length) { |
- _port = HttpClient.DEFAULT_HTTP_PORT; |
- } else { |
- try { |
- _port = parseInt(value.substring(pos + 1)); |
- } on FormatException catch (e) { |
- _port = null; |
- } |
- } |
- _set("host", value); |
- } |
- } else if (name.toLowerCase() == "content-type") { |
- _set("content-type", value); |
- } else { |
- name = name.toLowerCase(); |
- List<String> values = _headers[name]; |
- if (values == null) { |
- values = new List<String>(); |
- _headers[name] = values; |
- } |
- if (value is Date) { |
- values.add(_HttpUtils.formatDate(value)); |
- } else { |
- values.add(value.toString()); |
- } |
- } |
- } |
- |
- void _set(String name, String value) { |
- name = name.toLowerCase(); |
- List<String> values = new List<String>(); |
- _headers[name] = values; |
- values.add(value); |
- } |
- |
- _checkMutable() { |
- if (!_mutable) throw new HttpException("HTTP headers are not mutable"); |
- } |
- |
- _updateHostHeader() { |
- bool defaultPort = _port == null || _port == HttpClient.DEFAULT_HTTP_PORT; |
- String portPart = defaultPort ? "" : ":$_port"; |
- _set("host", "$host$portPart"); |
- } |
- |
- _foldHeader(String name) { |
- if (name == "set-cookie" || |
- (_noFoldingHeaders != null && |
- _noFoldingHeaders.indexOf(name) != -1)) { |
- return false; |
- } |
- return true; |
- } |
- |
- _write(_HttpConnectionBase connection) { |
- final COLONSP = const [_CharCode.COLON, _CharCode.SP]; |
- final COMMASP = const [_CharCode.COMMA, _CharCode.SP]; |
- final CRLF = const [_CharCode.CR, _CharCode.LF]; |
- |
- // Format headers. |
- _headers.forEach((String name, List<String> values) { |
- bool fold = _foldHeader(name); |
- List<int> data; |
- data = name.charCodes; |
- connection._write(data); |
- connection._write(COLONSP); |
- for (int i = 0; i < values.length; i++) { |
- if (i > 0) { |
- if (fold) { |
- connection._write(COMMASP); |
- } else { |
- connection._write(CRLF); |
- data = name.charCodes; |
- connection._write(data); |
- connection._write(COLONSP); |
- } |
- } |
- data = values[i].charCodes; |
- connection._write(data); |
- } |
- connection._write(CRLF); |
- }); |
- } |
- |
- String toString() { |
- StringBuffer sb = new StringBuffer(); |
- _headers.forEach((String name, List<String> values) { |
- sb.add(name); |
- sb.add(": "); |
- bool fold = _foldHeader(name); |
- for (int i = 0; i < values.length; i++) { |
- if (i > 0) { |
- if (fold) { |
- sb.add(", "); |
- } else { |
- sb.add("\n"); |
- sb.add(name); |
- sb.add(": "); |
- } |
- } |
- sb.add(values[i]); |
- } |
- sb.add("\n"); |
- }); |
- return sb.toString(); |
- } |
- |
- bool _mutable = true; // Are the headers currently mutable? |
- Map<String, List<String>> _headers; |
- List<String> _noFoldingHeaders; |
- |
- String _host; |
- int _port; |
-} |
- |
- |
-class _HeaderValue implements HeaderValue { |
- _HeaderValue([String this.value = ""]); |
- |
- _HeaderValue.fromString(String value, {this.parameterSeparator: ";"}) { |
- // Parse the string. |
- _parse(value); |
- } |
- |
- Map<String, String> get parameters { |
- if (_parameters == null) _parameters = new Map<String, String>(); |
- return _parameters; |
- } |
- |
- String toString() { |
- StringBuffer sb = new StringBuffer(); |
- sb.add(value); |
- if (parameters != null && parameters.length > 0) { |
- _parameters.forEach((String name, String value) { |
- sb.add("; "); |
- sb.add(name); |
- sb.add("="); |
- sb.add(value); |
- }); |
- } |
- return sb.toString(); |
- } |
- |
- void _parse(String s) { |
- int index = 0; |
- |
- bool done() => index == s.length; |
- |
- void skipWS() { |
- while (!done()) { |
- if (s[index] != " " && s[index] != "\t") return; |
- index++; |
- } |
- } |
- |
- String parseValue() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == " " || |
- s[index] == "\t" || |
- s[index] == parameterSeparator) break; |
- index++; |
- } |
- return s.substring(start, index).toLowerCase(); |
- } |
- |
- void expect(String expected) { |
- if (done() || s[index] != expected) { |
- throw new HttpException("Failed to parse header value"); |
- } |
- index++; |
- } |
- |
- void maybeExpect(String expected) { |
- if (s[index] == expected) index++; |
- } |
- |
- void parseParameters() { |
- _parameters = new Map<String, String>(); |
- |
- String parseParameterName() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == " " || s[index] == "\t" || s[index] == "=") break; |
- index++; |
- } |
- return s.substring(start, index).toLowerCase(); |
- } |
- |
- String parseParameterValue() { |
- if (s[index] == "\"") { |
- // Parse quoted value. |
- StringBuffer sb = new StringBuffer(); |
- index++; |
- while (!done()) { |
- if (s[index] == "\\") { |
- if (index + 1 == s.length) { |
- throw new HttpException("Failed to parse header value"); |
- } |
- index++; |
- } else if (s[index] == "\"") { |
- index++; |
- break; |
- } |
- sb.add(s[index]); |
- index++; |
- } |
- return sb.toString(); |
- } else { |
- // Parse non-quoted value. |
- return parseValue(); |
- } |
- } |
- |
- while (!done()) { |
- skipWS(); |
- if (done()) return; |
- String name = parseParameterName(); |
- skipWS(); |
- expect("="); |
- skipWS(); |
- String value = parseParameterValue(); |
- _parameters[name] = value; |
- skipWS(); |
- if (done()) return; |
- expect(parameterSeparator); |
- } |
- } |
- |
- skipWS(); |
- value = parseValue(); |
- skipWS(); |
- if (done()) return; |
- maybeExpect(parameterSeparator); |
- parseParameters(); |
- } |
- |
- String value; |
- String parameterSeparator; |
- Map<String, String> _parameters; |
-} |
- |
- |
-class _ContentType extends _HeaderValue implements ContentType { |
- _ContentType(String primaryType, String subType) |
- : _primaryType = primaryType, _subType = subType, super(""); |
- |
- _ContentType.fromString(String value) : super.fromString(value); |
- |
- String get value => "$_primaryType/$_subType"; |
- |
- void set value(String s) { |
- int index = s.indexOf("/"); |
- if (index == -1 || index == (s.length - 1)) { |
- primaryType = s.trim().toLowerCase(); |
- subType = ""; |
- } else { |
- primaryType = s.substring(0, index).trim().toLowerCase(); |
- subType = s.substring(index + 1).trim().toLowerCase(); |
- } |
- } |
- |
- String get primaryType => _primaryType; |
- |
- void set primaryType(String s) { |
- _primaryType = s; |
- } |
- |
- String get subType => _subType; |
- |
- void set subType(String s) { |
- _subType = s; |
- } |
- |
- String get charset => parameters["charset"]; |
- |
- void set charset(String s) { |
- parameters["charset"] = s; |
- } |
- |
- String _primaryType = ""; |
- String _subType = ""; |
-} |
- |
- |
-class _Cookie implements Cookie { |
- _Cookie([String this.name, String this.value]); |
- |
- _Cookie.fromSetCookieValue(String value) { |
- // Parse the Set-Cookie header value. |
- _parseSetCookieValue(value); |
- } |
- |
- // Parse a Set-Cookie header value according to the rules in RFC 6265. |
- void _parseSetCookieValue(String s) { |
- int index = 0; |
- |
- bool done() => index == s.length; |
- |
- String parseName() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == "=") break; |
- index++; |
- } |
- return s.substring(start, index).trim().toLowerCase(); |
- } |
- |
- String parseValue() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == ";") break; |
- index++; |
- } |
- return s.substring(start, index).trim().toLowerCase(); |
- } |
- |
- void expect(String expected) { |
- if (done()) throw new HttpException("Failed to parse header value [$s]"); |
- if (s[index] != expected) { |
- throw new HttpException("Failed to parse header value [$s]"); |
- } |
- index++; |
- } |
- |
- void parseAttributes() { |
- String parseAttributeName() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == "=" || s[index] == ";") break; |
- index++; |
- } |
- return s.substring(start, index).trim().toLowerCase(); |
- } |
- |
- String parseAttributeValue() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == ";") break; |
- index++; |
- } |
- return s.substring(start, index).trim().toLowerCase(); |
- } |
- |
- while (!done()) { |
- String name = parseAttributeName(); |
- String value = ""; |
- if (!done() && s[index] == "=") { |
- index++; // Skip the = character. |
- value = parseAttributeValue(); |
- } |
- if (name == "expires") { |
- expires = _HttpUtils.parseCookieDate(value); |
- } else if (name == "max-age") { |
- maxAge = parseInt(value); |
- } else if (name == "domain") { |
- domain = value; |
- } else if (name == "path") { |
- path = value; |
- } else if (name == "httponly") { |
- httpOnly = true; |
- } else if (name == "secure") { |
- secure = true; |
- } |
- if (!done()) index++; // Skip the ; character |
- } |
- } |
- |
- name = parseName(); |
- if (done() || name.length == 0) { |
- throw new HttpException("Failed to parse header value [$s]"); |
- } |
- index++; // Skip the = character. |
- value = parseValue(); |
- if (done()) return; |
- index++; // Skip the ; character. |
- parseAttributes(); |
- } |
- |
- String toString() { |
- StringBuffer sb = new StringBuffer(); |
- sb.add(name); |
- sb.add("="); |
- sb.add(value); |
- if (expires != null) { |
- sb.add("; Expires="); |
- sb.add(_HttpUtils.formatDate(expires)); |
- } |
- if (maxAge != null) { |
- sb.add("; Max-Age="); |
- sb.add(maxAge); |
- } |
- if (domain != null) { |
- sb.add("; Domain="); |
- sb.add(domain); |
- } |
- if (path != null) { |
- sb.add("; Path="); |
- sb.add(path); |
- } |
- if (secure) sb.add("; Secure"); |
- if (httpOnly) sb.add("; HttpOnly"); |
- return sb.toString(); |
- } |
- |
- String name; |
- String value; |
- Date expires; |
- int maxAge; |
- String domain; |
- String path; |
- bool httpOnly = false; |
- bool secure = false; |
-} |
- |
- |
-class _HttpRequestResponseBase { |
- final int START = 0; |
- final int HEADER_SENT = 1; |
- final int DONE = 2; |
- final int UPGRADED = 3; |
- |
- _HttpRequestResponseBase(_HttpConnectionBase this._httpConnection) |
- : _headers = new _HttpHeaders() { |
- _state = START; |
- _headResponse = false; |
- } |
- |
- int get contentLength => _contentLength; |
- HttpHeaders get headers => _headers; |
- |
- bool get persistentConnection { |
- List<String> connection = headers[HttpHeaders.CONNECTION]; |
- if (_protocolVersion == "1.1") { |
- if (connection == null) return true; |
- return !headers[HttpHeaders.CONNECTION].some( |
- (value) => value.toLowerCase() == "close"); |
- } else { |
- if (connection == null) return false; |
- return headers[HttpHeaders.CONNECTION].some( |
- (value) => value.toLowerCase() == "keep-alive"); |
- } |
- } |
- |
- void set persistentConnection(bool persistentConnection) { |
- if (_outputStream != null) throw new HttpException("Header already sent"); |
- |
- // Determine the value of the "Connection" header. |
- headers.remove(HttpHeaders.CONNECTION, "close"); |
- headers.remove(HttpHeaders.CONNECTION, "keep-alive"); |
- if (_protocolVersion == "1.1" && !persistentConnection) { |
- headers.add(HttpHeaders.CONNECTION, "close"); |
- } else if (_protocolVersion == "1.0" && persistentConnection) { |
- headers.add(HttpHeaders.CONNECTION, "keep-alive"); |
- } |
- } |
- |
- |
- bool _write(List<int> data, bool copyBuffer) { |
- if (_headResponse) return; |
- _ensureHeadersSent(); |
- bool allWritten = true; |
- if (data.length > 0) { |
- if (_contentLength < 0) { |
- // Write chunk size if transfer encoding is chunked. |
- _writeHexString(data.length); |
- _writeCRLF(); |
- _httpConnection._write(data, copyBuffer); |
- allWritten = _writeCRLF(); |
- } else { |
- _updateContentLength(data.length); |
- allWritten = _httpConnection._write(data, copyBuffer); |
- } |
- } |
- return allWritten; |
- } |
- |
- bool _writeList(List<int> data, int offset, int count) { |
- if (_headResponse) return; |
- _ensureHeadersSent(); |
- bool allWritten = true; |
- if (count > 0) { |
- if (_contentLength < 0) { |
- // Write chunk size if transfer encoding is chunked. |
- _writeHexString(count); |
- _writeCRLF(); |
- _httpConnection._writeFrom(data, offset, count); |
- allWritten = _writeCRLF(); |
- } else { |
- _updateContentLength(count); |
- allWritten = _httpConnection._writeFrom(data, offset, count); |
- } |
- } |
- return allWritten; |
- } |
- |
- bool _writeDone() { |
- bool allWritten = true; |
- if (_contentLength < 0) { |
- // Terminate the content if transfer encoding is chunked. |
- allWritten = _httpConnection._write(_Const.END_CHUNKED); |
- } else { |
- if (!_headResponse && _bodyBytesWritten < _contentLength) { |
- throw new HttpException("Sending less than specified content length"); |
- } |
- assert(_headResponse || _bodyBytesWritten == _contentLength); |
- } |
- // If we are done writing the response, and either the client has |
- // closed or the connection is not persistent, we can close. Also |
- // if using HTTP 1.0 and the content length was not known we must |
- // close to indicate end of body. |
- if (!persistentConnection || _httpConnection._closing || |
- (_protocolVersion == "1.0" && _contentLength < 0)) { |
- _httpConnection._close(); |
- } |
- return allWritten; |
- } |
- |
- bool _writeHeaders() { |
- _headers._mutable = false; |
- _headers._write(_httpConnection); |
- // Terminate header. |
- return _writeCRLF(); |
- } |
- |
- bool _writeHexString(int x) { |
- final List<int> hexDigits = [0x30, 0x31, 0x32, 0x33, 0x34, |
- 0x35, 0x36, 0x37, 0x38, 0x39, |
- 0x41, 0x42, 0x43, 0x44, 0x45, 0x46]; |
- List<int> hex = new Uint8List(10); |
- int index = hex.length; |
- while (x > 0) { |
- index--; |
- hex[index] = hexDigits[x % 16]; |
- x = x >> 4; |
- } |
- return _httpConnection._writeFrom(hex, index, hex.length - index); |
- } |
- |
- bool _writeCRLF() { |
- final CRLF = const [_CharCode.CR, _CharCode.LF]; |
- return _httpConnection._write(CRLF); |
- } |
- |
- bool _writeSP() { |
- final SP = const [_CharCode.SP]; |
- return _httpConnection._write(SP); |
- } |
- |
- void _ensureHeadersSent() { |
- // Ensure that headers are written. |
- if (_state == START) { |
- _writeHeader(); |
- } |
- } |
- |
- void _updateContentLength(int bytes) { |
- if (_bodyBytesWritten + bytes > _contentLength) { |
- throw new HttpException("Writing more than specified content length"); |
- } |
- _bodyBytesWritten += bytes; |
- } |
- |
- HttpConnectionInfo get connectionInfo => _httpConnection.connectionInfo; |
- |
- bool get _done => _state == DONE; |
- |
- int _state; |
- bool _headResponse; |
- |
- _HttpConnectionBase _httpConnection; |
- _HttpHeaders _headers; |
- List<Cookie> _cookies; |
- String _protocolVersion = "1.1"; |
- |
- // Length of the content body. If this is set to -1 (default value) |
- // when starting to send data chunked transfer encoding will be |
- // used. |
- int _contentLength = -1; |
- // Number of body bytes written. This is only actual body data not |
- // including headers or chunk information of using chinked transfer |
- // encoding. |
- int _bodyBytesWritten = 0; |
-} |
- |
- |
-// Parsed HTTP request providing information on the HTTP headers. |
-class _HttpRequest extends _HttpRequestResponseBase implements HttpRequest { |
- _HttpRequest(_HttpConnection connection) : super(connection); |
- |
- String get method => _method; |
- String get uri => _uri; |
- String get path => _path; |
- String get queryString => _queryString; |
- Map get queryParameters => _queryParameters; |
- |
- List<Cookie> get cookies { |
- if (_cookies != null) return _cookies; |
- |
- // Parse a Cookie header value according to the rules in RFC 6265. |
- void _parseCookieString(String s) { |
- int index = 0; |
- |
- bool done() => index == s.length; |
- |
- void skipWS() { |
- while (!done()) { |
- if (s[index] != " " && s[index] != "\t") return; |
- index++; |
- } |
- } |
- |
- String parseName() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == " " || s[index] == "\t" || s[index] == "=") break; |
- index++; |
- } |
- return s.substring(start, index).toLowerCase(); |
- } |
- |
- String parseValue() { |
- int start = index; |
- while (!done()) { |
- if (s[index] == " " || s[index] == "\t" || s[index] == ";") break; |
- index++; |
- } |
- return s.substring(start, index).toLowerCase(); |
- } |
- |
- void expect(String expected) { |
- if (done()) { |
- throw new HttpException("Failed to parse header value [$s]"); |
- } |
- if (s[index] != expected) { |
- throw new HttpException("Failed to parse header value [$s]"); |
- } |
- index++; |
- } |
- |
- while (!done()) { |
- skipWS(); |
- if (done()) return; |
- String name = parseName(); |
- skipWS(); |
- expect("="); |
- skipWS(); |
- String value = parseValue(); |
- _cookies.add(new _Cookie(name, value)); |
- skipWS(); |
- if (done()) return; |
- expect(";"); |
- } |
- } |
- |
- _cookies = new List<Cookie>(); |
- List<String> headerValues = headers["cookie"]; |
- if (headerValues != null) { |
- headerValues.forEach((headerValue) => _parseCookieString(headerValue)); |
- } |
- return _cookies; |
- } |
- |
- InputStream get inputStream { |
- if (_inputStream == null) { |
- _inputStream = new _HttpInputStream(this); |
- _inputStream._streamMarkedClosed = _dataEndCalled; |
- } |
- return _inputStream; |
- } |
- |
- String get protocolVersion => _protocolVersion; |
- |
- HttpSession session([init(HttpSession session)]) { |
- if (_session != null) { |
- // It's already mapped, use it. |
- return _session; |
- } |
- // Create session, store it in connection, and return. |
- var sessionManager = _httpConnection._server._sessionManager; |
- return _session = sessionManager.createSession(init); |
- } |
- |
- void _onRequestStart(String method, String uri, String version) { |
- _method = method; |
- _uri = uri; |
- _parseRequestUri(uri); |
- } |
- |
- void _onHeaderReceived(String name, String value) { |
- _headers.add(name, value); |
- } |
- |
- void _onHeadersComplete() { |
- if (_httpConnection._server._sessionManagerInstance != null) { |
- // Map to session if exists. |
- var sessionId = cookies.reduce(null, (last, cookie) { |
- if (last != null) return last; |
- return cookie.name.toUpperCase() == _DART_SESSION_ID ? |
- cookie.value : null; |
- }); |
- if (sessionId != null) { |
- var sessionManager = _httpConnection._server._sessionManager; |
- _session = sessionManager.getSession(sessionId); |
- if (_session != null) { |
- _session._markSeen(); |
- } |
- } |
- } |
- |
- // Get parsed content length. |
- _contentLength = _httpConnection._httpParser.contentLength; |
- |
- // Prepare for receiving data. |
- _headers._mutable = false; |
- _buffer = new _BufferList(); |
- } |
- |
- void _onDataReceived(List<int> data) { |
- _buffer.add(data); |
- if (_inputStream != null) _inputStream._dataReceived(); |
- } |
- |
- void _onDataEnd() { |
- if (_inputStream != null) _inputStream._closeReceived(); |
- _dataEndCalled = true; |
- } |
- |
- // Escaped characters in uri are expected to have been parsed. |
- void _parseRequestUri(String uri) { |
- int position; |
- position = uri.indexOf("?", 0); |
- if (position == -1) { |
- _path = _HttpUtils.decodeUrlEncodedString(_uri); |
- _queryString = null; |
- _queryParameters = new Map(); |
- } else { |
- _path = _HttpUtils.decodeUrlEncodedString(_uri.substring(0, position)); |
- _queryString = _uri.substring(position + 1); |
- _queryParameters = _HttpUtils.splitQueryString(_queryString); |
- } |
- } |
- |
- // Delegate functions for the HttpInputStream implementation. |
- int _streamAvailable() { |
- return _buffer.length; |
- } |
- |
- List<int> _streamRead(int bytesToRead) { |
- return _buffer.readBytes(bytesToRead); |
- } |
- |
- int _streamReadInto(List<int> buffer, int offset, int len) { |
- List<int> data = _buffer.readBytes(len); |
- buffer.setRange(offset, data.length, data); |
- } |
- |
- void _streamSetErrorHandler(callback(e)) { |
- _streamErrorHandler = callback; |
- } |
- |
- String _method; |
- String _uri; |
- String _path; |
- String _queryString; |
- Map<String, String> _queryParameters; |
- _HttpInputStream _inputStream; |
- _BufferList _buffer; |
- bool _dataEndCalled = false; |
- Function _streamErrorHandler; |
- _HttpSession _session; |
-} |
- |
- |
-// HTTP response object for sending a HTTP response. |
-class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse { |
- _HttpResponse(_HttpConnection httpConnection) |
- : super(httpConnection), |
- _statusCode = HttpStatus.OK; |
- |
- void set contentLength(int contentLength) { |
- if (_state >= HEADER_SENT) throw new HttpException("Header already sent"); |
- _contentLength = contentLength; |
- } |
- |
- int get statusCode => _statusCode; |
- void set statusCode(int statusCode) { |
- if (_outputStream != null) throw new HttpException("Header already sent"); |
- _statusCode = statusCode; |
- } |
- |
- String get reasonPhrase => _findReasonPhrase(_statusCode); |
- void set reasonPhrase(String reasonPhrase) { |
- if (_outputStream != null) throw new HttpException("Header already sent"); |
- _reasonPhrase = reasonPhrase; |
- } |
- |
- List<Cookie> get cookies { |
- if (_cookies == null) _cookies = new List<Cookie>(); |
- return _cookies; |
- } |
- |
- OutputStream get outputStream { |
- if (_state >= DONE) throw new HttpException("Response closed"); |
- if (_outputStream == null) { |
- _outputStream = new _HttpOutputStream(this); |
- } |
- return _outputStream; |
- } |
- |
- DetachedSocket detachSocket() { |
- if (_state >= DONE) throw new HttpException("Response closed"); |
- // Ensure that headers are written. |
- if (_state == START) { |
- _writeHeader(); |
- } |
- _state = UPGRADED; |
- // Ensure that any trailing data is written. |
- _writeDone(); |
- // Indicate to the connection that the response handling is done. |
- return _httpConnection._detachSocket(); |
- } |
- |
- void _responseEnd() { |
- _ensureHeadersSent(); |
- _state = DONE; |
- // Stop tracking no pending write events. |
- _httpConnection._onNoPendingWrites = null; |
- // Ensure that any trailing data is written. |
- _writeDone(); |
- // Indicate to the connection that the response handling is done. |
- _httpConnection._responseDone(); |
- } |
- |
- // Delegate functions for the HttpOutputStream implementation. |
- bool _streamWrite(List<int> buffer, bool copyBuffer) { |
- if (_done) throw new HttpException("Response closed"); |
- return _write(buffer, copyBuffer); |
- } |
- |
- bool _streamWriteFrom(List<int> buffer, int offset, int len) { |
- if (_done) throw new HttpException("Response closed"); |
- return _writeList(buffer, offset, len); |
- } |
- |
- void _streamFlush() { |
- _httpConnection._flush(); |
- } |
- |
- void _streamClose() { |
- _responseEnd(); |
- } |
- |
- void _streamSetNoPendingWriteHandler(callback()) { |
- if (_state != DONE) { |
- _httpConnection._onNoPendingWrites = callback; |
- } |
- } |
- |
- void _streamSetCloseHandler(callback()) { |
- // TODO(sgjesse): Handle this. |
- } |
- |
- void _streamSetErrorHandler(callback(e)) { |
- _streamErrorHandler = callback; |
- } |
- |
- String _findReasonPhrase(int statusCode) { |
- if (_reasonPhrase != null) { |
- return _reasonPhrase; |
- } |
- |
- switch (statusCode) { |
- case HttpStatus.CONTINUE: return "Continue"; |
- case HttpStatus.SWITCHING_PROTOCOLS: return "Switching Protocols"; |
- case HttpStatus.OK: return "OK"; |
- case HttpStatus.CREATED: return "Created"; |
- case HttpStatus.ACCEPTED: return "Accepted"; |
- case HttpStatus.NON_AUTHORITATIVE_INFORMATION: |
- return "Non-Authoritative Information"; |
- case HttpStatus.NO_CONTENT: return "No Content"; |
- case HttpStatus.RESET_CONTENT: return "Reset Content"; |
- case HttpStatus.PARTIAL_CONTENT: return "Partial Content"; |
- case HttpStatus.MULTIPLE_CHOICES: return "Multiple Choices"; |
- case HttpStatus.MOVED_PERMANENTLY: return "Moved Permanently"; |
- case HttpStatus.FOUND: return "Found"; |
- case HttpStatus.SEE_OTHER: return "See Other"; |
- case HttpStatus.NOT_MODIFIED: return "Not Modified"; |
- case HttpStatus.USE_PROXY: return "Use Proxy"; |
- case HttpStatus.TEMPORARY_REDIRECT: return "Temporary Redirect"; |
- case HttpStatus.BAD_REQUEST: return "Bad Request"; |
- case HttpStatus.UNAUTHORIZED: return "Unauthorized"; |
- case HttpStatus.PAYMENT_REQUIRED: return "Payment Required"; |
- case HttpStatus.FORBIDDEN: return "Forbidden"; |
- case HttpStatus.NOT_FOUND: return "Not Found"; |
- case HttpStatus.METHOD_NOT_ALLOWED: return "Method Not Allowed"; |
- case HttpStatus.NOT_ACCEPTABLE: return "Not Acceptable"; |
- case HttpStatus.PROXY_AUTHENTICATION_REQUIRED: |
- return "Proxy Authentication Required"; |
- case HttpStatus.REQUEST_TIMEOUT: return "Request Time-out"; |
- case HttpStatus.CONFLICT: return "Conflict"; |
- case HttpStatus.GONE: return "Gone"; |
- case HttpStatus.LENGTH_REQUIRED: return "Length Required"; |
- case HttpStatus.PRECONDITION_FAILED: return "Precondition Failed"; |
- case HttpStatus.REQUEST_ENTITY_TOO_LARGE: |
- return "Request Entity Too Large"; |
- case HttpStatus.REQUEST_URI_TOO_LONG: return "Request-URI Too Large"; |
- case HttpStatus.UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; |
- case HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE: |
- return "Requested range not satisfiable"; |
- case HttpStatus.EXPECTATION_FAILED: return "Expectation Failed"; |
- case HttpStatus.INTERNAL_SERVER_ERROR: return "Internal Server Error"; |
- case HttpStatus.NOT_IMPLEMENTED: return "Not Implemented"; |
- case HttpStatus.BAD_GATEWAY: return "Bad Gateway"; |
- case HttpStatus.SERVICE_UNAVAILABLE: return "Service Unavailable"; |
- case HttpStatus.GATEWAY_TIMEOUT: return "Gateway Time-out"; |
- case HttpStatus.HTTP_VERSION_NOT_SUPPORTED: |
- return "Http Version not supported"; |
- default: return "Status $statusCode"; |
- } |
- } |
- |
- bool _writeHeader() { |
- List<int> data; |
- |
- // Write status line. |
- if (_protocolVersion == "1.1") { |
- _httpConnection._write(_Const.HTTP11); |
- } else { |
- _httpConnection._write(_Const.HTTP10); |
- } |
- _writeSP(); |
- data = _statusCode.toString().charCodes; |
- _httpConnection._write(data); |
- _writeSP(); |
- data = reasonPhrase.charCodes; |
- _httpConnection._write(data); |
- _writeCRLF(); |
- |
- // Determine the value of the "Transfer-Encoding" header based on |
- // whether the content length is known. HTTP/1.0 does not support |
- // chunked. |
- if (_contentLength >= 0) { |
- _headers.set(HttpHeaders.CONTENT_LENGTH, _contentLength.toString()); |
- } else if (_contentLength < 0 && _protocolVersion == "1.1") { |
- _headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
- } |
- |
- var session = _httpConnection._request._session; |
- if (session != null && !session._destroyed) { |
- // Make sure we only send the current session id. |
- bool found = false; |
- for (int i = 0; i < cookies.length; i++) { |
- if (cookies[i].name.toUpperCase() == _DART_SESSION_ID) { |
- cookie.value = session.id; |
- found = true; |
- break; |
- } |
- } |
- if (!found) { |
- cookies.add(new Cookie(_DART_SESSION_ID, session.id)); |
- } |
- } |
- // Add all the cookies set to the headers. |
- if (_cookies != null) { |
- _cookies.forEach((cookie) { |
- _headers.add("set-cookie", cookie); |
- }); |
- } |
- |
- // Write headers. |
- bool allWritten = _writeHeaders(); |
- _state = HEADER_SENT; |
- return allWritten; |
- } |
- |
- int _statusCode; // Response status code. |
- String _reasonPhrase; // Response reason phrase. |
- _HttpOutputStream _outputStream; |
- Function _streamErrorHandler; |
-} |
- |
- |
-class _HttpInputStream extends _BaseDataInputStream implements InputStream { |
- _HttpInputStream(_HttpRequestResponseBase this._requestOrResponse) { |
- _checkScheduleCallbacks(); |
- } |
- |
- int available() { |
- return _requestOrResponse._streamAvailable(); |
- } |
- |
- void pipe(OutputStream output, {bool close: true}) { |
- _pipe(this, output, close: close); |
- } |
- |
- List<int> _read(int bytesToRead) { |
- List<int> result = _requestOrResponse._streamRead(bytesToRead); |
- _checkScheduleCallbacks(); |
- return result; |
- } |
- |
- void set onError(void callback(e)) { |
- _requestOrResponse._streamSetErrorHandler(callback); |
- } |
- |
- int _readInto(List<int> buffer, int offset, int len) { |
- int result = _requestOrResponse._streamReadInto(buffer, offset, len); |
- _checkScheduleCallbacks(); |
- return result; |
- } |
- |
- void _close() { |
- // TODO(sgjesse): Handle this. |
- } |
- |
- void _dataReceived() { |
- super._dataReceived(); |
- } |
- |
- _HttpRequestResponseBase _requestOrResponse; |
-} |
- |
- |
-class _HttpOutputStream extends _BaseOutputStream implements OutputStream { |
- _HttpOutputStream(_HttpRequestResponseBase this._requestOrResponse); |
- |
- bool write(List<int> buffer, [bool copyBuffer = true]) { |
- return _requestOrResponse._streamWrite(buffer, copyBuffer); |
- } |
- |
- bool writeFrom(List<int> buffer, [int offset = 0, int len]) { |
- return _requestOrResponse._streamWriteFrom(buffer, offset, len); |
- } |
- |
- void flush() { |
- _requestOrResponse._streamFlush(); |
- } |
- |
- void close() { |
- _requestOrResponse._streamClose(); |
- } |
- |
- bool get closed => _requestOrResponse._done; |
- |
- void destroy() { |
- throw "Not implemented"; |
- } |
- |
- void set onNoPendingWrites(void callback()) { |
- _requestOrResponse._streamSetNoPendingWriteHandler(callback); |
- } |
- |
- void set onClosed(void callback()) { |
- _requestOrResponse._streamSetCloseHandler(callback); |
- } |
- |
- void set onError(void callback(e)) { |
- _requestOrResponse._streamSetErrorHandler(callback); |
- } |
- |
- _HttpRequestResponseBase _requestOrResponse; |
-} |
- |
- |
-class _HttpConnectionBase { |
- _HttpConnectionBase() : _httpParser = new _HttpParser(), |
- hashCode = _nextHashCode { |
- _nextHashCode = (_nextHashCode + 1) & 0xFFFFFFF; |
- } |
- |
- void _connectionEstablished(Socket socket) { |
- _socket = socket; |
- // Register handler for socket events. |
- _socket.onData = _onData; |
- _socket.onClosed = _onClosed; |
- _socket.onError = _onError; |
- // Ignore errors in the socket output stream as this is getting |
- // the same errors as the socket itself. |
- _socket.outputStream.onError = (e) => null; |
- } |
- |
- bool _write(List<int> data, [bool copyBuffer = false]) { |
- if (!_error && !_closing) { |
- return _socket.outputStream.write(data, copyBuffer); |
- } |
- } |
- |
- bool _writeFrom(List<int> buffer, [int offset, int len]) { |
- if (!_error && !_closing) { |
- return _socket.outputStream.writeFrom(buffer, offset, len); |
- } |
- } |
- |
- bool _flush() { |
- _socket.outputStream.flush(); |
- } |
- |
- bool _close() { |
- _closing = true; |
- _socket.outputStream.close(); |
- } |
- |
- bool _destroy() { |
- _closing = true; |
- _socket.close(); |
- } |
- |
- void _onData() { |
- int available = _socket.available(); |
- if (available == 0) { |
- return; |
- } |
- |
- List<int> buffer = new Uint8List(available); |
- int bytesRead = _socket.readList(buffer, 0, available); |
- if (bytesRead > 0) { |
- int parsed = _httpParser.writeList(buffer, 0, bytesRead); |
- if (!_httpParser.upgrade) { |
- if (parsed != bytesRead) { |
- if (_socket != null) { |
- // TODO(sgjesse): Error handling. |
- _destroy(); |
- } |
- } |
- } |
- } |
- } |
- |
- void _onClosed() { |
- _closing = true; |
- _onConnectionClosed(null); |
- } |
- |
- void _onError(e) { |
- // If an error occurs, make sure to close the socket if one is associated. |
- _error = true; |
- if (_socket != null) { |
- _socket.close(); |
- } |
- _onConnectionClosed(e); |
- } |
- |
- DetachedSocket _detachSocket() { |
- _socket.onData = null; |
- _socket.onClosed = null; |
- _socket.onError = null; |
- _socket.outputStream.onNoPendingWrites = null; |
- Socket socket = _socket; |
- _socket = null; |
- if (onDetach != null) onDetach(); |
- return new _DetachedSocket(socket, _httpParser.unparsedData); |
- } |
- |
- HttpConnectionInfo get connectionInfo { |
- if (_socket == null || _closing || _error) return null; |
- try { |
- _HttpConnectionInfo info = new _HttpConnectionInfo(); |
- info.remoteHost = _socket.remoteHost; |
- info.remotePort = _socket.remotePort; |
- info.localPort = _socket.port; |
- return info; |
- } catch (e) { } |
- return null; |
- } |
- |
- abstract void _onConnectionClosed(e); |
- abstract void _responseDone(); |
- |
- void set _onNoPendingWrites(void callback()) { |
- if (!_error) { |
- _socket.outputStream.onNoPendingWrites = callback; |
- } |
- } |
- |
- Socket _socket; |
- bool _closing = false; // Is the socket closed by the client? |
- bool _error = false; // Is the socket closed due to an error? |
- _HttpParser _httpParser; |
- |
- Function onDetach; |
- |
- // Hash code for HTTP connection. Currently this is just a counter. |
- final int hashCode; |
- static int _nextHashCode = 0; |
-} |
- |
- |
-// HTTP server connection over a socket. |
-class _HttpConnection extends _HttpConnectionBase { |
- _HttpConnection(HttpServer this._server) { |
- // Register HTTP parser callbacks. |
- _httpParser.requestStart = |
- (method, uri, version) => _onRequestStart(method, uri, version); |
- _httpParser.responseStart = |
- (statusCode, reasonPhrase, version) => |
- _onResponseStart(statusCode, reasonPhrase, version); |
- _httpParser.headerReceived = |
- (name, value) => _onHeaderReceived(name, value); |
- _httpParser.headersComplete = () => _onHeadersComplete(); |
- _httpParser.dataReceived = (data) => _onDataReceived(data); |
- _httpParser.dataEnd = (close) => _onDataEnd(close); |
- _httpParser.error = (e) => _onError(e); |
- } |
- |
- void _onConnectionClosed(e) { |
- // Don't report errors when HTTP parser is in idle state. Clients |
- // can close the connection and cause a connection reset by peer |
- // error which is OK. |
- if (e != null && onError != null && !_httpParser.isIdle) { |
- onError(e); |
- // Propagate the error to the streams. |
- if (_request != null && _request._streamErrorHandler != null) { |
- _request._streamErrorHandler(e); |
- } |
- if (_response != null && _response._streamErrorHandler != null) { |
- _response._streamErrorHandler(e); |
- } |
- } |
- |
- // If currently not processing any request close the socket when |
- // we are done writing the response. |
- if (_httpParser.isIdle) { |
- _socket.outputStream.onClosed = () { |
- _destroy(); |
- if (onClosed != null && e == null) { |
- // Don't call onClosed if onError has been called. |
- onClosed(); |
- } |
- }; |
- // If the client closes and we are done writing the response |
- // the connection should be closed. |
- if (_response == null) _close(); |
- return; |
- } |
- |
- // Processing a request. |
- if (e == null) { |
- // Indicate connection close to the HTTP parser. |
- _httpParser.connectionClosed(); |
- } |
- } |
- |
- void _onRequestStart(String method, String uri, String version) { |
- // Create new request and response objects for this request. |
- _request = new _HttpRequest(this); |
- _response = new _HttpResponse(this); |
- _request._onRequestStart(method, uri, version); |
- _request._protocolVersion = version; |
- _response._protocolVersion = version; |
- _response._headResponse = method == "HEAD"; |
- } |
- |
- void _onResponseStart(int statusCode, String reasonPhrase, String version) { |
- // TODO(sgjesse): Error handling. |
- } |
- |
- void _onHeaderReceived(String name, String value) { |
- _request._onHeaderReceived(name, value); |
- } |
- |
- void _onHeadersComplete() { |
- _request._onHeadersComplete(); |
- _response.persistentConnection = _httpParser.persistentConnection; |
- if (onRequestReceived != null) { |
- onRequestReceived(_request, _response); |
- } |
- } |
- |
- void _onDataReceived(List<int> data) { |
- _request._onDataReceived(data); |
- } |
- |
- void _onDataEnd(bool close) { |
- _request._onDataEnd(); |
- } |
- |
- void _responseDone() { |
- // If the connection is closing then close the output stream to |
- // fully close the socket. |
- if (_closing) { |
- _socket.outputStream.onClosed = () { |
- _socket.close(); |
- }; |
- } |
- _response = null; |
- } |
- |
- HttpServer _server; |
- HttpRequest _request; |
- HttpResponse _response; |
- |
- // Callbacks. |
- Function onRequestReceived; |
- Function onClosed; |
- Function onError; |
-} |
- |
- |
-class _RequestHandlerRegistration { |
- _RequestHandlerRegistration(Function this._matcher, Function this._handler); |
- Function _matcher; |
- Function _handler; |
-} |
- |
-// HTTP server waiting for socket connections. The connections are |
-// managed by the server and as requests are received the request. |
-class _HttpServer implements HttpServer { |
- _HttpServer() : _connections = new Set<_HttpConnection>(), |
- _handlers = new List<_RequestHandlerRegistration>(); |
- |
- void listen(String host, int port, {int backlog: 128}) { |
- listenOn(new ServerSocket(host, port, backlog)); |
- _closeServer = true; |
- } |
- |
- void listenOn(ServerSocket serverSocket) { |
- void onConnection(Socket socket) { |
- // Accept the client connection. |
- _HttpConnection connection = new _HttpConnection(this); |
- connection._connectionEstablished(socket); |
- _connections.add(connection); |
- connection.onRequestReceived = _handleRequest; |
- connection.onClosed = () => _connections.remove(connection); |
- connection.onDetach = () => _connections.remove(connection); |
- connection.onError = (e) { |
- _connections.remove(connection); |
- if (_onError != null) { |
- _onError(e); |
- } else { |
- throw(e); |
- } |
- }; |
- } |
- serverSocket.onConnection = onConnection; |
- _server = serverSocket; |
- _closeServer = false; |
- } |
- |
- addRequestHandler(bool matcher(HttpRequest request), |
- void handler(HttpRequest request, HttpResponse response)) { |
- _handlers.add(new _RequestHandlerRegistration(matcher, handler)); |
- } |
- |
- void set defaultRequestHandler( |
- void handler(HttpRequest request, HttpResponse response)) { |
- _defaultHandler = handler; |
- } |
- |
- void close() { |
- if (_sessionManagerInstance != null) { |
- _sessionManagerInstance.close(); |
- _sessionManagerInstance = null; |
- } |
- if (_server != null && _closeServer) { |
- _server.close(); |
- } |
- _server = null; |
- for (_HttpConnection connection in _connections) { |
- connection._destroy(); |
- } |
- _connections.clear(); |
- } |
- |
- int get port { |
- if (_server === null) { |
- throw new HttpException("The HttpServer is not listening on a port."); |
- } |
- return _server.port; |
- } |
- |
- void set onError(void callback(e)) { |
- _onError = callback; |
- } |
- |
- int set sessionTimeout(int timeout) { |
- _sessionManager.sessionTimeout = timeout; |
- } |
- |
- void _handleRequest(HttpRequest request, HttpResponse response) { |
- for (int i = 0; i < _handlers.length; i++) { |
- if (_handlers[i]._matcher(request)) { |
- Function handler = _handlers[i]._handler; |
- try { |
- handler(request, response); |
- } catch (e) { |
- if (_onError != null) { |
- _onError(e); |
- } else { |
- throw e; |
- } |
- } |
- return; |
- } |
- } |
- |
- if (_defaultHandler != null) { |
- _defaultHandler(request, response); |
- } else { |
- response.statusCode = HttpStatus.NOT_FOUND; |
- response.contentLength = 0; |
- response.outputStream.close(); |
- } |
- } |
- |
- _HttpSessionManager get _sessionManager { |
- // Lazy init. |
- if (_sessionManagerInstance == null) { |
- _sessionManagerInstance = new _HttpSessionManager(); |
- } |
- return _sessionManagerInstance; |
- } |
- |
- |
- ServerSocket _server; // The server listen socket. |
- bool _closeServer = false; |
- Set<_HttpConnection> _connections; // Set of currently connected clients. |
- List<_RequestHandlerRegistration> _handlers; |
- Object _defaultHandler; |
- Function _onError; |
- _HttpSessionManager _sessionManagerInstance; |
-} |
- |
- |
-class _HttpClientRequest |
- extends _HttpRequestResponseBase implements HttpClientRequest { |
- _HttpClientRequest(String this._method, |
- Uri this._uri, |
- _HttpClientConnection connection) |
- : super(connection) { |
- _connection = connection; |
- // Default GET and HEAD requests to have no content. |
- if (_method == "GET" || _method == "HEAD") { |
- _contentLength = 0; |
- } |
- } |
- |
- void set contentLength(int contentLength) { |
- if (_state >= HEADER_SENT) throw new HttpException("Header already sent"); |
- _contentLength = contentLength; |
- } |
- |
- List<Cookie> get cookies { |
- if (_cookies == null) _cookies = new List<Cookie>(); |
- return _cookies; |
- } |
- |
- OutputStream get outputStream { |
- if (_done) throw new HttpException("Request closed"); |
- if (_outputStream == null) { |
- _outputStream = new _HttpOutputStream(this); |
- } |
- return _outputStream; |
- } |
- |
- // Delegate functions for the HttpOutputStream implementation. |
- bool _streamWrite(List<int> buffer, bool copyBuffer) { |
- if (_done) throw new HttpException("Request closed"); |
- return _write(buffer, copyBuffer); |
- } |
- |
- bool _streamWriteFrom(List<int> buffer, int offset, int len) { |
- if (_done) throw new HttpException("Request closed"); |
- return _writeList(buffer, offset, len); |
- } |
- |
- void _streamFlush() { |
- _httpConnection._flush(); |
- } |
- |
- void _streamClose() { |
- _ensureHeadersSent(); |
- _state = DONE; |
- // Stop tracking no pending write events. |
- _httpConnection._onNoPendingWrites = null; |
- // Ensure that any trailing data is written. |
- _writeDone(); |
- } |
- |
- void _streamSetNoPendingWriteHandler(callback()) { |
- if (_state != DONE) { |
- _httpConnection._onNoPendingWrites = callback; |
- } |
- } |
- |
- void _streamSetCloseHandler(callback()) { |
- // TODO(sgjesse): Handle this. |
- } |
- |
- void _streamSetErrorHandler(callback(e)) { |
- _streamErrorHandler = callback; |
- } |
- |
- void _writeHeader() { |
- List<int> data; |
- |
- // Write request line. |
- data = _method.toString().charCodes; |
- _httpConnection._write(data); |
- _writeSP(); |
- // Send the path for direct connections and the whole URL for |
- // proxy connections. |
- if (!_connection._usingProxy) { |
- String path = _uri.path; |
- if (path.length == 0) path = "/"; |
- if (_uri.query != "") { |
- if (_uri.fragment != "") { |
- path = "${path}?${_uri.query}#${_uri.fragment}"; |
- } else { |
- path = "${path}?${_uri.query}"; |
- } |
- } |
- data = path.charCodes; |
- } else { |
- data = _uri.toString().charCodes; |
- } |
- _httpConnection._write(data); |
- _writeSP(); |
- _httpConnection._write(_Const.HTTP11); |
- _writeCRLF(); |
- |
- // Determine the value of the "Transfer-Encoding" header based on |
- // whether the content length is known. If there is no content |
- // neither "Content-Length" nor "Transfer-Encoding" is set. |
- if (_contentLength > 0) { |
- _headers.set(HttpHeaders.CONTENT_LENGTH, _contentLength.toString()); |
- } else if (_contentLength < 0) { |
- _headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
- } |
- |
- // Add the cookies to the headers. |
- if (_cookies != null) { |
- StringBuffer sb = new StringBuffer(); |
- for (int i = 0; i < _cookies.length; i++) { |
- if (i > 0) sb.add("; "); |
- sb.add(_cookies[i].name); |
- sb.add("="); |
- sb.add(_cookies[i].value); |
- } |
- _headers.add("cookie", sb.toString()); |
- } |
- |
- // Write headers. |
- _writeHeaders(); |
- _state = HEADER_SENT; |
- } |
- |
- String _method; |
- Uri _uri; |
- _HttpClientConnection _connection; |
- _HttpOutputStream _outputStream; |
- Function _streamErrorHandler; |
-} |
- |
- |
-class _HttpClientResponse |
- extends _HttpRequestResponseBase implements HttpClientResponse { |
- _HttpClientResponse(_HttpClientConnection connection) |
- : super(connection) { |
- _connection = connection; |
- } |
- |
- int get statusCode => _statusCode; |
- String get reasonPhrase => _reasonPhrase; |
- |
- bool get isRedirect { |
- return statusCode == HttpStatus.MOVED_PERMANENTLY || |
- statusCode == HttpStatus.FOUND || |
- statusCode == HttpStatus.SEE_OTHER || |
- statusCode == HttpStatus.TEMPORARY_REDIRECT; |
- } |
- |
- List<Cookie> get cookies { |
- if (_cookies != null) return _cookies; |
- _cookies = new List<Cookie>(); |
- List<String> values = _headers["set-cookie"]; |
- if (values != null) { |
- values.forEach((value) { |
- _cookies.add(new Cookie.fromSetCookieValue(value)); |
- }); |
- } |
- return _cookies; |
- } |
- |
- InputStream get inputStream { |
- if (_inputStream == null) { |
- _inputStream = new _HttpInputStream(this); |
- _inputStream._streamMarkedClosed = _dataEndCalled; |
- } |
- return _inputStream; |
- } |
- |
- void _onRequestStart(String method, String uri, String version) { |
- // TODO(sgjesse): Error handling |
- } |
- |
- void _onResponseStart(int statusCode, String reasonPhrase, String version) { |
- _statusCode = statusCode; |
- _reasonPhrase = reasonPhrase; |
- } |
- |
- void _onHeaderReceived(String name, String value) { |
- _headers.add(name, value); |
- } |
- |
- void _handleUnauthorized() { |
- |
- void retryRequest(_Credentials cr) { |
- if (cr != null) { |
- if (cr.scheme == _AuthenticationScheme.DIGEST) { |
- cr.nonce = header.parameters["nonce"]; |
- cr.algorithm = header.parameters["algorithm"]; |
- cr.qop = header.parameters["qop"]; |
- } |
- // Drain body and retry. |
- // TODO(sjgesse): Support digest. |
- if (cr.scheme == _AuthenticationScheme.BASIC) { |
- inputStream.onData = inputStream.read; |
- inputStream.onClosed = _connection.retry; |
- return; |
- } |
- } |
- |
- // Fall through to here to perform normal response handling if |
- // there is no sensible authorization handling. |
- if (_connection._onResponse != null) { |
- _connection._onResponse(this); |
- } |
- } |
- |
- // Only try to authenticate if there is a challenge in the response. |
- List<String> challenge = _headers[HttpHeaders.WWW_AUTHENTICATE]; |
- if (challenge != null && challenge.length == 1) { |
- _HeaderValue header = |
- new _HeaderValue.fromString(challenge[0], parameterSeparator: ","); |
- _AuthenticationScheme scheme = |
- new _AuthenticationScheme.fromString(header.value); |
- String realm = header.parameters["realm"]; |
- |
- // See if any credentials are available. |
- _Credentials cr = |
- _connection._client._findCredentials( |
- _connection._request._uri, scheme); |
- |
- // Ask for more credentials if none found or the one found has |
- // already been used. If it has already been used it must now be |
- // invalid and is removed. |
- if (cr == null || cr.used) { |
- if (cr != null) { |
- _connection._client._removeCredentials(cr); |
- } |
- cr = null; |
- if (_connection._client._authenticate != null) { |
- Future authComplete = |
- _connection._client._authenticate( |
- _connection._request._uri, scheme.toString(), realm); |
- authComplete.then((credsAvailable) { |
- if (credsAvailable) { |
- cr = _connection._client._findCredentials( |
- _connection._request._uri, scheme); |
- retryRequest(cr); |
- } else { |
- if (_connection._onResponse != null) { |
- _connection._onResponse(this); |
- } |
- } |
- }); |
- return; |
- } |
- } else { |
- // If credentials found prepare for retrying the request. |
- retryRequest(cr); |
- return; |
- } |
- } |
- |
- // Fall through to here to perform normal response handling if |
- // there is no sensible authorization handling. |
- if (_connection._onResponse != null) { |
- _connection._onResponse(this); |
- } |
- } |
- |
- void _onHeadersComplete() { |
- // Get parsed content length. |
- _contentLength = _httpConnection._httpParser.contentLength; |
- |
- // Prepare for receiving data. |
- _headers._mutable = false; |
- _buffer = new _BufferList(); |
- |
- if (isRedirect && _connection.followRedirects) { |
- if (_connection._redirects == null || |
- _connection._redirects.length < _connection.maxRedirects) { |
- // Check the location header. |
- List<String> location = headers[HttpHeaders.LOCATION]; |
- if (location == null || location.length > 1) { |
- throw new RedirectException("Invalid redirect", |
- _connection._redirects); |
- } |
- // Check for redirect loop |
- if (_connection._redirects != null) { |
- Uri redirectUrl = new Uri.fromString(location[0]); |
- for (int i = 0; i < _connection._redirects.length; i++) { |
- if (_connection._redirects[i].location.toString() == |
- redirectUrl.toString()) { |
- throw new RedirectLoopException(_connection._redirects); |
- } |
- } |
- } |
- // Drain body and redirect. |
- inputStream.onData = inputStream.read; |
- inputStream.onClosed = _connection.redirect; |
- } else { |
- throw new RedirectLimitExceededException(_connection._redirects); |
- } |
- } else if (statusCode == HttpStatus.UNAUTHORIZED) { |
- _handleUnauthorized(); |
- } else if (_connection._onResponse != null) { |
- _connection._onResponse(this); |
- } |
- } |
- |
- void _onDataReceived(List<int> data) { |
- _buffer.add(data); |
- if (_inputStream != null) _inputStream._dataReceived(); |
- } |
- |
- void _onDataEnd() { |
- _connection._responseDone(); |
- if (_inputStream != null) _inputStream._closeReceived(); |
- _dataEndCalled = true; |
- } |
- |
- // Delegate functions for the HttpInputStream implementation. |
- int _streamAvailable() { |
- return _buffer.length; |
- } |
- |
- List<int> _streamRead(int bytesToRead) { |
- return _buffer.readBytes(bytesToRead); |
- } |
- |
- int _streamReadInto(List<int> buffer, int offset, int len) { |
- List<int> data = _buffer.readBytes(len); |
- buffer.setRange(offset, data.length, data); |
- return data.length; |
- } |
- |
- void _streamSetErrorHandler(callback(e)) { |
- _streamErrorHandler = callback; |
- } |
- |
- int _statusCode; |
- String _reasonPhrase; |
- |
- _HttpClientConnection _connection; |
- _HttpInputStream _inputStream; |
- _BufferList _buffer; |
- bool _dataEndCalled = false; |
- |
- Function _streamErrorHandler; |
-} |
- |
- |
-class _HttpClientConnection |
- extends _HttpConnectionBase implements HttpClientConnection { |
- _HttpClientConnection(_HttpClient this._client); |
- |
- void _connectionEstablished(_SocketConnection socketConn) { |
- super._connectionEstablished(socketConn._socket); |
- _socketConn = socketConn; |
- // Register HTTP parser callbacks. |
- _httpParser.requestStart = |
- (method, uri, version) => _onRequestStart(method, uri, version); |
- _httpParser.responseStart = |
- (statusCode, reasonPhrase, version) => |
- _onResponseStart(statusCode, reasonPhrase, version); |
- _httpParser.headerReceived = |
- (name, value) => _onHeaderReceived(name, value); |
- _httpParser.headersComplete = () => _onHeadersComplete(); |
- _httpParser.dataReceived = (data) => _onDataReceived(data); |
- _httpParser.dataEnd = (closed) => _onDataEnd(closed); |
- _httpParser.error = (e) => _onError(e); |
- } |
- |
- void _responseDone() { |
- if (_closing) { |
- if (_socket != null) { |
- _socket.close(); |
- } |
- } else { |
- _client._returnSocketConnection(_socketConn); |
- } |
- _socket = null; |
- _socketConn = null; |
- } |
- |
- HttpClientRequest open(String method, Uri uri) { |
- _method = method; |
- // Tell the HTTP parser the method it is expecting a response to. |
- _httpParser.responseToMethod = method; |
- _request = new _HttpClientRequest(method, uri, this); |
- _response = new _HttpClientResponse(this); |
- return _request; |
- } |
- |
- DetachedSocket detachSocket() { |
- return _detachSocket(); |
- } |
- |
- void _onConnectionClosed(e) { |
- // Socket is closed either due to an error or due to normal socket close. |
- if (e != null) { |
- if (_onErrorCallback != null) { |
- _onErrorCallback(e); |
- } else { |
- throw e; |
- } |
- } |
- _closing = true; |
- if (e != null) { |
- // Propagate the error to the streams. |
- if (_response != null && _response._streamErrorHandler != null) { |
- _response._streamErrorHandler(e); |
- } |
- _responseDone(); |
- } else { |
- // If there was no socket error the socket was closed |
- // normally. Indicate closing to the HTTP Parser as there might |
- // still be an HTTP error. |
- _httpParser.connectionClosed(); |
- } |
- } |
- |
- void _onRequestStart(String method, String uri, String version) { |
- // TODO(sgjesse): Error handling. |
- } |
- |
- void _onResponseStart(int statusCode, String reasonPhrase, String version) { |
- _response._onResponseStart(statusCode, reasonPhrase, version); |
- } |
- |
- void _onHeaderReceived(String name, String value) { |
- _response._onHeaderReceived(name, value); |
- } |
- |
- void _onHeadersComplete() { |
- _response._onHeadersComplete(); |
- } |
- |
- void _onDataReceived(List<int> data) { |
- _response._onDataReceived(data); |
- } |
- |
- void _onDataEnd(bool close) { |
- if (close) _closing = true; |
- _response._onDataEnd(); |
- } |
- |
- void set onRequest(void handler(HttpClientRequest request)) { |
- _onRequest = handler; |
- } |
- |
- void set onResponse(void handler(HttpClientResponse response)) { |
- _onResponse = handler; |
- } |
- |
- void set onError(void callback(e)) { |
- _onErrorCallback = callback; |
- } |
- |
- void retry() { |
- if (_socketConn != null) { |
- throw new HttpException("Cannot retry with body data pending"); |
- } |
- // Retry the URL using the same connection instance. |
- _client._openUrl(_method, _request._uri, this); |
- } |
- |
- void redirect([String method, Uri url]) { |
- if (_socketConn != null) { |
- throw new HttpException("Cannot redirect with body data pending"); |
- } |
- if (method == null) method = _method; |
- if (url == null) { |
- url = new Uri.fromString(_response.headers.value(HttpHeaders.LOCATION)); |
- } |
- if (_redirects == null) { |
- _redirects = new List<_RedirectInfo>(); |
- } |
- _redirects.add(new _RedirectInfo(_response.statusCode, method, url)); |
- _request = null; |
- _response = null; |
- // Open redirect URL using the same connection instance. |
- _client._openUrl(method, url, this); |
- } |
- |
- List<RedirectInfo> get redirects => _redirects; |
- |
- Function _onRequest; |
- Function _onResponse; |
- Function _onErrorCallback; |
- |
- _HttpClient _client; |
- _SocketConnection _socketConn; |
- HttpClientRequest _request; |
- HttpClientResponse _response; |
- String _method; |
- bool _usingProxy; |
- |
- // Redirect handling |
- bool followRedirects = true; |
- int maxRedirects = 5; |
- List<_RedirectInfo> _redirects; |
- |
- // Callbacks. |
- var requestReceived; |
-} |
- |
- |
-// Class for holding keep-alive sockets in the cache for the HTTP |
-// client together with the connection information. |
-class _SocketConnection { |
- _SocketConnection(String this._host, |
- int this._port, |
- Socket this._socket); |
- |
- void _markReturned() { |
- _socket.onData = null; |
- _socket.onClosed = null; |
- _socket.onError = null; |
- _returnTime = new Date.now(); |
- } |
- |
- Duration _idleTime(Date now) => now.difference(_returnTime); |
- |
- int get hashCode => _socket.hashCode; |
- |
- String _host; |
- int _port; |
- Socket _socket; |
- Date _returnTime; |
-} |
- |
-class _ProxyConfiguration { |
- static const String PROXY_PREFIX = "PROXY "; |
- static const String DIRECT_PREFIX = "DIRECT"; |
- |
- _ProxyConfiguration(String configuration) : proxies = new List<_Proxy>() { |
- if (configuration == null) { |
- throw new HttpException("Invalid proxy configuration $configuration"); |
- } |
- List<String> list = configuration.split(";"); |
- list.forEach((String proxy) { |
- proxy = proxy.trim(); |
- if (!proxy.isEmpty) { |
- if (proxy.startsWith(PROXY_PREFIX)) { |
- int colon = proxy.indexOf(":"); |
- if (colon == -1 || colon == 0 || colon == proxy.length - 1) { |
- throw new HttpException( |
- "Invalid proxy configuration $configuration"); |
- } |
- // Skip the "PROXY " prefix. |
- String host = proxy.substring(PROXY_PREFIX.length, colon).trim(); |
- String portString = proxy.substring(colon + 1).trim(); |
- int port; |
- try { |
- port = int.parse(portString); |
- } on FormatException catch (e) { |
- throw new HttpException( |
- "Invalid proxy configuration $configuration, " |
- "invalid port '$portString'"); |
- } |
- proxies.add(new _Proxy(host, port)); |
- } else if (proxy.trim() == DIRECT_PREFIX) { |
- proxies.add(new _Proxy.direct()); |
- } else { |
- throw new HttpException("Invalid proxy configuration $configuration"); |
- } |
- } |
- }); |
- } |
- |
- const _ProxyConfiguration.direct() |
- : proxies = const [const _Proxy.direct()]; |
- |
- final List<_Proxy> proxies; |
-} |
- |
-class _Proxy { |
- const _Proxy(this.host, this.port) : isDirect = false; |
- const _Proxy.direct() : host = null, port = null, isDirect = true; |
- |
- final String host; |
- final int port; |
- final bool isDirect; |
-} |
- |
-class _HttpClient implements HttpClient { |
- static const int DEFAULT_EVICTION_TIMEOUT = 60000; |
- |
- _HttpClient() : _openSockets = new Map(), |
- _activeSockets = new Set(), |
- credentials = new List<_Credentials>(), |
- _shutdown = false; |
- |
- HttpClientConnection open( |
- String method, String host, int port, String path) { |
- // TODO(sgjesse): The path set here can contain both query and |
- // fragment. They should be cracked and set correctly. |
- return _open(method, new Uri.fromComponents( |
- scheme: "http", domain: host, port: port, path: path)); |
- } |
- |
- HttpClientConnection _open(String method, |
- Uri uri, |
- [_HttpClientConnection connection]) { |
- if (_shutdown) throw new HttpException("HttpClient shutdown"); |
- if (method == null || uri.domain.isEmpty) { |
- throw new ArgumentError(null); |
- } |
- return _prepareHttpClientConnection(method, uri, connection); |
- } |
- |
- HttpClientConnection openUrl(String method, Uri url) { |
- return _openUrl(method, url); |
- } |
- |
- HttpClientConnection _openUrl(String method, |
- Uri url, |
- [_HttpClientConnection connection]) { |
- if (url.scheme != "http") { |
- throw new HttpException("Unsupported URL scheme ${url.scheme}"); |
- } |
- return _open(method, url, connection); |
- } |
- |
- HttpClientConnection get(String host, int port, String path) { |
- return open("GET", host, port, path); |
- } |
- |
- HttpClientConnection getUrl(Uri url) => _openUrl("GET", url); |
- |
- HttpClientConnection post(String host, int port, String path) { |
- return open("POST", host, port, path); |
- } |
- |
- HttpClientConnection postUrl(Uri url) => _openUrl("POST", url); |
- |
- set authenticate(bool f(Uri url, String scheme, String realm)) { |
- _authenticate = f; |
- } |
- |
- void addCredentials( |
- Uri url, String realm, HttpClientCredentials cr) { |
- credentials.add(new _Credentials(url, realm, cr)); |
- } |
- |
- set findProxy(String f(Uri uri)) => _findProxy = f; |
- |
- void shutdown() { |
- _openSockets.forEach((String key, Queue<_SocketConnection> connections) { |
- while (!connections.isEmpty) { |
- _SocketConnection socketConn = connections.removeFirst(); |
- socketConn._socket.close(); |
- } |
- }); |
- _activeSockets.forEach((_SocketConnection socketConn) { |
- socketConn._socket.close(); |
- }); |
- if (_evictionTimer != null) _cancelEvictionTimer(); |
- _shutdown = true; |
- } |
- |
- void _cancelEvictionTimer() { |
- _evictionTimer.cancel(); |
- _evictionTimer = null; |
- } |
- |
- String _connectionKey(String host, int port) { |
- return "$host:$port"; |
- } |
- |
- HttpClientConnection _prepareHttpClientConnection( |
- String method, |
- Uri url, |
- [_HttpClientConnection connection]) { |
- |
- void _establishConnection(String host, |
- int port, |
- _ProxyConfiguration proxyConfiguration, |
- int proxyIndex) { |
- |
- void _connectionOpened(_SocketConnection socketConn, |
- _HttpClientConnection connection, |
- bool usingProxy) { |
- connection._usingProxy = usingProxy; |
- connection._connectionEstablished(socketConn); |
- HttpClientRequest request = connection.open(method, url); |
- request.headers.host = host; |
- request.headers.port = port; |
- if (url.userInfo != null && !url.userInfo.isEmpty) { |
- // If the URL contains user information use that for basic |
- // authorization |
- _UTF8Encoder encoder = new _UTF8Encoder(); |
- String auth = |
- CryptoUtils.bytesToBase64(encoder.encodeString(url.userInfo)); |
- request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth"); |
- } else { |
- // Look for credentials. |
- _Credentials cr = _findCredentials(url); |
- if (cr != null) { |
- cr.authorize(request); |
- } |
- } |
- if (connection._onRequest != null) { |
- connection._onRequest(request); |
- } else { |
- request.outputStream.close(); |
- } |
- } |
- |
- assert(proxyIndex < proxyConfiguration.proxies.length); |
- |
- // Determine the actual host to connect to. |
- String connectHost; |
- int connectPort; |
- _Proxy proxy = proxyConfiguration.proxies[proxyIndex]; |
- if (proxy.isDirect) { |
- connectHost = host; |
- connectPort = port; |
- } else { |
- connectHost = proxy.host; |
- connectPort = proxy.port; |
- } |
- |
- // If there are active connections for this key get the first one |
- // otherwise create a new one. |
- String key = _connectionKey(connectHost, connectPort); |
- Queue socketConnections = _openSockets[key]; |
- if (socketConnections == null || socketConnections.isEmpty) { |
- Socket socket = new Socket(connectHost, connectPort); |
- // Until the connection is established handle connection errors |
- // here as the HttpClientConnection object is not yet associated |
- // with the socket. |
- socket.onError = (e) { |
- proxyIndex++; |
- if (proxyIndex < proxyConfiguration.proxies.length) { |
- // Try the next proxy in the list. |
- _establishConnection(host, port, proxyConfiguration, proxyIndex); |
- } else { |
- // Report the error through the HttpClientConnection object to |
- // the client. |
- connection._onError(e); |
- } |
- }; |
- socket.onConnect = () { |
- // When the connection is established, clear the error |
- // callback as it will now be handled by the |
- // HttpClientConnection object which will be associated with |
- // the connected socket. |
- socket.onError = null; |
- _SocketConnection socketConn = |
- new _SocketConnection(connectHost, connectPort, socket); |
- _activeSockets.add(socketConn); |
- _connectionOpened(socketConn, connection, !proxy.isDirect); |
- }; |
- } else { |
- _SocketConnection socketConn = socketConnections.removeFirst(); |
- _activeSockets.add(socketConn); |
- new Timer(0, (ignored) => |
- _connectionOpened(socketConn, connection, !proxy.isDirect)); |
- |
- // Get rid of eviction timer if there are no more active connections. |
- if (socketConnections.isEmpty) _openSockets.remove(key); |
- if (_openSockets.isEmpty) _cancelEvictionTimer(); |
- } |
- } |
- |
- // Find the TCP host and port. |
- String host = url.domain; |
- int port = url.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : url.port; |
- |
- // Create a new connection object if we are not re-using an existing one. |
- if (connection == null) { |
- connection = new _HttpClientConnection(this); |
- } |
- connection.onDetach = () => _activeSockets.remove(connection._socketConn); |
- |
- // Check to see if a proxy server should be used for this connection. |
- _ProxyConfiguration proxyConfiguration = const _ProxyConfiguration.direct(); |
- if (_findProxy != null) { |
- // TODO(sgjesse): Keep a map of these as normally only a few |
- // configuration strings will be used. |
- proxyConfiguration = new _ProxyConfiguration(_findProxy(url)); |
- } |
- |
- // Establish the connection starting with the first proxy configured. |
- _establishConnection(host, port, proxyConfiguration, 0); |
- |
- return connection; |
- } |
- |
- void _returnSocketConnection(_SocketConnection socketConn) { |
- // Mark socket as returned to unregister from the old connection. |
- socketConn._markReturned(); |
- |
- // If the HTTP client is beeing shutdown don't return the connection. |
- if (_shutdown) { |
- socketConn._socket.close(); |
- return; |
- }; |
- |
- String key = _connectionKey(socketConn._host, socketConn._port); |
- |
- // Get or create the connection list for this key. |
- Queue sockets = _openSockets[key]; |
- if (sockets == null) { |
- sockets = new Queue(); |
- _openSockets[key] = sockets; |
- } |
- |
- // If there is currently no eviction timer start one. |
- if (_evictionTimer == null) { |
- void _handleEviction(Timer timer) { |
- Date now = new Date.now(); |
- List<String> emptyKeys = new List<String>(); |
- _openSockets.forEach( |
- void _(String key, Queue<_SocketConnection> connections) { |
- // As returned connections are added at the head of the |
- // list remove from the tail. |
- while (!connections.isEmpty) { |
- _SocketConnection socketConn = connections.last; |
- if (socketConn._idleTime(now).inMilliseconds > |
- DEFAULT_EVICTION_TIMEOUT) { |
- connections.removeLast(); |
- socketConn._socket.close(); |
- if (connections.isEmpty) emptyKeys.add(key); |
- } else { |
- break; |
- } |
- } |
- }); |
- |
- // Remove the keys for which here are no more open connections. |
- emptyKeys.forEach((String key) => _openSockets.remove(key)); |
- |
- // If all connections where evicted cancel the eviction timer. |
- if (_openSockets.isEmpty) _cancelEvictionTimer(); |
- } |
- _evictionTimer = new Timer.repeating(10000, _handleEviction); |
- } |
- |
- // Return connection. |
- _activeSockets.remove(socketConn); |
- sockets.addFirst(socketConn); |
- } |
- |
- _Credentials _findCredentials(Uri url, [_AuthenticationScheme scheme]) { |
- // Look for credentials. |
- _Credentials cr = |
- credentials.reduce(null, (_Credentials prev, _Credentials value) { |
- if (value.applies(url, scheme)) { |
- if (prev == null) return value; |
- return value.uri.path.length > prev.uri.path.length ? value : prev; |
- } else { |
- return prev; |
- } |
- }); |
- return cr; |
- } |
- |
- void _removeCredentials(_Credentials cr) { |
- int index = credentials.indexOf(cr); |
- if (index != -1) { |
- credentials.removeAt(index); |
- } |
- } |
- |
- Function _onOpen; |
- Map<String, Queue<_SocketConnection>> _openSockets; |
- Set<_SocketConnection> _activeSockets; |
- List<_Credentials> credentials; |
- Timer _evictionTimer; |
- Function _findProxy; |
- Function _authenticate; |
- bool _shutdown; // Has this HTTP client been shutdown? |
-} |
- |
- |
-class _HttpConnectionInfo implements HttpConnectionInfo { |
- String remoteHost; |
- int remotePort; |
- int localPort; |
-} |
- |
- |
-class _DetachedSocket implements DetachedSocket { |
- _DetachedSocket(this._socket, this._unparsedData); |
- Socket get socket => _socket; |
- List<int> get unparsedData => _unparsedData; |
- Socket _socket; |
- List<int> _unparsedData; |
-} |
- |
- |
-class _AuthenticationScheme { |
- static const UNKNOWN = const _AuthenticationScheme(-1); |
- static const BASIC = const _AuthenticationScheme(0); |
- static const DIGEST = const _AuthenticationScheme(1); |
- |
- const _AuthenticationScheme(this._scheme); |
- |
- factory _AuthenticationScheme.fromString(String scheme) { |
- if (scheme.toLowerCase() == "basic") return BASIC; |
- if (scheme.toLowerCase() == "digest") return DIGEST; |
- return UNKNOWN; |
- } |
- |
- String toString() { |
- if (this == BASIC) return "Basic"; |
- if (this == DIGEST) return "Digest"; |
- return "Unknown"; |
- } |
- |
- final int _scheme; |
-} |
- |
- |
-class _Credentials { |
- _Credentials(this.uri, this.realm, this.credentials); |
- |
- _AuthenticationScheme get scheme => credentials.scheme; |
- |
- bool applies(Uri uri, _AuthenticationScheme scheme) { |
- if (scheme != null && credentials.scheme != scheme) return false; |
- if (uri.domain != this.uri.domain) return false; |
- int thisPort = |
- this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port; |
- int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; |
- if (otherPort != thisPort) return false; |
- return uri.path.startsWith(this.uri.path); |
- } |
- |
- void authorize(HttpClientRequest request) { |
- credentials.authorize(this, request); |
- used = true; |
- } |
- |
- bool used = false; |
- Uri uri; |
- String realm; |
- HttpClientCredentials credentials; |
- |
- // Digest specific fields. |
- String nonce; |
- String algorithm; |
- String qop; |
-} |
- |
- |
-class _HttpClientCredentials implements HttpClientCredentials { |
- abstract _AuthenticationScheme get scheme; |
- abstract void authorize(HttpClientRequest request); |
-} |
- |
- |
-class _HttpClientBasicCredentials implements HttpClientBasicCredentials { |
- _HttpClientBasicCredentials(this.username, |
- this.password); |
- |
- _AuthenticationScheme get scheme => _AuthenticationScheme.BASIC; |
- |
- void authorize(_Credentials _, HttpClientRequest request) { |
- // There is no mentioning of username/password encoding in RFC |
- // 2617. However there is an open draft for adding an additional |
- // accept-charset parameter to the WWW-Authenticate and |
- // Proxy-Authenticate headers, see |
- // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For |
- // now always use UTF-8 encoding. |
- _UTF8Encoder encoder = new _UTF8Encoder(); |
- String auth = |
- CryptoUtils.bytesToBase64(encoder.encodeString( |
- "$username:$password")); |
- request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth"); |
- } |
- |
- String username; |
- String password; |
-} |
- |
- |
-class _HttpClientDigestCredentials implements HttpClientDigestCredentials { |
- _HttpClientDigestCredentials(this.username, |
- this.password); |
- |
- _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST; |
- |
- void authorize(_Credentials credentials, HttpClientRequest request) { |
- // TODO(sgjesse): Implement!!! |
- throw new UnsupportedOperationException(); |
- } |
- |
- String username; |
- String password; |
-} |
- |
- |
- |
-class _RedirectInfo implements RedirectInfo { |
- const _RedirectInfo(int this.statusCode, |
- String this.method, |
- Uri this.location); |
- final int statusCode; |
- final String method; |
- final Uri location; |
-} |