Изменить ответ промежуточного программного обеспечения
Мое требование: написать промежуточное программное обеспечение, которое отфильтровывает все "плохие слова" из ответа, полученного от другого последующего промежуточного программного обеспечения (например, Mvc).
Проблема: потоковая передача ответа. Поэтому, когда мы вернемся к нашему FilterBadWordsMiddleware
из последующего промежуточного программного обеспечения, которое уже записало ответ, мы опоздали на вечеринку... потому что ответ уже начался, что приводит к известной ошибке response has already started
...
Так как это требование во многих различных ситуациях - как с этим бороться?
5 ответов
Заменить поток ответа на MemoryStream
предотвратить его отправку. Вернуть исходный поток после изменения ответа:
public class EditResponseMiddleware
{
private readonly RequestDelegate _next;
public EditResponseMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originBody = context.Response.Body;
var newBody = new MemoryStream();
context.Response.Body = newBody;
await _next(context);
newBody.Seek(0, SeekOrigin.Begin);
string json = new StreamReader(newBody).ReadToEnd();
context.Response.Body = originBody;
await context.Response.WriteAsync(modifiedJson);
}
}
Это обходной путь, и он может вызвать проблемы с производительностью. Я надеюсь увидеть лучшее решение здесь.
Более простая версия, основанная на коде, который я использовал:
/// <summary>
/// The middleware Invoke method.
/// </summary>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <returns>A Task to support async calls.</returns>
public async Task Invoke(HttpContext httpContext)
{
var originBody = httpContext.Response.Body;
try
{
var memStream = new MemoryStream();
httpContext.Response.Body = memStream;
await _next(httpContext).ConfigureAwait(false);
memStream.Position = 0;
var responseBody = new StreamReader(memStream).ReadToEnd();
//Custom logic to modify response
responseBody = responseBody.Replace("hello", "hi", StringComparison.InvariantCultureIgnoreCase);
var memoryStreamModified = new MemoryStream();
var sw = new StreamWriter(memoryStreamModified);
sw.Write(responseBody);
sw.Flush();
memoryStreamModified.Position = 0;
await memoryStreamModified.CopyToAsync(originBody).ConfigureAwait(false);
}
finally
{
httpContext.Response.Body = originBody;
}
}
К сожалению, мне не разрешено комментировать, так как моя оценка слишком низкая. Так что просто хотел опубликовать свое расширение отличного топового решения и модификацию для.NET Core 3.0+
Прежде всего
context.Request.EnableRewind();
был изменен на
context.Request.EnableBuffering();
в.net Core 3.0+
А вот как я читаю / записываю содержимое тела:
Сначала фильтр, поэтому мы просто изменяем интересующие нас типы контента.
private static readonly IEnumerable<string> validContentTypes = new HashSet<string>() { "text/html", "application/json", "application/javascript" };
Это решение для преобразования надуманных текстов типа [[[Translate me]]] в его перевод. Таким образом, я могу просто разметить все, что нужно перевести, прочитать po-файл, который мы получили от переводчика, а затем выполнить замену перевода в выходном потоке - независимо от того, находятся ли тексты в режиме бритвы, javascript или... что угодно. Вроде как пакет TurquoiseOwl i18n, но в.NET Core, который этот отличный пакет, к сожалению, не поддерживает.
if (modifyResponse)
{
//as we replaced the Response.Body with a MemoryStream instance before,
//here we can read/write Response.Body
//containing the data written by middlewares down the pipeline
var contentType = context.Response.ContentType?.ToLower();
contentType = contentType?.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); // Filter out text/html from "text/html; charset=utf-8"
if (validContentTypes.Contains(contentType))
{
using (var streamReader = new StreamReader(context.Response.Body))
{
// Read the body
context.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await streamReader.ReadToEndAsync();
// Replace [[[Bananas]]] with translated texts - or Bananas if a translation is missing
responseBody = NuggetReplacer.ReplaceNuggets(poCatalog, responseBody);
// Create a new stream with the modified body, and reset the content length to match the new stream
var requestContent = new StringContent(responseBody, Encoding.UTF8, contentType);
context.Response.Body = await requestContent.ReadAsStreamAsync();//modified stream
context.Response.ContentLength = context.Response.Body.Length;
}
}
//finally, write modified data to originBody and set it back as Response.Body value
ReturnBody(context.Response, originBody);
}
"Настоящий" производственный сценарий можно найти здесь: промежуточное ПО tethys logging
Если вы следуете логике, представленной в ссылке, не забудьте добавитьhttpContext.Request.EnableRewind()
предварительный вызов _next(httpContext)
(метод расширения Microsoft.AspNetCore.Http.Internal
пространство имен).
Если вы используете MVC, вы можете попробовать фильтры . Кажется, они позволяют изменить ответ.