serveFile method

void serveFile(
  1. File file,
  2. HttpRequest request
)

Serve the content of file to request.

Can be used in overrides of directoryHandler to redirect to an 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 requests response is closed with error-code HttpStatus.notFound.

Implementation

void serveFile(File file, HttpRequest request) async {
  var response = request.response;
  // TODO(ajohnsen): Set up Zone support for these errors.
  try {
    var lastModified = await file.lastModified();
    if (request.headers.ifModifiedSince != null &&
        !lastModified.isAfter(request.headers.ifModifiedSince!)) {
      response.statusCode = HttpStatus.notModified;
      await response.close();
      return null;
    }

    response.headers.set(HttpHeaders.lastModifiedHeader, lastModified);
    response.headers.set(HttpHeaders.acceptRangesHeader, 'bytes');

    var length = await file.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;
            await response.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') {
            await response.close();
          } else {
            try {
              await file
                  .openRead(start, end + 1)
                  .cast<List<int>>()
                  .pipe(_VirtualDirectoryFileStream(response, file.path));
            } catch (_) {
              // TODO(kevmoo): log errors
            }
          }
          return;
        }
      }
    }

    response.headers.set(HttpHeaders.contentLengthHeader, length);
    if (request.method == 'HEAD') {
      await response.close();
    } else {
      try {
        await file
            .openRead()
            .cast<List<int>>()
            .pipe(_VirtualDirectoryFileStream(response, file.path));
      } catch (_) {
        // TODO(kevmoo): log errors
      }
    }
  } catch (_) {
    response.statusCode = HttpStatus.notFound;
    await response.close();
  }
}