close method

  1. @override
Future<HttpClientResponse> close()
override

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