send method

  1. @override
Future<StreamedResponse> send(
  1. BaseRequest request
)

Sends an HTTP request and asynchronously returns the response.

Implementers should call BaseRequest.finalize to get the body of the request as a ByteStream. They shouldn't make any assumptions about the state of the stream; it could have data written to it asynchronously at a later point, or it could already be closed when it's returned. Any internal HTTP errors should be wrapped as ClientExceptions.

Implementation

@override
Future<StreamedResponse> send(BaseRequest request) async {
  // The expected success case flow (without redirects) is:
  // 1. send is called by BaseClient
  // 2. send starts the request with UrlSession.dataTaskWithRequest and waits
  //    on a Completer
  // 3. _onResponse is called with the HTTP headers, status code, etc.
  // 4. _onResponse calls complete on the Completer that send is waiting on.
  // 5. send continues executing and returns a StreamedResponse.
  //    StreamedResponse contains a Stream<UInt8List>.
  // 6. _onData is called one or more times and adds that to the
  //    StreamController that controls the Stream<UInt8List>
  // 7. _onComplete is called after all the data is read and closes the
  //    StreamController
  if (_urlSession == null) {
    throw ClientException(
        'HTTP request failed. Client is already closed.', request.url);
  }
  final urlSession = _urlSession!;

  final stream = request.finalize();

  final profile = _createProfile(request);
  profile?.connectionInfo = {
    'package': 'package:cupertino_http',
    'client': 'CupertinoClient',
    'configuration': _urlSession!.configuration.toString(),
  };
  profile?.requestData
    ?..contentLength = request.contentLength
    ..followRedirects = request.followRedirects
    ..headersCommaValues = request.headers
    ..maxRedirects = request.maxRedirects;

  final urlRequest = MutableURLRequest.fromUrl(request.url)
    ..httpMethod = request.method;

  if (request.contentLength != null) {
    profile?.requestData.headersListValues = {
      'Content-Length': ['${request.contentLength}'],
      ...profile.requestData.headers!
    };
    urlRequest.setValueForHttpHeaderField(
        'Content-Length', '${request.contentLength}');
  }

  if (request is Request) {
    // Optimize the (typical) `Request` case since assigning to
    // `httpBodyStream` requires a lot of expensive setup and data passing.
    urlRequest.httpBody = request.bodyBytes.toNSData();
    profile?.requestData.bodySink.add(request.bodyBytes);
  } else if (await _hasData(stream) case (true, final s)) {
    // If the request is supposed to be bodyless (e.g. GET requests)
    // then setting `httpBodyStream` will cause the request to fail -
    // even if the stream is empty.
    if (profile == null) {
      urlRequest.httpBodyStream = s.toNSInputStream();
    } else {
      final splitter = StreamSplitter(s);
      urlRequest.httpBodyStream = splitter.split().toNSInputStream();
      unawaited(profile.requestData.bodySink.addStream(splitter.split()));
    }
  }

  // This will preserve Apple default headers - is that what we want?
  request.headers.forEach(urlRequest.setValueForHttpHeaderField);
  final task = urlSession.dataTaskWithRequest(urlRequest);
  final subscription = StreamController<Uint8List>(onCancel: () {
    final taskTracker = _tasks[task];
    if (taskTracker == null) return;
    taskTracker.responseListenerCancelled = true;
    task.cancel();
  });
  final taskTracker = _TaskTracker(request, subscription, profile);
  _tasks[task] = taskTracker;
  task.resume();

  final maxRedirects = request.followRedirects ? request.maxRedirects : 0;

  late URLResponse result;
  result = await taskTracker.responseCompleter.future;

  final response = result as HTTPURLResponse;

  if (request.followRedirects && taskTracker.numRedirects > maxRedirects) {
    throw ClientException('Redirect limit exceeded', request.url);
  }

  final responseHeaders = response.allHeaderFields
      .map((key, value) => MapEntry(key.toLowerCase(), value));

  if (responseHeaders['content-length'] case final contentLengthHeader?
      when !_digitRegex.hasMatch(contentLengthHeader)) {
    throw ClientException(
      'Invalid content-length header [$contentLengthHeader].',
      request.url,
    );
  }

  final contentLength = response.expectedContentLength == -1
      ? null
      : response.expectedContentLength;
  final isRedirect = !request.followRedirects && taskTracker.numRedirects > 0;
  profile?.responseData
    ?..contentLength = contentLength
    ..headersCommaValues = responseHeaders
    ..isRedirect = isRedirect
    ..reasonPhrase = _findReasonPhrase(response.statusCode)
    ..startTime = DateTime.now()
    ..statusCode = response.statusCode;

  return _StreamedResponseWithUrl(
    taskTracker.responseController.stream,
    response.statusCode,
    url: taskTracker.lastUrl ?? request.url,
    contentLength: contentLength,
    reasonPhrase: _findReasonPhrase(response.statusCode),
    request: request,
    isRedirect: isRedirect,
    headers: responseHeaders,
  );
}