I am learning ASP.NET Core and for that I am trying to implement a simple proxy server.
Setup idea:
I will set the proxy settings of my browser to point to my server (currently localhost). The server will duplicate the http request and send to the intended server and receive the request. Then the original response header will be edited to match the received header from the actual request and sent to the client.
example: I type http://bing.com in my browser --> the proxy settings will take this request to my server --> I will create a http request called tempRequest with values from the original request --> the tempRequest is sent and a tempResponse is received from http://bing.com --> I will edit the original Http response to match tempResponse --> send the modified response to the browser.
I am trying to achieve this using middleware.
So far, I think I am successfully getting the response back from the original server (http://bing.com). I am having some trouble sending this received response to the browser.
Startup class:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseProxyServer();
}
}
Middleware implementation:
public static class ProxyMiddlewareExtensions
{
public static IApplicationBuilder UseProxyServer(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyMiddleware>();
}
}
public class ProxyMiddleware
{
private readonly RequestDelegate _next;
public ProxyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalRequest = context.Request;
var absoluteUri = string.Concat(
originalRequest.Scheme,
"://",
originalRequest.Host.ToUriComponent(),
originalRequest.PathBase.ToUriComponent(),
originalRequest.Path.ToUriComponent(),
originalRequest.QueryString.ToUriComponent());
if(!absoluteUri.Contains("localhost"))
{
HttpWebRequest newRequest = WebRequest.CreateHttp(absoluteUri);
//Make a copy of the original request
CopyRequestTo(context.Request, newRequest);
//Get a response using the copied http request.
var response = (HttpWebResponse)await newRequest.GetResponseAsync();
//Modify the response going back to the browser to match the response
//received from the intended server.
CopyResponseTo(response, context);
}
await this._next(context);
}
private void CopyResponseTo(HttpWebResponse source, HttpContext destination)
{
destination.Response.OnStarting(async() =>
{
await source.GetResponseStream().CopyToAsync(destination.Response.Body);
foreach (string headerKey in source.Headers.AllKeys)
{
//============Exception Here=========
destination.Response.Headers[headerKey] = source.Headers.Get(headerKey);
}
destination.Response.StatusCode = (int)source.StatusCode;
});
}
public static void CopyRequestTo(HttpRequest source, HttpWebRequest destination)
{
destination.Method = source.Method;
foreach (var headerKey in source.Headers.Keys)
{
destination.Headers[headerKey] = source.Headers[headerKey];
}
destination.ContentType = source.ContentType;
if (source.Method != "GET" && source.Method != "HEAD" && source.ContentLength > 0)
{
var destinationStream = destination.GetRequestStream();
source.Body.CopyTo(destinationStream);
destinationStream.Close();
}
}
}
But while changing the response I get this exception:
Headers are read only, response has already started
Why this exception even when I am using Response.OnStarting ?
I am running the application from console ( not using IIS )
Related
Previously in ASP.Net I would have written a Generic Http Handler to do this but it looks like in ASP.Net Core you write Middleware.
I am trying to get my Middleware to be called when the page GetUser is requested.
I've tried to understand how from this page https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0 - but have only got so far.
The Middleware code is as follows;
public class GetUser
{
public GetUser(RequestDelegate next)
{
// note this is a handler so no need to store the next
}
public async Task Invoke(HttpContext context)
{
String response = String.Empty;
try
{
response = GenerateResponse(context);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = 200;
await context.Response.WriteAsync(response);
}
catch ( System.Exception ex)
{
response = ex.Message;
}
}
public String GenerateResponse(HttpContext context)
{
String response = "";
response = "a response";
return response;
}
}
public static class GetUserExtension
{
public static IApplicationBuilder UseGetUser(this IApplicationBuilder builder)
{
return builder.UseMiddleware<GetUser>();
}
}
In Program.cs I have added the following line;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
app.Map("/GetUser",GetUserExtension.UseGetUser(builder));
Which fails because builder is a WebApplicationBuilder and not IApplicationBuilder.
So how can I use app.Map so that it calls GetUserExtension.UseGetUser when the GetUser Page is requested.
Make sure your middleware is correctly placed in the program.cs file, which when it is called contains all the resources that it needs to return the request.
This is not much but hopes this helps.
I want to log every SQL request in a request to the database with a middleware. so far I have only managed to create an empty middleware that is hit by every request but I have no idea how to get the SQL queries from that request.
I have read a few articles (1) about how to log requests and responses with middleware but they are getting the whole request (the whole HTML page) and I only want the SQL queries. so how can I get those queries?
public class LoggingEveryRequestToLogDataBase
{
private readonly RequestDelegate _next;
public LoggingEveryRequestToLogDataBase(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ILogDbContext dbContext)
{
var request = new
{
ipAddress = context.Connection.RemoteIpAddress.ToString(),
path = context.Request.Host + context.Request.Path + context.Request.QueryString,
};
await _next(context);
}
}
public static class LoggingEveryRequestToLogDataBaseMiddleware
{
public static IApplicationBuilder LoggingEveryRequestToLogDataBase(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoggingEveryRequestToLogDataBase>();
}
}
I have the following code:
public class CookieCheckMiddleware
{
private readonly RequestDelegate _next;
public CookieCheckMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if(httpContext.Request.Cookies["MyCookie"] == null && httpContext.Request.Path != "/WhereIShouldGo")
{
httpContext.Response.Redirect("/WhereIShouldGo");
}
await _next(httpContext); // calling next middleware
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CookieCheckMiddlewareExtensions
{
public static IApplicationBuilder UseCookieCheckMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CookieCheckMiddleware>();
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseCookieCheckMiddleware();
...
}
It basically redirects to the captive portal if no cookie is set. Now I need to up a level - I need to somehow save the httpContext.Request.Path and forward to it RIGHT AFTER the user accepted the cookies. So setting a cookie beforehand is not an option, since the user hasn't accepted it yet... How could I accomplish that?
The solution was giving the request URL via the redirect like this:
httpContext.Response.Redirect("/Cookies?q="+ httpContext.Request.Path);
and then via JavaScript getting the GET parameter and redirecting after clicking on the button.
Case closed :-)
I am trying to save changes to my DB on a filter that I created that inherits the ActionFilterAttribute. Basically the filter is a basic API log that logs all the requests made to my API. It is written in .NET Core 2.2.
The error I am getting is follows:
SqlException: The incoming tabular data stream (TDS) protocol stream is incorrect. The stream ended unexpectedly.
This only occurs on this filter and not on any of my controllers that are using the DB context.
I am registering the filter in my Startup.cs as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AthenaContext>(opts => opts.UseSqlServer(Configuration["ConnectionString:AthenaDB"]));
services.AddScoped<LogFilter>();
services.AddMvc();
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info {Title = "Athena API", Version = "v1"}); });
}
And then decorating the API class with: [ServiceFilter(typeof(LogFilter))]
The actual filter (for brevity I removed some of the unnecessary code):
public class LogFilter : ActionFilterAttribute
{
public readonly AthenaContext _db;
public LogFilter(AthenaContext context)
{
_db = context;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
........
//Log the request
_db.ApiLogs.Add(new ApiLog
{
EndPoint = $"{controllerName}/{actionName}",
RequestHeader = requestHeader,
RequestBody = requestBody,
IpAddress = ipAddress
});
//Save the DB changes
_db.SaveChanges();
//Execute the base request
base.OnActionExecuting(context);
}
I am trying to host an ASP.NET 5.0 (beta 4) website on Ubuntu. I have configured Kestrel with nginx as a reverse proxy, but there are a couple of problems that prevent this from being used for a production site:
HTTP 404 error pages are blank - is there a way to configure either ASP.NET/Kestrel or nginx to send a custom page instead of a blank page?
How can I configure URL rewriting - for example, I have some static .htm pages in addition to the ASP.NET content, and I would like to rewrite these to serve them without the .htm extension
Thanks to the suggestion from Matt DeKrey, I got this working using two pieces of middleware.
For custom 404 error pages, I used:
public class CustomErrorMiddleware
{
private readonly RequestDelegate next;
public CustomErrorMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.StatusCode = 404;
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync("/errors/404.html");
}
}
While for URL rewriting I used:
public class UrlRewriteMiddleware
{
private readonly RequestDelegate next;
public UrlRewriteMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
// Redirect from /some/page.htm to /some/page
Regex r1 = new Regex("^/some/[a-zA-Z0-9]+\\.htm$");
if (r1.IsMatch(context.Request.Path.Value))
{
context.Response.Redirect(context.Request.Path.Value.Substring(0, context.Request.Path.Value.Length - 4));
return;
}
// Rewrite from /some/page to /some/page.htm
Regex r2 = new Regex("^/some/[a-zA-Z0-9]+$");
if (r2.IsMatch(context.Request.Path.Value))
context.Request.Path = new PathString(context.Request.Path.Value + ".htm");
await next(context);
}
}
Then Startup.cs is modified to use each of these. Middleware is run in the order it is specified in, so the URL rewriting needs to be first to modify the requests as they are received. The custom 404 error middleware needs to be last to catch any requests not handled by any other middleware. For example:
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<UrlRewriteMiddleware>();
app.UseStaticFiles();
app.UseMvc();
app.UseMiddleware<CustomErrorMiddleware>();
}