-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
… Transfer-Encoding: chunked if Content-Length is known (#1972) * Avoid content if original request has no content and avoid Transfer-Encoding: chunked if Content-Length is known * * Optimized mapping of empty Content * Added tests for mapping requests * Added tests to verify that mapped content is streamed * Changes requested by review * Changes requested by review * Be a little more conservative in streaming test and use 25GB instead if 100GB test data * Reduced streaming test content to 1GB as requested * Convert to file-scoped namespace * Move `Dispose` closer to the constructor * Move `ChunkedContent` class out * IDE1006: Naming rule violation * More private helpers to setup a test * Code review: round 2 * Move common helpers to `Steps` --------- Co-authored-by: Alexander Reinert <a.reinert@impuls-systems.de> Co-authored-by: Raman Maksimchuk <dotnet044@gmail.com>
- Loading branch information
1 parent
8845d1b
commit 319e397
Showing
8 changed files
with
483 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,91 @@ | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Extensions; | ||
using Microsoft.Extensions.Primitives; | ||
using Ocelot.Configuration; | ||
|
||
namespace Ocelot.Request.Mapper; | ||
|
||
public class RequestMapper : IRequestMapper | ||
{ | ||
private static readonly HashSet<string> UnsupportedHeaders = new(StringComparer.OrdinalIgnoreCase) { "host" }; | ||
private static readonly string[] ContentHeaders = { "Content-Length", "Content-Language", "Content-Location", "Content-Range", "Content-MD5", "Content-Disposition", "Content-Encoding" }; | ||
|
||
public HttpRequestMessage Map(HttpRequest request, DownstreamRoute downstreamRoute) | ||
{ | ||
var requestMessage = new HttpRequestMessage | ||
{ | ||
Content = MapContent(request), | ||
Method = MapMethod(request, downstreamRoute), | ||
RequestUri = MapUri(request), | ||
Version = downstreamRoute.DownstreamHttpVersion, | ||
}; | ||
|
||
MapHeaders(request, requestMessage); | ||
|
||
return requestMessage; | ||
} | ||
|
||
private static HttpContent MapContent(HttpRequest request) | ||
{ | ||
// TODO We should check if we really need to call HttpRequest.Body.Length | ||
// But we assume that if CanSeek is true, the length is calculated without an important overhead | ||
if (request.Body is null or { CanSeek: true, Length: <= 0 }) | ||
{ | ||
return null; | ||
} | ||
|
||
var content = new StreamHttpContent(request.HttpContext); | ||
|
||
AddContentHeaders(request, content); | ||
|
||
return content; | ||
} | ||
|
||
private static void AddContentHeaders(HttpRequest request, HttpContent content) | ||
{ | ||
if (!string.IsNullOrEmpty(request.ContentType)) | ||
{ | ||
content.Headers | ||
.TryAddWithoutValidation("Content-Type", new[] { request.ContentType }); | ||
} | ||
|
||
// The performance might be improved by retrieving the matching headers from the request | ||
// instead of calling request.Headers.TryGetValue for each used content header | ||
var matchingHeaders = ContentHeaders.Where(header => request.Headers.ContainsKey(header)); | ||
|
||
foreach (var key in matchingHeaders) | ||
{ | ||
if (!request.Headers.TryGetValue(key, out var value)) | ||
{ | ||
continue; | ||
} | ||
|
||
content.Headers.TryAddWithoutValidation(key, value.ToArray()); | ||
} | ||
} | ||
|
||
private static HttpMethod MapMethod(HttpRequest request, DownstreamRoute downstreamRoute) => | ||
!string.IsNullOrEmpty(downstreamRoute?.DownstreamHttpMethod) ? | ||
new HttpMethod(downstreamRoute.DownstreamHttpMethod) : new HttpMethod(request.Method); | ||
|
||
// TODO Review this method, request.GetEncodedUrl() could throw a NullReferenceException | ||
private static Uri MapUri(HttpRequest request) => new(request.GetEncodedUrl()); | ||
|
||
private static void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) | ||
{ | ||
foreach (var header in request.Headers) | ||
{ | ||
if (IsSupportedHeader(header)) | ||
{ | ||
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); | ||
} | ||
} | ||
} | ||
|
||
private static bool IsSupportedHeader(KeyValuePair<string, StringValues> header) => | ||
!UnsupportedHeaders.Contains(header.Key); | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Extensions; | ||
using Microsoft.Extensions.Primitives; | ||
using Ocelot.Configuration; | ||
|
||
namespace Ocelot.Request.Mapper; | ||
|
||
public class RequestMapper : IRequestMapper | ||
{ | ||
private static readonly HashSet<string> UnsupportedHeaders = new(StringComparer.OrdinalIgnoreCase) { "host", "transfer-encoding" }; | ||
private static readonly string[] ContentHeaders = { "Content-Length", "Content-Language", "Content-Location", "Content-Range", "Content-MD5", "Content-Disposition", "Content-Encoding" }; | ||
|
||
public HttpRequestMessage Map(HttpRequest request, DownstreamRoute downstreamRoute) | ||
{ | ||
var requestMessage = new HttpRequestMessage | ||
{ | ||
Content = MapContent(request), | ||
Method = MapMethod(request, downstreamRoute), | ||
RequestUri = MapUri(request), | ||
Version = downstreamRoute.DownstreamHttpVersion, | ||
}; | ||
|
||
MapHeaders(request, requestMessage); | ||
|
||
return requestMessage; | ||
} | ||
|
||
private static HttpContent MapContent(HttpRequest request) | ||
{ | ||
HttpContent content; | ||
|
||
// No content if we have no body or if the request has no content according to RFC 2616 section 4.3 | ||
if (request.Body == null | ||
|| (!request.ContentLength.HasValue && StringValues.IsNullOrEmpty(request.Headers.TransferEncoding))) | ||
{ | ||
return null; | ||
} | ||
|
||
content = request.ContentLength is 0 | ||
? new ByteArrayContent(Array.Empty<byte>()) | ||
: new StreamHttpContent(request.HttpContext); | ||
|
||
AddContentHeaders(request, content); | ||
|
||
return content; | ||
} | ||
|
||
private static void AddContentHeaders(HttpRequest request, HttpContent content) | ||
{ | ||
if (!string.IsNullOrEmpty(request.ContentType)) | ||
{ | ||
content.Headers | ||
.TryAddWithoutValidation("Content-Type", new[] { request.ContentType }); | ||
} | ||
|
||
// The performance might be improved by retrieving the matching headers from the request | ||
// instead of calling request.Headers.TryGetValue for each used content header | ||
var matchingHeaders = ContentHeaders.Where(header => request.Headers.ContainsKey(header)); | ||
|
||
foreach (var key in matchingHeaders) | ||
{ | ||
if (!request.Headers.TryGetValue(key, out var value)) | ||
{ | ||
continue; | ||
} | ||
|
||
content.Headers.TryAddWithoutValidation(key, value.ToArray()); | ||
} | ||
} | ||
|
||
private static HttpMethod MapMethod(HttpRequest request, DownstreamRoute downstreamRoute) => | ||
!string.IsNullOrEmpty(downstreamRoute?.DownstreamHttpMethod) ? | ||
new HttpMethod(downstreamRoute.DownstreamHttpMethod) : new HttpMethod(request.Method); | ||
|
||
// TODO Review this method, request.GetEncodedUrl() could throw a NullReferenceException | ||
private static Uri MapUri(HttpRequest request) => new(request.GetEncodedUrl()); | ||
|
||
private static void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) | ||
{ | ||
foreach (var header in request.Headers) | ||
{ | ||
if (IsSupportedHeader(header)) | ||
{ | ||
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); | ||
} | ||
} | ||
} | ||
|
||
private static bool IsSupportedHeader(KeyValuePair<string, StringValues> header) => | ||
!UnsupportedHeaders.Contains(header.Key); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.