Skip to content

Commit 0a84117

Browse files
authored
Preserve header case in IOClient (#1918)
Closes #609 Some non-compliant servers misbehave with the default handling of headers in the `dart:io` HTTP implementation. Well behaved servers should not be impacted, but other use cases which require a particular formatting can be handled by always preserving header case.
1 parent 95b4830 commit 0a84117

3 files changed

Lines changed: 43 additions & 1 deletion

File tree

pkgs/http/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Clarified the behavior of response headers in API documentation comments.
44
* Make it more clear that `close` must be called for correctness.
55
* Replace references to `dart:web` with `package:web` dartdoc.
6+
* Preserve header cases in `IOClient`.
67

78
## 1.6.0
89

pkgs/http/lib/src/io_client.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class IOClient extends BaseClient {
117117
..contentLength = (request.contentLength ?? -1)
118118
..persistentConnection = request.persistentConnection;
119119
request.headers.forEach((name, value) {
120-
ioRequest.headers.set(name, value);
120+
ioRequest.headers.set(name, value, preserveHeaderCase: true);
121121
});
122122

123123
// SDK request aborting is only effective up until the request is closed,

pkgs/http/test/io/client_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ library;
88
import 'dart:async';
99
import 'dart:convert';
1010
import 'dart:io';
11+
import 'dart:typed_data';
1112

1213
import 'package:http/http.dart' as http;
1314
import 'package:http/io_client.dart' as http_io;
@@ -185,4 +186,44 @@ void main() {
185186
http.runWithClient(http.Client.new, http.Client.new);
186187
}, http.Client.new);
187188
});
189+
190+
test('preserves header case', () async {
191+
// Avoid `HttpServer` header normalization with a direct socket server.
192+
final server = await ServerSocket.bind('localhost', 0);
193+
final url = Uri.http('localhost:${server.port}', '');
194+
195+
final client = http.Client();
196+
final request = http.Request('POST', url)
197+
..headers['X-Custom-Header'] = 'value';
198+
199+
final responseFuture = client.send(request);
200+
201+
final socket = await server.first;
202+
final bytes = BytesBuilder();
203+
const needle = [13, 10, 13, 10];
204+
var needleIndex = 0;
205+
206+
collectHeader:
207+
await for (var data in socket) {
208+
bytes.add(data);
209+
for (final byte in data) {
210+
if (byte == needle[needleIndex]) {
211+
if (++needleIndex == 4) break collectHeader;
212+
} else {
213+
needleIndex = (byte == 13) ? 1 : 0;
214+
}
215+
}
216+
}
217+
218+
expect(utf8.decode(bytes.toBytes()), contains('X-Custom-Header: value'));
219+
220+
socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n');
221+
await socket.flush();
222+
await socket.close();
223+
await server.close();
224+
225+
final response = await responseFuture;
226+
expect(response.statusCode, equals(200));
227+
client.close();
228+
});
188229
}

0 commit comments

Comments
 (0)