Skip to content
This repository was archived by the owner on Jun 24, 2021. It is now read-only.

Commit fa20a3f

Browse files
committed
1 parent 16ea993 commit fa20a3f

File tree

1 file changed

+34
-52
lines changed

1 file changed

+34
-52
lines changed

src/i18n.Core/Middleware/I18NMiddleware.cs

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
using System.Globalization;
66
using System.IO;
77
using System.Linq;
8+
using System.Net.Http;
89
using System.Text;
910
using System.Threading.Tasks;
1011
using i18n.Core.Abstractions;
1112
using JetBrains.Annotations;
1213
using Microsoft.AspNetCore.Hosting;
1314
using Microsoft.AspNetCore.Http;
15+
using Microsoft.AspNetCore.Http.Features;
1416
using Microsoft.AspNetCore.Localization;
1517
using Microsoft.Extensions.Hosting;
1618
using Microsoft.Extensions.Logging;
@@ -48,7 +50,6 @@ public sealed class I18NMiddlewareOptions
4850
public ICollection<string> ExcludeUrls { get; }
4951
public bool CacheEnabled { get; [UsedImplicitly] set; }
5052
public Encoding RequestEncoding { get; [UsedImplicitly] set; }
51-
public int RequestBufferingThreshold { get; [UsedImplicitly] set; }
5253

5354
public I18NMiddlewareOptions()
5455
{
@@ -69,7 +70,6 @@ public I18NMiddlewareOptions()
6970
};
7071

7172
RequestEncoding = Encoding.UTF8;
72-
RequestBufferingThreshold = 84000; // Less than default GC LOH
7373
}
7474
}
7575

@@ -82,7 +82,7 @@ public sealed class I18NMiddleware
8282
readonly INuggetReplacer _nuggetReplacer;
8383
readonly I18NMiddlewareOptions _options;
8484

85-
public I18NMiddleware(RequestDelegate next, ILocalizationManager localizationManager, IOptions<I18NMiddlewareOptions> middleWareOptions,
85+
public I18NMiddleware(RequestDelegate next, ILocalizationManager localizationManager, IOptions<I18NMiddlewareOptions> middleWareOptions,
8686
[CanBeNull] ILogger<I18NMiddleware> logger, IPooledStreamManager pooledStreamManager, INuggetReplacer nuggetReplacer)
8787
{
8888
_next = next;
@@ -100,27 +100,33 @@ public I18NMiddleware(RequestDelegate next, ILocalizationManager localizationMan
100100
[UsedImplicitly]
101101
public async Task InvokeAsync(HttpContext context, IWebHostEnvironment webHostEnvironment)
102102
{
103+
var cancellationToken = context.RequestAborted;
104+
var requestEncoding = _options.RequestEncoding ?? Encoding.UTF8;
103105
var excludeUrls = _options.ExcludeUrls;
104-
var modifyResponse = excludeUrls == null || !excludeUrls.Any(bl => context.Request.Path.Value != null
106+
var modifyResponse = excludeUrls == null || !excludeUrls.Any(bl => context.Request.Path.Value != null
105107
&& context.Request.Path.Value.ToLowerInvariant().Contains(bl));
106108
if (!modifyResponse)
107109
{
108110
await _next(context);
109111
return;
110112
}
111113

112-
context.Request.EnableBuffering(_options.RequestBufferingThreshold);
113-
var originalResponseBodyStream = ReplaceHttpResponseBodyStream(context.Response);
114+
var responseBodyPooledStream = new DisposablePooledStream(_pooledStreamManager, nameof(I18NMiddleware));
115+
context.Response.RegisterForDisposeAsync(responseBodyPooledStream);
114116

115-
try
116-
{
117-
await _next(context);
118-
}
119-
catch
120-
{
121-
await ReturnHttpResponseBodyStreamAsync(context.Response, originalResponseBodyStream).ConfigureAwait(false);
122-
throw;
123-
}
117+
var httpResponseBodyFeature = context.Features.Get<IHttpResponseBodyFeature>();
118+
var httpResponseFeature = context.Features.Get<IHttpResponseFeature>();
119+
120+
var streamResponseBodyFeature = new StreamResponseBodyFeature(responseBodyPooledStream);
121+
context.Features.Set<IHttpResponseBodyFeature>(streamResponseBodyFeature);
122+
123+
await _next(context).ConfigureAwait(false);
124+
125+
// Force dynamic content type in order reset Content-Length header.
126+
httpResponseFeature.Headers.ContentLength = null;
127+
128+
var httpResponseBodyStream = (Stream) responseBodyPooledStream;
129+
httpResponseBodyStream.Seek(0, SeekOrigin.Begin);
124130

125131
var contentType = GetRequestContentType(context);
126132
var validContentTypes = _options.ValidContentTypes;
@@ -133,8 +139,8 @@ public async Task InvokeAsync(HttpContext context, IWebHostEnvironment webHostEn
133139
_logger?.LogDebug(
134140
$"Request path: {context.Request.Path}. Culture name: {cultureDictionary.CultureName}. Translations: {cultureDictionary.Translations.Count}.");
135141

136-
var responseBody = await ReadResponseBodyAsStringAsync(context);
137-
142+
var responseBody = await ReadResponseBodyAsStringAsync(httpResponseBodyStream, requestEncoding);
143+
138144
string responseBodyTranslated;
139145
if (webHostEnvironment.IsDevelopment())
140146
{
@@ -145,20 +151,20 @@ public async Task InvokeAsync(HttpContext context, IWebHostEnvironment webHostEn
145151

146152
_logger?.LogDebug($"Replaced body in {sw.ElapsedMilliseconds} ms.");
147153
const string i18NMiddlewareName = "X-" + nameof(I18NMiddleware) + "-Ms";
148-
context.Response.Headers[i18NMiddlewareName] = sw.ElapsedMilliseconds.ToString();
154+
httpResponseFeature.Headers[i18NMiddlewareName] = sw.ElapsedMilliseconds.ToString();
149155
}
150156
else
151157
{
152158
responseBodyTranslated = _nuggetReplacer.Replace(cultureDictionary, responseBody);
153159
}
154160

155-
context.Response.Body = originalResponseBodyStream;
156-
await context.Response.WriteAsync(responseBodyTranslated, _options.RequestEncoding ?? Encoding.UTF8);
161+
var stringContent = new StringContent(responseBodyTranslated, requestEncoding, contentType);
162+
await stringContent.CopyToAsync(httpResponseBodyFeature.Stream, cancellationToken);
157163

158164
return;
159165
}
160166

161-
await ReturnHttpResponseBodyStreamAsync(context.Response, originalResponseBodyStream).ConfigureAwait(false);
167+
await httpResponseBodyStream.CopyToAsync(httpResponseBodyFeature.Stream, cancellationToken).ConfigureAwait(false);
162168
}
163169

164170
[SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier")]
@@ -186,47 +192,23 @@ CultureInfo GetRequestCultureInfo(HttpContext context)
186192
return requestCultureInfo;
187193
}
188194

189-
static async Task<string> ReadResponseBodyAsStringAsync(HttpContext context)
195+
static async Task<string> ReadResponseBodyAsStringAsync(Stream stream, Encoding encoding)
190196
{
191-
context.Response.Body.Seek(0, SeekOrigin.Begin);
192-
193-
string responseBody;
194-
using (var streamReader = new StreamReader(context.Response.Body, Encoding.UTF8, false, leaveOpen: true))
195-
{
196-
responseBody = await streamReader.ReadToEndAsync().ConfigureAwait(false);
197-
}
198-
199-
context.Response.Body.Seek(0, SeekOrigin.Begin);
200-
201-
return responseBody;
202-
}
203-
204-
Stream ReplaceHttpResponseBodyStream(HttpResponse httpResponse)
205-
{
206-
var originBody = httpResponse.Body;
207-
httpResponse.Body = _pooledStreamManager.GetStream(nameof(I18NMiddleware)) ??
208-
throw new Exception($"{nameof(_pooledStreamManager)} must return a valid stream.");
209-
httpResponse.Body.Seek(0, SeekOrigin.Begin);
210-
httpResponse.RegisterForDisposeAsync(new DisposablePooledStream(_pooledStreamManager, httpResponse.Body));
211-
return originBody;
197+
using var streamReader = new StreamReader(stream, encoding, false);
198+
return await streamReader.ReadToEndAsync().ConfigureAwait(false);
212199
}
213200

214-
static async ValueTask ReturnHttpResponseBodyStreamAsync(HttpResponse httpResponse, Stream originalBodyResponseStream)
215-
{
216-
httpResponse.Body.Seek(0, SeekOrigin.Begin);
217-
await httpResponse.Body.CopyToAsync(originalBodyResponseStream).ConfigureAwait(false);
218-
httpResponse.Body = originalBodyResponseStream;
219-
}
220-
221201
readonly struct DisposablePooledStream : IAsyncDisposable
222202
{
223203
readonly IPooledStreamManager _pooledStreamManager;
224204
readonly Stream _stream;
225205

226-
public DisposablePooledStream(IPooledStreamManager pooledStreamManager, Stream stream)
206+
public static implicit operator Stream(DisposablePooledStream stream) => stream._stream;
207+
208+
public DisposablePooledStream(IPooledStreamManager pooledStreamManager, string streamName)
227209
{
228210
_pooledStreamManager = pooledStreamManager;
229-
_stream = stream;
211+
_stream = _pooledStreamManager.GetStream(streamName);
230212
}
231213

232214
public ValueTask DisposeAsync()

0 commit comments

Comments
 (0)