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 urlRequest = MutableURLRequest.fromUrl(request.url)
    ..httpMethod = request.method;

  if (request is Request) {
    // Optimize the (typical) `Request` case since assigning to
    // `httpBodyStream` requires a lot of expensive setup and data passing.
    urlRequest.httpBody = Data.fromList(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.
    urlRequest.httpBodyStream = s;
  }

  // This will preserve Apple default headers - is that what we want?
  request.headers.forEach(urlRequest.setValueForHttpHeaderField);

  final task = urlSession.dataTaskWithRequest(urlRequest);
  final taskTracker = _TaskTracker(request);
  _tasks[task] = taskTracker;
  task.resume();

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

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

  return _StreamedResponseWithUrl(
    taskTracker.responseController.stream,
    response.statusCode,
    url: taskTracker.lastUrl ?? request.url,
    contentLength: response.expectedContentLength == -1
        ? null
        : response.expectedContentLength,
    reasonPhrase: _findReasonPhrase(response.statusCode),
    request: request,
    isRedirect: !request.followRedirects && taskTracker.numRedirects > 0,
    headers: responseHeaders,
  );
}