close method
Close the request for input. Returns the value of done.
Implementation
@override
Future<HttpClientResponse> close() async {
int? contextId = KrakenHttpOverrides.getContextHeader(headers);
HttpClientRequest request = this;
if (contextId != null) {
// Standard reference: https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.2
// Most general-purpose user agents do not send the
// Referer header field when the referring resource is a local "file" or
// "data" URI. A user agent MUST NOT send a Referer header field in an
// unsecured HTTP request if the referring page was received with a
// secure protocol.
Uri referrer = getEntrypointUri(contextId);
bool isUnsafe = referrer.isScheme('https') && !uri.isScheme('https');
bool isLocalRequest = uri.isScheme('file') || uri.isScheme('data') || uri.isScheme('assets');
if (!isUnsafe && !isLocalRequest) {
headers.set(HttpHeaders.refererHeader, referrer.toString());
}
// Standard reference: https://fetch.spec.whatwg.org/#origin-header
// `if request’s method is neither `GET` nor `HEAD`, then follow referrer policy to append origin.`
// @TODO: Apply referrer policy.
String origin = getOrigin(referrer);
if (method != 'GET' && method != 'HEAD') {
headers.set(_HttpHeadersOrigin, origin);
}
HttpClientInterceptor? clientInterceptor;
if (_httpOverrides.hasInterceptor(contextId)) {
clientInterceptor = _httpOverrides.getInterceptor(contextId);
}
// Step 1: Handle request.
if (clientInterceptor != null) {
request = await _beforeRequest(clientInterceptor, request) ?? request;
}
// Step 2: Handle cache-control and expires,
// if hit, no need to open request.
HttpCacheObject? cacheObject;
if (HttpCacheController.mode != HttpCacheMode.NO_CACHE) {
HttpCacheController cacheController = HttpCacheController.instance(origin);
cacheObject = await cacheController.getCacheObject(request.uri);
if (cacheObject.hitLocalCache(request)) {
HttpClientResponse? cacheResponse = await cacheObject.toHttpClientResponse();
if (cacheResponse != null) {
return cacheResponse;
}
}
// Step 3: Handle negotiate cache request header.
if (cacheObject.valid && headers.ifModifiedSince == null && headers.value(HttpHeaders.ifNoneMatchHeader) == null) {
// ETag has higher priority of lastModified.
if (cacheObject.eTag != null) {
headers.set(HttpHeaders.ifNoneMatchHeader, cacheObject.eTag!);
} else if (cacheObject.lastModified != null) {
headers.set(HttpHeaders.ifModifiedSinceHeader, HttpDate.format(cacheObject.lastModified!));
}
}
}
request = await _createBackendClientRequest();
// Send the real data to backend client.
if (_data.isNotEmpty) {
await request.addStream(Stream.value(_data));
_data.clear();
}
// Step 4: Lifecycle of shouldInterceptRequest
HttpClientResponse? response;
if (clientInterceptor != null) {
response = await _shouldInterceptRequest(clientInterceptor, request);
}
bool hitInterceptorResponse = response != null;
bool hitNegotiateCache = false;
// If cache only, but no cache hit, throw error directly.
if (HttpCacheController.mode == HttpCacheMode.CACHE_ONLY
&& response == null) {
throw FlutterError('HttpCacheMode is CACHE_ONLY, but no cache hit for $uri');
}
// After this, response should not be null.
if (!hitInterceptorResponse) {
// Handle 304 here.
final HttpClientResponse rawResponse = await _requestQueue.add(request.close);
response = cacheObject == null
? rawResponse
: await HttpCacheController.instance(origin).interceptResponse(request, rawResponse, cacheObject);
hitNegotiateCache = rawResponse != response;
}
// Step 5: Lifecycle of afterResponse.
if (clientInterceptor != null) {
final HttpClientResponse? interceptorResponse = await _afterResponse(clientInterceptor, request, response);
if (interceptorResponse != null) {
hitInterceptorResponse = true;
response = interceptorResponse;
}
}
// Check match cache, and then return cache.
if (hitInterceptorResponse || hitNegotiateCache) {
return Future.value(response);
}
if (cacheObject != null) {
// Step 6: Intercept response by cache controller (handle 304).
// Note: No need to negotiate cache here, this is final response, hit or not hit.
return HttpCacheController.instance(origin).interceptResponse(request, response, cacheObject);
} else {
return response;
}
} else {
request = await _createBackendClientRequest();
// Not using request.add, because large data will cause core exception.
if (_data.isNotEmpty) {
await request.addStream(Stream.value(_data));
_data.clear();
}
}
return _requestQueue.add(request.close);
}