Skip to content

Commit 1df709b

Browse files
authored
Do not set empty body. Fixes #129 (#130)
1 parent 6fb4da3 commit 1df709b

File tree

5 files changed

+106
-38
lines changed

5 files changed

+106
-38
lines changed

lib/client.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
///
33
/// There are two clients implementation provided by this library.
44
///
5-
/// The firs one, [Client], is the most flexible but low level. It operates
5+
/// The first one, [Client], is the most flexible but low level. It operates
66
/// generic [Request] and [Response] objects and performs basic operations
77
/// such as JSON conversion and error handling. It is agnostic to the document
88
/// structure and accepts any target URIs.
@@ -25,6 +25,7 @@ library client;
2525

2626
export 'package:json_api/src/client/client.dart';
2727
export 'package:json_api/src/client/disposable_handler.dart';
28+
export 'package:json_api/src/client/message_converter.dart';
2829
export 'package:json_api/src/client/persistent_handler.dart';
2930
export 'package:json_api/src/client/request.dart';
3031
export 'package:json_api/src/client/response/collection_fetched.dart';

lib/src/client/message_converter.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/http.dart';
4+
import 'package:http_parser/http_parser.dart';
5+
import 'package:json_api/http.dart';
6+
7+
/// Converts HTTP messages to and from to ones used by the `http` package.
8+
class MessageConverter {
9+
/// Creates an instance of the converter.
10+
///
11+
/// Pass [defaultResponseEncoding] to use in cases when the server does
12+
/// not specify the encoding.
13+
MessageConverter({Encoding defaultResponseEncoding = utf8})
14+
: _defaultResponseEncoding = defaultResponseEncoding;
15+
16+
final Encoding _defaultResponseEncoding;
17+
18+
/// Converts [HttpRequest] to [Request].
19+
Request request(HttpRequest request) {
20+
final converted = Request(request.method, request.uri);
21+
final hasBody = !(request.isGet || request.isOptions);
22+
if (hasBody) {
23+
// The Request would set the content-type header if the body is assigned
24+
// a value (even an empty string). We want to avoid this extra header for
25+
// GET and OPTIONS requests.
26+
// See https://github.com/dart-lang/http/issues/841
27+
converted.body = request.body;
28+
}
29+
converted.headers.addAll(request.headers);
30+
return converted;
31+
}
32+
33+
/// Converts [Response] to [HttpResponse].
34+
HttpResponse response(Response response) {
35+
final encoding = _encodingForHeaders(response.headers);
36+
final body = encoding.decode(response.bodyBytes);
37+
return HttpResponse(response.statusCode, body: body)
38+
..headers.addAll(response.headers);
39+
}
40+
41+
/// Returns the [Encoding] that corresponds to [charset].
42+
///
43+
/// Returns [defaultResponseEncoding] if [charset] is null or if no [Encoding]
44+
/// was found that corresponds to [charset].
45+
Encoding _encodingForCharset(String? charset) {
46+
if (charset == null) return _defaultResponseEncoding;
47+
return Encoding.getByName(charset) ?? _defaultResponseEncoding;
48+
}
49+
50+
/// Returns the [MediaType] object for the given content-type.
51+
///
52+
/// Defaults to `application/octet-stream`.
53+
MediaType _mediaType(String? contentType) {
54+
if (contentType != null) return MediaType.parse(contentType);
55+
return MediaType('application', 'octet-stream');
56+
}
57+
58+
/// Returns the encoding to use for a response with the given headers.
59+
///
60+
/// Defaults to [defaultEncoding] if the headers don't specify the charset
61+
/// or if that charset is unknown.
62+
Encoding _encodingForHeaders(Map<String, String> headers) {
63+
final mediaType = _mediaType(headers['content-type']);
64+
final charset = mediaType.parameters['charset'];
65+
return _encodingForCharset(charset);
66+
}
67+
}
Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,35 @@
11
import 'dart:convert';
22

33
import 'package:http/http.dart';
4-
import 'package:http_parser/http_parser.dart';
54
import 'package:json_api/http.dart';
5+
import 'package:json_api/src/client/message_converter.dart';
66

77
/// Handler which relies on the built-in Dart HTTP client.
88
/// It is the developer's responsibility to instantiate the client and
99
/// call `close()` on it in the end pf the application lifecycle.
1010
class PersistentHandler implements HttpHandler {
1111
/// Creates a new instance of the handler. Do not forget to call `close()` on
1212
/// the [client] when it's not longer needed.
13-
PersistentHandler(this.client, {this.defaultEncoding = utf8});
13+
///
14+
/// Use [messageConverter] to fine tune the HTTP request/response conversion.
15+
PersistentHandler(
16+
this.client,
17+
{@Deprecated('Deprecated in favor of MessageConverter.'
18+
' To be removed in version 6.0.0')
19+
this.defaultEncoding = utf8,
20+
MessageConverter? messageConverter})
21+
: _converter = messageConverter ??
22+
MessageConverter(defaultResponseEncoding: defaultEncoding);
1423

1524
final Client client;
1625
final Encoding defaultEncoding;
26+
final MessageConverter _converter;
1727

1828
@override
1929
Future<HttpResponse> handle(HttpRequest request) async {
20-
final response = await Response.fromStream(
21-
await client.send(Request(request.method, request.uri)
22-
..headers.addAll(request.headers)
23-
..body = request.body));
24-
final responseBody =
25-
_encodingForHeaders(response.headers).decode(response.bodyBytes);
26-
return HttpResponse(response.statusCode, body: responseBody)
27-
..headers.addAll(response.headers);
28-
}
29-
30-
/// Returns the encoding to use for a response with the given headers.
31-
///
32-
/// Defaults to [defaultEncoding] if the headers don't specify a charset or if that
33-
/// charset is unknown.
34-
Encoding _encodingForHeaders(Map<String, String> headers) =>
35-
_encodingForCharset(
36-
_contentTypeForHeaders(headers).parameters['charset']);
37-
38-
/// Returns the [Encoding] that corresponds to [charset].
39-
///
40-
/// Returns [defaultEncoding] if [charset] is null or if no [Encoding] was found that
41-
/// corresponds to [charset].
42-
Encoding _encodingForCharset(String? charset) {
43-
if (charset == null) return defaultEncoding;
44-
return Encoding.getByName(charset) ?? defaultEncoding;
45-
}
46-
47-
/// Returns the [MediaType] object for the given headers's content-type.
48-
///
49-
/// Defaults to `application/octet-stream`.
50-
MediaType _contentTypeForHeaders(Map<String, String> headers) {
51-
final contentType = headers['content-type'];
52-
if (contentType != null) return MediaType.parse(contentType);
53-
return MediaType('application', 'octet-stream');
30+
final convertedRequest = _converter.request(request);
31+
final streamedResponse = await client.send(convertedRequest);
32+
final response = await Response.fromStream(streamedResponse);
33+
return _converter.response(response);
5434
}
5535
}

test/unit/http/encoding_test.dart renamed to test/unit/client/encoding_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import 'dart:convert';
22

33
import 'package:http/http.dart' as http;
44
import 'package:http/testing.dart';
5+
import 'package:json_api/client.dart';
56
import 'package:json_api/http.dart';
6-
import 'package:json_api/src/client/persistent_handler.dart';
77
import 'package:test/test.dart';
88

99
void main() {
@@ -23,6 +23,7 @@ void main() {
2323
return http.Response.bytes(bytesBody, 200);
2424
},
2525
),
26+
// ignore: deprecated_member_use_from_same_package
2627
defaultEncoding: encoding,
2728
);
2829

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'package:json_api/client.dart';
2+
import 'package:json_api/http.dart';
3+
import 'package:test/expect.dart';
4+
import 'package:test/scaffolding.dart';
5+
6+
void main() {
7+
final converter = MessageConverter();
8+
final uri = Uri.parse('https://example.com');
9+
10+
test('No headers are set for GET requests', () {
11+
final r = converter.request(HttpRequest('GET', uri));
12+
expect(r.headers, isEmpty);
13+
});
14+
15+
test('No headers are set for OPTIONS requests', () {
16+
final r = converter.request(HttpRequest('OPTIONS', uri));
17+
expect(r.headers, isEmpty);
18+
});
19+
}

0 commit comments

Comments
 (0)