diff --git a/lib/src/client/dart_http.dart b/lib/src/client/dart_http.dart index 5cfc12b..516d27b 100644 --- a/lib/src/client/dart_http.dart +++ b/lib/src/client/dart_http.dart @@ -1,21 +1,57 @@ +import 'dart:convert'; + import 'package:http/http.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:json_api/http.dart'; /// A handler using the Dart's built-in http client class DartHttp implements HttpHandler { - DartHttp(this._client); + DartHttp(this._client, [this._defaultEncoding = utf8]) + : assert(_defaultEncoding != null, "Default encoding can't be null"); @override Future call(HttpRequest request) async { final response = await _send(Request(request.method, request.uri) ..headers.addAll(request.headers) ..body = request.body); - return HttpResponse(response.statusCode, - body: response.body, headers: response.headers); + final responseBody = + _encodingForHeaders(response.headers).decode(response.bodyBytes); + return HttpResponse( + response.statusCode, + body: responseBody, + headers: response.headers, + ); } final Client _client; + final Encoding _defaultEncoding; Future _send(Request dartRequest) async => Response.fromStream(await _client.send(dartRequest)); + + /// Returns the encoding to use for a response with the given headers. + /// + /// Defaults to [_defaultEncoding] if the headers don't specify a charset or if that + /// charset is unknown. + Encoding _encodingForHeaders(Map headers) => + _encodingForCharset( + _contentTypeForHeaders(headers).parameters['charset']); + + /// Returns the [Encoding] that corresponds to [charset]. + /// + /// Returns [_defaultEncoding] if [charset] is null or if no [Encoding] was found that + /// corresponds to [charset]. + Encoding _encodingForCharset(String charset) { + if (charset == null) return _defaultEncoding; + return Encoding.getByName(charset) ?? _defaultEncoding; + } + + /// Returns the [MediaType] object for the given headers's content-type. + /// + /// Defaults to `application/octet-stream`. + MediaType _contentTypeForHeaders(Map headers) { + var contentType = headers['content-type']; + if (contentType != null) return MediaType.parse(contentType); + return MediaType('application', 'octet-stream'); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 5513155..55caba1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,6 +6,7 @@ environment: sdk: '>=2.6.0 <3.0.0' dependencies: http: ^0.12.0 + http_parser: ^3.1.4 dev_dependencies: args: ^1.5.2 pedantic: ^1.9.0 diff --git a/test/unit/client/dart_http_test.dart b/test/unit/client/dart_http_test.dart new file mode 100644 index 0000000..0c82eb6 --- /dev/null +++ b/test/unit/client/dart_http_test.dart @@ -0,0 +1,52 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:json_api/client.dart'; +import 'package:json_api/http.dart'; +import 'package:test/test.dart'; + +void main() { + group('Decode body with', () { + final stringBodyRu = 'йцукен'; + final bytesBodyRu = utf8.encode(stringBodyRu); + final stringBodyEn = 'qwerty'; + final bytesBodyEn = utf8.encode(stringBodyEn); + + final buildResponse = ( + List bytesBody, + Encoding encoding, + ) async { + final dartHttp = DartHttp( + MockClient( + (request) async { + return http.Response.bytes(bytesBody, 200); + }, + ), + encoding, + ); + + return dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); + }; + + test('UTF-8 ru', () async { + final response = await buildResponse(bytesBodyRu, utf8); + expect(response.body, equals(stringBodyRu)); + }); + + test('latin1 ru', () async { + final response = await buildResponse(bytesBodyRu, latin1); + expect(response.body, isNot(equals(stringBodyRu))); + }); + + test('UTF-8 en', () async { + final response = await buildResponse(bytesBodyEn, utf8); + expect(response.body, equals(stringBodyEn)); + }); + + test('latin1 en', () async { + final response = await buildResponse(bytesBodyEn, latin1); + expect(response.body, equals(stringBodyEn)); + }); + }); +}