Chromium Code Reviews| Index: runtime/bin/http_impl.dart |
| diff --git a/runtime/bin/http_impl.dart b/runtime/bin/http_impl.dart |
| index 784ad824070f285038a447b988ce56804dd4596f..e74acb54769ae55776469eedec2f79717c16e431 100644 |
| --- a/runtime/bin/http_impl.dart |
| +++ b/runtime/bin/http_impl.dart |
| @@ -2,17 +2,174 @@ |
| // 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]; |
| + } |
| + |
| + void add(String name, Object value) { |
| + if (value is List) { |
| + for (int i = 0; i < value.length; i++) { |
| + _add(name, vlaie[i]); |
| + } |
| + } else { |
| + _add(name, value); |
| + } |
| + } |
| + |
| + void set(String name, Object value) { |
| + removeAll(name); |
| + add(name, value); |
| + } |
| + |
| + void remove(String name, Object value) { |
| + 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) { |
| + name = name.toLowerCase(); |
| + _headers.remove(name); |
| + } |
| + |
| + String get host() => _host; |
| + void set host(String host) { |
| + _host = host; |
| + _updateHostHeader(); |
| + } |
| + |
| + int get port() => _port; |
| + void set port(int port) { |
| + _port = port; |
| + _updateHostHeader(); |
| + } |
| + |
| + Date get expires() { |
| + if (_expires == null) { |
| + List<String> values = _headers["expires"]; |
| + if (values != null) { |
| + _expires = _HttpUtils.parseDate(values[0]); |
| + } |
| + } |
| + return _expires; |
| + } |
| + |
| + void set expires(Date expires) { |
| + _expires = expires; |
| + // Format "Expires" header with date in Greenwich Mean Time (GMT). |
| + String formatted = |
| + _HttpUtils.formatDate(_expires.changeTimeZone(new TimeZone.utc())); |
| + _set("expires", formatted); |
| + } |
| + |
| + void _add(String name, Object value) { |
| + // TODO(sgjesse): Add immutable state throw HttpException is immutable. |
| + if (name.toLowerCase() == "expires") { |
| + if (value is Date) { |
| + expires = value; |
| + } else if (value is String) { |
| + expires = _HttpUtils.parseDate(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 { |
| + _host = value.substring(0, pos); |
| + if (pos + 1 == value.length) { |
| + _port = HttpClient.DEFAULT_HTTP_PORT; |
| + } else { |
| + _port = Math.parseInt(value.substring(pos + 1)); |
| + } |
| + } |
| + _updateHostHeader(); |
| + } else { |
| + name = name.toLowerCase(); |
| + List<String> values = _headers[name]; |
| + if (values == null) { |
| + values = new List<String>(); |
| + _headers[name] = values; |
| + } |
| + 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); |
| + } |
| + |
| + _updateHostHeader() { |
| + String portPart = _port == HttpClient.DEFAULT_HTTP_PORT ? "" : ":$_port"; |
| + _set("host", "$host$portPart"); |
| + } |
| + |
| + _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) { |
| + List<int> data; |
| + data = name.charCodes(); |
| + connection._write(data); |
| + connection._write(COLONSP); |
| + for (int i = 0; i < values.length; i++) { |
| + if (i > 0) { |
| + connection._write(COMMASP); |
| + } |
| + data = values[i].charCodes(); |
| + connection._write(data); |
| + } |
| + connection._write(CRLF); |
| + }); |
| + } |
| + |
| + String toString() { |
|
Anders Johnsen
2012/04/16 11:50:46
Looks good, and yes, let's use \n for line feed he
Søren Gjesse
2012/04/16 12:17:31
Good catch. Added return of sb.toString().
|
| + StringBuffer sb = new StringBuffer(); |
| + _headers.forEach((String name, List<String> values) { |
| + sb.add(name); |
| + sb.add(": "); |
| + for (int i = 0; i < values.length; i++) { |
| + if (i > 0) { |
| + sb.add(": "); |
| + } |
| + sb.add(values[i]); |
| + } |
| + sb.add("\n"); |
| + }); |
| + } |
| + |
| + Map<String, List<String>> _headers; |
| + |
| + String _host; |
| + int _port; |
| + Date _expires; |
| +} |
| + |
| + |
| class _HttpRequestResponseBase { |
| _HttpRequestResponseBase(_HttpConnectionBase this._httpConnection) |
| : _contentLength = -1, |
| - _headers = new Map(); |
| + _headers = new _HttpHeaders(); |
| int get contentLength() => _contentLength; |
| - Map get headers() => _headers; |
| - |
| - void _setHeader(String name, String value) { |
| - _headers[name.toLowerCase()] = value; |
| - } |
| + HttpHeaders get headers() => _headers; |
| bool _write(List<int> data, bool copyBuffer) { |
| bool allWritten = true; |
| @@ -56,18 +213,7 @@ class _HttpRequestResponseBase { |
| } |
| bool _writeHeaders() { |
| - List<int> data; |
| - |
| - // Format headers. |
| - _headers.forEach((String name, String value) { |
| - data = name.charCodes(); |
| - _httpConnection._write(data); |
| - data = ": ".charCodes(); |
| - _httpConnection._write(data); |
| - data = value.charCodes(); |
| - _httpConnection._write(data); |
| - _writeCRLF(); |
| - }); |
| + _headers._write(_httpConnection); |
| // Terminate header. |
| return _writeCRLF(); |
| } |
| @@ -97,7 +243,7 @@ class _HttpRequestResponseBase { |
| } |
| _HttpConnectionBase _httpConnection; |
| - Map<String, String> _headers; |
| + _HttpHeaders _headers; |
| // Length of the content body. If this is set to -1 (default value) |
| // when starting to send data chunked transfer encoding will be |
| @@ -130,7 +276,7 @@ class _HttpRequest extends _HttpRequestResponseBase implements HttpRequest { |
| } |
| void _onHeaderReceived(String name, String value) { |
| - _setHeader(name, value); |
| + _headers.add(name, value); |
| } |
| void _onHeadersComplete() { |
| @@ -219,27 +365,6 @@ class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse { |
| _reasonPhrase = reasonPhrase; |
| } |
| - Date get expires() => _expires; |
| - void set expires(Date expires) { |
| - if (_outputStream != null) throw new HttpException("Header already sent"); |
| - _expires = expires; |
| - // Format "Expires" header with date in Greenwich Mean Time (GMT). |
| - String formatted = |
| - _HttpUtils.formatDate(_expires.changeTimeZone(new TimeZone.utc())); |
| - _setHeader("Expires", formatted); |
| - } |
| - |
| - // Set a header on the response. NOTE: If the same header is set |
| - // more than once only the last one will be part of the response. |
| - void setHeader(String name, String value) { |
| - if (_outputStream != null) return new HttpException("Header already sent"); |
| - if (name.toLowerCase() == "expires") { |
| - expires = _HttpUtils.parseDate(value); |
| - } else { |
| - _setHeader(name, value); |
| - } |
| - } |
| - |
| OutputStream get outputStream() { |
| if (_state == DONE) throw new HttpException("Response closed"); |
| if (_outputStream == null) { |
| @@ -363,16 +488,16 @@ class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse { |
| // Determine the value of the "Connection" header. |
| if (_protocolVersion == "1.1" && !_persistentConnection) { |
| - setHeader("Connection", "close"); |
| + _headers.set("Connection", "close"); |
| } else if (_protocolVersion == "1.0" && _persistentConnection) { |
| - setHeader("Connection", "keep-alive"); |
| + _headers.set("Connection", "keep-alive"); |
| } |
| // Determine the value of the "Transfer-Encoding" header based on |
| // whether the content length is known. |
| if (_contentLength >= 0) { |
| - setHeader("Content-Length", _contentLength.toString()); |
| + _headers.set("Content-Length", _contentLength.toString()); |
| } else { |
| - setHeader("Transfer-Encoding", "chunked"); |
| + _headers.set("Transfer-Encoding", "chunked"); |
| } |
| // Write headers. |
| @@ -385,7 +510,6 @@ class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse { |
| int _statusCode; |
| String _reasonPhrase; |
| String _protocolVersion; |
| - Date _expires; |
| bool _persistentConnection; |
| _HttpOutputStream _outputStream; |
| int _state; |
| @@ -733,39 +857,6 @@ class _HttpClientRequest |
| void set contentLength(int contentLength) => _contentLength = contentLength; |
| - String get host() => _host; |
| - void set host(String host) { |
| - _host = host; |
| - _updateHostHeader(); |
| - } |
| - |
| - int get port() => _port; |
| - void set port(int port) { |
| - _port = port; |
| - _updateHostHeader(); |
| - } |
| - |
| - void setHeader(String name, String value) { |
| - if (_state != START) throw new HttpException("Header already sent"); |
| - if (name.toLowerCase() == "host") { |
| - int pos = value.indexOf(":"); |
| - if (pos == -1) { |
| - _host = value; |
| - _port = HttpClient.DEFAULT_HTTP_PORT; |
| - } else { |
| - _host = value.substring(0, pos); |
| - if (pos + 1 == value.length) { |
| - _port = HttpClient.DEFAULT_HTTP_PORT; |
| - } else { |
| - _port = Math.parseInt(value.substring(pos + 1)); |
| - } |
| - } |
| - _updateHostHeader(); |
| - return; |
| - } |
| - _setHeader(name, value); |
| - } |
| - |
| OutputStream get outputStream() { |
| if (_state == DONE) throw new HttpException("Request closed"); |
| if (_outputStream == null) { |
| @@ -778,11 +869,6 @@ class _HttpClientRequest |
| return _outputStream; |
| } |
| - _updateHostHeader() { |
| - String portPart = _port == HttpClient.DEFAULT_HTTP_PORT ? "" : ":$_port"; |
| - _setHeader("Host", "$host$portPart"); |
| - } |
| - |
| // Delegate functions for the HttpOutputStream implementation. |
| bool _streamWrite(List<int> buffer, bool copyBuffer) { |
| return _write(buffer, copyBuffer); |
| @@ -830,9 +916,9 @@ class _HttpClientRequest |
| // Determine the value of the "Transfer-Encoding" header based on |
| // whether the content length is known. |
| if (_contentLength >= 0) { |
| - setHeader("Content-Length", _contentLength.toString()); |
| + _headers.set("Content-Length", _contentLength.toString()); |
| } else { |
| - setHeader("Transfer-Encoding", "chunked"); |
| + _headers.set("Transfer-Encoding", "chunked"); |
| } |
| // Write headers. |
| @@ -842,8 +928,6 @@ class _HttpClientRequest |
| String _method; |
| String _uri; |
| - String _host; |
| - int _port; |
| _HttpClientConnection _connection; |
| _HttpOutputStream _outputStream; |
| int _state; |
| @@ -861,14 +945,6 @@ class _HttpClientResponse |
| int get statusCode() => _statusCode; |
| String get reasonPhrase() => _reasonPhrase; |
| - Date get expires() { |
| - String str = _headers["expires"]; |
| - if (str == null) return null; |
| - return _HttpUtils.parseDate(str); |
| - } |
| - |
| - Map get headers() => _headers; |
| - |
| InputStream get inputStream() { |
| if (_inputStream == null) { |
| _inputStream = new _HttpInputStream(this); |
| @@ -886,7 +962,7 @@ class _HttpClientResponse |
| } |
| void _onHeaderReceived(String name, String value) { |
| - _setHeader(name, value); |
| + _headers.add(name, value); |
| } |
| void _onHeadersComplete() { |
| @@ -1149,8 +1225,8 @@ class _HttpClient implements HttpClient { |
| _HttpClientConnection connection) { |
| connection._connectionEstablished(socketConn); |
| HttpClientRequest request = connection.open(method, path); |
| - request.host = host; |
| - request.port = port; |
| + request.headers.host = host; |
| + request.headers.port = port; |
| if (connection._onRequest != null) { |
| connection._onRequest(request); |
| } else { |