Skip to content

Commit fff0ade

Browse files
author
Cesar Blum Silveira
committed
Faster response Content-Length parsing.
1 parent 7858479 commit fff0ade

File tree

2 files changed

+103
-36
lines changed

2 files changed

+103
-36
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,41 @@ public static void ValidateHeaderCharacters(string headerCharacters)
233233
}
234234
}
235235

236-
public static long ParseContentLength(StringValues value)
236+
public static unsafe long ParseContentLength(StringValues value)
237237
{
238-
try
239-
{
240-
return long.Parse(value, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture);
241-
}
242-
catch (FormatException ex)
238+
var input = value.ToString();
239+
var parsed = 0L;
240+
241+
fixed (char* ptr = input)
243242
{
244-
throw new InvalidOperationException("Content-Length value must be an integral number.", ex);
243+
var ch = (ushort*)ptr;
244+
var end = ch + input.Length;
245+
246+
if (ch == end)
247+
{
248+
ThrowInvalidContentLengthException(value);
249+
}
250+
251+
ushort digit = 0;
252+
while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
253+
{
254+
parsed *= 10;
255+
parsed += digit;
256+
ch++;
257+
}
258+
259+
if (ch != end)
260+
{
261+
ThrowInvalidContentLengthException(value);
262+
}
245263
}
264+
265+
return parsed;
266+
}
267+
268+
private static void ThrowInvalidContentLengthException(string value)
269+
{
270+
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
246271
}
247272

248273
private static void ThrowInvalidHeaderCharacter(char ch)

test/Microsoft.AspNetCore.Server.KestrelTests/FrameResponseHeadersTests.cs

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Globalization;
67
using System.Text;
78
using Microsoft.AspNetCore.Http;
89
using Microsoft.AspNetCore.Server.Kestrel;
@@ -149,75 +150,88 @@ public void ThrowsWhenClearingHeadersAfterReadOnlyIsSet()
149150
Assert.Throws<InvalidOperationException>(() => dictionary.Clear());
150151
}
151152

152-
[Fact]
153-
public void ThrowsWhenAddingContentLengthWithNonNumericValue()
153+
[Theory]
154+
[MemberData(nameof(BadContentLengths))]
155+
public void ThrowsWhenAddingContentLengthWithNonNumericValue(string contentLength)
154156
{
155157
var headers = new FrameResponseHeaders();
156158
var dictionary = (IDictionary<string, StringValues>)headers;
157159

158-
Assert.Throws<InvalidOperationException>(() => dictionary.Add("Content-Length", new[] { "bad" }));
160+
var exception = Assert.Throws<InvalidOperationException>(() => dictionary.Add("Content-Length", new[] { contentLength }));
161+
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
159162
}
160163

161-
[Fact]
162-
public void ThrowsWhenSettingContentLengthToNonNumericValue()
164+
[Theory]
165+
[MemberData(nameof(BadContentLengths))]
166+
public void ThrowsWhenSettingContentLengthToNonNumericValue(string contentLength)
163167
{
164168
var headers = new FrameResponseHeaders();
165169
var dictionary = (IDictionary<string, StringValues>)headers;
166170

167-
Assert.Throws<InvalidOperationException>(() => ((IHeaderDictionary)headers)["Content-Length"] = "bad");
171+
var exception = Assert.Throws<InvalidOperationException>(() => ((IHeaderDictionary)headers)["Content-Length"] = contentLength);
172+
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
168173
}
169174

170-
[Fact]
171-
public void ThrowsWhenSettingRawContentLengthToNonNumericValue()
175+
[Theory]
176+
[MemberData(nameof(BadContentLengths))]
177+
public void ThrowsWhenSettingRawContentLengthToNonNumericValue(string contentLength)
172178
{
173179
var headers = new FrameResponseHeaders();
174180

175-
Assert.Throws<InvalidOperationException>(() => headers.SetRawContentLength("bad", Encoding.ASCII.GetBytes("bad")));
181+
var exception = Assert.Throws<InvalidOperationException>(() => headers.SetRawContentLength(contentLength, Encoding.ASCII.GetBytes(contentLength)));
182+
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
176183
}
177184

178-
[Fact]
179-
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue()
185+
[Theory]
186+
[MemberData(nameof(BadContentLengths))]
187+
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue(string contentLength)
180188
{
181189
var headers = new FrameResponseHeaders();
182-
Assert.Throws<InvalidOperationException>(() => headers.HeaderContentLength = "bad");
190+
191+
var exception = Assert.Throws<InvalidOperationException>(() => headers.HeaderContentLength = contentLength);
192+
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
183193
}
184194

185-
[Fact]
186-
public void ContentLengthValueCanBeReadAsLongAfterAddingHeader()
195+
[Theory]
196+
[MemberData(nameof(GoodContentLengths))]
197+
public void ContentLengthValueCanBeReadAsLongAfterAddingHeader(string contentLength)
187198
{
188199
var headers = new FrameResponseHeaders();
189200
var dictionary = (IDictionary<string, StringValues>)headers;
190-
dictionary.Add("Content-Length", "42");
201+
dictionary.Add("Content-Length", contentLength);
191202

192-
Assert.Equal(42, headers.HeaderContentLengthValue);
203+
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
193204
}
194205

195-
[Fact]
196-
public void ContentLengthValueCanBeReadAsLongAfterSettingHeader()
206+
[Theory]
207+
[MemberData(nameof(GoodContentLengths))]
208+
public void ContentLengthValueCanBeReadAsLongAfterSettingHeader(string contentLength)
197209
{
198210
var headers = new FrameResponseHeaders();
199211
var dictionary = (IDictionary<string, StringValues>)headers;
200-
dictionary["Content-Length"] = "42";
212+
dictionary["Content-Length"] = contentLength;
201213

202-
Assert.Equal(42, headers.HeaderContentLengthValue);
214+
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
203215
}
204216

205-
[Fact]
206-
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader()
217+
[Theory]
218+
[MemberData(nameof(GoodContentLengths))]
219+
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader(string contentLength)
207220
{
208221
var headers = new FrameResponseHeaders();
209-
headers.SetRawContentLength("42", Encoding.ASCII.GetBytes("42"));
222+
headers.SetRawContentLength(contentLength, Encoding.ASCII.GetBytes(contentLength));
210223

211-
Assert.Equal(42, headers.HeaderContentLengthValue);
224+
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
212225
}
213226

214-
[Fact]
215-
public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader()
227+
[Theory]
228+
[MemberData(nameof(GoodContentLengths))]
229+
public void ContentLengthValueCanBeReadAsLongAfterAssigningHeader(string contentLength)
216230
{
217231
var headers = new FrameResponseHeaders();
218-
headers.HeaderContentLength = "42";
232+
headers.HeaderContentLength = contentLength;
219233

220-
Assert.Equal(42, headers.HeaderContentLengthValue);
234+
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
221235
}
222236

223237
[Fact]
@@ -243,5 +257,33 @@ public void ContentLengthValueClearedWhenHeadersCleared()
243257

244258
Assert.Equal(null, headers.HeaderContentLengthValue);
245259
}
260+
261+
private static long ParseLong(string value)
262+
{
263+
return long.Parse(value, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture);
264+
}
265+
266+
public static TheoryData<string> GoodContentLengths => new TheoryData<string>
267+
{
268+
"0",
269+
"00",
270+
"042",
271+
"42",
272+
long.MaxValue.ToString(CultureInfo.InvariantCulture)
273+
};
274+
275+
public static TheoryData<string> BadContentLengths => new TheoryData<string>
276+
{
277+
"",
278+
" ",
279+
" 42",
280+
"42 ",
281+
"bad",
282+
"!",
283+
"!42",
284+
"42!",
285+
"42,000",
286+
"42.000",
287+
};
246288
}
247-
}
289+
}

0 commit comments

Comments
 (0)