blob: 92ef77ffd27447abbc3b21b5ad68dce41090e33b [file] [log] [blame]
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cocoon_common/core_extensions.dart';
import 'package:http/http.dart' as http;
import 'http_utils.dart' as http_utils;
import 'request_handler.dart';
/// Creates a [Request] by wrapping an existing [HttpRequest].
extension ToRequest on HttpRequest {
Request toRequest() => _HttpRequest(this);
}
/// A request that is backed by an [HttpRequest].
final class _HttpRequest implements Request {
_HttpRequest(this._request);
final HttpRequest _request;
@override
Uri get uri => _request.uri;
@override
String get method => _request.method;
@override
late final RequestResponse response = _HttpResponse(_request.response);
@override
String? header(String name) {
return _request.headers.value(name);
}
@override
Future<Uint8List> readBodyAsBytes() async {
if (_bodyAsBytes case final previousCall?) {
return previousCall;
}
final builder = await _request.fold(BytesBuilder(copy: false), (
builder,
data,
) {
builder.add(data);
return builder;
});
return _bodyAsBytes = builder.takeBytes();
}
Uint8List? _bodyAsBytes;
@override
Future<String> readBodyAsString() async {
return utf8.decode(await readBodyAsBytes());
}
@override
Future<Map<String, Object?>> readBodyAsJson() async {
final bytes = await readBodyAsBytes();
return bytes.isEmpty ? {} : bytes.parseAsJsonObject();
}
}
final class _HttpResponse implements RequestResponse {
_HttpResponse(this._response);
final HttpResponse _response;
@override
int get statusCode => _response.statusCode;
@override
set statusCode(int value) => _response.statusCode = value;
@override
set contentType(http_utils.MediaType? value) {
if (value != null) {
_response.headers.contentType = ContentType(
value.type,
value.subtype,
charset: value.parameters['charset'],
);
} else {
_response.headers.contentType = null;
}
}
@override
Future<void> addStream(Stream<Uint8List> stream) =>
_response.addStream(stream);
@override
Future<void> flush() => _response.flush();
@override
Future<void> close() => _response.close();
@override
Future<dynamic> redirect(
Uri location, {
int status = http_utils.HttpStatus.movedTemporarily,
}) => _response.redirect(location, status: status);
}
/// An [http.Client] that maps [SocketException] from dart:io to the cocoon
/// [http_utils.SocketException].
///
/// This is used to maintain platform neutrality in the core of app_dart.
class MappingHttpClient extends http.BaseClient {
MappingHttpClient(this._inner);
final http.Client _inner;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
try {
return await _inner.send(request);
} on SocketException catch (e) {
throw http_utils.SocketException('$e');
} on HttpException catch (e) {
throw http_utils.HttpException('$e');
}
}
@override
void close() => _inner.close();
}