handleIoRequest method

Future<void> handleIoRequest(
  1. HttpRequest ioReq, {
  2. int? maxRequestBodyBytes,
})

Bridges a native dart:io HttpRequest to the Bloom router pipeline.

Converts ioReq into a typed BloomRequest, executes the matching route handler and middleware chain, and streams or buffers the resulting BloomResponse back to the underlying HttpResponse socket.

If maxRequestBodyBytes is exceeded, responds with HTTP 413 Payload Too Large. If an uncaught exception occurs, responds with HTTP 500 Internal Server Error. If a streaming response encounters a failure mid-stream, the socket connection is aborted to notify the client of incomplete transmission.

Implementation

Future<void> handleIoRequest(HttpRequest ioReq,
    {int? maxRequestBodyBytes}) async {
  try {
    final headers = <String, String>{};
    ioReq.headers.forEach((k, v) => headers[k] = v.join(', '));

    final isSecure = ioReq.certificate != null ||
        ioReq.requestedUri.scheme.toLowerCase() == 'https' ||
        ioReq.uri.scheme.toLowerCase() == 'https' ||
        headers['x-forwarded-proto']?.toLowerCase() == 'https' ||
        headers['x-forwarded-ssl']?.toLowerCase() == 'on';

    final contentType = headers['content-type'] ?? '';
    final isMultipart =
        contentType.toLowerCase().contains('multipart/form-data');

    final BloomRequest bloomReq;
    if (isMultipart) {
      bloomReq = BloomRequest(
        method: ioReq.method,
        uri: ioReq.requestedUri,
        headers: headers,
        streamBody: ioReq,
        maxRequestBodyBytes: maxRequestBodyBytes,
        isSecure: isSecure,
      );
    } else {
      final bodyBytes =
          await _readStreamBytes(ioReq, maxBytes: maxRequestBodyBytes);
      bloomReq = BloomRequest(
        method: ioReq.method,
        uri: ioReq.requestedUri,
        headers: headers,
        rawBody: bodyBytes,
        isSecure: isSecure,
      );
    }

    final bloomRes = await handleRequest(bloomReq);

    ioReq.response.statusCode = bloomRes.statusCode;
    bloomRes.headers.forEach((k, v) => ioReq.response.headers.set(k, v));
    if (bloomRes.isStreaming) {
      // addStream propagates backpressure from the socket to the source,
      // so a slow client throttles the producer instead of filling memory.
      //
      // Status and headers are already committed by the time the first
      // chunk is written, so a mid-stream failure cannot be reported as an
      // error status. Aborting the connection is the only honest signal:
      // the client sees a truncated chunked body rather than a well-formed
      // response that silently lost data.
      try {
        await ioReq.response.addStream(bloomRes.takeBodyStream());
      } catch (_) {
        await ioReq.response.close().catchError((_) {});
        return;
      }
    } else {
      ioReq.response.add(bloomRes.body);
    }
    await ioReq.response.close();
  } on BloomPayloadTooLargeException catch (e) {
    try {
      final payloadRes = BloomResponse.payloadTooLarge(e.message);
      ioReq.response.statusCode = payloadRes.statusCode;
      payloadRes.headers.forEach((k, v) => ioReq.response.headers.set(k, v));
      ioReq.response.add(payloadRes.body);
      await ioReq.response.close();
    } catch (_) {}
  } on _PayloadTooLargeException catch (e) {
    try {
      final payloadRes = BloomResponse.payloadTooLarge(e.message);
      ioReq.response.statusCode = payloadRes.statusCode;
      payloadRes.headers.forEach((k, v) => ioReq.response.headers.set(k, v));
      ioReq.response.add(payloadRes.body);
      await ioReq.response.close();
    } catch (_) {}
  } catch (e) {
    try {
      final errRes = BloomResponse.error('Internal Server Error: $e');
      ioReq.response.statusCode = errRes.statusCode;
      errRes.headers.forEach((k, v) => ioReq.response.headers.set(k, v));
      ioReq.response.add(errRes.body);
      await ioReq.response.close();
    } catch (_) {}
  }
}