serveFile method
- File file,
- HttpRequest request
Serve the content of file
to request
.
This is useful when e.g. overriding directoryHandler to redirect to some index file.
In the request contains the HttpHeaders.ifModifiedSince
header,
serveFile will send a HttpStatus.notModified
response if the file
was not changed.
Note that if it was unable to read from file
, the request
s response
is closed with error-code HttpStatus.notFound
.
Implementation
void serveFile(File file, HttpRequest request) {
var response = request.response;
// TODO(ajohnsen): Set up Zone support for these errors.
file.lastModified().then((lastModified) {
if (request.headers.ifModifiedSince != null &&
!lastModified.isAfter(request.headers.ifModifiedSince)) {
response.statusCode = HttpStatus.notModified;
response.close();
return null;
}
response.headers.set(HttpHeaders.lastModifiedHeader, lastModified);
response.headers.set(HttpHeaders.acceptRangesHeader, "bytes");
return file.length().then((length) {
var range = request.headers.value(HttpHeaders.rangeHeader);
if (range != null) {
// We only support one range, where the standard support several.
var matches = RegExp(r"^bytes=(\d*)\-(\d*)$").firstMatch(range);
// If the range header have the right format, handle it.
if (matches != null &&
(matches[1].isNotEmpty || matches[2].isNotEmpty)) {
// Serve sub-range.
int start; // First byte position - inclusive.
int end; // Last byte position - inclusive.
if (matches[1].isEmpty) {
start = length - int.parse(matches[2]);
if (start < 0) start = 0;
end = length - 1;
} else {
start = int.parse(matches[1]);
end = matches[2].isEmpty ? length - 1 : int.parse(matches[2]);
}
// If the range is syntactically invalid the Range header
// MUST be ignored (RFC 2616 section 14.35.1).
if (start <= end) {
if (end >= length) {
end = length - 1;
}
if (start >= length) {
response
..statusCode = HttpStatus.requestedRangeNotSatisfiable
..close();
return;
}
// Override Content-Length with the actual bytes sent.
response.headers
.set(HttpHeaders.contentLengthHeader, end - start + 1);
// Set 'Partial Content' status code.
response
..statusCode = HttpStatus.partialContent
..headers.set(HttpHeaders.contentRangeHeader,
'bytes $start-$end/$length');
// Pipe the 'range' of the file.
if (request.method == 'HEAD') {
response.close();
} else {
file
.openRead(start, end + 1)
.cast<List<int>>()
.pipe(_VirtualDirectoryFileStream(response, file.path))
.catchError((_) {
// TODO(kevmoo): log errors
});
}
return;
}
}
}
response.headers.set(HttpHeaders.contentLengthHeader, length);
if (request.method == 'HEAD') {
response.close();
} else {
file
.openRead()
.cast<List<int>>()
.pipe(_VirtualDirectoryFileStream(response, file.path))
.catchError((_) {
// TODO(kevmoo): log errors
});
}
});
}).catchError((_) {
response.statusCode = HttpStatus.notFound;
response.close();
});
}