serveFile method
Writes the contents of a file to a response.
Implementation
Future<bool> serveFile(
File file, FileStat stat, RequestContext req, ResponseContext res) async {
res.headers['accept-ranges'] = 'bytes';
if (callback != null) {
return await req.app?.executeHandler(
(RequestContext req, ResponseContext res) =>
callback!(file, req, res),
req,
res) ??
true;
}
var type =
app.mimeTypeResolver.lookup(file.path) ?? 'application/octet-stream';
res.headers['accept-ranges'] = 'bytes';
_ensureContentTypeAllowed(type, req);
res.headers['accept-ranges'] = 'bytes';
res.contentType = MediaType.parse(type);
if (useBuffer == true) res.useBuffer();
if (req.headers == null) {
_log.severe('Missing headers in the RequestContext');
throw ArgumentError('Missing headers in the RequestContext');
}
var reqHeaders = req.headers!;
if (reqHeaders.value('range')?.startsWith('bytes=') != true) {
await res.streamFile(file);
} else {
var header = RangeHeader.parse(reqHeaders.value('range')!);
var items = RangeHeader.foldItems(header.items);
header = RangeHeader(items);
var totalFileSize = await file.length();
for (var item in header.items) {
var invalid = false;
if (item.start != -1) {
invalid = item.end != -1 && item.end < item.start;
} else {
invalid = item.end == -1;
}
if (invalid) {
throw AngelHttpException(
//Exception('Semantically invalid, or unbounded range.'),
errors: ['Semantically invalid, or unbounded range.'],
statusCode: 416,
message: 'Semantically invalid, or unbounded range.');
}
// Ensure it's within range.
if (item.start >= totalFileSize || item.end >= totalFileSize) {
throw AngelHttpException(
//Exception('Given range $item is out of bounds.'),
errors: ['Given range $item is out of bounds.'],
statusCode: 416,
message: 'Given range $item is out of bounds.');
}
}
if (header.items.isEmpty) {
throw AngelHttpException(
statusCode: 416, message: '`Range` header may not be empty.');
} else if (header.items.length == 1) {
var item = header.items[0];
Stream<List<int>> stream;
var len = 0;
var total = totalFileSize;
if (item.start == -1) {
if (item.end == -1) {
len = total;
stream = file.openRead();
} else {
len = item.end + 1;
stream = file.openRead(0, item.end + 1);
}
} else {
if (item.end == -1) {
len = total - item.start;
stream = file.openRead(item.start);
} else {
len = item.end - item.start + 1;
stream = file.openRead(item.start, item.end + 1);
}
}
res.contentType = MediaType.parse(
app.mimeTypeResolver.lookup(file.path) ??
'application/octet-stream');
res.statusCode = 206;
res.headers['content-length'] = len.toString();
res.headers['content-range'] = 'bytes ${item.toContentRange(total)}';
await stream.cast<List<int>>().pipe(res);
return false;
} else {
var transformer = RangeHeaderTransformer(
header,
app.mimeTypeResolver.lookup(file.path) ??
'application/octet-stream',
await file.length());
res.statusCode = 206;
res.headers['content-length'] =
transformer.computeContentLength(totalFileSize).toString();
res.contentType = MediaType(
'multipart', 'byteranges', {'boundary': transformer.boundary});
await file
.openRead()
.cast<List<int>>()
.transform(transformer)
.pipe(res);
return false;
}
}
return false;
}