Partilhar via


Implementação de API Web

Uma API Web RESTful cuidadosamente projetada define os recursos, as relações e os esquemas de navegação que são acessíveis para as aplicações cliente. Quando implementar e implementar uma API web, deve considerar os requisitos físicos do ambiente que hospeda a API web e a forma como a API web é construída, em vez da estrutura lógica dos dados. Esta orientação foca-se nas melhores práticas para implementar uma API web e publicá-la, tornando-a disponível para aplicações cliente. Para obter informações detalhadas sobre o design da API da Web, consulte Design da API da Web.

Processando pedidos

Considere os seguintes pontos quando implementar o código para lidar com pedidos.

As ações GET, PUT, DELETE, HEAD e PATCH devem ser idempotentes

O código que implementa estas solicitações não deve impor quaisquer efeitos secundários. O mesmo pedido repetido sobre o mesmo recurso deve resultar no mesmo estado. Por exemplo, o envio de várias solicitações DELETE para o mesmo URI deve ter o mesmo efeito, embora o código de status HTTP nas mensagens de resposta possa ser diferente. O primeiro pedido DELETE poderá devolver o código de estado 204 (Sem Conteúdo), enquanto um pedido DELETE subsequente poderá devolver o código de estado 404 (Não Encontrado).

Nota

O artigo Idempotency Patterns no blog de Jonathan Oliver fornece uma visão geral da idempotência e como ela se relaciona com as operações de gerenciamento de dados.

As ações POST que criam novos recursos não devem ter efeitos colaterais não relacionados.

Se um pedido POST tiver como objetivo criar um novo recurso, os efeitos do pedido devem ser limitados ao novo recurso (e possivelmente a quaisquer recursos diretamente relacionados, se houver algum tipo de ligação envolvida). Por exemplo, num sistema de comércio eletrónico, um pedido POST que cria uma nova encomenda para um cliente pode também ajustar os níveis de inventário e gerar informações de faturação, mas não deve modificar informações não diretamente relacionadas com a encomenda ou ter quaisquer outros efeitos secundários no estado geral do sistema.

Evite implementar operações verborrágicas de POST, PUT e DELETE

Evite projetar a sua API com base em operações verbosas. Cada pedido envolve sobrecarga de protocolo, rede e computação. Por exemplo, executar 100 pedidos menores em vez de um único pedido em lote maior acarreta um aumento de overhead no cliente, na rede e no servidor de recursos. Sempre que possível, disponibilize suporte para verbos HTTP em coleções de recursos em vez de apenas recursos individuais.

  • Um pedido GET a uma coleção pode recuperar múltiplos recursos ao mesmo tempo.
  • Um pedido POST pode conter os detalhes de múltiplos novos recursos e adicioná-los todos à mesma coleção.
  • Um pedido PUT pode substituir todo o conjunto de recursos numa coleção.
  • Uma solicitação DELETE pode remover uma coleção inteira.

O suporte ao Protocolo de Dados Abertos (OData) incluído no ASP.NET Web API 2 oferece a capacidade de agrupar pedidos. Uma aplicação cliente pode agrupar vários pedidos de API da web e enviá-los ao servidor em um único pedido HTTP, recebendo uma única resposta HTTP que contém as respostas para cada pedido. Para obter mais informações, consulte Ativar Batch no serviço OData da API Web.

Siga a especificação HTTP ao enviar uma resposta.

Uma API web deve retornar mensagens que contenham o código de status HTTP correto para permitir que o cliente determine como lidar com o resultado, os cabeçalhos HTTP apropriados para que o cliente compreenda a natureza do resultado, e um corpo devidamente formatado para permitir que o cliente analise o resultado.

Por exemplo, uma operação POST deve retornar o código de estado 201 (Criado) e a mensagem de resposta deve incluir o URI do recurso recém-criado no cabeçalho de Localização da mensagem de resposta.

Suporte à negociação de conteúdo

O corpo de uma mensagem de resposta pode conter dados em uma variedade de formatos. Por exemplo, um pedido HTTP GET pode retornar dados no formato JSON ou XML. Quando o cliente submete um pedido, pode incluir um cabeçalho Accept que especifica os formatos de dados que consegue processar. Estes formatos são especificados como tipos de mídia. Por exemplo, um cliente que emite um pedido GET que recupera uma imagem pode especificar um cabeçalho Accept que lista os tipos de media que o cliente pode manipular, como image/jpeg, image/gif, image/png. Quando a API da web retornar o resultado, deve formatar os dados usando um destes tipos de mídia e especificar o formato no cabeçalho Content-Type da resposta.

Se o cliente não especificar um cabeçalho de Aceitação, então use um formato padrão sensato para o corpo da resposta. Como exemplo, a framework ASP.NET Web API utiliza JSON por padrão para dados baseados em texto.

A abordagem HATEOAS permite que um cliente navegue e descubra recursos a partir de um ponto de partida inicial. Isto é conseguido através da utilização de links contendo URIs; quando um cliente faz uma solicitação HTTP GET para obter um recurso, a resposta deve conter URIs que permitam a uma aplicação cliente localizar rapidamente quaisquer recursos diretamente relacionados. Por exemplo, numa API web que suporta uma solução de comércio eletrónico, um cliente pode ter feito muitos pedidos. Quando uma aplicação cliente recupera os detalhes de um cliente, a resposta deve incluir links que permitam à aplicação cliente enviar pedidos HTTP GET que possam recuperar essas encomendas. Além disso, os links no estilo HATEOAS devem descrever as outras operações (POST, PUT, DELETE, etc.) que cada recurso ligado suporta juntamente com o URI correspondente para realizar cada pedido. Essa abordagem é descrita com mais detalhes no design da API.

Atualmente, não existem normas que regulem a implementação de HATEOAS, mas o exemplo seguinte ilustra uma abordagem possível. Neste exemplo, uma solicitação HTTP GET que encontra os detalhes de um cliente devolve uma resposta que inclui links HATEOAS que fazem referência às encomendas desse cliente.

GET https://adventure-works.com/customers/2 HTTP/1.1
Accept: text/json
...
HTTP/1.1 200 OK
...
Content-Type: application/json; charset=utf-8
...
Content-Length: ...
{"CustomerID":2,"CustomerName":"Bert","Links":[
    {"rel":"self",
    "href":"https://adventure-works.com/customers/2",
    "action":"GET",
    "types":["text/xml","application/json"]},
    {"rel":"self",
    "href":"https://adventure-works.com/customers/2",
    "action":"PUT",
    "types":["application/x-www-form-urlencoded"]},
    {"rel":"self",
    "href":"https://adventure-works.com/customers/2",
    "action":"DELETE",
    "types":[]},
    {"rel":"orders",
    "href":"https://adventure-works.com/customers/2/orders",
    "action":"GET",
    "types":["text/xml","application/json"]},
    {"rel":"orders",
    "href":"https://adventure-works.com/customers/2/orders",
    "action":"POST",
    "types":["application/x-www-form-urlencoded"]}
]}

Neste exemplo, os dados do cliente são representados pela classe Customer mostrada no trecho de código seguinte. Os links HATEOAS são mantidos na propriedade de coleção Links.

public class Customer
{
    public int CustomerID { get; set; }
    public string CustomerName { get; set; }
    public List<Link> Links { get; set; }
    ...
}

public class Link
{
    public string Rel { get; set; }
    public string Href { get; set; }
    public string Action { get; set; }
    public string [] Types { get; set; }
}

A operação HTTP GET recupera os dados do cliente do armazenamento, constrói um objeto Customer e, em seguida, preenche a coleção Links. O resultado é formatado como uma mensagem de resposta JSON. Cada link inclui os seguintes campos:

  • A relação (Rel) entre o objeto que está a ser devolvido e o objeto descrito pela ligação. Neste caso, self indica que o link é uma referência de volta para o próprio objeto (semelhante a um ponteiro this em muitas linguagens orientadas a objetos), e orders é o nome de uma coleção que contém as informações relacionadas ao pedido.
  • O hiperligações (Href) para o objeto sendo descrito pelo link na forma de um URI.
  • O tipo de pedido HTTP (Action) que pode ser enviado para este URI.
  • O formato de quaisquer dados (Types) que devem ser fornecidos no pedido HTTP ou que podem ser retornados na resposta, dependendo do tipo de pedido.

Os links HATEOAS mostrados na resposta HTTP de exemplo indicam que uma aplicação cliente pode realizar as seguintes operações:

  • Uma solicitação HTTP GET para o URI https://adventure-works.com/customers/2 para obter os detalhes do cliente (novamente). Os dados podem ser retornados como XML ou JSON.
  • Uma solicitação HTTP PUT para o URI https://adventure-works.com/customers/2 para modificar os detalhes do cliente. Os novos dados devem ser fornecidos na mensagem de pedido no formato x-www-form-urlencoded.
  • Uma solicitação HTTP DELETE para o URI https://adventure-works.com/customers/2 para apagar o cliente. A solicitação não espera qualquer informação adicional ou dados de retorno no corpo da mensagem de resposta.
  • Um pedido HTTP GET para o URI https://adventure-works.com/customers/2/orders para encontrar todas as encomendas do cliente. Os dados podem ser retornados como XML ou JSON.
  • Uma solicitação HTTP POST para o URI https://adventure-works.com/customers/2/orders para criar uma nova encomenda para este cliente. Os dados devem ser fornecidos na mensagem de pedido em formato x-www-form-urlencoded.

Tratamento de exceções

Considere os seguintes pontos se uma operação lançar uma exceção não capturada.

Capture exceções e devolva uma resposta significativa aos clientes

O código que implementa uma operação HTTP deve fornecer um tratamento abrangente de exceções, em vez de permitir que exceções não capturadas se propaguem para o framework. Se uma exceção tornar impossível concluir a operação com sucesso, a exceção pode ser incluída na mensagem de resposta, mas deve conter uma descrição significativa do erro que causou a exceção. A exceção deve também incluir o código de estado HTTP apropriado em vez de simplesmente retornar o código de estado 500 para todas as situações. Por exemplo, se um pedido de utilizador provocar uma atualização da base de dados que viole uma restrição (como tentar eliminar um cliente que tenha encomendas pendentes), deve devolver o código de estado 409 (Conflito) e uma mensagem no corpo a indicar a razão do conflito. Se alguma outra condição tornar o pedido impossível de realizar, pode retornar o código de estado 400 (Pedido Inválido). Você pode encontrar uma lista completa de códigos de status HTTP na página Definições de código de status no site do W3C.

O exemplo de código captura diferentes condições e devolve uma resposta apropriada.

[HttpDelete]
[Route("customers/{id:int}")]
public IHttpActionResult DeleteCustomer(int id)
{
    try
    {
        // Find the customer to be deleted in the repository
        var customerToDelete = repository.GetCustomer(id);

        // If there is no such customer, return an error response
        // with status code 404 (Not Found)
        if (customerToDelete == null)
        {
            return NotFound();
        }

        // Remove the customer from the repository
        // The DeleteCustomer method returns true if the customer
        // was successfully deleted
        if (repository.DeleteCustomer(id))
        {
            // Return a response message with status code 204 (No Content)
            // To indicate that the operation was successful
            return StatusCode(HttpStatusCode.NoContent);
        }
        else
        {
            // Otherwise return a 400 (Bad Request) error response
            return BadRequest(Strings.CustomerNotDeleted);
        }
    }
    catch
    {
        // If an uncaught exception occurs, return an error response
        // with status code 500 (Internal Server Error)
        return InternalServerError();
    }
}

Sugestão

Não inclua informações que possam ser úteis a um atacante que tente penetrar na sua API.

Muitos servidores web capturam as condições de erro por si mesmos antes de chegarem à API web. Por exemplo, se configurar a autenticação para um site e o utilizador não fornecer as informações corretas de autenticação, o servidor web deverá responder com o código de estado 401 (Não autorizado). Depois de um cliente ter sido autenticado, o seu código pode executar as suas próprias verificações para garantir que o cliente deve poder aceder ao recurso solicitado. Se esta autorização falhar, deve devolver o código de estado 403 (Proibido).

Trate as exceções de forma consistente e registe informações sobre erros.

Para lidar com exceções de maneira consistente, considere implementar uma estratégia global de tratamento de erros em toda a API web. Deve também incorporar o registo de erros que capture todos os detalhes de cada exceção; este registo de erros pode conter informações detalhadas, desde que não seja acessível na web aos clientes.

Distinguir entre erros do lado do cliente e erros do lado do servidor

O protocolo HTTP distingue entre erros que ocorrem devido à aplicação cliente (os códigos de status HTTP 4xx) e erros que são causados por problemas no servidor (os códigos de status HTTP 5xx). Certifique-se de que respeita esta convenção em todas as mensagens de resposta a erros.

Otimização do acesso aos dados do lado do cliente

Em um ambiente distribuído, como aquele que envolve um servidor web e aplicações cliente, uma das principais fontes de preocupação é a rede. Isto pode atuar como um gargalo considerável, especialmente se uma aplicação cliente estiver frequentemente a enviar pedidos ou a receber dados. Portanto, deve procurar minimizar a quantidade de tráfego que atravessa a rede. Considere os seguintes pontos ao implementar o código para recuperar e manter dados:

Suporte de cache no lado do cliente

O protocolo HTTP 1.1 suporta o armazenamento em cache em clientes e servidores intermediários através dos quais um pedido é encaminhado, utilizando o cabeçalho Cache-Control. Quando uma aplicação cliente envia um pedido HTTP GET à API web, a resposta pode incluir um cabeçalho Cache-Control que indica se os dados no corpo da resposta podem ser armazenados em cache de forma segura pelo cliente ou por um servidor intermediário através do qual o pedido foi encaminhado, e por quanto tempo antes de expirar e ser considerado desatualizado.

O exemplo seguinte mostra um pedido HTTP GET e a respetiva resposta, que inclui um cabeçalho Cache-Control.

GET https://adventure-works.com/orders/2 HTTP/1.1
HTTP/1.1 200 OK
...
Cache-Control: max-age=600, private
Content-Type: text/json; charset=utf-8
Content-Length: ...
{"orderID":2,"productID":4,"quantity":2,"orderValue":10.00}

Neste exemplo, o cabeçalho Cache-Control especifica que os dados retornados devem expirar após 600 segundos e só são adequados para um único cliente e não devem ser armazenados em um cache compartilhado usado por outros clientes (é privado). O cabeçalho Cache-Control pode especificar público em vez de privado , caso em que os dados podem ser armazenados em um cache compartilhado, ou pode especificar no-store , caso em que os dados não devem ser armazenados em cache pelo cliente. O exemplo de código a seguir mostra como construir um cabeçalho Cache-Control numa mensagem de resposta:

public class OrdersController : ApiController
{
    ...
    [Route("api/orders/{id:int:min(0)}")]
    [HttpGet]
    public IHttpActionResult FindOrderByID(int id)
    {
        // Find the matching order
        Order order = ...;
        ...
        // Create a Cache-Control header for the response
        var cacheControlHeader = new CacheControlHeaderValue();
        cacheControlHeader.Private = true;
        cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
        ...

        // Return a response message containing the order and the cache control header
        OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
        {
            CacheControlHeader = cacheControlHeader
        };
        return response;
    }
    ...
}

Este código utiliza uma classe IHttpActionResult personalizada chamada OkResultWithCaching. Esta classe permite que o controlador defina os conteúdos do cabeçalho de cache.

public class OkResultWithCaching<T> : OkNegotiatedContentResult<T>
{
    public OkResultWithCaching(T content, ApiController controller)
        : base(content, controller) { }

    public OkResultWithCaching(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
        : base(content, contentNegotiator, request, formatters) { }

    public CacheControlHeaderValue CacheControlHeader { get; set; }
    public EntityTagHeaderValue ETag { get; set; }

    public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response;
        try
        {
            response = await base.ExecuteAsync(cancellationToken);
            response.Headers.CacheControl = this.CacheControlHeader;
            response.Headers.ETag = ETag;
        }
        catch (OperationCanceledException)
        {
            response = new HttpResponseMessage(HttpStatusCode.Conflict) {ReasonPhrase = "Operation was cancelled"};
        }
        return response;
    }
}

Nota

O protocolo HTTP também define a diretiva no-cache para o cabeçalho Cache-Control. Confusamente, esta diretiva não significa "não armazenar em cache", mas sim "revalidar a informação em cache com o servidor antes de a devolver"; os dados podem ainda ser armazenados em cache, mas são verificados cada vez que são utilizados para assegurar que ainda estão atualizados.

A gestão de cache é da responsabilidade da aplicação cliente ou do servidor intermediário, mas se for corretamente implementada, pode poupar largura de banda e melhorar o desempenho, eliminando a necessidade de buscar dados que já foram recentemente recuperados.

O valor max-age no cabeçalho Cache-Control é apenas um guia e não uma garantia de que os dados correspondentes não serão alterados durante o tempo especificado. A API web deve definir a idade máxima para um valor adequado, dependendo da volatilidade esperada dos dados. Quando este período expirar, o cliente deve descartar o objeto da cache.

Nota

A maioria dos navegadores web modernos suporta o armazenamento em cache do lado do cliente, adicionando os cabeçalhos de controle de cache apropriados às solicitações e examinando os cabeçalhos dos resultados, conforme descrito. No entanto, alguns navegadores mais antigos não armazenam em cache os valores retornados de um URL que inclui uma string de consulta. Normalmente, isto não é um problema para aplicações cliente personalizadas que implementam a sua própria estratégia de gestão de cache com base no protocolo aqui discutido.

Alguns proxies mais antigos exibem o mesmo comportamento e podem não armazenar em cache solicitações baseadas em URLs com strings de consulta. Isto pode ser um problema para aplicações de cliente personalizadas que se conectam a um servidor web através de tal proxy.

Forneça ETags para otimizar o processamento de consultas

Quando um aplicativo cliente recupera um objeto, a mensagem de resposta também pode incluir uma marca de entidade (ETag). Uma ETag é uma cadeia de caracteres opaca que indica a versão de um recurso; sempre que um recurso é alterado, a ETag é também modificada. Este ETag deve ser armazenado em cache como parte dos dados pela aplicação cliente. O exemplo de código a seguir mostra como adicionar um ETag como parte da resposta a um pedido HTTP GET. Este código utiliza o método GetHashCode de um objeto para gerar um valor numérico que identifica o objeto (pode substituir este método, se necessário, e gerar o seu próprio hash usando um algoritmo como MD5):

public class OrdersController : ApiController
{
    ...
    public IHttpActionResult FindOrderByID(int id)
    {
        // Find the matching order
        Order order = ...;
        ...

        var hashedOrder = order.GetHashCode();
        string hashedOrderEtag = $"\"{hashedOrder}\"";
        var eTag = new EntityTagHeaderValue(hashedOrderEtag);

        // Return a response message containing the order and the cache control header
        OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
        {
            ...,
            ETag = eTag
        };
        return response;
    }
    ...
}

A mensagem de resposta publicada pela API web parece assim:

HTTP/1.1 200 OK
...
Cache-Control: max-age=600, private
Content-Type: text/json; charset=utf-8
ETag: "2147483648"
Content-Length: ...
{"orderID":2,"productID":4,"quantity":2,"orderValue":10.00}

Sugestão

Por razões de segurança, não permita que dados sensíveis ou dados retornados através de uma conexão autenticada (HTTPS) sejam armazenados em cache.

Uma aplicação cliente pode emitir um pedido GET subsequente para recuperar o mesmo recurso a qualquer momento; se o recurso tiver mudado (tem um ETag diferente), a versão em cache deve ser descartada e a nova versão adicionada ao cache. Se um recurso for grande e exigir uma quantidade significativa de largura de banda para ser transmitido de volta ao cliente, pedidos repetidos para buscar os mesmos dados podem tornar-se ineficientes. Para combater isto, o protocolo HTTP define o seguinte processo para otimizar pedidos GET que deve suportar numa API web: Siga estes passos:

  • O cliente constrói um pedido GET contendo o ETag para a versão atualmente armazenada em cache do recurso referenciado num cabeçalho HTTP If-None-Match:

    GET https://adventure-works.com/orders/2 HTTP/1.1
    If-None-Match: "2147483648"
    
  • A operação GET na API da web obtém o ETag atual para os dados solicitados (pedido 2 no exemplo acima) e compara-o com o valor no cabeçalho If-None-Match.

  • Se o ETag atual para os dados solicitados corresponder ao ETag fornecido pelo pedido, o recurso não foi alterado e a API da web deve retornar uma resposta HTTP com um corpo de mensagem vazio e um código de status de 304 (Não Modificado).

  • Se o ETag atual para os dados solicitados não corresponder ao ETag fornecido pelo pedido, então os dados foram alterados e a API web deve devolver uma resposta HTTP com os novos dados no corpo da mensagem e um código de status de 200 (OK).

  • Se os dados solicitados já não existirem, então a API web deve devolver uma resposta HTTP com o código de estado 404 (Não Encontrado).

  • O cliente usa o código de estado para gerir a cache. Se os dados não tiverem mudado (código de estado 304), então o objeto pode permanecer em cache e a aplicação cliente deve continuar a usar esta versão do objeto. Se os dados tiverem mudado (código de status 200), então o objeto em cache deve ser descartado e o novo deve ser inserido. Se os dados deixarem de estar disponíveis (código de status 404), o objeto deve ser removido da cache.

Nota

Se o cabeçalho da resposta contiver o cabeçalho Cache-Control com no-store, o objeto deve ser sempre removido da cache, independentemente do código de estado HTTP.

O código seguinte mostra o método FindOrderByID expandido para suportar o cabeçalho If-None-Match. Note que, se o cabeçalho If-None-Match for omitido, a ordem especificada será sempre recuperada.

public class OrdersController : ApiController
{
    [Route("api/orders/{id:int:min(0)}")]
    [HttpGet]
    public IHttpActionResult FindOrderByID(int id)
    {
        try
        {
            // Find the matching order
            Order order = ...;

            // If there is no such order then return NotFound
            if (order == null)
            {
                return NotFound();
            }

            // Generate the ETag for the order
            var hashedOrder = order.GetHashCode();
            string hashedOrderEtag = $"\"{hashedOrder}\"";

            // Create the Cache-Control and ETag headers for the response
            IHttpActionResult response;
            var cacheControlHeader = new CacheControlHeaderValue();
            cacheControlHeader.Public = true;
            cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
            var eTag = new EntityTagHeaderValue(hashedOrderEtag);

            // Retrieve the If-None-Match header from the request (if it exists)
            var nonMatchEtags = Request.Headers.IfNoneMatch;

            // If there is an ETag in the If-None-Match header and
            // this ETag matches that of the order just retrieved,
            // then create a Not Modified response message
            if (nonMatchEtags.Count > 0 &&
                String.CompareOrdinal(nonMatchEtags.First().Tag, hashedOrderEtag) == 0)
            {
                response = new EmptyResultWithCaching()
                {
                    StatusCode = HttpStatusCode.NotModified,
                    CacheControlHeader = cacheControlHeader,
                    ETag = eTag
                };
            }
            // Otherwise create a response message that contains the order details
            else
            {
                response = new OkResultWithCaching<Order>(order, this)
                {
                    CacheControlHeader = cacheControlHeader,
                    ETag = eTag
                };
            }

            return response;
        }
        catch
        {
            return InternalServerError();
        }
    }
...
}

Este exemplo incorpora uma classe IHttpActionResult personalizada adicional chamada EmptyResultWithCaching. Esta classe simplesmente atua como um invólucro em torno de um objeto HttpResponseMessage que não contém um corpo de resposta:

public class EmptyResultWithCaching : IHttpActionResult
{
    public CacheControlHeaderValue CacheControlHeader { get; set; }
    public EntityTagHeaderValue ETag { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public Uri Location { get; set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = new HttpResponseMessage(StatusCode);
        response.Headers.CacheControl = this.CacheControlHeader;
        response.Headers.ETag = this.ETag;
        response.Headers.Location = this.Location;
        return response;
    }
}

Sugestão

Neste exemplo, o ETag para os dados é gerado através da hash dos dados recuperados da fonte de dados subjacente. Se o ETag puder ser calculado de outra forma, o processo pode ser ainda mais otimizado e os dados só precisam ser recuperados da fonte de dados se tiverem mudado. Esta abordagem é especialmente útil se os dados forem grandes ou se o acesso à fonte de dados puder resultar em latência significativa (por exemplo, se a fonte de dados for uma base de dados remota).

Utilize ETags para Suportar Concorrência Otimista

Para permitir atualizações sobre dados previamente em cache, o protocolo HTTP suporta uma estratégia de concorrência otimista. Se, após buscar e armazenar em cache um recurso, a aplicação cliente posteriormente enviar uma solicitação PUT ou DELETE para alterar ou remover o recurso, deve incluir um cabeçalho If-Match que faça referência ao ETag. Em seguida, a API da web pode usar essa informação para determinar se o recurso já foi alterado por outro utilizador desde que foi recuperado e enviar uma resposta apropriada de volta para a aplicação cliente da seguinte forma:

  • O cliente constrói uma solicitação PUT que contém os novos detalhes para o recurso e o ETag da versão atualmente armazenada em cache do recurso referenciada em um cabeçalho HTTP If-Match. O exemplo seguinte mostra um pedido PUT que atualiza uma encomenda:

    PUT https://adventure-works.com/orders/1 HTTP/1.1
    If-Match: "2282343857"
    Content-Type: application/x-www-form-urlencoded
    Content-Length: ...
    productID=3&quantity=5&orderValue=250
    
  • A operação PUT na API da web obtém o ETag atual para os dados solicitados (pedido 1 no exemplo acima) e compara-o ao valor no cabeçalho If-Match.

  • Se o ETag atual para os dados solicitados corresponder ao ETag fornecido pelo pedido, o recurso não foi alterado e a API web deve realizar a atualização, devolvendo uma mensagem com o código de estado HTTP 204 (Sem Conteúdo) se for bem-sucedida. A resposta pode incluir os cabeçalhos Cache-Control e ETag para a versão atualizada do recurso. A resposta deve sempre incluir o cabeçalho Location que faz referência ao URI do recurso recém-atualizado.

  • Se o ETag atual para os dados solicitados não corresponder ao ETag fornecido pelo pedido, isso significa que os dados foram alterados por outro utilizador desde que foram buscados, e a API web deverá retornar uma resposta HTTP com um corpo de mensagem vazio e um código de estado 412 (Falha na Pré-condição).

  • Se o recurso a ser atualizado já não existir, então a API web deverá retornar uma resposta HTTP com o código de estado 404 (Não Encontrado).

  • O cliente utiliza o código de estado e os cabeçalhos de resposta para manter a cache. Se os dados tiverem sido atualizados (código de estado 204), então o objeto pode permanecer em cache (desde que o cabeçalho Cache-Control não especifique no-store), mas o ETag deve ser atualizado. Se os dados forem alterados por outro utilizador (código de estado 412) ou não forem encontrados (código de estado 404), então o objeto em cache deve ser descartado.

O exemplo de código seguinte mostra uma implementação da operação PUT para o controlador de encomendas.

public class OrdersController : ApiController
{
    [HttpPut]
    [Route("api/orders/{id:int}")]
    public IHttpActionResult UpdateExistingOrder(int id, DTOOrder order)
    {
        try
        {
            var baseUri = Constants.GetUriFromConfig();
            var orderToUpdate = this.ordersRepository.GetOrder(id);
            if (orderToUpdate == null)
            {
                return NotFound();
            }

            var hashedOrder = orderToUpdate.GetHashCode();
            string hashedOrderEtag = $"\"{hashedOrder}\"";

            // Retrieve the If-Match header from the request (if it exists)
            var matchEtags = Request.Headers.IfMatch;

            // If there is an ETag in the If-Match header and
            // this ETag matches that of the order just retrieved,
            // or if there is no ETag, then update the Order
            if (((matchEtags.Count > 0 &&
                String.CompareOrdinal(matchEtags.First().Tag, hashedOrderEtag) == 0)) ||
                matchEtags.Count == 0)
            {
                // Modify the order
                orderToUpdate.OrderValue = order.OrderValue;
                orderToUpdate.ProductID = order.ProductID;
                orderToUpdate.Quantity = order.Quantity;

                // Save the order back to the data store
                // ...

                // Create the No Content response with Cache-Control, ETag, and Location headers
                var cacheControlHeader = new CacheControlHeaderValue();
                cacheControlHeader.Private = true;
                cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);

                hashedOrder = order.GetHashCode();
                hashedOrderEtag = $"\"{hashedOrder}\"";
                var eTag = new EntityTagHeaderValue(hashedOrderEtag);

                var location = new Uri($"{baseUri}/{Constants.ORDERS}/{id}");
                var response = new EmptyResultWithCaching()
                {
                    StatusCode = HttpStatusCode.NoContent,
                    CacheControlHeader = cacheControlHeader,
                    ETag = eTag,
                    Location = location
                };

                return response;
            }

            // Otherwise return a Precondition Failed response
            return StatusCode(HttpStatusCode.PreconditionFailed);
        }
        catch
        {
            return InternalServerError();
        }
    }
    ...
}

Sugestão

O uso do cabeçalho If-Match é totalmente opcional, e se for omitido, a API da web tentará sempre atualizar a ordem especificada, possivelmente substituindo cegamente uma atualização feita por outro utilizador. Para evitar problemas devido a atualizações perdidas, forneça sempre um cabeçalho If-Match.

Manipulação de pedidos e respostas grandes

Pode haver ocasiões em que uma aplicação cliente precise emitir pedidos que enviam ou recebem dados que podem ter vários megabytes (ou mais) de tamanho. Aguardar enquanto esta quantidade de dados é transmitida pode fazer com que a aplicação cliente se torne não responsiva. Considere os seguintes pontos quando precisar lidar com pedidos que incluam quantidades significativas de dados:

Otimize pedidos e respostas que envolvem grandes objetos

Alguns recursos podem ser objetos grandes ou incluir campos amplos, como imagens gráficas ou outros tipos de dados binários. A API web deve suportar streaming para permitir o carregamento e descarregamento otimizado desses recursos.

O protocolo HTTP fornece o mecanismo de codificação de transferência por blocos para transmitir grandes objetos de dados de volta a um cliente. Quando o cliente envia uma solicitação HTTP GET para um objeto grande, a API da Web pode enviar a resposta de volta em partes fragmentadas através de uma conexão HTTP. O comprimento dos dados na resposta pode não ser conhecido inicialmente (pode ser gerado), então o servidor que hospeda a API web deve enviar uma mensagem de resposta com cada fragmento que especifica o cabeçalho Transfer-Encoding: Chunked em vez de um cabeçalho Content-Length. A aplicação cliente pode receber cada fragmento por sua vez para construir a resposta completa. A transferência de dados é concluída quando o servidor envia de volta um bloco final de tamanho zero.

Um único pedido poderia, por ventura, resultar em um objeto massivo que consome consideráveis recursos. Se durante o processo de streaming a API web determinar que a quantidade de dados num pedido excedeu alguns limites aceitáveis, pode abortar a operação e devolver uma mensagem de resposta com o código de status 413 (Request Entity Too Large).

Pode minimizar o tamanho de objetos grandes transmitidos pela rede ao usar compressão HTTP. Esta abordagem ajuda a reduzir a quantidade de tráfego de rede e a latência associada da rede, mas ao custo de exigir processamento adicional no cliente e no servidor que hospeda a API web. Por exemplo, uma aplicação cliente que espera receber dados comprimidos pode incluir um cabeçalho de pedido Accept-Encoding: gzip (também podem ser especificados outros algoritmos de compressão de dados). Se o servidor suportar compressão, deverá responder com o conteúdo em formato gzip no corpo da mensagem e no cabeçalho da resposta Content-Encoding: gzip.

Pode combinar a compressão codificada com streaming; comprima os dados primeiro antes de os transmitir e especifique a codificação de conteúdo gzip e a codificação de transferência segmentada nos cabeçalhos das mensagens. Note também que alguns servidores web (como o Internet Information Server) podem ser configurados para comprimir automaticamente as respostas HTTP, independentemente de a API da web comprimir ou não os dados.

Implementar respostas parciais para clientes que não suportam operações assíncronas

Como alternativa ao streaming assíncrono, uma aplicação cliente pode solicitar explicitamente dados para objetos grandes em partes, conhecidas como respostas parciais. A aplicação cliente envia uma solicitação HTTP HEAD para obter informações sobre o objeto. Se a API da web suportar respostas parciais, deve responder à solicitação HEAD com uma mensagem de resposta que contenha um cabeçalho Accept-Ranges e um cabeçalho Content-Length que indique o tamanho total do objeto, mas o corpo da mensagem deve estar vazio. A aplicação cliente pode usar esta informação para construir uma série de pedidos GET que especificam um intervalo de bytes a receber. A API web deve devolver uma mensagem de resposta com o estado HTTP 206 (Conteúdo Parcial), um cabeçalho Content-Length que especifique a quantidade exata de dados incluídos no corpo da mensagem de resposta, e um cabeçalho Content-Range que indique qual parte (como bytes 4000 a 8000) do objeto representa esses dados.

As solicitações HTTP HEAD e as respostas parciais são descritas com mais detalhes no design da API.

Evite enviar mensagens de estado 100-Continue desnecessárias em aplicações cliente

Uma aplicação cliente que está prestes a enviar uma grande quantidade de dados para um servidor pode primeiro determinar se o servidor está realmente disposto a aceitar o pedido. Antes de enviar os dados, a aplicação cliente pode submeter um pedido HTTP com um cabeçalho Expect: 100-Continue, um cabeçalho Content-Length que indica o tamanho dos dados, mas com um corpo de mensagem vazio. Se o servidor estiver disposto a lidar com a solicitação, deve responder com uma mensagem que especifique o status HTTP 100 (Continuar). A aplicação cliente pode então prosseguir e enviar o pedido completo, incluindo os dados no corpo da mensagem.

Se hospedar um serviço usando o Serviços de Informação da Internet (IIS), o driver HTTP.sys detecta e lida automaticamente com os cabeçalhos Expect: 100-Continue antes de passar as solicitações para a sua aplicação web. Isto significa que é pouco provável que veja estes cabeçalhos no código da sua aplicação, e pode assumir que o IIS já filtrou quaisquer mensagens que considere inadequadas ou demasiado grandes.

Se desenvolver aplicações cliente utilizando o .NET Framework, todas as mensagens POST e PUT enviarão primeiro mensagens com cabeçalhos Expect: 100-Continue por padrão. Tal como no lado do servidor, o processo é tratado de forma transparente pelo .NET Framework. No entanto, este processo resulta em cada pedido POST e PUT causar duas idas e voltas ao servidor, mesmo para pedidos pequenos. Se a sua aplicação não estiver a enviar pedidos com grandes quantidades de dados, pode desativar esta funcionalidade utilizando a classe ServicePointManager para criar objetos ServicePoint na aplicação cliente. Um objeto ServicePoint gere as ligações que o cliente estabelece com um servidor com base no esquema e nos fragmentos de anfitrião de URIs que identificam recursos no servidor. Pode então definir a propriedade Expect100Continue do objeto ServicePoint para falso. Todas as solicitações POST e PUT subsequentes feitas pelo cliente através de um URI que corresponda aos fragmentos de esquema e host do objeto ServicePoint serão enviadas sem os cabeçalhos Expect: 100-Continue. O seguinte código mostra como configurar um objeto ServicePoint que configura todas as solicitações enviadas para URIs com um esquema de http e um host de www.contoso.com.

Uri uri = new Uri("https://www.contoso.com/");
ServicePoint sp = ServicePointManager.FindServicePoint(uri);
sp.Expect100Continue = false;

Você também pode definir a propriedade static Expect100Continue da ServicePointManager classe para especificar o valor padrão dessa propriedade para todos os objetos ServicePoint criados subsequentemente.

Suporte à paginação para solicitações que podem retornar grandes quantidades de objetos

Se uma coleção contiver um grande número de recursos, realizar um pedido GET para o URI correspondente poderá resultar em processamento significativo no servidor que hospeda a API web, afetando o desempenho, e gerar uma quantidade significativa de tráfego de rede, resultando em aumento da latência.

Para lidar com esses casos, a API web deve suportar strings de consulta que permitam à aplicação cliente refinar pedidos ou obter dados em blocos mais gerenciáveis e discretos (ou páginas). O código a seguir mostra o método GetAllOrders no controlador Orders. Este método recupera os detalhes das encomendas. Se este método não tivesse restrições, poderia, conceivivelmente, retornar uma grande quantidade de dados. Os parâmetros limit e offset destinam-se a reduzir o volume de dados para um subconjunto menor, neste caso, por padrão, apenas as primeiras 10 encomendas.

public class OrdersController : ApiController
{
    ...
    [Route("api/orders")]
    [HttpGet]
    public IEnumerable<Order> GetAllOrders(int limit=10, int offset=0)
    {
        // Find the number of orders specified by the limit parameter
        // starting with the order specified by the offset parameter
        var orders = ...
        return orders;
    }
    ...
}

Uma aplicação cliente pode emitir um pedido para recuperar 30 encomendas começando a partir do deslocamento 50 usando o URI https://www.adventure-works.com/api/orders?limit=30&offset=50.

Sugestão

Evite permitir que as aplicações cliente especifiquem cadeias de consulta que resultem num URI com mais de 2000 caracteres. Muitos clientes e servidores da web não conseguem lidar com URIs que são tão longas.

Manter a capacidade de resposta, escalabilidade e disponibilidade

A mesma API da web pode ser utilizada por muitas aplicações cliente a funcionar em qualquer parte do mundo. É importante garantir que a API web seja implementada para manter a capacidade de resposta sob uma carga pesada, ser escalável para suportar uma carga de trabalho altamente variável e garantir disponibilidade para clientes que realizam operações críticas para o negócio. Considere os seguintes pontos ao determinar como atender a estes requisitos:

Fornecer suporte assíncrono para solicitações de longa duração

Um pedido que possa levar muito tempo para processar deve ser realizado sem bloquear o cliente que submeteu o pedido. A API da web pode realizar algumas verificações iniciais para validar o pedido, iniciar uma tarefa separada para executar o trabalho e, em seguida, retornar uma mensagem de resposta com o código HTTP 202 (Aceite). A tarefa pode ser executada de forma assincrónica como parte do processamento da API web, ou pode ser descarregada para uma tarefa em segundo plano.

A API web também deverá fornecer um mecanismo para devolver os resultados do processamento à aplicação cliente. Pode conseguir isso fornecendo um mecanismo de sondagem para que as aplicações cliente consultem periodicamente se o processamento foi concluído e obtenham o resultado, ou habilitando a API Web para enviar uma notificação quando a operação estiver completa.

Você pode implementar um mecanismo de sondagem simples fornecendo um URI de sondagem que atua como um recurso virtual usando a seguinte abordagem:

  1. A aplicação cliente envia o pedido inicial para a API da web.
  2. A API Web armazena informações sobre a solicitação em uma tabela mantida no Armazenamento de Tabela do Azure ou no Cache do Microsoft Azure e gera uma chave exclusiva para essa entrada, possivelmente na forma de um GUID (identificador global exclusivo). Como alternativa, uma mensagem contendo informações sobre a solicitação e a chave exclusiva também pode ser enviada por meio do Barramento de Serviço do Azure .
  3. A API da Web inicia o processamento como uma tarefa separada ou com uma biblioteca como o Hangfire. A API da Web registra o estado da tarefa na tabela como Em execução.
    • Se você usar o Barramento de Serviço do Azure, o processamento de mensagens será feito separadamente da API, possivelmente usando o Azure Functions ou AKS.
  4. A API da Web retorna uma mensagem de resposta com o código de status HTTP 202 (Aceito) e um URI contendo a chave exclusiva gerada - algo como /polling/{guid}.
  5. Quando a tarefa for concluída, a API da Web armazenará os resultados na tabela e definirá o estado da tarefa como Concluído. Observe que, se a tarefa falhar, a API da Web também poderá armazenar informações sobre a falha e definir o status como Falha.
    • Considere aplicar técnicas de repetição para resolver falhas possivelmente transitórias.
  6. Enquanto a tarefa está em execução, o cliente pode continuar executando seu próprio processamento. Pode enviar periodicamente um pedido para o URI que recebeu anteriormente.
  7. A API da Web no URI consulta o estado da tarefa correspondente na tabela e retorna uma mensagem de resposta com o código de status HTTP 200 (OK) contendo esse estado (Em execução, Concluída ou Falha). Se a tarefa foi concluída ou falhou, a mensagem de resposta pode também incluir os resultados do processamento ou qualquer informação disponível sobre o motivo da falha.
    • Se o processo de longa duração tiver mais estados intermediários, é melhor usar uma biblioteca que suporte o padrão saga, como NServiceBus ou MassTransit.

Opções para implementar notificações incluem:

Certifique-se de que cada pedido seja sem estado

Cada pedido deve ser considerado indivisível. Não deve haver dependências entre um pedido feito por uma aplicação cliente e quaisquer pedidos subsequentes submetidos pelo mesmo cliente. Esta abordagem ajuda na escalabilidade; instâncias do serviço web podem ser implantadas em vários servidores. As solicitações dos clientes podem ser direcionadas para qualquer uma dessas instâncias e os resultados devem ser sempre os mesmos. Melhora também a disponibilidade por uma razão semelhante; se um servidor web falhar, os pedidos podem ser redirecionados para outra instância (usando o Azure Traffic Manager) enquanto o servidor é reiniciado, sem efeitos negativos nas aplicações dos clientes.

Registar clientes e implementar limitação para reduzir as chances de ataques DoS

Se um cliente específico fizer um grande número de pedidos dentro de um determinado período de tempo, pode monopolizar o serviço e afetar o desempenho de outros clientes. Para mitigar este problema, uma API web pode monitorizar chamadas de aplicações cliente, seja rastreando o endereço IP de todos os pedidos recebidos ou registando cada acesso autenticado. Pode utilizar esta informação para limitar o acesso aos recursos. Se um cliente exceder um limite definido, a API web pode devolver uma mensagem de resposta com o status 503 (Serviço Indisponível) e incluir um cabeçalho Retry-After que especifica quando o cliente pode enviar o próximo pedido sem que ele seja rejeitado. Essa estratégia pode ajudar a reduzir as chances de um ataque de negação de serviço (DoS) de um conjunto de clientes paralisar o sistema.

Gerencie as conexões HTTP persistentes com cuidado

O protocolo HTTP suporta ligações HTTP persistentes onde estão disponíveis. A especificação HTTP 1.0 adicionou o cabeçalho Connection:Keep-Alive que permite a uma aplicação cliente indicar ao servidor que pode usar a mesma conexão para enviar pedidos subsequentes em vez de abrir novas conexões. A conexão fecha automaticamente se o cliente não a reutilizar num período definido pelo host. Este comportamento é o padrão no HTTP 1.1 conforme utilizado pelos serviços Azure, portanto não é necessário incluir cabeçalhos Keep-Alive nas mensagens.

Manter uma conexão aberta pode ajudar a melhorar a capacidade de resposta, reduzindo a latência e a congestão da rede, mas pode ser prejudicial para a escalabilidade, pois mantém conexões desnecessárias abertas por mais tempo do que o necessário, limitando a capacidade de outros clientes simultâneos de se conectarem. Pode também afetar a vida útil da bateria se a aplicação cliente estiver a correr num dispositivo móvel; se a aplicação apenas fizer pedidos ocasionais ao servidor, manter uma ligação aberta pode fazer com que a bateria se esgote mais rapidamente. Para garantir que uma conexão não se torne persistente com o HTTP 1.1, o cliente pode incluir um cabeçalho Connection:Close nas mensagens para substituir o comportamento padrão. Da mesma forma, se um servidor estiver a lidar com um número muito grande de clientes, pode incluir um cabeçalho Connection:Close nas mensagens de resposta, o que deve fechar a conexão e poupar recursos do servidor.

Nota

As conexões HTTP persistentes são um recurso opcional que você pode usar para reduzir a sobrecarga da rede, evitando o estabelecimento repetido de um canal de comunicação. No entanto, nem a API da Web nem o aplicativo cliente devem depender da disponibilidade de uma conexão HTTP persistente. Não use conexões HTTP persistentes para implementar sistemas de notificação no estilo Comet. Use soquetes em vez disso, ou WebSockets, se disponíveis, na camada do Protocolo de Controle de Transmissão. Keep-Alive cabeçalhos têm utilidade limitada quando um aplicativo cliente se comunica com um servidor por meio de um proxy. Somente a conexão entre o cliente e o proxy permanece persistente.

Publicação e gestão de uma API web

Para disponibilizar uma API web para aplicações cliente, a API web deve ser implementada num ambiente de hospedagem. Este ambiente é normalmente um servidor web, embora possa ser algum outro tipo de processo de hospedagem. Deves considerar os seguintes pontos ao publicar uma API web:

  • Todas as solicitações devem ser autenticadas e autorizadas, e o nível adequado de controle de acesso deve ser aplicado.
  • Uma API web comercial pode estar sujeita a várias garantias de qualidade relativas aos tempos de resposta. É importante garantir que o ambiente de alojamento seja escalável, se a carga puder variar significativamente ao longo do tempo.
  • Pode ser necessário limitar os pedidos para fins de monetização.
  • Poderá ser necessário regular o fluxo de tráfego para a API web e implementar um sistema de limitação para clientes específicos que tenham excedido as suas quotas.
  • Os requisitos regulamentares podem exigir o registo e a auditoria de todos os pedidos e respostas.
  • Para garantir a disponibilidade, pode ser necessário monitorizar a saúde do servidor que hospeda a API web e reiniciá-lo, se necessário.

É útil poder separar essas questões das questões técnicas relativas à implementação da API web. Por esse motivo, considere criar uma fachada, executada como um processo separado e que roteie solicitações para a API da Web. A fachada pode fornecer as operações de gerenciamento e encaminhar solicitações validadas para a API da Web. Usar uma fachada pode também trazer muitas vantagens funcionais, incluindo:

  • Agindo como um ponto de integração para múltiplas APIs web.
  • Transformação de mensagens e tradução de protocolos de comunicação para clientes construídos utilizando tecnologias diversificadas.
  • Cacheamento de pedidos e respostas para reduzir a carga no servidor que hospeda a API da web.

Testar uma web API

Uma API web deve ser testada tão minuciosamente quanto qualquer outra peça de software. Deverá considerar criar testes unitários para validar a funcionalidade.

A natureza de uma API web traz os seus próprios requisitos adicionais para verificar se opera corretamente. Deve prestar particular atenção aos seguintes aspetos:

  • Teste todas as rotas para verificar se invocam as operações corretas. Esteja especialmente atento ao código de status HTTP 405 (Método Não Permitido) sendo retornado inesperadamente, pois isso pode indicar uma incompatibilidade entre uma rota e os métodos HTTP (GET, POST, PUT, DELETE) que podem ser despachados para essa rota.

    Envie solicitações HTTP para rotas que não as suportam, como enviar uma solicitação POST para um recurso específico (as solicitações POST devem ser enviadas apenas para coleções de recursos). Nestes casos, a única resposta válida deverá ser o código de status 405 (Não Permitido).

  • Verifique se todas as rotas estão devidamente protegidas e estão sujeitas às verificações adequadas de autenticação e autorização.

    Nota

    Certos aspetos de segurança, como a autenticação de utilizadores, são mais provavelmente da responsabilidade do ambiente anfitrião em vez da API web, mas ainda é necessário incluir testes de segurança como parte do processo de implementação.

  • Teste o tratamento de exceções realizado por cada operação e verifique se uma resposta HTTP adequada e significativa é passada de volta para a aplicação cliente.

  • Verifique se as mensagens de solicitação e resposta estão bem formadas. Para exemplo, se um pedido HTTP POST contiver os dados para um novo recurso no formato x-www-form-urlencoded, confirme que a operação correspondente analisa corretamente os dados, cria os recursos e retorna uma resposta contendo os detalhes do novo recurso, incluindo o cabeçalho Location correto.

  • Verifique todos os links e URIs nas mensagens de resposta. Por exemplo, uma mensagem HTTP POST deve retornar o URI do recurso recém-criado. Todos os links HATEOAS devem ser válidos.

  • Certifique-se de que cada operação retorna os códigos de status corretos para diferentes combinações de entrada. Por exemplo:

    • Se uma consulta for bem-sucedida, deve retornar o código de status 200 (OK)
    • Se um recurso não for encontrado, a operação deve retornar o código de estado HTTP 404 (Não Encontrado).
    • Se o cliente enviar um pedido que exclua com sucesso um recurso, o código de estado deve ser 204 (Sem Conteúdo).
    • Se o cliente enviar uma solicitação que crie um novo recurso, o código de status deve ser 201 (Criado).

Fique atento a códigos de status de resposta inesperados na faixa 5xx. Estas mensagens são geralmente reportadas pelo servidor host para indicar que não foi possível atender a uma solicitação válida.

  • Teste as diferentes combinações de cabeçalho de solicitação que um aplicativo cliente pode especificar e certifique-se de que a API da Web retorne as informações esperadas nas mensagens de resposta.

  • Testar sequências de consulta. Se uma operação puder aceitar parâmetros opcionais (como pedidos de paginação), teste as diferentes combinações e a ordem dos parâmetros.

  • Verifique se as operações assíncronas são concluídas com sucesso. Se a API web suporta streaming para pedidos que retornam grandes objetos binários (como vídeo ou áudio), certifique-se de que as solicitações dos clientes não fiquem bloqueadas enquanto os dados são transmitidos. Se a API web implementar a sondagem para operações de modificação de dados de longa duração, verifique se as operações reportam corretamente o seu estado à medida que avançam.

Também deve criar e executar testes de desempenho para verificar se a API da web funciona satisfatoriamente sob pressão. Pode criar um projeto de teste de desempenho e carga web utilizando o Visual Studio Ultimate.

Utilizando a Gestão de API do Azure

No Azure, considere usar o Gerenciamento de API do Azure para publicar e gerenciar uma API Web. Utilizando esta funcionalidade, pode gerar um serviço que atua como uma fachada para um ou mais APIs web. O serviço é, em si, um serviço web escalável que pode ser criado e configurado utilizando o portal do Azure. Pode utilizar este serviço para publicar e gerir uma API web da seguinte forma:

  1. Implemente a API web num site, serviço de nuvem Azure ou máquina virtual Azure.

  2. Conecte o serviço de gerenciamento de API à API da web. As solicitações enviadas para o URL da API de gestão são mapeadas para URIs na API web. O mesmo serviço de gestão de API pode encaminhar pedidos para mais do que uma API web. Isso permite que você agregue várias APIs da web em um único serviço de gestão. Da mesma forma, a mesma API web pode ser referenciada por mais do que um serviço de gestão de API se precisar de restringir ou dividir a funcionalidade disponível para diferentes aplicações.

    Nota

    As URIs, nos links HATEOAS que são gerados como parte da resposta para pedidos HTTP GET, devem referenciar o URL do serviço de gestão de APIs e não o servidor web que está a hospedar a API web.

  3. Para cada API da web, especifique as operações HTTP que a API da web expõe, juntamente com quaisquer parâmetros opcionais que uma operação possa receber como entrada. Também pode configurar se o serviço de gestão de API deve armazenar em cache a resposta recebida da API web para otimizar pedidos repetidos para os mesmos dados. Registe os detalhes das respostas HTTP que cada operação pode gerar. A informação é usada para gerar documentação para os desenvolvedores, por isso é importante que seja precisa e completa.

    Pode definir operações manualmente usando os assistentes fornecidos pelo portal do Azure, ou pode importá-las de um ficheiro que contenha as definições em formato WADL ou Swagger.

  4. Configure as definições de segurança para as comunicações entre o serviço de gestão de API e o servidor web que aloja a API web. O serviço de gestão de API atualmente suporta autenticação básica e autenticação mútua usando certificados, e autorização de utilizador OAuth 2.0 (Open Authorization).

  5. Crie um produto. Um produto é a unidade de publicação; adiciona as APIs da web que anteriormente ligou ao serviço de gestão ao produto. Quando o produto é publicado, as APIs web ficam disponíveis para os desenvolvedores.

    Nota

    Antes de publicar um produto, pode também definir grupos de utilizadores que podem aceder ao produto e adicionar utilizadores a estes grupos. Isto dá-lhe controlo sobre os programadores e as aplicações que podem utilizar a API da web. Se uma API da Web estiver sujeita à aprovação, antes de poder acessá-la, um desenvolvedor deverá enviar uma solicitação ao administrador do produto. O administrador pode conceder ou negar acesso ao programador. Desenvolvedores existentes também podem ser bloqueados se as circunstâncias mudarem.

  6. Configurar políticas para cada API web. As políticas governam aspetos como se devem ser permitidas chamadas entre domínios, como autenticar clientes, se se deve converter entre formatos de dados XML e JSON de forma transparente, se se devem restringir chamadas de um determinado intervalo de IPs, quotas de utilização, e se se deve limitar a taxa de chamadas. As políticas podem ser aplicadas globalmente em todo o produto, para uma única API web em um produto ou para operações individuais em uma API web.

Para obter mais informações, consulte a documentação do Gerenciamento de API.

Sugestão

O Azure fornece o Azure Traffic Manager, que permite implementar failover e balanceamento de carga e reduzir a latência em várias instâncias de um site hospedado em diferentes locais geográficos. Pode utilizar o Azure Traffic Manager em conjunto com o API Management Service; o API Management Service pode encaminhar pedidos para instâncias de um site através do Azure Traffic Manager. Para obter mais informações, consulte Métodos de roteamento do Gerenciador de Tráfego.

Nesta estrutura, se utilizar nomes DNS personalizados para os seus sites, deve configurar o registo CNAME apropriado para cada site que aponte para o nome DNS do site do Azure Traffic Manager.

Apoiar os desenvolvedores do lado do cliente

Os desenvolvedores que criam aplicações de clientes geralmente necessitam de informações sobre como aceder à API da web, bem como de documentação sobre os parâmetros, tipos de dados, tipos de retorno e códigos de retorno que descrevem os diferentes pedidos e respostas entre o serviço web e a aplicação de cliente.

Documentar as operações REST de uma API web

O Serviço de Gestão de API do Azure inclui um portal para programadores que descreve as operações REST expostas por uma API web. Quando um produto é publicado, ele aparece neste portal. Os desenvolvedores podem usar este portal para se registar; o administrador pode então aprovar ou negar o pedido. Se o programador for aprovado, é-lhe atribuído uma chave de subscrição que é usada para autenticar chamadas das aplicações cliente que desenvolvem. Esta chave deve ser fornecida em cada chamada à API web, caso contrário, será rejeitada.

Este portal também fornece:

  • Documentação do produto, listando as operações que ele expõe, os parâmetros necessários e as diferentes respostas que podem ser retornadas. Note que esta informação é gerada a partir dos detalhes fornecidos no passo 3 na lista na secção de Publicação de uma API web usando o serviço de Gestão de API da Microsoft Azure.
  • Fragmentos de código que mostram como invocar operações em várias linguagens, incluindo JavaScript, C#, Java, Ruby, Python e PHP.
  • Uma consola de programadores que permite a um programador enviar uma solicitação HTTP para testar cada operação no produto e visualizar os resultados.
  • Uma página onde o desenvolvedor pode reportar quaisquer questões ou problemas encontrados.

O portal do Azure permite-lhe personalizar o portal do programador para alterar o estilo e a disposição, de forma a corresponder à imagem da sua organização.

Implementar um SDK de cliente

Criar uma aplicação cliente que invoque pedidos REST para aceder a uma API da web requer escrever uma quantidade significativa de código para construir cada pedido e formatá-lo de forma apropriada, enviar o pedido para o servidor que hospeda o serviço web, e interpretar a resposta para determinar se o pedido foi bem-sucedido ou falhou e extrair quaisquer dados devolvidos. Para isolar a aplicação cliente dessas preocupações, pode fornecer um SDK que encapsule a interface REST e abstraia esses detalhes em baixo nível dentro de um conjunto de métodos mais funcionais. Uma aplicação cliente usa estes métodos, que convertem chamadas em pedidos REST de forma transparente e depois convertem as respostas de volta em valores de retorno dos métodos. Esta é uma técnica comum implementada por muitos serviços, incluindo o SDK do Azure.

Criar um SDK no lado do cliente é uma tarefa considerável, pois precisa ser implementado consistentemente e testado cuidadosamente. No entanto, grande parte deste processo pode ser mecanizada, e muitos fornecedores oferecem ferramentas que podem automatizar muitas dessas tarefas.

Monitorização de uma API web

Dependendo de como tiver publicado e implementado a sua API web, pode monitorizar a API web diretamente ou pode reunir informações de uso e saúde analisando o tráfego que passa através do serviço de gestão de API.

Monitorização de uma API web diretamente

Se implementou a sua API web utilizando o modelo ASP.NET Web API (quer como um projeto Web API ou como uma função Web num serviço de nuvem Azure) e o Visual Studio 2013, pode recolher dados de disponibilidade, desempenho e utilização utilizando o ASP.NET Application Insights. Application Insights é um pacote que acompanha e regista informações de forma transparente sobre pedidos e respostas quando a API web é implementada na nuvem; uma vez que o pacote está instalado e configurado, não precisa alterar nenhum código na sua API web para a utilizar. Quando implementa a API web num site web do Azure, todo o tráfego é analisado e as seguintes estatísticas são recolhidas:

  • Tempo de resposta do servidor.
  • Número de pedidos ao servidor e os detalhes de cada pedido.
  • As solicitações mais lentas em termos de tempo médio de resposta.
  • Os detalhes de quaisquer pedidos falhados.
  • O número de sessões iniciadas por diferentes navegadores e agentes de utilizador.
  • As páginas mais frequentemente visualizadas (principalmente úteis para aplicações web em vez de APIs web).
  • Os diferentes papéis de utilizador a aceder à API da web.

Pode visualizar estes dados em tempo real no portal Azure. Também pode criar testes web que monitorizam o estado da API web. Um teste web envia um pedido periódico para um URI especificado na API web e captura a resposta. Pode especificar a definição de uma resposta bem-sucedida (como o código de estado HTTP 200), e, se o pedido não devolver esta resposta, pode providenciar para que um alerta seja enviado a um administrador. Se necessário, o administrador pode reiniciar o servidor que hospeda a API web caso tenha falhado.

Para obter mais informações, consulte Application Insights - Introdução ao ASP.NET.

Monitorização de uma API web através do Serviço de Gestão de API

Se publicou a sua API da web utilizando o serviço de Gestão de APIs, a página de Gestão de APIs no portal Azure contém um painel que lhe permite visualizar o desempenho geral do serviço. A página de análise permite-lhe investigar em detalhe como o produto está a ser utilizado. Esta página contém as seguintes abas:

  • Utilização. Esta aba fornece informações sobre o número de chamadas da API realizadas e a largura de banda utilizada para gerir essas chamadas ao longo do tempo. Pode filtrar os detalhes de utilização por produto, API e operação.
  • Saúde. Este separador permite-lhe ver o resultado dos pedidos de API (os códigos de estado HTTP retornados), a eficácia da política de cache, o tempo de resposta da API e o tempo de resposta do serviço. Mais uma vez, pode filtrar os dados de saúde por produto, API e operação.
  • Atividade. Esta aba fornece um resumo em texto dos números de chamadas bem-sucedidas, chamadas falhadas, chamadas bloqueadas, tempo médio de resposta e tempos de resposta para cada produto, API web e operação. Esta página também lista o número de chamadas feitas por cada desenvolvedor.
  • Em resumo. Esta aba exibe um resumo dos dados de desempenho, incluindo os programadores responsáveis por fazer o maior número de chamadas de API, e os produtos, APIs da web e operações que receberam essas chamadas.

Pode usar esta informação para determinar se uma API web ou operação específica está a causar um estrangulamento e, se necessário, dimensionar o ambiente do anfitrião e adicionar mais servidores. Também pode verificar se uma ou mais aplicações estão a utilizar um volume desproporcional de recursos e aplicar as políticas apropriadas para definir quotas e limitar as taxas de chamadas.

Nota

Pode alterar os detalhes de um produto publicado, e as alterações são aplicadas imediatamente. Por exemplo, pode adicionar ou remover uma operação de uma API da web sem precisar republicar o produto que contém a API da web.

Próximos passos