serveFile method

void serveFile (
  1. File file,
  2. 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 requests 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();
  });
}