Bonjour, Habr! Je vous présente la traduction de l'article " Création d'une passerelle API simple dans ASP.NET Core ".
10 minutes pour lire
Dans mon article précédent, Authentification JWT pour les microservices dans .NET , j'ai examiné le processus de création d'un microservice pour l'authentification des utilisateurs. Cela peut être utilisé pour vérifier l'identité de l'utilisateur avant d'effectuer toute action dans d'autres composants du système.

Un autre élément essentiel pour que le produit fonctionne est une passerelle API - un système entre l'application et le backend, qui, d'une part, achemine les demandes entrantes vers le microservice correspondant, et d'autre part, autorise l'utilisateur.
Il existe de nombreux frameworks qui peuvent être utilisés pour créer une passerelle API, par exemple Ocelot dans le noyau .NET ou Netflix Zuul en Java. Cependant, dans cet article, je décrirai le processus de création d'une passerelle API simple à partir de zéro dans .NET Core.
Création de projet
Tout d'abord, créez une nouvelle application en sélectionnant Application Web ASP.NET Core dans la fenêtre de création de projet et Videz comme modèle.


Le projet contiendra les classes de démarrage et de programme . Pour nous, la partie la plus importante est la méthode Configure de la classe Startup . Ici, nous pouvons traiter la requête HTTP entrante et y répondre. Peut-être que le code suivant sera dans la méthode Configure :
app.Run(async (context) => { await context.Response.WriteAsync("Hello, World!"); });
Orthographe du routeur
Puisque c'est dans la méthode Configure que nous traiterons les requêtes, nous écrirons la logique nécessaire:
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()); });
Nous créons d'abord un objet de type Router . Ses tâches comprennent le stockage des itinéraires existants, la validation et l'envoi de demandes en fonction des itinéraires. Pour rendre le code plus propre, nous chargerons les routes à partir du fichier JSON.
Le résultat est la logique suivante: une fois la demande arrivée à la passerelle, elle sera redirigée vers le routeur, qui, à son tour, l'enverra au microservice correspondant.
Avant d'écrire la classe Router , créez un fichier routes.json . Dans ce fichier, nous indiquons une liste de routes, chacune contenant une adresse externe (endpoint) et une adresse de destination (destination). De plus, nous ajouterons un indicateur signalant la nécessité d'autoriser l'utilisateur avant de rediriger.
Voici à quoi pourrait ressembler le fichier:
{ "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/" } }
Créer une classe de destination
Nous savons maintenant que chaque route doit avoir un endpoint
et une destination
, et chaque destination doit avoir un uri
et requiresAuthentication
champs d' uri
.
Écrivons maintenant la classe Destination , en nous souvenant de cela. J'ajouterai deux champs, deux constructeurs et un constructeur privé sans paramètres pour la désérialisation JSON.
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; } }
En outre, il sera correct d'écrire la méthode SendRequest
dans cette classe. Cela montrera que chaque objet de la classe Destination sera responsable de l'envoi de la demande. Cette méthode prendra un objet de type HttpRequest
, qui décrit la demande entrante, en retirera toutes les informations nécessaires et enverra la demande à l'URI cible. Pour ce faire, nous écrivons une méthode auxiliaire CreateDestinationUri
, qui connectera les lignes avec l'adresse et les paramètres de la ligne d'adresse (chaîne de requête) du client.
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; }
Nous pouvons maintenant écrire une méthode SendRequest
qui enverra une demande de microservice et recevra une réponse.
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; }
Créez un analyseur JSON.
Avant d'écrire la classe Router , nous devons créer une logique pour désérialiser le fichier JSON avec les routes. Je vais créer une classe d'assistance pour cela, dans laquelle il y aura deux méthodes: l'une pour créer un objet à partir d'un fichier JSON, et l'autre pour la désérialisation.
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)); } }
Routeur de classe.
La dernière chose que nous ferons avant d'écrire Router est de décrire le modèle de route:
public class Route { public string Endpoint { get; set; } public Destination Destination { get; set; } }
Écrivons maintenant la classe Router en y ajoutant des champs et un constructeur.
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) ); } }
J'utilise un type dynamique pour lire à partir de JSON et y écrire des propriétés d'objet.
Maintenant, tout est prêt à décrire les principales fonctionnalités de la passerelle API: le routage et l'autorisation utilisateur, qui se produiront dans la méthode RouteRequest
. Nous devons décompresser la partie de base de l'adresse externe (point de terminaison de base) de l'objet de demande. Par exemple, pour l'adresse /movies/add
base serait /movies/
. Après cela, nous devons vérifier s'il existe une description de cet itinéraire. Si c'est le cas, autorisez l'utilisateur et envoyez la demande, sinon nous renverrons une erreur. J'ai également créé la classe ConstructErrorMessage pour plus de commodité.
Pour l'autorisation, j'ai préféré la méthode suivante: nous extrayons le jeton de l'en-tête de la demande et l'envoyons comme paramètre de demande. Une autre option est possible: laissez le jeton dans l'en-tête, puis le microservice auquel la demande est destinée devrait déjà l'extraire.
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; }
Conclusion
La création d'une passerelle API de base ne nécessite pas beaucoup d'efforts, mais elle ne fournit pas les fonctionnalités appropriées. Si vous avez besoin d'un équilibreur de charge, vous pouvez consulter les infrastructures ou plates-formes existantes qui offrent des bibliothèques pour le routage des demandes.
Tout le code de cet article est disponible dans le référentiel GitHub.