Olá Habr! Apresento a você a tradução do artigo " Criando um simples API Gateway no ASP.NET Core ".
10 minutos para ler
No artigo anterior, Autenticação JWT para microsserviços no .NET , observei o processo de criação de um microsserviço para autenticação do usuário. Isso pode ser usado para verificar a identidade do usuário antes de executar qualquer ação em outros componentes do sistema.

Outro componente vital para o produto funcionar é um gateway de API - um sistema entre o aplicativo e o back-end, que primeiro encaminha as solicitações recebidas para o microsserviço correspondente e, em segundo lugar, autoriza o usuário.
Existem muitas estruturas que podem ser usadas para criar um gateway de API, por exemplo, Ocelot no núcleo do .NET ou Netflix Zuul em Java. No entanto, neste artigo, descreverei o processo de criação de um gateway de API simples do zero no .NET Core.
Criação de projeto
Primeiro, crie um novo aplicativo selecionando ASP.NET Core Web Application na janela de criação do projeto e Vazio como modelo.


O projeto conterá as classes Startup e Program . Para nós, a parte mais importante é o método Configure da classe Startup . Aqui, podemos processar a solicitação HTTP recebida e responder a ela. Talvez o código a seguir esteja no método Configure :
app.Run(async (context) => { await context.Response.WriteAsync("Hello, World!"); });
Ortografia do roteador
Como é no método Configure que processaremos as solicitações, escreveremos a lógica necessária:
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()); });
Primeiro, criamos um objeto do tipo Roteador . Suas tarefas incluem armazenar rotas existentes, validar e enviar solicitações de acordo com as rotas. Para tornar o código mais limpo, carregaremos as rotas do arquivo JSON.
O resultado é a seguinte lógica: após a solicitação chegar ao gateway, ela será redirecionada para o roteador, que, por sua vez, a enviará ao microsserviço correspondente.
Antes de escrever a classe Router , crie um arquivo routes.json . Neste arquivo, indicamos uma lista de rotas, cada uma contendo um endereço externo (ponto final) e um endereço de destino (destino). Além disso, adicionaremos uma bandeira sinalizando a necessidade de autorizar o usuário antes de redirecionar.
Aqui está a aparência do arquivo:
{ "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/" } }
Criar uma classe de destino
Agora sabemos que cada rota deve ter um endpoint
e um destination
, e cada destino deve ter um campo uri
e requiresAuthentication
autenticação.
Agora vamos escrever a classe Destination , lembrando disso. Vou adicionar dois campos, dois construtores e um construtor privado sem parâmetros para desserialização 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; } }
Além disso, será correto escrever o método SendRequest
nesta classe. Isso mostrará que cada objeto da classe Destination será responsável pelo envio da solicitação. Esse método pega um objeto do tipo HttpRequest
, que descreve a solicitação de entrada, retira todas as informações necessárias e envia a solicitação ao URI de destino. Para fazer isso, escrevemos um método auxiliar CreateDestinationUri
, que conectará as linhas com o endereço e os parâmetros da linha de endereço (string de consulta) do 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; }
Agora podemos escrever um método SendRequest
que enviará uma solicitação para um microsserviço e receberá uma resposta de volta.
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; }
Crie um analisador JSON.
Antes de escrever a classe Router , precisamos criar lógica para desserializar o arquivo JSON com as rotas. Vou criar uma classe auxiliar para isso, na qual haverá dois métodos: um para criar um objeto a partir de um arquivo JSON e outro para desserialização.
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)); } }
Roteador de classe.
A última coisa que faremos antes de escrever o roteador é descrever o modelo de rota:
public class Route { public string Endpoint { get; set; } public Destination Destination { get; set; } }
Agora vamos escrever a classe Router adicionando campos e um construtor lá.
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) ); } }
Eu uso um tipo dinâmico para ler do JSON e gravar propriedades do objeto nele.
Agora tudo está pronto para descrever as principais funcionalidades do gateway da API: roteamento e autorização do usuário, que ocorrerão no método RouteRequest
. Precisamos descompactar a parte base do endereço externo (ponto de extremidade base) do objeto de solicitação. Por exemplo, para o endereço /movies/add
base seria /movies/
. Depois disso, precisamos verificar se há uma descrição dessa rota. Nesse caso, autorize o usuário e envie a solicitação, caso contrário, retornamos um erro. Também criei a classe ConstructErrorMessage por conveniência.
Para autorização, preferi a seguinte maneira: extraímos o token do cabeçalho da solicitação e enviamos como um parâmetro de solicitação. Outra opção é possível: deixe o token no cabeçalho e o microsserviço ao qual a solicitação se destina já deve extraí-lo.
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; }
Conclusão
Criar um gateway de API básico não requer muito esforço, mas não fornece a funcionalidade adequada. Se você precisar de um balanceador de carga, poderá examinar estruturas ou plataformas existentes que oferecem bibliotecas para solicitações de roteamento.
Todo o código deste artigo está disponível no repositório GitHub.