Hallo Habr! Ich präsentiere Ihnen die Übersetzung des Artikels " Erstellen eines einfachen API-Gateways in ASP.NET Core ".
10 Minuten zum Lesen
In meinem vorherigen Artikel, JWT-Authentifizierung für Microservices in .NET , habe ich mich mit dem Erstellen eines Microservices für die Benutzerauthentifizierung befasst. Dies kann verwendet werden, um die Identität des Benutzers zu überprüfen, bevor Aktionen in anderen Komponenten des Systems ausgeführt werden.

Eine weitere wichtige Komponente für das Funktionieren des Produkts ist ein API-Gateway - ein System zwischen der Anwendung und dem Backend, das eingehende Anforderungen zum einen an den entsprechenden Microservice weiterleitet und zum anderen den Benutzer autorisiert.
Es gibt viele Frameworks, die zum Erstellen eines API-Gateways verwendet werden können, z. B. Ocelot im .NET-Kern oder Netflix Zuul in Java. In diesem Artikel werde ich jedoch den Prozess zum Erstellen eines einfachen API-Gateways von Grund auf in .NET Core beschreiben.
Projekterstellung
Erstellen Sie zunächst eine neue Anwendung, indem Sie im Projekterstellungsfenster ASP.NET Core-Webanwendung auswählen und als Vorlage leeren .


Das Projekt enthält die Klassen Startup und Program . Für uns ist der wichtigste Teil die Configure- Methode der Startup- Klasse. Hier können wir die eingehende HTTP-Anfrage verarbeiten und darauf antworten. Möglicherweise befindet sich der folgende Code in der Configure- Methode:
app.Run(async (context) => { await context.Response.WriteAsync("Hello, World!"); });
Rechtschreibung des Routers
Da wir die Anforderungen in der Configure- Methode verarbeiten, schreiben wir die erforderliche Logik:
Router router = new Router("routes.json"); app.Run(async (context) => { var content = await router.RouteRequest(context.Request); await context.Response.WriteAsync(await content.Content.ReadAsStringAsync()); });
Zuerst erstellen wir ein Objekt vom Typ Router . Zu seinen Aufgaben gehören das Speichern vorhandener Routen, das Validieren und Senden von Anfragen nach Routen. Um den Code übersichtlicher zu gestalten, laden wir die Routen aus der JSON-Datei.
Das Ergebnis ist die folgende Logik: Nachdem die Anforderung am Gateway eingegangen ist, wird sie an den Router umgeleitet, der sie wiederum an den entsprechenden Mikrodienst sendet.
Erstellen Sie vor dem Schreiben der Router- Klasse eine Datei route.json . In dieser Datei geben wir eine Liste von Routen an, von denen jede eine externe Adresse (Endpunkt) und eine Zieladresse (Ziel) enthält. Außerdem fügen wir ein Flag hinzu, das die Notwendigkeit signalisiert, den Benutzer vor der Umleitung zu autorisieren.
So könnte die Datei aussehen:
{ "routes": [ { "endpoint": "/movies", "destination": { "uri": "http://localhost:8090/movies/", "requiresAuthentication": "true" } }, { "endpoint": "/songs", "destination": { "uri": "http://localhost:8091/songs/", "requiresAuthentication": "false" } } ], "authenticationService": { "uri": "http://localhost:8080/api/auth/" } }
Erstellen Sie eine Zielklasse
Wir wissen jetzt, dass jede Route einen endpoint
und ein destination
muss und dass jedes Ziel uri
haben muss und uri
requiresAuthentication
.
Schreiben wir nun die Zielklasse und erinnern uns daran. Ich werde zwei Felder, zwei Konstruktoren und einen privaten Konstruktor ohne Parameter für die JSON-Deserialisierung hinzufügen.
public class Destination { public string Uri { get; set; } public bool RequiresAuthentication { get; set; } public Destination(string uri, bool requiresAuthentication) { Uri = path; RequiresAuthentication = requiresAuthentication; } public Destination(string uri) :this(uri, false) { } private Destination() { Uri = "/"; RequiresAuthentication = false; } }
Es ist auch richtig, die SendRequest
Methode in diese Klasse zu schreiben. Dies zeigt, dass jedes Objekt der Zielklasse für das Senden der Anforderung verantwortlich ist. Diese Methode nimmt ein Objekt vom Typ HttpRequest
, das die eingehende Anforderung beschreibt, nimmt alle erforderlichen Informationen von dort heraus und sendet die Anforderung an den Ziel-URI. Zu diesem CreateDestinationUri
schreiben wir eine CreateDestinationUri
, die die Zeilen mit der Adresse und den Parametern der Adresszeile (Abfragezeichenfolge) des Clients verbindet.
private string CreateDestinationUri(HttpRequest request) { string requestPath = request.Path.ToString(); string queryString = request.QueryString.ToString(); string endpoint = ""; string[] endpointSplit = requestPath.Substring(1).Split('/'); if (endpointSplit.Length > 1) endpoint = endpointSplit[1]; return Uri + endpoint + queryString; }
Jetzt können wir eine SendRequest
Methode schreiben, die eine Anfrage für einen Microservice sendet und eine Antwort zurückerhält.
public async Task<HttpResponseMessage> SendRequest(HttpRequest request) { string requestContent; using (Stream receiveStream = request.Body) { using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8)) { requestContent = readStream.ReadToEnd(); } } HttpClient client = new HttpClient(); HttpRequestMessage newRequest = new HttpRequestMessage(new HttpMethod(request.Method), CreateDestinationUri(request)); HttpResponseMessage response = await client.SendAsync(newRequest); return response; }
Erstellen Sie einen JSON-Parser.
Bevor wir die Router- Klasse schreiben, müssen wir eine Logik erstellen, um die JSON-Datei mit den Routen zu deserialisieren. Ich werde dafür eine Hilfsklasse erstellen, in der es zwei Methoden gibt: eine zum Erstellen eines Objekts aus einer JSON-Datei und die andere zum Deserialisieren.
public class JsonLoader { public static T LoadFromFile<T>(string filePath) { using (StreamReader reader = new StreamReader(filePath)) { string json = reader.ReadToEnd(); T result = JsonConvert.DeserializeObject<T>(json); return result; } } public static T Deserialize<T>(object jsonObject) { return JsonConvert.DeserializeObject<T>(Convert.ToString(jsonObject)); } }
Klassenrouter.
Das Letzte, was wir vor dem Schreiben von Router tun werden, ist die Beschreibung des Routenmodells:
public class Route { public string Endpoint { get; set; } public Destination Destination { get; set; } }
Schreiben wir nun die Router-Klasse, indem wir dort Felder und einen Konstruktor hinzufügen.
public class Router { public List<Route> Routes { get; set; } public Destination AuthenticationService { get; set; } public Router(string routeConfigFilePath) { dynamic router = JsonLoader.LoadFromFile<dynamic>(routeConfigFilePath); Routes = JsonLoader.Deserialize<List<Route>>( Convert.ToString(router.routes) ); AuthenticationService = JsonLoader.Deserialize<Destination>( Convert.ToString(router.authenticationService) ); } }
Ich verwende einen dynamischen Typ, um aus JSON zu lesen und Objekteigenschaften darauf zu schreiben.
Jetzt ist alles bereit, um die Hauptfunktionalität des API-Gateways zu beschreiben: Routing und Benutzerautorisierung, die in der RouteRequest
Methode erfolgen. Wir müssen den Basisteil der externen Adresse (Basisendpunkt) aus dem Anforderungsobjekt entpacken. Für die Adresse /movies/add
wäre beispielsweise /movies/add
Basis /movies/
. Danach müssen wir überprüfen, ob es eine Beschreibung dieser Route gibt. Wenn ja, autorisieren Sie den Benutzer und senden Sie die Anfrage, andernfalls geben wir einen Fehler zurück. Der Einfachheit halber habe ich auch die ConstructErrorMessage- Klasse erstellt.
Für die Autorisierung habe ich Folgendes bevorzugt: Wir extrahieren das Token aus dem Anforderungsheader und senden es als Anforderungsparameter. Eine andere Option ist möglich: Lassen Sie das Token im Header, dann sollte der Microservice, für den die Anforderung bestimmt ist, es bereits extrahieren.
public async Task<HttpResponseMessage> RouteRequest(HttpRequest request) { string path = request.Path.ToString(); string basePath = '/' + path.Split('/')[1]; Destination destination; try { destination = Routes.First(r => r.Endpoint.Equals(basePath)).Destination; } catch { return ConstructErrorMessage("The path could not be found."); } if (destination.RequiresAuthentication) { string token = request.Headers["token"]; request.Query.Append(new KeyValuePair<string, StringValues>("token", new StringValues(token))); HttpResponseMessage authResponse = await AuthenticationService.SendRequest(request); if (!authResponse.IsSuccessStatusCode) return ConstructErrorMessage("Authentication failed."); } return await destination.SendRequest(request); } private HttpResponseMessage ConstructErrorMessage(string error) { HttpResponseMessage errorMessage = new HttpResponseMessage { StatusCode = HttpStatusCode.NotFound, Content = new StringContent(error) }; return errorMessage; }
Fazit
Das Erstellen eines grundlegenden API-Gateways erfordert keinen großen Aufwand, bietet jedoch keine ordnungsgemäße Funktionalität. Wenn Sie einen Load Balancer benötigen, können Sie sich vorhandene Frameworks oder Plattformen ansehen, die Bibliotheken für Routing-Anforderungen anbieten.
Der gesamte Code in diesem Artikel ist im GitHub-Repository verfügbar .