Cree una puerta de enlace API simple en ASP.NET Core

Hola Habr! Le presento la traducción del artículo " Creación de una puerta de enlace API simple en ASP.NET Core ".


10 minutos para leer


En mi artículo anterior, Autenticación JWT para microservicios en .NET , analicé el proceso de creación de un microservicio para la autenticación de usuarios. Esto se puede usar para verificar la identidad del usuario antes de realizar cualquier acción en otros componentes del sistema.


Diagrama de arquitectura de microservicios


Otro componente vital para que el producto funcione es una puerta de enlace API: un sistema entre la aplicación y el backend, que, en primer lugar, enruta las solicitudes entrantes al microservicio correspondiente y, en segundo lugar, autoriza al usuario.


Hay muchos marcos que se pueden usar para crear una puerta de enlace API, por ejemplo, Ocelot en el núcleo de .NET o Netflix Zuul en Java. Sin embargo, en este artículo describiré el proceso de crear una puerta de enlace API simple desde cero en .NET Core.


Creación de proyectos


Primero, cree una nueva aplicación seleccionando ASP.NET Core Web Application en la ventana de creación del proyecto y Vaciar como plantilla.




El proyecto contendrá las clases Startup y Program . Para nosotros, la parte más importante es el método Configure de la clase Startup . Aquí podemos procesar la solicitud HTTP entrante y responder a ella. Quizás el siguiente código estará en el método Configure :


app.Run(async (context) => { await context.Response.WriteAsync("Hello, World!"); }); 

Deletreando el enrutador


Dado que es en el método Configurar que procesaremos las solicitudes, escribiremos la lógica necesaria:


 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()); }); 

Primero creamos un objeto de tipo Router . Sus tareas incluyen almacenar rutas existentes, validar y enviar solicitudes de acuerdo con las rutas. Para hacer el código más limpio, cargaremos las rutas desde el archivo JSON.


El resultado es la siguiente lógica: después de que la solicitud llega a la puerta de enlace, será redirigida al enrutador, que, a su vez, la enviará al microservicio correspondiente.


Antes de escribir la clase Router , cree un archivo routes.json . En este archivo, indicamos una lista de rutas, cada una de las cuales contendrá una dirección externa (punto final) y una dirección de destino (destino). Además, agregaremos una bandera que indica la necesidad de autorizar al usuario antes de redirigir.


Así es como se vería el archivo:


 { "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/" } } 

Crear una clase de destino


Ahora sabemos que cada ruta debe tener un endpoint y un destination , y cada destino debe tener uri y requiresAuthentication campos de uri .


Ahora escribamos la clase Destino , recordando eso. Agregaré dos campos, dos constructores y un constructor privado sin parámetros para la deserialización de 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; } } 

Además, será correcto escribir el método SendRequest en esta clase. Esto mostrará que cada objeto de la clase Destino será responsable de enviar la solicitud. Este método tomará un objeto de tipo HttpRequest , que describe la solicitud entrante, extraerá toda la información necesaria de allí y enviará la solicitud al URI de destino. Para hacer esto, escribimos un método auxiliar CreateDestinationUri , que conectará las líneas con la dirección y los parámetros de la línea de dirección (cadena de consulta) desde el cliente.


  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; } 

Ahora podemos escribir un método SendRequest que enviará una solicitud de microservicio y recibirá una respuesta.


 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; } 

Crea un analizador JSON.


Antes de escribir la clase Router , necesitamos crear lógica para deserializar el archivo JSON con las rutas. Crearé una clase auxiliar para esto, en la que habrá dos métodos: uno para crear un objeto a partir de un archivo JSON y el otro para la deserialización.


 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)); } } 

Router de clase.


Lo último que haremos antes de escribir Router es describir el modelo de ruta:


  public class Route { public string Endpoint { get; set; } public Destination Destination { get; set; } } 

Ahora escribamos la clase Router agregando campos y un constructor allí.


 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) ); } } 

Utilizo un tipo dinámico para leer desde JSON y escribirle propiedades de objeto.


Ahora todo está listo para describir la funcionalidad principal de la puerta de enlace API: enrutamiento y autorización del usuario, que ocurrirá en el método RouteRequest . Necesitamos desempaquetar la parte base de la dirección externa (punto final base) del objeto de solicitud. Por ejemplo, para la dirección /movies/add base sería /movies/ . Después de eso, debemos verificar si hay una descripción de esta ruta. Si es así, autorice al usuario y envíe la solicitud; de lo contrario, devolveremos un error. También creé la clase ConstructErrorMessage por conveniencia.


Para la autorización, preferí la siguiente manera: extraemos el token del encabezado de la solicitud y lo enviamos como un parámetro de solicitud. Otra opción es posible: deje el token en el encabezado, luego el microservicio al que está destinada la solicitud ya debería extraerlo.


 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; } 

Conclusión


Crear una puerta de enlace API básica no requiere mucho esfuerzo, pero no proporciona la funcionalidad adecuada. Si necesita un equilibrador de carga, puede consultar los marcos o plataformas existentes que ofrecen bibliotecas para las solicitudes de enrutamiento.


Todo el código de este artículo está disponible en el repositorio de GitHub.

Source: https://habr.com/ru/post/es419393/


All Articles