How to set up Web API Routing for a Proxy Controll

2019-01-23 10:10发布

问题:

Part of my application needs to act as a Proxy Server for a third party RESTful web service. Is there a way to set up Web API routing so that all requests of the same type will go to the same method?

For example, if the client sends in either of these GET requests I want them to go into a single GET action method that then sends on the request to the downstream server.

api/Proxy/Customers/10045
api/Proxy/Customers/10045/orders
api/Proxy/Customers?lastname=smith

The single action method for GET would pick up any one of these three requests and send them on to the respective service (I know how to work with HttpClient to make that happen effectively):

http://otherwebservice.com/Customers/10045
http://otherwebservice.com/Customers/10045/orders
http://otherwebservice.com/Customers?lastname=smith

I don't want to have to tightly couple my web service to the third party web service and replicate their entire API as method calls inside mine.

One workaround that I have thought of is to simply encode the target URL in JavaScript on the client and pass this into the Web API which will then only see one parameter. It would work, but I'd prefer to use the routing capabilities in Web API if possible.

回答1:

Here's how I got this to work. First, create a controller with a method for each verb you want to support:

public class ProxyController : ApiController
{
    private Uri _baseUri = new Uri("http://otherwebservice.com");

    public async Task<HttpResponseMessage> Get(string url)
    {
    }

    public async Task<HttpResponseMessage> Post(string url)
    {
    }

    public async Task<HttpResponseMessage> Put(string url)
    {
    }

    public async Task<HttpResponseMessage> Delete(string url)
    {
    }
}

The methods are async because they're going to use an HttpClient. Map your route like this:

config.Routes.MapHttpRoute(
  name: "Proxy",
  routeTemplate: "api/Proxy/{*url}",
  defaults: new { controller = "Proxy" });

Now back to the Get method in the controller. Create an HttpClient object, create a new HttpRequestMessage object with the appropriate Url, copy everything (or almost everything) from the original request message, then call SendAsync():

public async Task<HttpResponseMessage> Get(string url)
{
    using (var httpClient = new HttpClient())         
    {
        string absoluteUrl = _baseUri.ToString() + "/" + url + Request.RequestUri.Query;
        var proxyRequest = new HttpRequestMessage(Request.Method, absoluteUrl);
        foreach (var header in Request.Headers)
        {
            proxyRequest.Headers.Add(header.Key, header.Value);
        }

        return await httpClient.SendAsync(proxyRequest, HttpCompletionOption.ResponseContentRead);
    }
}

The URL combining could be more sophisticated, but that's the basic idea. For the Post and Put methods, you'll also need to copy the request body

Also please note a HttpCompletionOption.ResponseContentRead parameter passed in SendAsync call, because without it, ASP.NET will spend an exremeley long time reading the content if the content is large (in my case, it changed a 500KB 100ms request into a 60s request).