From def03abe558238cfb5a7b8bac1b4273991b600e6 Mon Sep 17 00:00:00 2001 From: Aleksandr Yurkovskiy Date: Wed, 27 May 2020 23:25:50 +0300 Subject: [PATCH 1/4] UTF-8 as default charset for response body decoding --- lib/src/client/dart_http.dart | 37 +++++++++++++++++++++++++++++++++-- pubspec.yaml | 1 + 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/src/client/dart_http.dart b/lib/src/client/dart_http.dart index 5cfc12b..0847dad 100644 --- a/lib/src/client/dart_http.dart +++ b/lib/src/client/dart_http.dart @@ -1,4 +1,7 @@ +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 @@ -10,12 +13,42 @@ class DartHttp implements HttpHandler { 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; 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 [UTF-8] 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 [fallback] if [charset] is null or if no [Encoding] was found that + /// corresponds to [charset]. + Encoding encodingForCharset(String charset, [Encoding fallback = utf8]) { + if (charset == null) return fallback; + return Encoding.getByName(charset) ?? fallback; + } + + /// 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 From 5d24f015b8aba61b0121c524258f10d77c3dc81b Mon Sep 17 00:00:00 2001 From: Aleksandr Yurkovskiy Date: Thu, 28 May 2020 14:33:39 +0300 Subject: [PATCH 2/4] make encodingForCharset private add opportunity to configure default encoding --- lib/src/client/dart_http.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/src/client/dart_http.dart b/lib/src/client/dart_http.dart index 0847dad..516d27b 100644 --- a/lib/src/client/dart_http.dart +++ b/lib/src/client/dart_http.dart @@ -6,7 +6,8 @@ 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 { @@ -23,24 +24,26 @@ class DartHttp implements HttpHandler { } 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 [UTF-8] if the headers don't specify a charset or if that + /// 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']); + _encodingForCharset( + _contentTypeForHeaders(headers).parameters['charset']); /// Returns the [Encoding] that corresponds to [charset]. /// - /// Returns [fallback] if [charset] is null or if no [Encoding] was found that + /// Returns [_defaultEncoding] if [charset] is null or if no [Encoding] was found that /// corresponds to [charset]. - Encoding encodingForCharset(String charset, [Encoding fallback = utf8]) { - if (charset == null) return fallback; - return Encoding.getByName(charset) ?? fallback; + 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. From 77a12d8ef3be9963ca7418ac65ea86a656614d89 Mon Sep 17 00:00:00 2001 From: Aleksandr Yurkovskiy Date: Thu, 28 May 2020 14:33:55 +0300 Subject: [PATCH 3/4] add tests --- test/unit/client/dart_http_test.dart | 71 ++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test/unit/client/dart_http_test.dart diff --git a/test/unit/client/dart_http_test.dart b/test/unit/client/dart_http_test.dart new file mode 100644 index 0000000..2d9297e --- /dev/null +++ b/test/unit/client/dart_http_test.dart @@ -0,0 +1,71 @@ +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:json_api/routing.dart'; +import 'package:json_api/server.dart'; +import 'package:json_api/src/document/resource.dart'; +import 'package:test/test.dart'; + +import '../../helper/test_http_handler.dart'; + +void main() { + group("Decode body with", () { + final stringBodyRu = 'йцукен'; + final bytesBodyRu = utf8.encode(stringBodyRu); + final stringBodyEn = 'qwerty'; + final bytesBodyEn = utf8.encode(stringBodyEn); + + final dartHttpWithBytesBodyAndEncoding = ( + List bytesBody, + Encoding encoding, + ) { + return DartHttp( + MockClient( + (request) async { + return http.Response.bytes(bytesBody, 200); + }, + ), + encoding, + ); + }; + + test('UTF-8 ru', () async { + final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyRu, utf8); + + final response = + await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); + + expect(response.body, equals(stringBodyRu)); + }); + + test('latin1 ru', () async { + final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyRu, latin1); + + final response = + await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); + + expect(response.body, isNot(equals(stringBodyRu))); + }); + + test('UTF-8 en', () async { + final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyEn, utf8); + + final response = + await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); + + expect(response.body, equals(stringBodyEn)); + }); + + test('latin1 en', () async { + final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyEn, latin1); + + final response = + await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); + + expect(response.body, equals(stringBodyEn)); + }); + }); +} From a9af339a899e1ce3cc190b3f3e06fa251b9cc88e Mon Sep 17 00:00:00 2001 From: Aleksandr Yurkovskiy Date: Thu, 28 May 2020 14:52:04 +0300 Subject: [PATCH 4/4] refactor --- test/unit/client/dart_http_test.dart | 39 +++++++--------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/test/unit/client/dart_http_test.dart b/test/unit/client/dart_http_test.dart index 2d9297e..0c82eb6 100644 --- a/test/unit/client/dart_http_test.dart +++ b/test/unit/client/dart_http_test.dart @@ -4,25 +4,20 @@ 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:json_api/routing.dart'; -import 'package:json_api/server.dart'; -import 'package:json_api/src/document/resource.dart'; import 'package:test/test.dart'; -import '../../helper/test_http_handler.dart'; - void main() { - group("Decode body with", () { + group('Decode body with', () { final stringBodyRu = 'йцукен'; final bytesBodyRu = utf8.encode(stringBodyRu); final stringBodyEn = 'qwerty'; final bytesBodyEn = utf8.encode(stringBodyEn); - final dartHttpWithBytesBodyAndEncoding = ( + final buildResponse = ( List bytesBody, Encoding encoding, - ) { - return DartHttp( + ) async { + final dartHttp = DartHttp( MockClient( (request) async { return http.Response.bytes(bytesBody, 200); @@ -30,41 +25,27 @@ void main() { ), encoding, ); + + return dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); }; test('UTF-8 ru', () async { - final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyRu, utf8); - - final response = - await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); - + final response = await buildResponse(bytesBodyRu, utf8); expect(response.body, equals(stringBodyRu)); }); test('latin1 ru', () async { - final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyRu, latin1); - - final response = - await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); - + final response = await buildResponse(bytesBodyRu, latin1); expect(response.body, isNot(equals(stringBodyRu))); }); test('UTF-8 en', () async { - final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyEn, utf8); - - final response = - await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); - + final response = await buildResponse(bytesBodyEn, utf8); expect(response.body, equals(stringBodyEn)); }); test('latin1 en', () async { - final dartHttp = dartHttpWithBytesBodyAndEncoding(bytesBodyEn, latin1); - - final response = - await dartHttp.call(HttpRequest('', Uri.parse('http://test.com'))); - + final response = await buildResponse(bytesBodyEn, latin1); expect(response.body, equals(stringBodyEn)); }); });