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

Unified 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, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: runtime/bin/http_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;
-}

Powered by Google App Engine
This is Rietveld 408576698