serveFile method

  1. @override
Future<bool> serveFile(
  1. File file,
  2. FileStat stat,
  3. RequestContext req,
  4. ResponseContext res,
)
override

Writes the contents of a file to a response.

Implementation

@override
Future<bool> serveFile(
    File file, FileStat stat, RequestContext req, ResponseContext res) {
  res.headers['accept-ranges'] = 'bytes';

  if (onlyInProduction == true && req.app?.environment.isProduction != true) {
    return super.serveFile(file, stat, req, res);
  }

  if (req.headers == null) {
    _log.severe('Missing headers in the RequestContext');
    throw ArgumentError('Missing headers in the RequestContext');
  }
  var reqHeaders = req.headers!;

  var shouldNotCache = noCache == true;

  if (!shouldNotCache) {
    shouldNotCache = reqHeaders.value('cache-control') == 'no-cache' ||
        reqHeaders.value('pragma') == 'no-cache';
  }

  if (shouldNotCache) {
    res.headers['cache-control'] = 'private, max-age=0, no-cache';
    return super.serveFile(file, stat, req, res);
  } else {
    var ifModified = reqHeaders.ifModifiedSince;
    var ifRange = false;

    try {
      if (reqHeaders.value('if-range') != null) {
        ifModified = HttpDate.parse(reqHeaders.value('if-range')!);
        ifRange = true;
      }
    } catch (_) {
      // Fail silently...
    }

    if (ifModified != null) {
      try {
        var ifModifiedSince = ifModified;

        if (ifModifiedSince.compareTo(stat.modified) >= 0) {
          res.statusCode = 304;
          setCachedHeaders(stat.modified, req, res);

          if (useEtags && _etags.containsKey(file.absolute.path)) {
            if (_etags[file.absolute.path] != null) {
              res.headers['ETag'] = _etags[file.absolute.path]!;
            }
          }

          if (ifRange) {
            // Send the 206 like normal
            res.statusCode = 206;
            return super.serveFile(file, stat, req, res);
          }

          return Future.value(false);
        } else if (ifRange) {
          return super.serveFile(file, stat, req, res);
        }
      } catch (_) {
        _log.severe(
            'Invalid date for ${ifRange ? 'if-range' : 'if-not-modified-since'} header.');
        throw AngelHttpException.badRequest(
            message:
                'Invalid date for ${ifRange ? 'if-range' : 'if-not-modified-since'} header.');
      }
    }

    // If-modified didn't work; try etags

    if (useEtags == true) {
      var etagsToMatchAgainst = reqHeaders['if-none-match'] ?? [];
      ifRange = false;

      if (etagsToMatchAgainst.isEmpty) {
        etagsToMatchAgainst = reqHeaders['if-range'] ?? [];
        ifRange = etagsToMatchAgainst.isNotEmpty;
      }

      if (etagsToMatchAgainst.isNotEmpty) {
        var hasBeenModified = false;

        for (var etag in etagsToMatchAgainst) {
          if (etag == '*') {
            hasBeenModified = true;
          } else {
            hasBeenModified = !_etags.containsKey(file.absolute.path) ||
                _etags[file.absolute.path] != etag;
          }
        }

        if (!ifRange) {
          if (!hasBeenModified) {
            res.statusCode = 304;
            setCachedHeaders(stat.modified, req, res);
            return Future.value(false);
          }
        } else {
          return super.serveFile(file, stat, req, res);
        }
      }
    }

    return file.lastModified().then((stamp) {
      if (useEtags) {
        res.headers['ETag'] = _etags[file.absolute.path] =
            stamp.millisecondsSinceEpoch.toString();
      }

      setCachedHeaders(stat.modified, req, res);
      return super.serveFile(file, stat, req, res);
    });
  }
}