httpRequest function

Future<HttpResponse> httpRequest(
  1. Uri uri,
  2. String method,
  3. SocketInterface socket, {
  4. Map<String, String> requestHeaders,
  5. Uint8List body,
  6. StringCallback debugPrint,
  7. bool persistentConnection = true,
})

Makes HTTP request over SocketInterface, e.g. SSHTunneledSocketImpl.

Implementation

Future<HttpResponse> httpRequest(Uri uri, String method, SocketInterface socket,
    {Map<String, String> requestHeaders,
    Uint8List body,
    StringCallback debugPrint,
    bool persistentConnection = true}) async {
  /// Initialize connection state.
  String headerText;
  List<String> statusLine;
  Map<String, String> headers;
  int contentLength = 0, contentRead = 0;
  QueueBuffer buffer = QueueBuffer(Uint8List(0));
  Completer<String> readHeadersCompleter = Completer<String>();
  StreamController<List<int>> contentController = StreamController<List<int>>();

  if (!socket.connected && !socket.connecting) {
    socket = await connectUri(uri, socket);
  }
  socket.handleDone((String reason) {
    if (debugPrint != null) {
      debugPrint('SSHTunneledBaseClient.socket.handleDone');
    }
    socket.close();
    contentController.close();
    if (headerText == null) readHeadersCompleter.complete('done');
  });

  socket.handleError((error) {
    if (debugPrint != null) {
      debugPrint('SSHTunneledBaseClient.socket.handleError');
    }
    socket.close();
    contentController.close();
    if (headerText == null) readHeadersCompleter.complete('$error');
  });

  socket.listen((Uint8List m) {
    if (debugPrint != null) {
      debugPrint('SSHTunneledBaseClient.socket.listen: read ${m.length} bytes');
    }
    if (headerText == null) {
      buffer.add(m);
      int headersEnd = searchUint8List(
          buffer.data, Uint8List.fromList('\r\n\r\n'.codeUnits));

      /// Parse HTTP headers.
      if (headersEnd != -1) {
        headerText = utf8.decode(viewUint8List(buffer.data, 0, headersEnd));
        buffer.flush(headersEnd + 4);
        var lines = LineSplitter.split(headerText);
        statusLine = lines.first.split(' ');
        headers = Map<String, String>.fromIterable(lines.skip(1),
            key: (h) => h.substring(0, h.indexOf(': ')),
            value: (h) => h.substring(h.indexOf(': ') + 2).trim());
        headers.forEach((key, value) {
          if (key.toLowerCase() == 'content-length') {
            contentLength = int.parse(value);
          }
        });
        readHeadersCompleter.complete(null);

        /// If there's no content then we're already done.
        if (contentLength == 0) {
          if (debugPrint != null) {
            debugPrint(
                'SSHTunneledBaseClient.socket.listen: Content-Length: 0, remaining=${buffer.data.length}');
          }
          contentController.close();
          if (!persistentConnection) {
            socket.close();
          }
          return;
        }

        /// Handle any remaining data in the read buffer.
        if (buffer.data.isEmpty) return;
        m = buffer.data;
      }
    }

    /// Add content to the stream until completed.
    contentController.add(m);
    contentRead += m.length;
    if (contentRead >= contentLength) {
      if (debugPrint != null) {
        debugPrint(
            'SSHTunneledBaseClient.socket.listen: done $contentRead / $contentLength');
      }
      contentController.close();
      if (!persistentConnection || contentRead > contentLength) {
        socket.close();
      }
    }
  });

  requestHeaders['Host'] = '${uri.host}';
  if (method == 'POST') {
    requestHeaders['Content-Length'] = '${body.length}';
  }
  socket.send('${method} /${uri.path} HTTP/1.1\r\n' +
      requestHeaders.entries
          .map((header) => '${header.key}: ${header.value}')
          .join('\r\n') +
      '\r\n\r\n');
  if (method == 'POST') socket.sendRaw(body);

  String readHeadersError = await readHeadersCompleter.future;
  if (readHeadersError != null) throw FormatException(readHeadersError);

  return HttpResponse(int.parse(statusLine[1]),
      reason: statusLine.sublist(2).join(' '),
      headers: headers,
      contentLength: contentLength,
      contentStream: contentController.stream);
}