Obslužné rutiny zpráv HTTP ve webovém rozhraní API ASP.NET

Obslužná rutina zprávy je třída, která přijímá požadavek HTTP a vrací odpověď HTTP. Obslužné rutiny zpráv jsou odvozeny z abstraktní HttpMessageHandler třídy.

Obvykle je řada obslužných rutin zpráv zřetězených dohromady. První obslužná rutina obdrží požadavek HTTP, provede zpracování a poskytne požadavek další obslužné rutině. V určitém okamžiku se vytvoří odpověď a putuje zpět nahoru po řetězci. Tento vzor se nazývá delegovaný obslužný modul.

Diagram obslužných rutin zpráv zřetězených dohromady, který znázorňuje proces přijetí požadavku H T T P a vrácení odpovědi H T T P

obslužné rutiny zpráv Server-Side

Na straně serveru používá kanál webového rozhraní API některé integrované obslužné rutiny zpráv:

  • HttpServer získá požadavek od hostitele.
  • HttpRoutingDispatcher odešle požadavek na základě trasy.
  • HttpControllerDispatcher odešle požadavek do kontroleru webového rozhraní API.

Do potrubí můžete přidat vlastní obslužné rutiny. Obslužné rutiny zpráv jsou vhodné pro průřezové aspekty, které fungují na úrovni zpráv HTTP (místo akcí kontroleru). Například, zpracovatel zpráv může:

  • Čtení nebo úprava hlaviček požadavků
  • Přidejte do odpovědí hlavičku odpovědi.
  • Ověřte požadavky, než se dostanou ke kontroleru.

Tento diagram zobrazuje dvě vlastní obslužné rutiny vložené do potrubí.

Diagram obslužných rutin zpráv na straně serveru zobrazující dva vlastní obslužné rutiny vložené do kanálu web A P I

Poznámka:

Na straně klienta používá HttpClient také obslužné rutiny zpráv. Další informace naleznete v tématu HttpClient Message Handlers.

Vlastní obslužné rutiny zpráv

Chcete-li napsat vlastní obslužnou rutinu zprávy, odvoďte ji z System.Net.Http.DelegatingHandler a přepište metodu SendAsync. Tato metoda má následující podpis:

Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken);

Metoda vezme HttpRequestMessage jako vstup asynchronně vrátí HttpResponseMessage. Typická implementace provede následující:

  1. Zpracujte zprávu požadavku.
  2. Zavolejte base.SendAsync k odeslání požadavku do vnitřního zpracovatele.
  3. Vnitřní obsluha vrátí odpověď. (Tento krok je asynchronní.)
  4. Zpracujte odpověď a vraťte ji volajícímu.

Tady je triviální příklad:

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = await base.SendAsync(request, cancellationToken);
        Debug.WriteLine("Process response");
        return response;
    }
}

Poznámka:

Volání na base.SendAsync je asynchronní. Pokud obslužná rutina po tomto volání funguje, použijte klíčové slovo await , jak je znázorněno.

Delegující handler může také přeskočit vnitřní handler a přímo vytvořit odpověď.

public class MessageHandler2 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Create the response.
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Hello!")
        };

        // Note: TaskCompletionSource creates a task that does not contain a delegate.
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        tsc.SetResult(response);   // Also sets the task state to "RanToCompletion"
        return tsc.Task;
    }
}

Pokud delegující obslužná rutina vytvoří odpověď bez volání base.SendAsync, požadavek přeskočí zbytek kanálu. To může být užitečné pro obslužnou rutinu, která ověří požadavek (vytvoření chybové odpovědi).

Diagram vlastních obslužných rutin zpráv, který ukazuje proces vytvoření odpovědi bez volání základní funkce SendAsync

Přidání obslužné rutiny do pipeline

Chcete-li přidat obslužnou rutinu zprávy na straně serveru, přidejte obslužnou rutinu do kolekce HttpConfiguration.MessageHandlers . Pokud jste k vytvoření projektu použili šablonu "ASP.NET webové aplikace MVC 4", můžete to udělat uvnitř třídy WebApiConfig :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new MessageHandler1());
        config.MessageHandlers.Add(new MessageHandler2());

        // Other code not shown...
    }
}

Obslužné rutiny zpráv jsou volány ve stejném pořadí, v jakém jsou uvedeny v kolekci MessageHandlers. Protože jsou vnořené, zpráva odpovědi putuje opačným směrem. To znamená, že poslední obslužná rutina je první, která obdrží odpověď.

Všimněte si, že není nutné nastavovat vnitřní obslužné rutiny; Rozhraní webového rozhraní API automaticky připojí obslužné rutiny zpráv.

Pokud hostujete sami, vytvořte instanci třídy HttpSelfHostConfiguration a přidejte zpracovatele do kolekce MessageHandlers.

var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());

Teď se podíváme na některé příklady vlastních obslužných rutin zpráv.

Příklad: X-HTTP-Method-Override

X-HTTP-Method-Override je nestandardní hlavička HTTP. Je určená pro klienty, kteří nemůžou odesílat určité typy požadavků HTTP, například PUT nebo DELETE. Místo toho klient odešle požadavek POST a nastaví hlavičku X-HTTP-Method-Override na požadovanou metodu. Například:

X-HTTP-Method-Override: PUT

Tady je zpracovatel zpráv, který přidává podporu pro X-HTTP-Method-Override:

public class MethodOverrideHandler : DelegatingHandler      
{
    readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
    const string _header = "X-HTTP-Method-Override";

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Check for HTTP POST with the X-HTTP-Method-Override header.
        if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
        {
            // Check if the header value is in our methods list.
            var method = request.Headers.GetValues(_header).FirstOrDefault();
            if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
            {
                // Change the request method.
                request.Method = new HttpMethod(method);
            }
        }
        return base.SendAsync(request, cancellationToken);
    }
}

V metodě SendAsync zpracovatel zkontroluje, zda je požadavek zprávou POST a zda obsahuje hlavičku X-HTTP-Method-Override. Pokud ano, ověří hodnotu hlavičky a pak upraví metodu požadavku. Nakonec obslužná rutina volá base.SendAsync, aby předala zprávu další obslužné rutině.

Když požadavek dosáhne Třídy HttpControllerDispatcher , HttpControllerDispatcher bude směrovat požadavek na základě aktualizované metody požadavku.

Příklad: Přidání vlastní hlavičky odpovědi

Zde je obslužná rutina, která do každé odpovědní zprávy přidá vlastní hlavičku:

// .Net 4.5
public class CustomHeaderHandler : DelegatingHandler
{
    async protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        response.Headers.Add("X-Custom-Header", "This is my custom header.");
        return response;
    }
}

Nejprve obslužná rutina zavolá base.SendAsync, aby předala požadavek vnitřní obslužné rutině zpráv. Vnitřní obslužná rutina vrátí zprávu odpovědi, ale provede to asynchronně pomocí objektu Task<T> . Zpráva odpovědi není k dispozici, dokud base.SendAsync nebude dokončena asynchronně.

Tento příklad používá klíčové slovo await k tomu, aby byla práce provedena asynchronně po dokončení SendAsync. Pokud cílíte na .NET Framework 4.0, použijte metodu Task<T>.ContinueWith:

public class CustomHeaderHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(
            (task) =>
            {
                HttpResponseMessage response = task.Result;
                response.Headers.Add("X-Custom-Header", "This is my custom header.");
                return response;
            }
        );
    }
}

Příklad: Kontrola klíče rozhraní API

Některé webové služby vyžadují, aby klienti do své žádosti zahrnuli klíč rozhraní API. Následující příklad ukazuje, jak může obslužná rutina zpráv kontrolovat požadavky na platný klíč rozhraní API:

public class ApiKeyHandler : DelegatingHandler
{
    public string Key { get; set; }

    public ApiKeyHandler(string key)
    {
        this.Key = key;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!ValidateKey(request))
        {
            var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
            var tsc = new TaskCompletionSource<HttpResponseMessage>();
            tsc.SetResult(response);    
            return tsc.Task;
        }
        return base.SendAsync(request, cancellationToken);
    }

    private bool ValidateKey(HttpRequestMessage message)
    {
        var query = message.RequestUri.ParseQueryString();
        string key = query["key"];
        return (key == Key);
    }
}

Tato obslužná rutina hledá klíč rozhraní API v řetězci dotazu URI. (V tomto příkladu předpokládáme, že klíč je statický řetězec. Skutečná implementace by pravděpodobně používala složitější ověřování.) Pokud řetězec dotazu obsahuje klíč, obslužná rutina předá požadavek vnitřní obslužné rutině.

Pokud požadavek nemá platný klíč, obslužná rutina vytvoří zprávu odpovědi se stavem 403, Zakázáno. V tomto případě obslužná rutina nevolá base.SendAsync, takže vnitřní obslužná rutina nikdy neobdrží požadavek, ani kontroler. Kontroler proto může předpokládat, že všechny příchozí požadavky mají platný klíč rozhraní API.

Poznámka:

Pokud se klíč rozhraní API vztahuje pouze na určité akce kontroleru, zvažte použití filtrem akcí místo obslužného programu zpráv. Filtry akcí se spustí po dokončení směrování identifikátoru URI.

obslužné rutiny zpráv Per-Route

Obslužné rutiny v kolekci HttpConfiguration.MessageHandlers platí globálně.

Alternativně můžete přidat zprávový handler do specifické trasy při jejím definování.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Route1",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "Route2",
            routeTemplate: "api2/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: null,
            handler: new MessageHandler2()  // per-route message handler
        );

        config.MessageHandlers.Add(new MessageHandler1());  // global message handler
    }
}

Pokud v tomto příkladu identifikátor URI požadavku odpovídá route2, odešle se požadavek do MessageHandler2. Následující diagram znázorňuje kanál pro tyto dvě trasy:

Diagram kanálu obslužných rutin pro směrování zpráv, který znázorňuje proces přidání obslužné rutiny zprávy do konkrétní trasy jejím definováním

Všimněte si, že MessageHandler2 nahrazuje výchozí HttpControllerDispatcher. V tomto příkladu MessageHandler2 vytvoří odpověď, a požadavky, které odpovídají "Route2", nikdy nepřejdou na kontroler. To vám umožní nahradit celý mechanismus kontroleru webového rozhraní API vlastním koncovým bodem.

Alternativně může obslužná rutina zpráv pro každou trasu delegovat na HttpControllerDispatcher, který pak odešle do kontroleru.

Diagram kanálu obslužných rutin směrování zpráv, zobrazující proces delegování na HTTP controladorový dispečer, který pak předává na kontroler.

Následující kód ukazuje, jak nakonfigurovat tuto trasu:

// List of delegating handlers.
DelegatingHandler[] handlers = new DelegatingHandler[] {
    new MessageHandler3()
};

// Create a message handler chain with an end-point.
var routeHandlers = HttpClientFactory.CreatePipeline(
    new HttpControllerDispatcher(config), handlers);

config.Routes.MapHttpRoute(
    name: "Route2",
    routeTemplate: "api2/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: null,
    handler: routeHandlers
);