onRequest method

  1. @override
Future<void> onRequest(
  1. RequestOptions options,
  2. RequestInterceptorHandler handler
)

Called when the request is about to be sent.

Implementation

@override
Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
  final uri = options.uri;

  // Check if this is a Fetch/XHR request and save to extras for tracking
  final isFetchRequest = options.headers['X-WebF-Request-Type'] == 'fetch';
  if (isFetchRequest) {
    options.extra['webf_is_xhr'] = true;
  }

  // Save context ID to extras for internal use
  if (contextId != null) {
    options.extra['webf_context_id'] = contextId;
  }

  // Remove internal WebF headers that shouldn't be sent to the server
  options.headers.remove('X-WebF-Request-Type');
  options.headers.remove(httpHeaderContext); // Remove x-context header

  // Attach Referer/Origin based on entrypoint
  if (contextId != null) {
    final referrer = getEntrypointUri(contextId);
    final isLocalRequest = uri.isScheme('file') || uri.isScheme('data') || uri.isScheme('assets');
    final isUnsafe = referrer.isScheme('https') && !uri.isScheme('https');
    if (!isLocalRequest && !isUnsafe) {
      options.headers[HttpHeaders.refererHeader] = referrer.toString();
    }
    if (options.method != 'GET' && options.method != 'HEAD') {
      options.headers['origin'] = getOrigin(referrer);
    }
  }

  // If caller marks XHR via header/extra, leave it as-is.

  // Load cookies
  final cookies = <Cookie>[];
  await CookieManager.loadForRequest(uri, cookies);
  if (cookies.isNotEmpty) {
    options.headers[HttpHeaders.cookieHeader] = cookies.map((c) => '${c.name}=${c.value}').join('; ');
  } else {
    options.headers[HttpHeaders.cookieHeader] = '';
  }

  // Cache negotiation
  final ref = contextId != null ? getEntrypointUri(contextId) : uri;
  final origin = getOrigin(ref);
  options.extra[_kOriginKey] = origin;

  // Determine cache enablement by controller override or global mode.
  final ctrl = contextId != null ? WebFController.getControllerOfJSContextId(contextId) : null;
  final bool controllerWantsCache = ctrl?.networkOptions?.effectiveEnableHttpCache == true;
  final bool controllerForbidsCache = ctrl?.networkOptions?.effectiveEnableHttpCache == false;
  final bool globalCacheOn = (useWebFHttpCache && HttpCacheController.mode != HttpCacheMode.NO_CACHE);
  final bool cacheEnabled = controllerForbidsCache ? false : (controllerWantsCache ? true : globalCacheOn);

  if (cacheEnabled) {
    final controller = HttpCacheController.instance(origin);
    final cacheObject = await controller.getCacheObject(uri);
    options.extra[_kCacheObjectKey] = cacheObject;

    if (cacheObject.hitLocalCache(_DummyRequest(options))) {
      // Serve from local cache immediately.
      final native = HttpClient();
      final cached = await cacheObject.toHttpClientResponse(native);
      if (cached != null) {
        final bytes = await consolidateHttpClientResponseBytes(cached);
        final headers = <String, List<String>>{};
        cached.headers.forEach((k, v) => headers[k] = List<String>.from(v));
        options.extra[_kCacheHitKey] = true;
        return handler.resolve(
          Response<Uint8List>(
            requestOptions: options,
            data: Uint8List.fromList(bytes),
            statusCode: cached.statusCode,
            statusMessage: '',
            headers: Headers.fromMap(headers),
          ),
        );
      }
    }

    // Add negotiation headers when needed
    if (cacheObject.valid &&
        options.headers[HttpHeaders.ifNoneMatchHeader] == null &&
        options.headers[HttpHeaders.ifModifiedSinceHeader] == null) {
      if (cacheObject.eTag != null) {
        options.headers[HttpHeaders.ifNoneMatchHeader] = cacheObject.eTag!;
      } else if (cacheObject.lastModified != null) {
        options.headers[HttpHeaders.ifModifiedSinceHeader] = HttpDate.format(cacheObject.lastModified!);
      }
    }
  }

  handler.next(options);
}