Programy obsługi komunikatów HTTP w internetowym interfejsie API ASP.NET
Procedura obsługi komunikatów to klasa, która odbiera żądanie HTTP i zwraca odpowiedź HTTP. Programy obsługi komunikatów pochodzą z abstrakcyjnej klasy HttpMessageHandler .
Zazwyczaj szereg procedur obsługi komunikatów jest ze sobą połączone. Pierwsza procedura obsługi odbiera żądanie HTTP, wykonuje pewne przetwarzanie i przekazuje żądanie następnej procedurze obsługi. W pewnym momencie odpowiedź jest tworzona i przechodzi z powrotem do łańcucha. Ten wzorzec jest nazywany procedurą obsługi delegowania .
programy obsługi komunikatów Server-Side
Po stronie serwera potok internetowego interfejsu API używa niektórych wbudowanych programów obsługi komunikatów:
- Serwer HttpServer pobiera żądanie z hosta.
- HttpRoutingDispatcher wysyła żądanie na podstawie trasy.
- HttpControllerDispatcher wysyła żądanie do kontrolera internetowego interfejsu API.
Do potoku można dodać niestandardowe programy obsługi. Programy obsługi komunikatów są dobre w przypadku problemów krzyżowych, które działają na poziomie komunikatów HTTP (a nie akcji kontrolera). Na przykład program obsługi komunikatów może:
- Odczytywanie lub modyfikowanie nagłówków żądań.
- Dodaj nagłówek odpowiedzi do odpowiedzi.
- Sprawdź poprawność żądań przed dotarciem do kontrolera.
Na tym diagramie przedstawiono dwa niestandardowe programy obsługi wstawione do potoku:
Uwaga
Po stronie klienta klient HttpClient używa również programów obsługi komunikatów. Aby uzyskać więcej informacji, zobacz Programy obsługi komunikatów HttpClient.
Niestandardowe programy obsługi komunikatów
Aby napisać niestandardową procedurę obsługi komunikatów, pochodzą z programu System.Net.Http.DelegatingHandler i przesłaniają metodę SendAsync . Ta metoda ma następujący podpis:
Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken);
Metoda przyjmuje komunikat HttpRequestMessage jako dane wejściowe i asynchronicznie zwraca komunikat HttpResponseMessage. Typowa implementacja wykonuje następujące czynności:
- Przetwórz komunikat żądania.
- Wywołaj metodę
base.SendAsync
, aby wysłać żądanie do procedury obsługi wewnętrznej. - Program obsługi wewnętrznej zwraca komunikat odpowiedzi. (Ten krok jest asynchroniczny).
- Przetwórz odpowiedź i wróć do elementu wywołującego.
Oto trywialny przykład:
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;
}
}
Uwaga
Wywołanie metody jest base.SendAsync
asynchroniczne. Jeśli program obsługi wykona jakąkolwiek pracę po tym wywołaniu, użyj słowa kluczowego await , jak pokazano.
Program obsługi delegowania może również pominąć procedurę obsługi wewnętrznej i bezpośrednio utworzyć odpowiedź:
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;
}
}
Jeśli program obsługi delegowania tworzy odpowiedź bez wywoływania base.SendAsync
, żądanie pomija resztę potoku. Może to być przydatne w przypadku procedury obsługi, która weryfikuje żądanie (tworzenie odpowiedzi na błąd).
Dodawanie programu obsługi do potoku
Aby dodać procedurę obsługi komunikatów po stronie serwera, dodaj procedurę obsługi do kolekcji HttpConfiguration.MessageHandlers . Jeśli do utworzenia projektu użyto szablonu "ASP.NET MVC 4 Web Application", możesz to zrobić w klasie 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...
}
}
Programy obsługi komunikatów są wywoływane w takiej samej kolejności, jak w kolekcji MessageHandlers . Ponieważ są zagnieżdżone, komunikat odpowiedzi jest przesyłany w innym kierunku. Oznacza to, że ostatnia procedura obsługi jest pierwszą, aby uzyskać komunikat odpowiedzi.
Zwróć uwagę, że nie trzeba ustawiać procedur obsługi wewnętrznej; Platforma internetowego interfejsu API automatycznie łączy programy obsługi komunikatów.
Jeśli samodzielnie hostujesz, utwórz wystąpienie klasy HttpSelfHostConfiguration i dodaj programy obsługi do kolekcji MessageHandlers .
var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());
Teraz przyjrzyjmy się kilku przykładom niestandardowych programów obsługi komunikatów.
Przykład: X-HTTP-Method-Override
X-HTTP-Method-Override to nietypowy nagłówek HTTP. Jest przeznaczony dla klientów, którzy nie mogą wysyłać niektórych typów żądań HTTP, takich jak PUT lub DELETE. Zamiast tego klient wysyła żądanie POST i ustawia nagłówek X-HTTP-Method-Override do żądanej metody. Na przykład:
X-HTTP-Method-Override: PUT
Oto procedura obsługi komunikatów, która dodaje obsługę funkcji 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);
}
}
W metodzie SendAsync program obsługi sprawdza, czy komunikat żądania jest żądaniem POST i czy zawiera nagłówek X-HTTP-Method-Override. Jeśli tak, weryfikuje wartość nagłówka, a następnie modyfikuje metodę żądania. Na koniec program obsługi wywołuje polecenie base.SendAsync
, aby przekazać komunikat do następnej procedury obsługi.
Gdy żądanie osiągnie klasę HttpControllerDispatcher , httpControllerDispatcher będzie kierować żądanie na podstawie zaktualizowanej metody żądania.
Przykład: dodawanie niestandardowego nagłówka odpowiedzi
Oto procedura obsługi komunikatów, która dodaje niestandardowy nagłówek do każdego komunikatu odpowiedzi:
// .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;
}
}
Najpierw program obsługi wywołuje polecenie base.SendAsync
, aby przekazać żądanie do wewnętrznego programu obsługi komunikatów. Program obsługi wewnętrznej zwraca komunikat odpowiedzi, ale wykonuje to asynchronicznie przy użyciu obiektu T zadania<>. Komunikat odpowiedzi nie jest dostępny, dopóki base.SendAsync
nie zostanie ukończony asynchronicznie.
W tym przykładzie użyto słowa kluczowego await , aby wykonać pracę asynchronicznie po SendAsync
zakończeniu. Jeśli celujesz .NET Framework 4.0, użyj T zadania<>. ContinueWith, metoda:
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;
}
);
}
}
Przykład: sprawdzanie klucza interfejsu API
Niektóre usługi internetowe wymagają, aby klienci dołączali klucz interfejsu API do żądania. W poniższym przykładzie pokazano, jak program obsługi komunikatów może sprawdzać żądania prawidłowego klucza interfejsu 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);
}
}
Ta procedura obsługi wyszukuje klucz interfejsu API w ciągu zapytania identyfikatora URI. (W tym przykładzie przyjęto założenie, że klucz jest ciągiem statycznym. Rzeczywista implementacja prawdopodobnie będzie używać bardziej złożonej weryfikacji). Jeśli ciąg zapytania zawiera klucz, procedura obsługi przekazuje żądanie do procedury obsługi wewnętrznej.
Jeśli żądanie nie ma prawidłowego klucza, program obsługi tworzy komunikat odpowiedzi o stanie 403, Zabronione. W takim przypadku program obsługi nie wywołuje metody base.SendAsync
, więc program obsługi wewnętrznej nigdy nie odbiera żądania, ani kontrolera. W związku z tym kontroler może założyć, że wszystkie żądania przychodzące mają prawidłowy klucz interfejsu API.
Uwaga
Jeśli klucz interfejsu API ma zastosowanie tylko do niektórych akcji kontrolera, rozważ użycie filtru akcji zamiast procedury obsługi komunikatów. Filtry akcji są uruchamiane po wykonaniu routingu identyfikatora URI.
programy obsługi komunikatów Per-Route
Programy obsługi w kolekcji HttpConfiguration.MessageHandlers są stosowane globalnie.
Alternatywnie można dodać procedurę obsługi komunikatów do określonej trasy podczas definiowania trasy:
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
}
}
W tym przykładzie, jeśli identyfikator URI żądania jest zgodny z trasą "Route2", żądanie jest wysyłane do MessageHandler2
. Na poniższym diagramie przedstawiono potok dla tych dwóch tras:
Zwróć uwagę, że MessageHandler2
zastępuje domyślną wartość HttpControllerDispatcher. W tym przykładzie MessageHandler2
tworzona jest odpowiedź, a żądania zgodne z trasą "Route2" nigdy nie przechodzą do kontrolera. Umożliwia to zastąpienie całego mechanizmu kontrolera internetowego interfejsu API własnym niestandardowym punktem końcowym.
Alternatywnie program obsługi komunikatów dla trasy może delegować do protokołu HttpControllerDispatcher, który następnie wysyła do kontrolera.
Poniższy kod pokazuje, jak skonfigurować tę trasę:
// 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
);