I have a .NET Core Web API project with a request and response logging middleware. I registered both middleware files in the Configure method in the Startup file
app.UseMiddleware<RequestLoggingMiddleware>();
app.UseMiddleware<ResponseLoggingMiddleware>();
For now I'm just trying to log the body, the request logging seems to work fine
public class RequestLoggingMiddleware
{
private readonly RequestDelegate requestDelegate;
public RequestLoggingMiddleware(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task InvokeAsync(HttpContext httpContext)
{
HttpRequest httpRequest = httpContext.Request;
httpRequest.EnableBuffering();
ReadResult bodyReadResult = await httpRequest.BodyReader.ReadAsync();
ReadOnlySequence<byte> bodyBuffer = bodyReadResult.Buffer;
if (bodyBuffer.Length > 0)
{
byte[] bodyBytes = bodyBuffer.ToArray();
string bodyText = Encoding.UTF8.GetString(bodyBytes);
Console.WriteLine(bodyText);
}
// Reset
httpRequest.Body.Seek(0, SeekOrigin.Begin);
await requestDelegate(httpContext);
}
}
My response logging middleware does not have access to a BodyReader. I tried to go with this code
public class ResponseLoggingMiddleware
{
private readonly RequestDelegate requestDelegate;
public ResponseLoggingMiddleware(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task InvokeAsync(HttpContext httpContext)
{
await requestDelegate(httpContext);
Stream responseBody = httpContext.Response.Body;
using (StreamReader reader = new StreamReader(responseBody))
{
string bodyText = await reader.ReadToEndAsync();
// Reset
responseBody.Seek(0, SeekOrigin.Begin);
Console.WriteLine(bodyText);
}
}
}
but unfortunately I get this exception
System.ArgumentException: Stream was not readable.
Does someone know how to fix it?
You may use StreamReader to read the request body. Below code, you may follow.
string body = string.Empty;
Request.EnableRewind();
using (var reader = new StreamReader(Request.Body))
{
Request.Body.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
In the same way, you can get a response body.
using (var reader = new StreamReader(Response.Body))
{
Response.Body.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
Notes: Above code is based on .net core 2.2
Below is the code supported by .net core 5
string body = string.Empty;
using (var reader = new StreamReader(Request.Body))
{
//Request.Body.Seek(0, SeekOrigin.Begin);
//body = reader.ReadToEnd();
body = await reader.ReadToEndAsync();
}
Now, you have the response in the body property, do your kinds of stuff (JSON Deserilize).
Stream is an Abstract class, you must tu use a MemoryStream, check this:
using (var ms = new MemoryStream())
{
var cuerpoOriginalRespuesta = contexto.Response.Body;
contexto.Response.Body = ms;
await siguiente(contexto);
ms.Seek(0, SeekOrigin.Begin);
string respuesta = new StreamReader(ms).ReadToEnd();
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(cuerpoOriginalRespuesta);
contexto.Response.Body = cuerpoOriginalRespuesta;
}
Anyone who is looking for the solution of .net standard can use the following snippet
For ApiController
string requestBody = string.Empty;
using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
{
reader.BaseStream.Seek(0, SeekOrigin.Begin);
requestBody = reader.ReadToEnd();
}
Console.WriteLine(requestBody);
For regular controller
string requestBody = string.Empty;
using (var reader = new StreamReader(HttpContext.Request.InputStream))
{
reader.BaseStream.Seek(0, SeekOrigin.Begin);
requestBody = reader.ReadToEnd();
}
Console.WriteLine(requestBody);
Related
This is the code below (C#) To upload in https://nft.storage/ It Works fine But when I upload mp4 file (Uploaded successfully) , The uploaded file doesn’t work . Source Code https://github.com/filipepolizel/unity-nft-storage
I used many different HTTPCLIENT example but it same
broken Uploaded mp4 File: http://ipfs.io/ipfs/bafybeibt4jqvncw6cuyih27mujbpdmsjl46pykablvravh3qg63vuvcdqy
// nft.storage API endpoint
private static readonly string nftStorageApiUrl = "https://api.nft.storage/";
// HTTP client to communicate with nft.storage
private static readonly HttpClient nftClient = new HttpClient();
// http client to communicate with IPFS API
private static readonly HttpClient ipfsClient = new HttpClient();
// nft.storage API key
public string apiToken;
void Start()
{
nftClient.DefaultRequestHeaders.Add("Accept", "application/json");
if (apiToken != null)
{
nftClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + apiToken);
}
else
{
// log in console in case no API key is found during initialization
Debug.Log("Starting NFT Storage Client without API key, please call 'SetApiToken' method before using class methods.");
}
}
public async Task<NFTStorageUploadResponse> UploadDataFromFile(string path)
{
StreamReader reader = new StreamReader(path);
string data = reader.ReadToEnd();
reader.Close();
print("Uploading...");
return await UploadDataFromString(data);
}
public async Task<NFTStorageUploadResponse> UploadDataFromString(string data)
{
string requestUri = nftStorageApiUrl + "/upload";
string rawResponse = await Upload(requestUri, data);
NFTStorageUploadResponse parsedResponse = JsonUtility.FromJson<NFTStorageUploadResponse>(rawResponse);
return parsedResponse;
}
private async Task<string> Upload(string uri, string paramString)
{
try
{
using (HttpContent content = new StringContent(paramString))
{
//content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentType = new MediaTypeHeaderValue("*/*");
HttpResponseMessage response = await nftClient.PostAsync(uri, content);
response.EnsureSuccessStatusCode();
Stream responseStream = await response.Content.ReadAsStreamAsync();
StreamReader reader = new StreamReader(responseStream);
return reader.ReadToEnd();
}
}
catch (HttpRequestException e)
{
Debug.Log("HTTP Request Exception: " + e.Message);
Debug.Log(e);
return null;
}
}
the answer helped me . thanks , I changed the Update method to :
public static async Task<string> Upload(string uri, string pathFile)
{
byte[] bytes = System.IO.File.ReadAllBytes(pathFile);
using (var content = new ByteArrayContent(bytes))
{
content.Headers.ContentType = new MediaTypeHeaderValue("*/*");
//Send it
var response = await nftClient.PostAsync(uri, content);
response.EnsureSuccessStatusCode();
Stream responseStream = await response.Content.ReadAsStreamAsync();
StreamReader reader = new StreamReader(responseStream);
return reader.ReadToEnd();
}
}
It works great now
I have one memory stream object in my server-side, this object should be accessible in another party, which I call it a client or a consumer for my API.
In server-side I have a method like this (parameters.Save is related to a third-party library)
public MemoryStream GetSerializedParameters()
{
var parameters = GetParameters();
MemoryStream memory = new MemoryStream();
parameters.Save(memory);
return memory;
}
I'm thinking about sending this memory stream to a client with web API, so my action is something like this:
[HttpGet("parameters")]
public HttpResponseMessage GetParameters()
{
var stream = _server.GetSerializedParameters();
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentLength = stream.Length;
return result;
}
I'm not sure if it is the right way and this implementation is correct because I am in trouble to consume it:
I do not know which method of httpClient I have to use: ReadAsStreamAsync() or anything else, because I could not find anything to work
Sure you can:
[HttpGet]
public async Task Get()
{
var randomString = "thisIsCool";
var randomStringBytes = Encoding.UTF8.GetBytes(randomString);
using (var ms = new MemoryStream(randomStringBytes))
{
await ms.CopyToAsync(this.Response.Body);
}
Based on my under standing below code may help you:
WEB API:
[HttpGet]
public HttpResponseMessage ReadToStream(HttpRequestMessage requestMessage)
{
var streamObj = _server.GetSerializedParameters();
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StreamContent(streamObj);
requestMessage.RegisterForDispose(streamObj);
response.StatusCode = HttpStatusCode.OK;
return response;
}
Client Side
public async Task<string> DownloadFile(string guid)
{
var fileInfo = new FileInfo($"{guid}.txt");
var response = await _httpClient.GetAsync($"{url}/api/fileDownloadAPI?guid={guid}");
response.EnsureSuccessStatusCode();
await using var ms = await response.Content.ReadAsStreamAsync();
await using var fs = File.Create(fileInfo.FullName);
ms.Seek(0, SeekOrigin.Begin);
ms.CopyTo(fs);
return fileInfo.FullName;
}
I found the solution like this:
here is in server side:
[HttpGet("parameters")]
public IActionResult GetParameters()
{
var stream = _server.GetSerializedParameters();
stream.Seek(0, SeekOrigin.Begin);
return File(stream, MediaTypeNames.Text.Plain, "parameters.txt");
}
and here is in client-side:
public MemoryStream StoreParameters()
{
var request =new HttpRequestMessage
{
RequestUri = new Uri("https://localhost:44316/api/parameters"),
Method = HttpMethod.Get
};
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result;
var ms = new MemoryStream();
result.Content.CopyToAsync(ms).Wait();
return result.IsSuccessStatusCode ? ms: null;
}
I am having a simple middleware which fetches the body of the request and store it in a string. It is reading fine the stream, but the issue is it wont call my controller which called just after I read the stream and throw the error
A non-empty request body is required
. Below is my code.
public async Task Invoke(HttpContext httpContext)
{
var timer = Stopwatch.StartNew();
ReadBodyFromHttpContext(httpContext);
await _next(httpContext);
timer.Stop();
}
private string ReadBodyFromHttpContext(HttpContext httpContext)
{
return await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
}
You need to convert HttpContext.Request.Body from a forward only memory stream to a seekable stream, shown below.
// Enable seeking
context.Request.EnableBuffering();
// Read the stream as text
var bodyAsText = await new System.IO.StreamReader(context.Request.Body).ReadToEndAsync();
// Set the position of the stream to 0 to enable rereading
context.Request.Body.Position = 0;
when it comes to capturing the body of an HTTP request and/or response, this is no trivial effort. In ASP .NET Core, the body is a stream – once you consume it (for logging, in this case), it’s gone, rendering the rest of the pipeline useless.
Ref:http://www.palador.com/2017/05/24/logging-the-body-of-http-request-and-response-in-asp-net-core/
public async Task Invoke(HttpContext httpContext)
{
var timer = Stopwatch.StartNew();
string bodyAsText = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
var injectedRequestStream = new MemoryStream();
var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText);
injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length);
injectedRequestStream.Seek(0, SeekOrigin.Begin);
httpContext.Request.Body = injectedRequestStream;
await _next(httpContext);
timer.Stop();
}
Few things are crucial here:
enable buffering
last flag leaveOpen in StreamReader
reset request body stream position (SeekOrigin.Begin)
public void UseMyMiddleware(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Request.EnableBuffering();
using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, false, 1024, true))
{
var body = await reader.ReadToEndAsync();
context.Request.Body.Seek(0, SeekOrigin.Begin);
}
await next.Invoke();
});
}
using (var mem = new MemoryStream())
using (var reader = new StreamReader(mem))
{
Request.Body.CopyTo(mem);
var body = reader.ReadToEnd();
//and this you can reset the position of the stream.
mem.Seek(0, SeekOrigin.Begin);
body = reader.ReadToEnd();
}
Here you are can read how it works. https://gunnarpeipman.com/aspnet-core-request-body/
You can try this
public async Task Invoke(HttpContext context)
{
var request = context.Request;
request.EnableBuffering();
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var requestContent = Encoding.UTF8.GetString(buffer);
request.Body.Position = 0; //rewinding the stream to 0
}
I have a self-hosted app that's using OWIN to provide a basic web server. The key part of the configuration is the following line:
appBuilder.UseFileServer(new FileServerOptions {
FileSystem = new PhysicalFileSystem(filePath)
});
This provides the static files listed in the filePath for browsing, and this much is working as expected.
However I've run into a case where I want to slightly modify one of the files on a request-by-request basis. In particular, I want to load the "normal" version of the file from the filesystem, alter it slightly based on the incoming web request's headers, and then return the altered version to the client instead of the original. All other files should remain unmodified.
How do I go about doing this?
Well, I don't know if this is a good way to do this, but it seems to work:
internal class FileReplacementMiddleware : OwinMiddleware
{
public FileReplacementMiddleware(OwinMiddleware next) : base(next) {}
public override async Task Invoke(IOwinContext context)
{
MemoryStream memStream = null;
Stream httpStream = null;
if (ShouldAmendResponse(context))
{
memStream = new MemoryStream();
httpStream = context.Response.Body;
context.Response.Body = memStream;
}
await Next.Invoke(context);
if (memStream != null)
{
var content = await ReadStreamAsync(memStream);
if (context.Response.StatusCode == 200)
{
content = AmendContent(context, content);
}
var contentBytes = Encoding.UTF8.GetBytes(content);
context.Response.Body = httpStream;
context.Response.ETag = null;
context.Response.ContentLength = contentBytes.Length;
await context.Response.WriteAsync(contentBytes, context.Request.CallCancelled);
}
}
private static async Task<string> ReadStreamAsync(MemoryStream stream)
{
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
return await reader.ReadToEndAsync();
}
}
private bool ShouldAmendResponse(IOwinContext context)
{
// logic
}
private string AmendContent(IOwinContext context, string content)
{
// logic
}
}
Add this to the pipeline somewhere before the static files middleware.
I'm trying to write a Owin midleware component that would LOG every incoming request and response to the database.
Here's how far I managed to get.
I got stuck on reading the response.body. Says:
Stream does not support reading.
How can I read the Response.Body ?
public class LoggingMiddleware : OwinMiddleware
{
private static Logger log = LogManager.GetLogger("WebApi");
public LoggingMiddleware(OwinMiddleware next, IAppBuilder app)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
using (var db = new HermesEntities())
{
var sw = new Stopwatch();
sw.Start();
var logRequest = new log_Request
{
Body = new StreamReader(context.Request.Body).ReadToEndAsync().Result,
Headers = Json.Encode(context.Request.Headers),
IPTo = context.Request.LocalIpAddress,
IpFrom = context.Request.RemoteIpAddress,
Method = context.Request.Method,
Service = "Api",
Uri = context.Request.Uri.ToString(),
UserName = context.Request.User.Identity.Name
};
db.log_Request.Add(logRequest);
context.Request.Body.Position = 0;
await Next.Invoke(context);
var mem2 = new MemoryStream();
await context.Response.Body.CopyToAsync(mem2);
var logResponse = new log_Response
{
Headers = Json.Encode(context.Response.Headers),
Body = new StreamReader(mem2).ReadToEndAsync().Result,
ProcessingTime = sw.Elapsed,
ResultCode = context.Response.StatusCode,
log_Request = logRequest
};
db.log_Response.Add(logResponse);
await db.SaveChangesAsync();
}
}
}
Response Body can be logged in this manner:
public class LoggingMiddleware : OwinMiddleware
{
private static Logger log = LogManager.GetLogger("WebApi");
public LoggingMiddleware(OwinMiddleware next, IAppBuilder app)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
using (var db = new HermesEntities())
{
var sw = new Stopwatch();
sw.Start();
var logRequest = new log_Request
{
Body = new StreamReader(context.Request.Body).ReadToEndAsync().Result,
Headers = Json.Encode(context.Request.Headers),
IPTo = context.Request.LocalIpAddress,
IpFrom = context.Request.RemoteIpAddress,
Method = context.Request.Method,
Service = "Api",
Uri = context.Request.Uri.ToString(),
UserName = context.Request.User.Identity.Name
};
db.log_Request.Add(logRequest);
context.Request.Body.Position = 0;
Stream stream = context.Response.Body;
MemoryStream responseBuffer = new MemoryStream();
context.Response.Body = responseBuffer;
await Next.Invoke(context);
responseBuffer.Seek(0, SeekOrigin.Begin);
var responseBody = new StreamReader(responseBuffer).ReadToEnd();
//do logging
var logResponse = new log_Response
{
Headers = Json.Encode(context.Response.Headers),
Body = responseBody,
ProcessingTime = sw.Elapsed,
ResultCode = context.Response.StatusCode,
log_Request = logRequest
};
db.log_Response.Add(logResponse);
responseBuffer.Seek(0, SeekOrigin.Begin);
await responseBuffer.CopyToAsync(stream);
await db.SaveChangesAsync();
}
}
}
Response body is a write-only network stream by default for Katana hosts. You will need to replace it with a MemoryStream, read the stream, log the content and then copy the memory stream content back into the original network stream. BTW, if your middleware reads the request body, downstream components cannot, unless the request body is buffered. So, you might need to consider buffering the request body as well. If you want to look at some code, http://lbadri.wordpress.com/2013/08/03/owin-authentication-middleware-for-hawk-in-thinktecture-identitymodel-45/ could be a starting point. Look at the class HawkAuthenticationHandler.
I've solved the problem by applying an action attribute writing the request body to OWIN environment dictionary. After that, the logging middleware can access it by a key.
public class LogResponseBodyInterceptorAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
if (actionExecutedContext?.Response?.Content is ObjectContent)
{
actionExecutedContext.Request.GetOwinContext().Environment["log-responseBody"] =
await actionExecutedContext.Response.Content.ReadAsStringAsync();
}
}
}
And then in the middleware:
public class RequestLoggingMiddleware
{
...
private void LogResponse(IOwinContext owinContext)
{
var message = new StringBuilder()
.AppendLine($"{owinContext.Response.StatusCode}")
.AppendLine(string.Join(Environment.NewLine, owinContext.Response.Headers.Select(x => $"{x.Key}: {string.Join("; ", x.Value)}")));
if (owinContext.Environment.ContainsKey("log-responseBody"))
{
var responseBody = (string)owinContext.Environment["log-responseBody"];
message.AppendLine()
.AppendLine(responseBody);
}
var logEvent = new LogEventInfo
{
Level = LogLevel.Trace,
Properties =
{
{"correlationId", owinContext.Environment["correlation-id"]},
{"entryType", "Response"}
},
Message = message.ToString()
};
_logger.Log(logEvent);
}
}
If you're facing the issue where the Stream does not support reading error occurs when trying to read the request body more than once, you can try the following workaround.
In your Startup.cs file, add the following middleware to enable buffering of the request body, which allows you to re-read the request body for logging purposes:
app.Use(async (context, next) =>
{
using (var streamCopy = new MemoryStream())
{
await context.Request.Body.CopyToAsync(streamCopy);
streamCopy.Position = 0;
string body = new StreamReader(streamCopy).ReadToEnd();
streamCopy.Position = 0;
context.Request.Body = streamCopy;
await next();
}
});
This middleware creates a copy of the request body stream, reads the entire stream into a string, sets the stream position back to the beginning, sets the request body to the copied stream, and then calls the next middleware.
After this middleware, you can now use context.Request.Body.Position = 0; to set the position of the request body stream back to the beginning so you can re-read the request body.
I hope this helps!