I've telah berjuang untuk mendapatkan Respon.Tubuhproperti dari ASP.NET Inti aksi dan satu-satunya solusi I've telah mampu mengidentifikasi tampaknya sub-optimal. Solusi membutuhkan swapping out
Respon.Tubuhdengan
MemoryStreamsaat membaca stream ke dalam variabel string, kemudian menukar kembali sebelum dikirim ke client. Dalam contoh di bawah ini, saya'm mencoba untuk mendapatkan Respon.Tubuh
nilai di custom middleware kelas. Respon.Tubuh
adalah set satu-satunya properti di ASP.NET Inti untuk beberapa alasan? Aku hanya kehilangan sesuatu di sini, atau ini merupakan pengawasan/bug/masalah desain? Apakah ada cara yang lebih baik untuk membaca Respon.Tubuh`?
Saat ini (sub-optimal) solusi:
public class MyMiddleWare
{
private readonly RequestDelegate _next;
public MyMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
using (var swapStream = new MemoryStream())
{
var originalResponseBody = context.Response.Body;
context.Response.Body = swapStream;
await _next(context);
swapStream.Seek(0, SeekOrigin.Begin);
string responseBody = new StreamReader(swapStream).ReadToEnd();
swapStream.Seek(0, SeekOrigin.Begin);
await swapStream .CopyToAsync(originalResponseBody);
context.Response.Body = originalResponseBody;
}
}
}
Berusaha larutan menggunakan EnableRewind():
Ini hanya bekerja untuk Permintaan.Tubuh
, bukan Respon.Tubuh
. Ini hasil dalam membaca sebuah string kosong dari Respon.Tubuh
daripada yang sebenarnya respon tubuh isi.
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifeTime)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.Use(async (context, next) => {
context.Request.EnableRewind();
await next();
});
app.UseMyMiddleWare();
app.UseMvc();
// Dispose of Autofac container on application stop
appLifeTime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}
MyMiddleWare.cs
public class MyMiddleWare
{
private readonly RequestDelegate _next;
public MyMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
string responseBody = new StreamReader(context.Request.Body).ReadToEnd(); //responseBody is ""
context.Request.Body.Position = 0;
}
}
Di awal saya respon saya telah benar-benar salah membaca pertanyaan dan pemikiran poster bertanya bagaimana membaca Permintaan.Tubuh
Tapi dia bertanya bagaimana membaca Respon.Tubuh
. I'm meninggalkan saya asli jawab untuk melestarikan sejarah, tetapi juga memperbarui itu untuk menunjukkan bagaimana saya akan menjawab pertanyaan setelah membaca dengan benar.
Jawaban asli
Jika anda ingin buffered aliran yang mendukung membaca beberapa kali anda perlu untuk mengatur
context.Request.EnableRewind()
Idealnya lakukan ini di awal middleware sebelum apa-apa yang perlu dibaca dalam tubuh.
Jadi misalnya anda bisa letakkan kode berikut di awal Configure
metode Startup.cs file:
app.Use(async (context, next) => {
context.Request.EnableRewind();
await next();
});
Sebelum mengaktifkan Mundur stream yang terkait dengan Permintaan.Tubuh
maju-satunya aliran yang doesn't dukungan yang mencari atau membaca aliran yang kedua kalinya. Hal ini dilakukan untuk membuat konfigurasi default dari penanganan permintaan seperti yang ringan dan performant mungkin. Tapi setelah anda mengaktifkan mundur stream upgrade ke aliran yang mendukung mencari dan membaca beberapa kali. Anda dapat mengamati hal ini "meng-upgrade" dengan mengatur breakpoint sebelum dan setelah panggilan untuk EnableRewind
dan mengamati Permintaan.Tubuh
properties. Jadi misalnya Permintaan.Tubuh.CanSeek
akan berubah dari palsu
untuk benar
.
update: Mulai ASP.NET Inti 2.1 Permintaan.EnableBuffering()
tersedia yang upgrade Permintaan.Tubuh
ke FileBufferingReadStream
seperti Permintaan.EnableRewind()
dan karena Permintaan.EnableBuffering()
adalah di publik namespace daripada internal salah satu hal yang harus diutamakan di atas EnableRewind(). (Terima kasih untuk @ArjanEinbu untuk menunjuk keluar)
Kemudian untuk membaca tubuh stream anda bisa misalnya melakukan hal ini:
string bodyContent = new StreamReader(Request.Body).ReadToEnd();
Don't bungkus StreamReader
penciptaan dalam menggunakan pernyataan atau meskipun itu akan menutup mendasari tubuh stream pada akhir menggunakan blok dan kode kemudian dalam siklus hidup tidak akan mampu membaca tubuh.
Juga hanya untuk menjadi aman, itu mungkin ide yang baik untuk mengikuti instruksi di atas baris kode yang berbunyi tubuh isi dengan baris kode untuk reset tubuh's stream posisi kembali ke 0.
request.Body.Position = 0;
Dengan cara itu setiap kode kemudian dalam siklus hidup akan menemukan permintaan.Tubuh dalam keadaan seperti ini masih't pernah baca belum.
Diperbarui Jawaban
Maaf saya awalnya salah membaca pertanyaan anda. Konsep upgrade terkait aliran menjadi buffered stream masih berlaku. Namun anda harus melakukannya secara manual, saya'm tidak menyadari apapun yang dibangun di .Net fungsionalitas Inti yang memungkinkan anda membaca respon stream sekali tertulis dalam cara yang EnableRewind()
memungkinkan seorang pengembang membaca permintaan stream setelah itu's telah baca.
Anda "hacky" pendekatan ini mungkin benar-benar sesuai. Anda pada dasarnya mengkonversi aliran yang dapat't berusaha untuk salah satu yang bisa. Pada akhir hari Respon.Tubuh
stream telah mendapatkan bertukar keluar dengan aliran yang buffered dan mendukung apapun. Berikut ini adalah mengambil middleware untuk melakukan itu, tetapi anda akan melihat itu's sangat mirip dengan pendekatan anda. Namun aku memilih untuk menggunakan akhirnya blok sebagai perlindungan tambahan untuk menempatkan aliran asli kembali pada Respon.Tubuh
dan saya menggunakan Posisi
milik stream daripada Mencari
metode sejak sintaks ini sedikit lebih sederhana tetapi efeknya tidak berbeda dari pendekatan anda.
public class ResponseRewindMiddleware {
private readonly RequestDelegate next;
public ResponseRewindMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(HttpContext context) {
Stream originalBody = context.Response.Body;
try {
using (var memStream = new MemoryStream()) {
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
string responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
} finally {
context.Response.Body = originalBody;
}
}
Apa yang anda jelaskan sebagai hack ini benar-benar disarankan pendekatan tentang bagaimana untuk mengelola respon sungai di custom middleware.
Karena pipa sifat middle ware desain di mana masing-masing tengah ware tidak menyadari sebelumnya atau berikutnya handler di dalam pipa. Tidak ada jaminan bahwa saat ini tengah ware akan menjadi salah satu yang menulis respon kecuali berpegang pada respon aliran itu diberikan sebelum lewat di sungai itu (saat ini tengah ware) kontrol. Desain ini terlihat di OWIN dan akhirnya dipanggang dalam asp.net-core.
Setelah anda mulai menulis untuk respon aliran ini mengirimkan body dan header (respon) untuk klien. Jika yang lain handler bawah pipa itu sebelum saat handler punya kesempatan maka itu tidak akan mampu menambahkan sesuatu untuk respon setelah itu telah sudah dikirim.
Yang lagi-lagi tidak dijamin untuk menjadi aktual respon stream jika sebelumnya middleware dalam pipa mengikuti strategi yang sama lewat aliran lain ke bawah garis.
Referensi ASP.NET Inti Middleware dasar-Dasar
Peringatan
hati-hati memodifikasi
HttpResponse
setelah menyerukannext
, karena respon yang mungkin telah dikirim ke klien. Anda dapat menggunakan HttpResponse.HasStarted untuk memeriksa apakah header sudah dikirim.Peringatan
Jangan panggil
berikutnya.Memanggil
setelah memanggilmenulis
metode. Sebuah middleware komponen baik menghasilkan respon atau panggilanberikutnya.Memanggil
, tapi tidak keduanya.
Contoh dibangun di dasar middlewares dari aspnet/BasicMiddleware Github repo
ResponseCompressionMiddleware.cs
/// <summary>
/// Invoke the middleware.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
if (!_provider.CheckRequestAcceptsCompression(context))
{
await _next(context);
return;
}
var bodyStream = context.Response.Body;
var originalBufferFeature = context.Features.Get<IHttpBufferingFeature>();
var originalSendFileFeature = context.Features.Get<IHttpSendFileFeature>();
var bodyWrapperStream = new BodyWrapperStream(context, bodyStream, _provider,
originalBufferFeature, originalSendFileFeature);
context.Response.Body = bodyWrapperStream;
context.Features.Set<IHttpBufferingFeature>(bodyWrapperStream);
if (originalSendFileFeature != null)
{
context.Features.Set<IHttpSendFileFeature>(bodyWrapperStream);
}
try
{
await _next(context);
// This is not disposed via a using statement because we don't want to flush the compression buffer for unhandled exceptions,
// that may cause secondary exceptions.
bodyWrapperStream.Dispose();
}
finally
{
context.Response.Body = bodyStream;
context.Features.Set(originalBufferFeature);
if (originalSendFileFeature != null)
{
context.Features.Set(originalSendFileFeature);
}
}
}
Anda dapat menggunakan middleware dalam permintaan pipa, dalam rangka untuk log permintaan dan tanggapan.
Namun peningkatan bahaya kebocoran memori
, karena facth bahwa:
dapat berakhir dengan Tumpukan Besar Objek (dalam kasus tubuh dari permintaan atau respon yang lebih besar dari 85.000 byte). Hal ini meningkatkan bahaya dari kebocoran memori dalam aplikasi anda. Dalam rangka untuk menghindari LOH, memori aliran dapat diganti dengan Didaur ulang Memori streaming menggunakan relevan perpustakaan.
Sebuah implementasi yang menggunakan Didaur ulang memori aliran:
public class RequestResponseLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
private const int ReadChunkBufferLength = 4096;
public RequestResponseLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory
.CreateLogger<RequestResponseLoggingMiddleware>();
_recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
}
public async Task Invoke(HttpContext context)
{
LogRequest(context.Request);
await LogResponseAsync(context);
}
private void LogRequest(HttpRequest request)
{
request.EnableRewind();
using (var requestStream = _recyclableMemoryStreamManager.GetStream())
{
request.Body.CopyTo(requestStream);
_logger.LogInformation($"Http Request Information:{Environment.NewLine}" +
$"Schema:{request.Scheme} " +
$"Host: {request.Host} " +
$"Path: {request.Path} " +
$"QueryString: {request.QueryString} " +
$"Request Body: {ReadStreamInChunks(requestStream)}");
}
}
private async Task LogResponseAsync(HttpContext context)
{
var originalBody = context.Response.Body;
using (var responseStream = _recyclableMemoryStreamManager.GetStream())
{
context.Response.Body = responseStream;
await _next.Invoke(context);
await responseStream.CopyToAsync(originalBody);
_logger.LogInformation($"Http Response Information:{Environment.NewLine}" +
$"Schema:{context.Request.Scheme} " +
$"Host: {context.Request.Host} " +
$"Path: {context.Request.Path} " +
$"QueryString: {context.Request.QueryString} " +
$"Response Body: {ReadStreamInChunks(responseStream)}");
}
context.Response.Body = originalBody;
}
private static string ReadStreamInChunks(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
string result;
using (var textWriter = new StringWriter())
using (var reader = new StreamReader(stream))
{
var readChunk = new char[ReadChunkBufferLength];
int readChunkLength;
//do while: is useful for the last iteration in case readChunkLength < chunkLength
do
{
readChunkLength = reader.ReadBlock(readChunk, 0, ReadChunkBufferLength);
textWriter.Write(readChunk, 0, readChunkLength);
} while (readChunkLength > 0);
result = textWriter.ToString();
}
return result;
}
}
NB. Bahaya LOH ini tidak sepenuhnya memberantas karena textWriter.ToString()
di sisi lain anda dapat menggunakan login client library yang mendukung terstruktur logging (ie. Serilog) dan inject contoh yang dapat Didaur ulang Memori Streaming.