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:
Lasse R.H. Nielsen 2017-01-13 10:12:19 +01:00
parent d3a839fb84
commit 5119795422
5 changed files with 100 additions and 146 deletions

View file

@ -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) {

View file

@ -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() {

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}