Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jul 3, 2021. It is now read-only.
/ProxyKitPublic archive

A toolkit to create code-first HTTP reverse proxies on ASP.NET Core

License

NotificationsYou must be signed in to change notification settings

ProxyKit/ProxyKit

Repository files navigation

Image

Build StatusNuGetFeedz

⚠️ProxyKit is obsolescent. Seeannouncement. Migrate toYARP.

A toolkit to create code-firstHTTP Reverse Proxies hosted in ASP.NET Core as middleware. Thisallows focused code-first proxies that can be embedded in existing ASP.NET Coreapplications or deployed as a standalone server. Deployable anywhere ASP.NETCore is deployable such as Windows, Linux, Containers and Serverless (withcaveats).

Having built proxies many times before, I felt it is time to make a package. ForkedfromASP.NET labs, it has been heavily modified with a differentAPI, to facilitate a wider variety of proxying scenarios (i.e. routing based ona JWT claim) and interception of the proxy requests / responses forcustomization of headers and (optionally) request / response bodies. It alsousesHttpClientFactory internally that will mitigate against DNS cachingissues making it suitable for microservice / container environments.

1. Quick Start

1.1. Install

ProxyKit is aNetStandard2.0 package. Install into your ASP.NET Core project:

dotnet add package ProxyKit

1.2. Forward HTTP Requests

In yourStartup, add the proxy service:

publicvoidConfigureServices(IServiceCollectionservices){    ...services.AddProxy();    ...}

Forward HTTP requests toupstream-server:5001:

publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(context=>context.ForwardTo("http://upstream-server:5001/").AddXForwardedHeaders().Send());}

What is happening here?

  1. context.ForwardTo(upstreamHost) is an extension method onHttpContext that creates and initializes anHttpRequestMessage withthe original request headers copied over, yielding aForwardContext.
  2. AddXForwardedHeaders addsX-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto andX-Forwarded-PathBase headers to the upstreamrequest.
  3. Send Sends the forward request to the upstream server and returns anHttpResponseMessage.
  4. The proxy middleware then takes the response and applies it toHttpContext.Response.

Note:RunProxy isterminal - anything added to the pipelineafterRunProxy will never be executed.

1.3. Forward WebSocket Requests

Forward WebSocket requests toupstream-server:5002:

publicvoidConfigure(IApplicationBuilderapp){app.UseWebSockets();app.UseWebSocketProxy(        context=>newUri("ws://upstream-host:80/"),        options=>options.AddXForwardedHeaders());}

What is happening here?

  1. app.UseWebSockets() must first be added otherwise websocket requests willnever be handled by ProxyKit.
  2. The first parameter must return the URI of the upstream host with a schemeofws://.
  3. The second parameteroptions allows you to do some customisation of theinitial upstream requests such as adding some headers.

2. Core Features

2.1. Customising the upstream HTTP request

One can modify the upstream request headers prior to sending them to suitcustomisation needs. ProxyKit doesn't add, remove, nor modify any headers bydefault; one must opt in any behaviours explicitly.

In this example we will add aX-Correlation-ID header if the incoming request does not bear one:

publicconststringXCorrelationId="X-Correlation-ID";publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(context=>{varforwardContext=context.ForwardTo("http://upstream-server:5001/");if(!forwardContext.UpstreamRequest.Headers.Contains(XCorrelationId)){forwardContext.UpstreamRequest.Headers.Add(XCorrelationId,Guid.NewGuid().ToString());}returnforwardContext.Send();});}

This can be encapsulated as an extension method:

publicstaticclassCorrelationIdExtensions{publicconststringXCorrelationId="X-Correlation-ID";publicstaticForwardContextApplyCorrelationId(thisForwardContextforwardContext){if(!forwardContext.UpstreamRequest.Headers.Contains(XCorrelationId)){forwardContext.UpstreamRequest.Headers.Add(XCorrelationId,Guid.NewGuid().ToString());}returnforwardContext;}}

... making the proxy code a little nicer to read:

publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(context=>context.ForwardTo("http://upstream-server:5001/").ApplyCorrelationId().Send());}

2.2. Customising the upstream response

The response from an upstream server can be modified before it is sent to theclient. In this example we are removing a header:

publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(async context=>{varresponse=awaitcontext.ForwardTo("http://localhost:5001/").Send();response.Headers.Remove("MachineID");returnresponse;});}

2.3. X-Forwarded Headers

2.3.1. Client Sent X-Forwarded-Headers

⚠️ To mitigate against spoofing attacks and misconfiguration ProxyKitdoes not copyX-Forward-* headers from the incoming request to the upstreamrequest by default. Copying them requires opting in; see2.3.3 CopyingX-Forwarded headers below.

2.3.2. AddingX-Forwarded-* Headers

Many applications will need to know what their "outside" host / URL is in orderto generate correct values. This is achieved usingX-Forwarded-* andForwarded headers. ProxyKit supports applyingX-Forward-* headers out of thebox (applyingForwarded headers support is on backlog). At the time of writing,Forwarded isnot supportedin ASP.NET Core.

To addX-Forwarded-* headers to the request to the upstream server:

publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(context=>context.ForwardTo("http://upstream-server:5001/").AddXForwardedHeaders().Send());}

This will addX-Forwarded-For,X-Forwarded-Host andX-Forwarded-Protoheaders to the upstream request using values fromHttpContext. If the proxymiddleware is hosted on a path and aPathBase exists on the request, then anX-Forwarded-PathBase is also added.

2.3.3. CopyingX-Forwarded headers

Chaining proxies is a common pattern in more complex setups. In this case, ifthe proxy is an "internal" proxy, you will want to copy the "X-Forwarded-*"headers from previous proxy. To do so, useCopyXForwardedHeaders():

publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(context=>context.ForwardTo("http://upstream-server:5001/").CopyXForwardedHeaders().Send());}

You may optionally also add the "internal" proxy details to theX-Forwarded-*header values by combiningCopyXForwardedHeaders() andAddXForwardedHeaders() (note the order is important):

publicvoidConfigure(IApplicationBuilderapp){app.RunProxy(context=>context.ForwardTo("http://upstream-server:5001/").CopyXForwardedHeaders().AddXForwardedHeaders().Send());}

2.4. Configuring ProxyKit's HttpClient

When adding the Proxy to your application's service collection, there is anopportunity to configure the internal HttpClient. AsHttpClientFactoryis used, its builder is exposed for you to configure:

services.AddProxy(httpClientBuilder=>/* configure http client builder */);

Below are two examples of what you might want to do:

  1. Configure the HTTP Client's timeout to 5 seconds:

    services.AddProxy(httpClientBuilder=>httpClientBuilder.ConfigureHttpClient=        client=>client.Timeout=TimeSpan.FromSeconds(5));
  2. Configure the primaryHttpMessageHandler. This is typically used in testingto inject a test handler (seeTesting below).

    services.AddProxy(httpClientBuilder=>httpClientBuilder.ConfigurePrimaryHttpMessageHandler=()=>_testMessageHandler);

2.5. Error handling

WhenHttpClient throws, the following logic applies:

  1. When upstream server is not reachable, then503 ServiceUnavailable is returned.
  2. When upstream server is slow and client timeouts, then504 GatewayTimeout isreturned.

Not all exception scenarios and variations are caught, which may result in aInternalServerError being returned to your clients. Please create an issue ifa scenario is missing.

2.6. Testing

As ProxyKit is a standard ASP.NET Core middleware, it can be tested using thestandard in-memoryTestServer mechanism.

Often you will want to test ProxyKit with your application and perhaps test thebehaviour of your application when load balanced with two or more instances asindicated below.

                               +----------+                               |"Outside" |                               |HttpClient|                               +-----+----+                                     |                                     |                                     |                         +-----------+---------+    +-------------------->RoutingMessageHandler|    |                    +-----------+---------+    |                                |    |                                |    |           +--------------------+-------------------------+    |           |                    |                         |+---+-----------v----+      +--------v---------+     +---------v--------+|Proxy TestServer    |      |Host1 TestServer  |     |Host2 TestServer  ||with Routing Handler|      |HttpMessageHandler|     |HttpMessageHandler|+--------------------+      +------------------+     +------------------+

RoutingMessageHandler is anHttpMessageHandler that will route requeststo specific hosts based on the origin it is configured with. For ProxyKitto forward requests (in memory) to the upstream hosts, it needs to be configuredto use theRoutingMessageHandler as its primaryHttpMessageHandler.

Full example can been viewed inRecipe 6.

2.7. Load Balancing

Load balancing is a mechanism to decide which upstream server to forward therequest to. Out of the box, ProxyKit currently supports one type ofload balancing - Weighted Round Robin. Other types are planned.

2.7.1. Weighted Round Robin

Round Robin simply distributes requests as they arrive to the next host in adistribution list. With optional weighting, more requests are sent to the host withthe greater weight.

publicvoidConfigure(IApplicationBuilderapp){varroundRobin=newRoundRobin{newUpstreamHost("http://localhost:5001/",weight:1),newUpstreamHost("http://localhost:5002/",weight:2)};app.RunProxy(async context=>{varhost=roundRobin.Next();returnawaitcontext.ForwardTo(host).Send();});}

2.8. Typed Handlers

New in version 2.1.0

Instead of specifying a delegate, it is possible to use a typed handler. Thereason you may want to do this is when you want to better leverage dependencyinjection.

Typed handlers must implementIProxyHandler that has a single method with samesignature asHandleProxyRequest. In this example our typed handler has adependency on an imaginary service to lookup hosts:

publicclassMyTypedHandler:IProxyHandler{privateIUpstreamHostLookup_upstreamHostLookup;publicMyTypeHandler(IUpstreamHostLookupupstreamHostLookup){_upstreamHostLookup=upstreamHostLookup;}publicTask<HttpResponseMessage>HandleProxyRequest(HttpContextcontext){varupstreamHost=_upstreamHostLookup.Find(context);returncontext.ForwardTo(upstreamHost).AddXForwardedHeaders().Send();}}

We then need to register our typed handler service:

publicvoidConfigureServices(IServiceCollectionservices){    ...services.AddSingleton<MyTypedHandler>();    ...}

When adding the proxy to the pipeline, use the generic form:

publicvoidConfigureServices(IServiceCollectionservices){    ...appInner.RunProxy<MyTypedHandler>());    ...}

3. Recipes

Recipes have moved toown repo.

4. Making upstream servers reverse proxy friendly

Applications that are deployed behind a reverse proxy typically need to besomewhat aware of that so they can generate correct URLs and paths whenresponding to a browser. That is, they look atX-Forward-* /Forwardedheaders and use their values.

In ASP.NET Core, this means using theForwardedHeaders middleware in yourapplication. Please refer to thedocumentationfor correct usage (and note the security advisory!).

Note: the Forwarded Headers middleware does not supportX-Forwarded-PathBase. This means if you proxyhttp://example.com/foo/ tohttp://upstream-host/ the/foo/ part is lost and absolute URLs cannot begenerated unless you configure your application'sPathBase directly.

Related issues and discussions:

To support PathBase dynamically in your application withX-Forwarded-PathBase,examine the header early in your pipeline and set thePathBase accordingly:

varoptions=newForwardedHeadersOptions{   ...};app.UseForwardedHeaders(options);app.Use((context,next)=>{if(context.Request.Headers.TryGetValue("X-Forwarded-PathBase",outvarpathBases)){context.Request.PathBase=pathBases.First();}returnnext();});

Alternatively you can use ProxyKit'sUseXForwardedHeaders extension thatperforms the same as the above (including callingUseForwardedHeaders):

varoptions=newForwardedHeadersOptions{   ...};app.UseXForwardedHeaders(options);

5. Performance considerations

According to TechEmpower's Web Framework Benchmarks, ASP.NET Coreis up therewith the fastest for plaintext.As ProxyKit simply captures headers and async copies request and response bodystreams, it will be fast enough for most scenarios.

If absolute raw throughput is a concern for you, thenconsider nginx or alternatives. For me being able to create flexible proxiesusing C# is a reasonable tradeoff for the (small) performance cost. Note thatwhat your specific proxy (and its specific configuration) does will impact performanceso you should measure for yourself in your context.

On Windows, ProxyKit is ~3x faster than nginx. However, nginx has clearlydocumented thatit has knownperformance issues on Windows. Sinceone wouldn't be running production nginx on Windows, this comparison isacademic.

Memory wise, ProxyKit maintained a steady ~20MB of RAM after processing millionsof requests for simple forwarding. Again, it depends on what your proxy does soyou should analyse and measure yourself.

6. Note about serverless

Whilst it is possible to run full ASP.NET Core web application inAWSLambda andAzure Functions it should be noted that Serverless systems aremessage based and not stream based. Incoming and outgoing HTTP request messageswill be buffered and potentially encoded as Base64 if binary (so larger). Thismeans ProxyKit should only be used for API (json) proxying in production onServerless. (Though proxying other payloads is fine for dev / exploration /quick'n'dirty purposes.)

7. Comparison with Ocelot

Ocelot is an API Gateway that also runs on ASP.NET Core. A key differencebetween API Gateways and general Reverse Proxies is that the former tend to bemessage based whereas a reverse proxy isstream based. That is, an APIGateway will typically buffer every request and response message to be ableto perform transformations. This is fine for an API Gateway but not suitable fora general reverse proxy performance wise nor for responses that arechunked-encoded. SeeNot Supported Ocelot docs.

Combining ProxyKit with Ocelot would give some nice options for a variety ofscenarios.

8. How to build

Requirements: .NET Core SDK 2.2.100 or later.

On Windows:

.\build.cmd

On Linux:

./build.sh

9. Contributing / Feedback / Questions

Any ideas for features, bugs or questions, please create an issue. Pull requestsgratefully accepted but please create an issue for discussion first.

I can be reached on twitter at@randompunter

10. Articles, blogs and other external links


logo isdistribute byChangHoon Baek fromthe Noun Project.

About

A toolkit to create code-first HTTP reverse proxies on ASP.NET Core

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors23

Languages


[8]ページ先頭

©2009-2025 Movatter.jp