onRequest method
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);
}