mirror of
https://github.com/dart-lang/sdk
synced 2024-09-20 23:51:23 +00:00
Make HTTP headers use a growing buffer, not a fixed-size 8K one.
Issue #28251 BUG= http://dartbug.com/28251 R=sgjesse@google.com Review-Url: https://codereview.chromium.org/2618523005 .
This commit is contained in:
parent
d3a839fb84
commit
5119795422
|
@ -83,24 +83,22 @@ class _CopyingBytesBuilder implements BytesBuilder {
|
|||
// Start with 1024 bytes.
|
||||
static const int _INIT_SIZE = 1024;
|
||||
|
||||
static final _emptyList = new Uint8List(0);
|
||||
|
||||
int _length = 0;
|
||||
Uint8List _buffer;
|
||||
|
||||
_CopyingBytesBuilder([int initialCapacity = 0])
|
||||
: _buffer = (initialCapacity <= 0)
|
||||
? _emptyList
|
||||
: new Uint8List(_pow2roundup(initialCapacity));
|
||||
|
||||
void add(List<int> bytes) {
|
||||
int bytesLength = bytes.length;
|
||||
if (bytesLength == 0) return;
|
||||
int required = _length + bytesLength;
|
||||
if (_buffer == null) {
|
||||
int size = _pow2roundup(required);
|
||||
size = max(size, _INIT_SIZE);
|
||||
_buffer = new Uint8List(size);
|
||||
} else if (_buffer.length < required) {
|
||||
// We will create a list in the range of 2-4 times larger than
|
||||
// required.
|
||||
int size = _pow2roundup(required) * 2;
|
||||
var newBuffer = new Uint8List(size);
|
||||
newBuffer.setRange(0, _buffer.length, _buffer);
|
||||
_buffer = newBuffer;
|
||||
if (_buffer.length < required) {
|
||||
_grow(required);
|
||||
}
|
||||
assert(_buffer.length >= required);
|
||||
if (bytes is Uint8List) {
|
||||
|
@ -113,17 +111,40 @@ class _CopyingBytesBuilder implements BytesBuilder {
|
|||
_length = required;
|
||||
}
|
||||
|
||||
void addByte(int byte) { add([byte]); }
|
||||
void addByte(int byte) {
|
||||
if (_buffer.length == _length) {
|
||||
// The grow algorithm always at least doubles.
|
||||
// If we added one to _length it would quadruple unnecessarily.
|
||||
_grow(_length);
|
||||
}
|
||||
assert(_buffer.length > _length);
|
||||
_buffer[_length] = byte;
|
||||
_length++;
|
||||
}
|
||||
|
||||
void _grow(int required) {
|
||||
// We will create a list in the range of 2-4 times larger than
|
||||
// required.
|
||||
int newSize = required * 2;
|
||||
if (newSize < _INIT_SIZE) {
|
||||
newSize = _INIT_SIZE;
|
||||
} else {
|
||||
newSize = _pow2roundup(newSize);
|
||||
}
|
||||
var newBuffer = new Uint8List(newSize);
|
||||
newBuffer.setRange(0, _buffer.length, _buffer);
|
||||
_buffer = newBuffer;
|
||||
}
|
||||
|
||||
List<int> takeBytes() {
|
||||
if (_buffer == null) return new Uint8List(0);
|
||||
if (_length == 0) return _emptyList;
|
||||
var buffer = new Uint8List.view(_buffer.buffer, 0, _length);
|
||||
clear();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
List<int> toBytes() {
|
||||
if (_buffer == null) return new Uint8List(0);
|
||||
if (_length == 0) return _emptyList;
|
||||
return new Uint8List.fromList(
|
||||
new Uint8List.view(_buffer.buffer, 0, _length));
|
||||
}
|
||||
|
@ -136,10 +157,11 @@ class _CopyingBytesBuilder implements BytesBuilder {
|
|||
|
||||
void clear() {
|
||||
_length = 0;
|
||||
_buffer = null;
|
||||
_buffer = _emptyList;
|
||||
}
|
||||
|
||||
int _pow2roundup(int x) {
|
||||
static int _pow2roundup(int x) {
|
||||
assert(x > 0);
|
||||
--x;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
|
@ -166,12 +188,15 @@ class _BytesBuilder implements BytesBuilder {
|
|||
_length += typedBytes.length;
|
||||
}
|
||||
|
||||
void addByte(int byte) { add([byte]); }
|
||||
void addByte(int byte) {
|
||||
_chunks.add(new Uint8List(1)..[0] = byte);
|
||||
_length++;
|
||||
}
|
||||
|
||||
List<int> takeBytes() {
|
||||
if (_chunks.length == 0) return new Uint8List(0);
|
||||
if (_length == 0) return _CopyingBytesBuilder._emptyList;
|
||||
if (_chunks.length == 1) {
|
||||
var buffer = _chunks.single;
|
||||
var buffer = _chunks[0];
|
||||
clear();
|
||||
return buffer;
|
||||
}
|
||||
|
@ -186,7 +211,7 @@ class _BytesBuilder implements BytesBuilder {
|
|||
}
|
||||
|
||||
List<int> toBytes() {
|
||||
if (_chunks.length == 0) return new Uint8List(0);
|
||||
if (_length == 0) return _CopyingBytesBuilder._emptyList;
|
||||
var buffer = new Uint8List(_length);
|
||||
int offset = 0;
|
||||
for (var chunk in _chunks) {
|
||||
|
|
|
@ -465,42 +465,32 @@ class _HttpHeaders implements HttpHeaders {
|
|||
_mutable = false;
|
||||
}
|
||||
|
||||
int _write(Uint8List buffer, int offset) {
|
||||
void write(List<int> bytes) {
|
||||
int len = bytes.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
buffer[offset + i] = bytes[i];
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
|
||||
// Format headers.
|
||||
void _build(BytesBuilder builder) {
|
||||
for (String name in _headers.keys) {
|
||||
List<String> values = _headers[name];
|
||||
bool fold = _foldHeader(name);
|
||||
var nameData = name.codeUnits;
|
||||
write(nameData);
|
||||
buffer[offset++] = _CharCode.COLON;
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
builder.add(nameData);
|
||||
builder.addByte(_CharCode.COLON);
|
||||
builder.addByte(_CharCode.SP);
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (i > 0) {
|
||||
if (fold) {
|
||||
buffer[offset++] = _CharCode.COMMA;
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
builder.addByte(_CharCode.COMMA);
|
||||
builder.addByte(_CharCode.SP);
|
||||
} else {
|
||||
buffer[offset++] = _CharCode.CR;
|
||||
buffer[offset++] = _CharCode.LF;
|
||||
write(nameData);
|
||||
buffer[offset++] = _CharCode.COLON;
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
builder.addByte(_CharCode.CR);
|
||||
builder.addByte(_CharCode.LF);
|
||||
builder.add(nameData);
|
||||
builder.addByte(_CharCode.COLON);
|
||||
builder.addByte(_CharCode.SP);
|
||||
}
|
||||
}
|
||||
write(values[i].codeUnits);
|
||||
builder.add(values[i].codeUnits);
|
||||
}
|
||||
buffer[offset++] = _CharCode.CR;
|
||||
buffer[offset++] = _CharCode.LF;
|
||||
builder.addByte(_CharCode.CR);
|
||||
builder.addByte(_CharCode.LF);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
String toString() {
|
||||
|
|
|
@ -574,29 +574,20 @@ class _HttpResponse extends _HttpOutboundMessage<HttpResponse>
|
|||
}
|
||||
|
||||
void _writeHeader() {
|
||||
Uint8List buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
|
||||
int offset = 0;
|
||||
|
||||
void write(List<int> bytes) {
|
||||
int len = bytes.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
buffer[offset + i] = bytes[i];
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
BytesBuilder buffer = new _CopyingBytesBuilder(_OUTGOING_BUFFER_SIZE);
|
||||
|
||||
// Write status line.
|
||||
if (headers.protocolVersion == "1.1") {
|
||||
write(_Const.HTTP11);
|
||||
buffer.add(_Const.HTTP11);
|
||||
} else {
|
||||
write(_Const.HTTP10);
|
||||
buffer.add(_Const.HTTP10);
|
||||
}
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
write(statusCode.toString().codeUnits);
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
write(reasonPhrase.codeUnits);
|
||||
buffer[offset++] = _CharCode.CR;
|
||||
buffer[offset++] = _CharCode.LF;
|
||||
buffer.addByte(_CharCode.SP);
|
||||
buffer.add(statusCode.toString().codeUnits);
|
||||
buffer.addByte(_CharCode.SP);
|
||||
buffer.add(reasonPhrase.codeUnits);
|
||||
buffer.addByte(_CharCode.CR);
|
||||
buffer.addByte(_CharCode.LF);
|
||||
|
||||
var session = _httpRequest._session;
|
||||
if (session != null && !session._destroyed) {
|
||||
|
@ -630,10 +621,11 @@ class _HttpResponse extends _HttpOutboundMessage<HttpResponse>
|
|||
headers._finalize();
|
||||
|
||||
// Write headers.
|
||||
offset = headers._write(buffer, offset);
|
||||
buffer[offset++] = _CharCode.CR;
|
||||
buffer[offset++] = _CharCode.LF;
|
||||
_outgoing.setHeader(buffer, offset);
|
||||
headers._build(buffer);
|
||||
buffer.addByte(_CharCode.CR);
|
||||
buffer.addByte(_CharCode.LF);
|
||||
Uint8List headerBytes = buffer.takeBytes();
|
||||
_outgoing.setHeader(headerBytes, headerBytes.length);
|
||||
}
|
||||
|
||||
String _findReasonPhrase(int statusCode) {
|
||||
|
@ -821,27 +813,18 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse>
|
|||
}
|
||||
|
||||
void _writeHeader() {
|
||||
Uint8List buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
|
||||
int offset = 0;
|
||||
|
||||
void write(List<int> bytes) {
|
||||
int len = bytes.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
buffer[offset + i] = bytes[i];
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
BytesBuilder buffer = new _CopyingBytesBuilder(_OUTGOING_BUFFER_SIZE);
|
||||
|
||||
// Write the request method.
|
||||
write(method.codeUnits);
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
buffer.add(method.codeUnits);
|
||||
buffer.addByte(_CharCode.SP);
|
||||
// Write the request URI.
|
||||
write(_requestUri().codeUnits);
|
||||
buffer[offset++] = _CharCode.SP;
|
||||
buffer.add(_requestUri().codeUnits);
|
||||
buffer.addByte(_CharCode.SP);
|
||||
// Write HTTP/1.1.
|
||||
write(_Const.HTTP11);
|
||||
buffer[offset++] = _CharCode.CR;
|
||||
buffer[offset++] = _CharCode.LF;
|
||||
buffer.add(_Const.HTTP11);
|
||||
buffer.addByte(_CharCode.CR);
|
||||
buffer.addByte(_CharCode.LF);
|
||||
|
||||
// Add the cookies to the headers.
|
||||
if (!cookies.isEmpty) {
|
||||
|
@ -856,10 +839,11 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse>
|
|||
headers._finalize();
|
||||
|
||||
// Write headers.
|
||||
offset = headers._write(buffer, offset);
|
||||
buffer[offset++] = _CharCode.CR;
|
||||
buffer[offset++] = _CharCode.LF;
|
||||
_outgoing.setHeader(buffer, offset);
|
||||
headers._build(buffer);
|
||||
buffer.addByte(_CharCode.CR);
|
||||
buffer.addByte(_CharCode.LF);
|
||||
Uint8List headerBytes = buffer.takeBytes();
|
||||
_outgoing.setHeader(headerBytes, headerBytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -935,18 +919,6 @@ class _HttpOutgoing implements StreamConsumer<List<int>> {
|
|||
// Returns either a future or 'null', if it was able to write headers
|
||||
// immediately.
|
||||
Future writeHeaders({bool drainRequest: true, bool setOutgoing: true}) {
|
||||
Future write() {
|
||||
try {
|
||||
outbound._writeHeader();
|
||||
} catch (_) {
|
||||
// Headers too large.
|
||||
return new Future.error(new HttpException(
|
||||
"Headers size exceeded the of '$_OUTGOING_BUFFER_SIZE'"
|
||||
" bytes"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (headersWritten) return null;
|
||||
headersWritten = true;
|
||||
Future drainFuture;
|
||||
|
@ -975,22 +947,22 @@ class _HttpOutgoing implements StreamConsumer<List<int>> {
|
|||
} else {
|
||||
drainRequest = false;
|
||||
}
|
||||
if (ignoreBody) {
|
||||
return write();
|
||||
}
|
||||
if (setOutgoing) {
|
||||
int contentLength = outbound.headers.contentLength;
|
||||
if (outbound.headers.chunkedTransferEncoding) {
|
||||
chunked = true;
|
||||
if (gzip) this.gzip = true;
|
||||
} else if (contentLength >= 0) {
|
||||
this.contentLength = contentLength;
|
||||
if (!ignoreBody) {
|
||||
if (setOutgoing) {
|
||||
int contentLength = outbound.headers.contentLength;
|
||||
if (outbound.headers.chunkedTransferEncoding) {
|
||||
chunked = true;
|
||||
if (gzip) this.gzip = true;
|
||||
} else if (contentLength >= 0) {
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
}
|
||||
if (drainFuture != null) {
|
||||
return drainFuture.then((_) => outbound._writeHeader());
|
||||
}
|
||||
}
|
||||
if (drainFuture != null) {
|
||||
return drainFuture.then((_) => write());
|
||||
}
|
||||
return write();
|
||||
outbound._writeHeader();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1160,7 +1132,6 @@ class _HttpOutgoing implements StreamConsumer<List<int>> {
|
|||
|
||||
void setHeader(List<int> data, int length) {
|
||||
assert(_length == 0);
|
||||
assert(data.length == _OUTGOING_BUFFER_SIZE);
|
||||
_buffer = data;
|
||||
_length = length;
|
||||
}
|
||||
|
|
|
@ -108,25 +108,8 @@ void testBadResponseClose() {
|
|||
}
|
||||
|
||||
|
||||
void testBadHeaders() {
|
||||
asyncStart();
|
||||
testClientRequest((request) {
|
||||
var value = "a";
|
||||
for (int i = 0; i < 8 * 1024; i++) {
|
||||
value += 'a';
|
||||
}
|
||||
request.headers.set('name', value);
|
||||
request.done.catchError((error) {
|
||||
asyncEnd();
|
||||
}, test: (e) => e is HttpException);
|
||||
return request.close();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
testResponseDone();
|
||||
testBadResponseAdd();
|
||||
testBadResponseClose();
|
||||
testBadHeaders();
|
||||
}
|
||||
|
|
|
@ -281,20 +281,6 @@ void testIgnoreRequestData() {
|
|||
}
|
||||
|
||||
|
||||
void testBadHeaders() {
|
||||
testServerRequest((server, request) {
|
||||
var value = "a";
|
||||
for (int i = 0; i < 8 * 1024; i++) {
|
||||
value += 'a';
|
||||
}
|
||||
request.response.headers.set('name', value);
|
||||
request.response.close().catchError((error) {
|
||||
server.close();
|
||||
}, test: (e) => e is HttpException);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void testWriteCharCode() {
|
||||
testServerRequest((server, request) {
|
||||
// Test that default is latin-1 (only 2 bytes).
|
||||
|
@ -315,6 +301,5 @@ void main() {
|
|||
testBadResponseAdd();
|
||||
testBadResponseClose();
|
||||
testIgnoreRequestData();
|
||||
testBadHeaders();
|
||||
testWriteCharCode();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue