weidazhao / Hosting

Hosting prototype
170 stars 35 forks source link

X-Forwarded-Host header #19

Closed rmja closed 8 years ago

rmja commented 8 years ago

In the gateway, would it be possible to set the original Host header as a X-Forwarded-Host header, so that the downstream service has the possibility to read the original Host header?

weidazhao commented 8 years ago

Hi, yes it is possible. There are at least two ways:

  1. Modify the code of GatewayMiddleware directly.
  2. Extend HttpRequestDispatcher (which is basically a plain HttpClient) to hook up your HttpMessageHandler, and inject the custom HttpRequestDispatcher in Startup.ConfigureServices() with services.AddHttpRequestDispatcherProvider(HttpRequestDispatcherProvider provider).

I didn't add the X-Forwarded-Host header to GatewayMiddleware by default, since I think the gateway needs to run inside the same cluster as other services to which it forwards requests. They are in the same 'host'.

rmja commented 8 years ago

I went with 1. and added the following line here after all existing headers are copied over, so that if the header was already set, then it is not overwritten:

requestMessage.Headers.TryAddWithoutValidation("X-Forwarded-Host", context.Request.Host.Value);

It would be nice if you would consider adding it in the package, so that I don't have to carry around my own middleware:) Maybe as an option?

weidazhao commented 8 years ago

Thanks for the suggestion. I will take a look and get back to you.

rmja commented 8 years ago

Nevermind, I found a much better way to do this, namely to make my own middleware which sets the header, and run it just before the RunGateway() handler.

cwe1ss commented 8 years ago

Could you share the code? thx! :)

rmja commented 8 years ago

@cwe1ss Yes of cause. The middleware to be used on the gateway looks like this:

public class SetForwardedMiddleware
    {
        private const string XForwardedPathBase = "X-Forwarded-PathBase";
        private const string XForwardedHost = "X-Forwarded-Host";

        private readonly RequestDelegate _next;

        public SetForwardedMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            if (!context.Request.Headers.ContainsKey(XForwardedPathBase))
            {
                context.Request.Headers.Add(XForwardedPathBase, context.Request.PathBase.Value);
            }

            if (!context.Request.Headers.ContainsKey(XForwardedHost))
            {
                context.Request.Headers.Add(XForwardedHost, context.Request.Host.Value);
            }

            return _next(context);
        }
    }

Note that I am also forwarding the path base, because I need it on the backend service. You can simply remove that. Now in Starup.cs this would look something like this:

            app.Map("/sms",
                subApp =>
                {
                    subApp
                        .UseMiddleware<SetForwardedMiddleware>()
                        .RunGateway(smsOptions);
                }
            );

On the backend service, you can restore the forwarded headers using the UseForwardedHeaders() like this:

app.UseForwardedHeaders(new ForwardedHeadersOptions()
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedHost
            });
app.UseMvc();
cwe1ss commented 8 years ago

thx! I was wondering how you would add the headers to the HttpClient through a middleware - adding it to the incoming request is a smart solution :)

johnkattenhorn commented 8 years ago

This look good @rmja do you like this would solve the problem I'm having in #26 ?

rmja commented 8 years ago

@johnkattenhorn yes it will. I have put up the middleware layer here.

Note that for it to work you also need to forward the base path. There is no official http header for that, so I made up X-Forwarded-BasePath.

On the gateway, you need to make sure that you set the headers right before the gateway is run, on the subApp. The reason is that the Map() adds the matched path segment to the base path.

Example on the gateway:

app.Map("/sms", subApp =>
                subApp
                    .UseForwardingHeaders(new ForwardingHeadersOptions()
                    {
                        ForwardingHeaders = ForwardingHeaders.XForwardedHost | ForwardingHeaders.XForwardedPathBase
                    })
                    .RunGateway(smsOptions));

Example on the backend service:

        public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
        {
            app.UseForwardedHeaders(new ForwardedHeadersOptions()
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedHost
            });
            app.UseCustomForwardedHeaders(new CustomForwardedHeadersOptions()
            {
                ForwardedHeaders = CustomForwardedHeaders.XForwardedPathBase
            });
            app.UseMvc();
         }
johnkattenhorn commented 8 years ago

Awesome, do I need to do anything additional on the backend service with routing or anything or will it automatically straight out the url links ?

Thanks for the rapid response and the code!

johnkattenhorn commented 8 years ago

It works, straight out of the box, thanks a lot.