mirror of
https://github.com/dart-lang/sdk
synced 2024-09-23 06:33:30 +00:00
Add handling of HTTP header "Expires"
The value of the "Expires" header is now available as a property on the HttpResponse and the HttpClientResponse. Added formatting of dates as HTTP dates in RFC 1123 format and parsing of HTTP dates in all three formats mentioned in RFC 2616. R=ajohnsen@google.com BUG=none TEST=tests/standalone/src/io/HttpDateTest.dart Review URL: https://chromiumcodereview.appspot.com//9602011 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@5016 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
93eecd778e
commit
144e6d044c
|
@ -176,6 +176,12 @@ interface HttpResponse default _HttpResponse {
|
|||
*/
|
||||
String reasonPhrase;
|
||||
|
||||
/**
|
||||
* Gets and sets the expiry date. The value of this property will
|
||||
* reflect the "Expires" header
|
||||
*/
|
||||
Date expires;
|
||||
|
||||
/**
|
||||
* Sets a header on the response. NOTE: If the same header name is
|
||||
* set more than once only the last value will be part of the
|
||||
|
@ -365,6 +371,14 @@ interface HttpClientResponse default _HttpClientResponse {
|
|||
*/
|
||||
bool get keepAlive();
|
||||
|
||||
/**
|
||||
* Returns the date value for the "Expires" header. Returns null if
|
||||
* the response has no "Expires" header. Throws a HttpException if
|
||||
* the response has an "Expires" header which is not formatted as a
|
||||
* valid HTTP date.
|
||||
*/
|
||||
Date get expires();
|
||||
|
||||
/**
|
||||
* Returns the response headers.
|
||||
*/
|
||||
|
|
|
@ -61,7 +61,7 @@ class _HttpRequestResponseBase {
|
|||
Map get headers() => _headers;
|
||||
|
||||
void _setHeader(String name, String value) {
|
||||
_headers[name] = value;
|
||||
_headers[name.toLowerCase()] = value;
|
||||
}
|
||||
|
||||
bool _write(List<int> data, bool copyBuffer) {
|
||||
|
@ -280,11 +280,25 @@ 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");
|
||||
_setHeader(name, value);
|
||||
if (name.toLowerCase() == "expires") {
|
||||
expires = _HttpUtils.parseDate(value);
|
||||
} else {
|
||||
_setHeader(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream get outputStream() {
|
||||
|
@ -431,6 +445,7 @@ class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse {
|
|||
// Response status code.
|
||||
int _statusCode;
|
||||
String _reasonPhrase;
|
||||
Date _expires;
|
||||
_HttpOutputStream _outputStream;
|
||||
int _state;
|
||||
}
|
||||
|
@ -766,7 +781,7 @@ class _HttpClientRequest
|
|||
|
||||
_updateHostHeader() {
|
||||
String portPart = _port == HttpClient.DEFAULT_HTTP_PORT ? "" : ":$_port";
|
||||
_setHeader("host", "$host$portPart");
|
||||
_setHeader("Host", "$host$portPart");
|
||||
}
|
||||
|
||||
// Delegate functions for the HttpOutputStream implementation.
|
||||
|
@ -855,6 +870,14 @@ 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);
|
||||
|
|
|
@ -59,4 +59,172 @@ class _HttpUtils {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// From RFC 2616 section "3.3.1 Full Date"
|
||||
// HTTP-date = rfc1123-date | rfc850-date | asctime-date
|
||||
// rfc1123-date = wkday "," SP date1 SP time SP "GMT"
|
||||
// rfc850-date = weekday "," SP date2 SP time SP "GMT"
|
||||
// asctime-date = wkday SP date3 SP time SP 4DIGIT
|
||||
// date1 = 2DIGIT SP month SP 4DIGIT
|
||||
// ; day month year (e.g., 02 Jun 1982)
|
||||
// date2 = 2DIGIT "-" month "-" 2DIGIT
|
||||
// ; day-month-year (e.g., 02-Jun-82)
|
||||
// date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
|
||||
// ; month day (e.g., Jun 2)
|
||||
// time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
|
||||
// ; 00:00:00 - 23:59:59
|
||||
// wkday = "Mon" | "Tue" | "Wed"
|
||||
// | "Thu" | "Fri" | "Sat" | "Sun"
|
||||
// weekday = "Monday" | "Tuesday" | "Wednesday"
|
||||
// | "Thursday" | "Friday" | "Saturday" | "Sunday"
|
||||
// month = "Jan" | "Feb" | "Mar" | "Apr"
|
||||
// | "May" | "Jun" | "Jul" | "Aug"
|
||||
// | "Sep" | "Oct" | "Nov" | "Dec"
|
||||
|
||||
// Format as RFC 1123 date.
|
||||
static String formatDate(Date date) {
|
||||
List wkday = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||
List month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
Date d = date.changeTimeZone(new TimeZone.utc());
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.add(wkday[d.weekday]);
|
||||
sb.add(", ");
|
||||
sb.add(d.day.toString());
|
||||
sb.add(" ");
|
||||
sb.add(month[d.month - 1]);
|
||||
sb.add(" ");
|
||||
sb.add(d.year.toString());
|
||||
d.hours < 9 ? sb.add(" 0") : sb.add(" ");
|
||||
sb.add(d.hours.toString());
|
||||
d.minutes < 9 ? sb.add(":0") : sb.add(":");
|
||||
sb.add(d.minutes.toString());
|
||||
d.seconds < 9 ? sb.add(":0") : sb.add(":");
|
||||
sb.add(d.seconds.toString());
|
||||
sb.add(" GMT");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static Date parseDate(String date) {
|
||||
final int SP = 32;
|
||||
List wkdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||
List weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday",
|
||||
"Friday", "Saturday", "Sunday"];
|
||||
List months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
final int formatRfc1123 = 0;
|
||||
final int formatRfc850 = 1;
|
||||
final int formatAsctime = 2;
|
||||
|
||||
int index = 0;
|
||||
String tmp;
|
||||
int format;
|
||||
|
||||
void expect(String s) {
|
||||
if (date.length - index < s.length) {
|
||||
throw new HttpException("Invalid HTTP date $date");
|
||||
}
|
||||
String tmp = date.substring(index, index + s.length);
|
||||
if (tmp != s) {
|
||||
throw new HttpException("Invalid HTTP date $date");
|
||||
}
|
||||
index += s.length;
|
||||
}
|
||||
|
||||
int expectWeekday() {
|
||||
int weekday;
|
||||
// The formatting of the weekday signals the format of the date string.
|
||||
int pos = date.indexOf(",", index);
|
||||
if (pos == -1) {
|
||||
int pos = date.indexOf(" ", index);
|
||||
if (pos == -1) throw new HttpException("Invalid HTTP date $date");
|
||||
tmp = date.substring(index, pos);
|
||||
index = pos + 1;
|
||||
weekday = wkdays.indexOf(tmp);
|
||||
if (weekday != -1) {
|
||||
format = formatAsctime;
|
||||
return weekday;
|
||||
}
|
||||
} else {
|
||||
tmp = date.substring(index, pos);
|
||||
index = pos + 1;
|
||||
weekday = wkdays.indexOf(tmp);
|
||||
if (weekday != -1) {
|
||||
format = formatRfc1123;
|
||||
return weekday;
|
||||
}
|
||||
weekday = weekdays.indexOf(tmp);
|
||||
if (weekday != -1) {
|
||||
format = formatRfc850;
|
||||
return weekday;
|
||||
}
|
||||
}
|
||||
throw new HttpException("Invalid HTTP date $date");
|
||||
}
|
||||
|
||||
int expectMonth(String separator) {
|
||||
int pos = date.indexOf(separator, index);
|
||||
if (pos - index != 3) throw new HttpException("Invalid HTTP date $date");
|
||||
tmp = date.substring(index, pos);
|
||||
index = pos + 1;
|
||||
int month = months.indexOf(tmp);
|
||||
if (month != -1) return month;
|
||||
throw new HttpException("Invalid HTTP date $date");
|
||||
}
|
||||
|
||||
int expectNum(String separator) {
|
||||
int pos;
|
||||
if (separator.length > 0) {
|
||||
pos = date.indexOf(separator, index);
|
||||
} else {
|
||||
pos = date.length;
|
||||
}
|
||||
String tmp = date.substring(index, pos);
|
||||
index = pos + separator.length;
|
||||
try {
|
||||
int value = Math.parseInt(tmp);
|
||||
return value;
|
||||
} catch (BadNumberFormatException e) {
|
||||
throw new HttpException("Invalid HTTP date $date");
|
||||
}
|
||||
}
|
||||
|
||||
void expectEnd() {
|
||||
if (index != date.length) {
|
||||
throw new HttpException("Invalid HTTP date $date");
|
||||
}
|
||||
}
|
||||
|
||||
int weekday = expectWeekday();
|
||||
int day;
|
||||
int month;
|
||||
int year;
|
||||
int hours;
|
||||
int minutes;
|
||||
int seconds;
|
||||
if (format == formatAsctime) {
|
||||
month = expectMonth(" ");
|
||||
if (date.charCodeAt(index) == SP) index++;
|
||||
day = expectNum(" ");
|
||||
hours = expectNum(":");
|
||||
minutes = expectNum(":");
|
||||
seconds = expectNum(" ");
|
||||
year = expectNum("");
|
||||
} else {
|
||||
expect(" ");
|
||||
day = expectNum(format == formatRfc1123 ? " " : "-");
|
||||
month = expectMonth(format == formatRfc1123 ? " " : "-");
|
||||
year = expectNum(" ");
|
||||
hours = expectNum(":");
|
||||
minutes = expectNum(":");
|
||||
seconds = expectNum(" ");
|
||||
expect("GMT");
|
||||
}
|
||||
expectEnd();
|
||||
TimeZone utc = new TimeZone.utc();
|
||||
return new Date.withTimeZone(
|
||||
year, month + 1, day, hours, minutes, seconds, 0, utc);
|
||||
}
|
||||
}
|
||||
|
|
91
tests/standalone/src/io/HttpDateTest.dart
Normal file
91
tests/standalone/src/io/HttpDateTest.dart
Normal file
|
@ -0,0 +1,91 @@
|
|||
// 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.
|
||||
|
||||
#source("../../../../runtime/bin/http_utils.dart");
|
||||
|
||||
class HttpException implements Exception {
|
||||
const HttpException([String this.message = ""]);
|
||||
String toString() => "HttpException: $message";
|
||||
final String message;
|
||||
}
|
||||
|
||||
void testParseHttpDate() {
|
||||
TimeZone utc = new TimeZone.utc();
|
||||
Date date;
|
||||
date = new Date.withTimeZone(1999, Date.JUN, 11, 18, 46, 53, 0, utc);
|
||||
Expect.equals(date, _HttpUtils.parseDate("Fri, 11 Jun 1999 18:46:53 GMT"));
|
||||
Expect.equals(date, _HttpUtils.parseDate("Friday, 11-Jun-1999 18:46:53 GMT"));
|
||||
Expect.equals(date, _HttpUtils.parseDate("Fri Jun 11 18:46:53 1999"));
|
||||
|
||||
date = new Date.withTimeZone(1970, Date.JAN, 1, 0, 0, 0, 0, utc);
|
||||
Expect.equals(date, _HttpUtils.parseDate("Thu, 1 Jan 1970 00:00:00 GMT"));
|
||||
Expect.equals(date,
|
||||
_HttpUtils.parseDate("Thursday, 1-Jan-1970 00:00:00 GMT"));
|
||||
Expect.equals(date, _HttpUtils.parseDate("Thu Jan 1 00:00:00 1970"));
|
||||
|
||||
date = new Date.withTimeZone(2012, Date.MAR, 5, 23, 59, 59, 0, utc);
|
||||
Expect.equals(date, _HttpUtils.parseDate("Mon, 5 Mar 2012 23:59:59 GMT"));
|
||||
Expect.equals(date, _HttpUtils.parseDate("Monday, 5-Mar-2012 23:59:59 GMT"));
|
||||
Expect.equals(date, _HttpUtils.parseDate("Mon Mar 5 23:59:59 2012"));
|
||||
}
|
||||
|
||||
void testFormatParseHttpDate() {
|
||||
test(int year,
|
||||
int month,
|
||||
int day,
|
||||
int hours,
|
||||
int minutes,
|
||||
int seconds,
|
||||
String expectedFormatted) {
|
||||
TimeZone utc = new TimeZone.utc();
|
||||
Date date;
|
||||
String formatted;
|
||||
date = new Date.withTimeZone(
|
||||
year, month, day, hours, minutes, seconds, 0, utc);
|
||||
formatted = _HttpUtils.formatDate(date);
|
||||
Expect.equals(expectedFormatted, formatted);
|
||||
Expect.equals(date, _HttpUtils.parseDate(formatted));
|
||||
}
|
||||
|
||||
test(1999, Date.JUN, 11, 18, 46, 53, "Fri, 11 Jun 1999 18:46:53 GMT");
|
||||
test(1970, Date.JAN, 1, 0, 0, 0, "Thu, 1 Jan 1970 00:00:00 GMT");
|
||||
test(2012, Date.MAR, 5, 23, 59, 59, "Mon, 5 Mar 2012 23:59:59 GMT");
|
||||
}
|
||||
|
||||
void testParseHttpDateFailures() {
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate("");
|
||||
});
|
||||
String valid = "Mon, 5 Mar 2012 23:59:59 GMT";
|
||||
for (int i = 1; i < valid.length - 1; i++) {
|
||||
String tmp = valid.substring(0, i);
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate(tmp);
|
||||
});
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate(" $tmp");
|
||||
});
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate(" $tmp ");
|
||||
});
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate("$tmp ");
|
||||
});
|
||||
}
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate(" $valid");
|
||||
});
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate(" $valid ");
|
||||
});
|
||||
Expect.throws(() {
|
||||
_HttpUtils.parseDate("$valid ");
|
||||
});
|
||||
}
|
||||
|
||||
void main() {
|
||||
testParseHttpDate();
|
||||
testFormatParseHttpDate();
|
||||
testParseHttpDateFailures();
|
||||
}
|
|
@ -124,13 +124,33 @@ class TestServer extends Isolate {
|
|||
response.outputStream.close();
|
||||
}
|
||||
|
||||
// Return a 301 with a custom reason phrase.
|
||||
// Check the "Host" header.
|
||||
void _hostHandler(HttpRequest request, HttpResponse response) {
|
||||
Expect.equals("www.dartlang.org:1234", request.headers["host"]);
|
||||
response.statusCode = HttpStatus.OK;
|
||||
response.outputStream.close();
|
||||
}
|
||||
|
||||
// Set the "Expires" header using the expires property.
|
||||
void _expires1Handler(HttpRequest request, HttpResponse response) {
|
||||
Date date =
|
||||
new Date.withTimeZone(
|
||||
1999, Date.JUN, 11, 18, 46, 53, 0, new TimeZone.utc());
|
||||
response.expires = date;
|
||||
Expect.equals(date, response.expires);
|
||||
response.outputStream.close();
|
||||
}
|
||||
|
||||
// Set the "Expires" header.
|
||||
void _expires2Handler(HttpRequest request, HttpResponse response) {
|
||||
response.setHeader("Expires", "Fri, 11 Jun 1999 18:46:53 GMT");
|
||||
Date date =
|
||||
new Date.withTimeZone(
|
||||
1999, Date.JUN, 11, 18, 46, 53, 0, new TimeZone.utc());
|
||||
Expect.equals(date, response.expires);
|
||||
response.outputStream.close();
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Setup request handlers.
|
||||
_requestHandlers = new Map();
|
||||
|
@ -149,6 +169,14 @@ class TestServer extends Isolate {
|
|||
(HttpRequest request, HttpResponse response) {
|
||||
_hostHandler(request, response);
|
||||
};
|
||||
_requestHandlers["/expires1"] =
|
||||
(HttpRequest request, HttpResponse response) {
|
||||
_expires1Handler(request, response);
|
||||
};
|
||||
_requestHandlers["/expires2"] =
|
||||
(HttpRequest request, HttpResponse response) {
|
||||
_expires2Handler(request, response);
|
||||
};
|
||||
|
||||
this.port.receive((var message, SendPort replyTo) {
|
||||
if (message.isStart) {
|
||||
|
@ -445,6 +473,39 @@ void testHost() {
|
|||
testServerMain.start();
|
||||
}
|
||||
|
||||
void testExpires() {
|
||||
TestServerMain testServerMain = new TestServerMain();
|
||||
testServerMain.setServerStartedHandler((int port) {
|
||||
int responses = 0;
|
||||
HttpClient httpClient = new HttpClient();
|
||||
|
||||
void processResponse(HttpClientResponse response) {
|
||||
Expect.equals(HttpStatus.OK, response.statusCode);
|
||||
Expect.equals("Fri, 11 Jun 1999 18:46:53 GMT",
|
||||
response.headers["expires"]);
|
||||
Expect.equals(
|
||||
new Date.withTimeZone(
|
||||
1999, Date.JUN, 11, 18, 46, 53, 0, new TimeZone.utc()),
|
||||
response.expires);
|
||||
responses++;
|
||||
if (responses == 2) {
|
||||
httpClient.shutdown();
|
||||
testServerMain.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
HttpClientConnection conn1 = httpClient.get("127.0.0.1", port, "/expires1");
|
||||
conn1.onResponse = (HttpClientResponse response) {
|
||||
processResponse(response);
|
||||
};
|
||||
HttpClientConnection conn2 = httpClient.get("127.0.0.1", port, "/expires2");
|
||||
conn2.onResponse = (HttpClientResponse response) {
|
||||
processResponse(response);
|
||||
};
|
||||
});
|
||||
testServerMain.start();
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
testStartStop();
|
||||
|
@ -458,4 +519,5 @@ void main() {
|
|||
test404();
|
||||
testReasonPhrase();
|
||||
testHost();
|
||||
testExpires();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue