Implementação de API Web

Uma API da Web RESTful cuidadosamente projetada define os recursos, relacionamentos e esquemas de navegação acessíveis aos aplicativos cliente. Quando implementa e executa uma API Web, deve considerar os requisitos físicos do ambiente a alojar a API Web e a forma como esta é construída em vez da estrutura lógica dos dados. Este guia se concentra nas práticas recomendadas para implementar uma API da Web e publicá-la para disponibilizá-la para aplicativos cliente. Para obter informações detalhadas sobre o design da API da Web, consulte Design da API da Web.

Processamento de pedidos

Considere os seguintes pontos ao implementar o código para processar pedidos.

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

O código que implementa estes pedidos não deve impor quaisquer efeitos secundários. O mesmo pedido repetido sobre o mesmo recurso deve produzir o 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 pode devolver o código de estado 204 (Sem Conteúdo), enquanto um pedido DELETE subsequente pode 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 secundários não relacionados

Se uma solicitação POST se destinar a criar um novo recurso, os efeitos da solicitação devem ser limitados ao novo recurso (e possivelmente a quaisquer recursos diretamente relacionados, se houver algum tipo de ligação envolvida). Por exemplo, em um sistema de comércio eletrônico, uma solicitação POST que cria um novo pedido para um cliente também pode alterar os níveis de estoque e gerar informações de faturamento, mas não deve modificar informações não diretamente relacionadas ao pedido ou ter quaisquer outros efeitos colaterais sobre o estado geral do sistema.

Evitar implementar operações POST, PUT e DELETE conversadoras

Ofereça suporte a solicitações POST, PUT e DELETE em coleções de recursos. Um pedido POST pode conter os detalhes para vários novos recursos e adicioná-los à mesma coleção, um pedido PUT pode substituir todo o conjunto de recursos numa coleção e um pedido DELETE pode remover uma coleção completa.

O suporte de OData incluído na API Web 2 da ASP.NET fornece a capacidade de pedidos de lote. Uma aplicação cliente pode empacotar vários pedidos da API Web e enviá-los para o servidor num único pedido HTTP e receber uma única resposta HTTP que contém as respostas para cada pedido. Para obter mais informações, consulte Introducing batch support in Web API and Web API OData.

Siga a especificação HTTP ao enviar uma resposta

Uma API Web tem de devolver mensagens que contêm o código de estado HTTP correto para permitir ao cliente determinar como processar o resultado, os cabeçalhos HTTP adequados para que o cliente compreenda a natureza do resultado e um corpo corretamente formatado para permitir ao cliente analisar o resultado.

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

Suporte a negociação do conteúdo

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

Se o cliente não especificar um cabeçalho Accept, utilize um formato sensível predefinido para o corpo da resposta. Por exemplo, a estrutura da API Web da ASP.NET está predefinida como JSON para os dados de texto.

A abordagem HATEOAS permite que um cliente navegue e detete recursos a partir de um ponto de partida inicial. Tal é conseguido através de ligações que contém URIs. Quando um cliente emite um pedido HTTP GET para obter um recurso, a resposta deve conter os URIs que permitem que uma aplicação cliente localize rapidamente quaisquer recursos diretamente relacionados. Por exemplo, numa API Web que suporta uma solução de comércio eletrónico, um cliente pode ter realizado muitas encomendas. Quando uma aplicação cliente obtém os detalhes de um cliente, a resposta deve incluir ligações que permitem que a aplicação cliente envie pedidos HTTP GET que podem obter estas encomendas. Além disso, as ligações de estilo HATEOAS devem descrever as outras operações (POST, PUT, DELETE, entre outras) 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 existe nenhuma norma que regule a implementação de HATEOAS, mas o exemplo a seguir ilustra uma abordagem possível. Neste exemplo, uma solicitação HTTP GET que localiza os detalhes de um cliente retorna uma resposta que inclui links HATEOAS que fazem referência aos pedidos 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 fragmento de código seguinte. As ligações HATEOAS são guardadas na propriedade da 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 obtém os dados dos clientes do armazenamento e 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á sendo retornado e o objeto descrito pelo link. Nesse caso self , indica que o link é uma referência ao próprio objeto (semelhante a um this ponteiro em muitas linguagens orientadas a objetos) e orders é o nome de uma coleção que contém as informações de ordem relacionadas.
  • A hiperligação (Href) do objeto que está a ser descrito pela ligação sob a 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 devolvidos na resposta, depende do tipo de pedido.

As ligações HATEOAS mostradas na resposta HTTP de exemplo indicam que uma aplicação cliente pode realizar as seguintes operações:

  • Um pedido HTTP GET enviado ao URI https://adventure-works.com/customers/2 para obter os detalhes do cliente (novamente). Os dados podem ser devolvidos como XML ou JSON.
  • Um pedido HTTP PUT enviado ao URI https://adventure-works.com/customers/2 para modificar os detalhes do cliente. Os novos dados devem ser fornecidos na mensagem do pedido no formato x-www-form-urlencoded.
  • Um pedido HTTP DELETE enviado ao URI https://adventure-works.com/customers/2 para eliminar o cliente. O pedido não espera receber quaisquer informações adicionais nem dados devolvidos no corpo da mensagem de resposta.
  • Um pedido HTTP GET enviado ao URI https://adventure-works.com/customers/2/orders para localizar todas as encomendas do cliente. Os dados podem ser devolvidos como XML ou JSON.
  • Uma solicitação HTTP POST para o URI https://adventure-works.com/customers/2/orders para criar um novo pedido para este cliente. Os dados devem ser fornecidos na mensagem do pedido no formato x-www-form-urlencoded.

Processamento de exceções

Considere os seguintes pontos se uma operação emitir uma exceção não identificada.

Recolher exceções e devolver uma resposta significativa aos clientes

O código que implementa uma operação HTTP deve fornecer um processamento de exceção abrangente ao invés de permitir que exceções não identificadas se propaguem para a estrutura. Se uma exceção tornar impossível concluir a operação com sucesso, a exceção poderá ser transmitida de volta na mensagem de resposta, mas deve incluir uma descrição significativa do erro que provocou a exceção. A exceção também deve incluir o código de estado HTTP adequado ao invés de devolver simplesmente um código de estado 500 para cada situação. Por exemplo, se um pedido de utilizador der origem a uma atualização da base de dados que viola uma restrição (por exemplo, tentar eliminar um cliente com encomendas pendentes), deverá devolver o código de estado 409 (Conflito) e um corpo de mensagem que indica o motivo do conflito. Se alguma outra condição tornar o pedido irrealizável, poderá devolver o código de estado 400 (Pedido Incorreto). 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 interceta condições diferentes e devolve uma resposta adequada.

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

Gorjeta

Não inclua informações que possam ser úteis para um invasor que tente penetrar em sua API.

Muitos servidores Web intercetam pessoalmente condições de erro antes de atingirem a API Web. Por exemplo, se configurar uma autenticação para um site e o utilizador não conseguir fornecer as informações de autenticação corretas, o servidor Web deverá responder com o código de estado 401 (Não Autorizado). Assim que o cliente tenha sido autenticado, o código pode executar as suas próprias verificações para confirmar que o cliente é capaz de aceder ao recurso pedido. Se esta autorização falhar, o código de estado 403 (Proibido) deverá ser devolvido.

Processar exceções de forma consistente e registar informações sobre erros

Para resolver exceções de uma forma consistente, pondere implementar uma estratégia de processamento de erros em toda a API Web. Também deve incorporar o registo de erros que recolhem todos os detalhes de cada exceção. Este registo de erros pode conter informações detalhadas, desde que não esteja acessível aos clientes através da Web.

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

O protocolo HTTP distingue entre os erros que ocorrem devido a uma aplicação cliente (os códigos de estado HTTP 4xx) e os erros provocados por um erro no servidor (os códigos de estado HTTP 5xx). Confirme que respeitou esta convenção em todas as mensagens de resposta de erro.

Otimizar o acesso a dados do lado do cliente

Num ambiente distribuído, tal como o servidor Web e as aplicações cliente envolventes, uma das principais fontes de preocupação é a rede, que pode agir como um estrangulamento considerável, especialmente se uma aplicação cliente enviar pedidos ou receber dados frequentemente. Portanto, deve procurar minimizar a quantidade de tráfego que passa pela rede. Considere os seguintes pontos ao implementar o código para obter e manter dados dos pedidos:

Suporte de colocação em cache do lado do cliente

O protocolo HTTP 1.1 suporta a colocação em cache de clientes e servidores intermédios através do qual um pedido é encaminhado pela utilização do cabeçalho Cache-Control. Quando uma aplicação cliente envia um pedido HTTP GET para a API Web, a resposta pode incluir um cabeçalho Cache-Control que indica se os dados no corpo da resposta podem ser colocados em cache de forma segura pelo cliente ou por um servidor intermédio, através do qual o pedido foi encaminhado, e o período de tempo até expirar e ser considerado desatualizado.

O exemplo seguinte mostra um pedido HTTP GET e a resposta correspondente 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 devolvidos devem expirar após 600 segundos, sendo apenas adequado para um único cliente, e não devem ser armazenados numa cache partilhada utilizada por outros clientes de cache (é private). O cabeçalho Cache-Control pode especificar public em vez de private, caso os dados possam ser armazenados numa cache partilhada, ou pode especificar no-store, caso os dados não sejam colocadas em cache pelo cliente. O exemplo de código seguinte 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;
    }
    ...
}

Esse código usa uma classe personalizada IHttpActionResult chamada OkResultWithCaching. Esta classe permite que o controlador defina os conteúdos do cabeçalho da 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. Curiosamente, esta diretiva não significa “não colocar em cache”, mas antes “revalidar as informações em cache com o servidor antes de as devolver”. Os dados podem ainda ser colocados em cache, mas são verificados sempre que são utilizados para garantir que ainda estão atuais.

A gestão de cache é da responsabilidade da aplicação cliente ou do servidor intermédio, porém, se implementada corretamente, pode poupar largura de banda e melhorar o desempenho ao remover a necessidade de obter dados que já tenham sido obtidos.

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

Nota

A maioria dos browsers modernos suporta a colocação em cache do lado do cliente ao adicionar os cabeçalhos cache-control adequados e ao examinar os cabeçalhos dos resultados, conforme descrito. No entanto, alguns browsers mais antigos não colocarão em cache os valores devolvidos por um URL que inclua uma cadeia de consulta. Normalmente, não é um problema para as aplicações cliente personalizadas que implementam a sua própria estratégia de gestão de cache com base no protocolo abordado aqui.

Alguns proxies mais antigos apresentam o mesmo comportamento e podem não colocar em cache pedidos com base nos URLs com cadeias de consulta. Tal pode ser um problema para aplicações cliente personalizadas que se ligam a um servidor Web através de um proxy deste tipo.

Fornecer ETags para otimizar o processamento da consulta

Quando uma aplicação cliente obtém um objeto, a mensagem de resposta também pode incluir uma ETag (Etiqueta de Entidade). Uma ETag é uma cadeia de caracteres opaca que indica a versão de um recurso; cada vez que um recurso é alterado, o ETag também é modificado. Esta ETag deve ser colocada em cache como parte dos dados pela aplicação cliente. O exemplo de código seguinte mostra como adicionar uma ETag como parte da resposta de 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 (se necessário, pode substituir este método e gerar a suas próprias hashes com 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 tem o seguinte aspeto:

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}

Gorjeta

Por motivos de segurança, não permita que dados confidenciais ou dados retornados por uma conexão autenticada (HTTPS) sejam armazenados em cache.

Uma aplicação cliente pode emitir um pedido GET subsequente para obter o mesmo recurso em qualquer altura e, se o recurso tiver sido alterado (tem uma ETag diferente), a versão em cache deverá ser eliminada e a nova versão adicionada à cache. Se um recurso for grande e precisar de uma quantidade significativa de largura de banda para transmitir de volta para o cliente, os pedidos repetidos para obter os mesmos dados poderão tornar-se ineficientes. Para combater isto, o protocolo HTTP define o seguinte processo para otimizar pedidos GET que deve suportar numa API Web:

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

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

  • Se a ETag atual dos dados solicitados corresponder à ETag fornecida pelo pedido, o recurso não foi alterado e a API Web deverá devolver uma resposta HTTP com um corpo de mensagem vazio e um código de estado 304 (Não Modificado).

  • Se a ETag atual dos dados solicitados não corresponder à ETag fornecida pelo pedido, os dados foram alterados e a API Web deverá devolver uma resposta HTTP com os novos dados no corpo da mensagem vazio e um código de estado 200 (OK).

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

  • O cliente utiliza o código de estado para manter a cache. Se os dados não tiverem sido alterados (código de estado 304), o objeto poderá permanecer em cache e a aplicação cliente deverá continuar a utilizar esta versão do objeto. Se os dados tiverem sido alterados (código de estado 200), o objeto em cache deverá ser eliminado e um novo inserido. Se os dados já não estiverem disponíveis (código de estado 404), o objeto deverá ser removido da cache.

Nota

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

O código a seguir mostra o FindOrderByID método estendido para suportar o cabeçalho If-None-Match. Tenha em atenção que, se o cabeçalho If-None-Match for omitido, a encomenda especificada será sempre obtida:

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 denominada EmptyResultWithCaching. Esta classe simplesmente age como um wrapper em torno de um objeto HttpResponseMessage que não contem 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;
    }
}

Gorjeta

Neste exemplo, a ETag dos dados é gerada por hashes dos dados obtidos a partir da origem de dados subjacente. Se a ETag puder ser calculada de outro modo, o processo poderá ser ainda mais otimizado e os dados apenas precisarão de ser obtidos a partir da origem de dados se tiverem sido alterados. Esta abordagem é particularmente útil quando os dados são grandes ou o acesso à origem de dados pode resultar numa latência significativa (por exemplo, se a origem de dados é uma base de dados remota).

Utilizar ETags para Suportar a Simultaneidade Otimista

Para ativar as atualizações de dados colocados em cache anteriormente, o protocolo HTTP suporta uma estratégia de simultaneidade otimista. Se, depois de buscar e armazenar em cache um recurso, o aplicativo cliente enviar posteriormente uma solicitação PUT ou DELETE para alterar ou remover o recurso, ele deverá incluir um cabeçalho If-Match que faça referência à ETag. A API Web pode, em seguida, utilizar estas informações para determinar se o recurso já foi alterado por outro utilizador desde que foi obtido e enviar uma resposta adequada de volta para a aplicação cliente da seguinte forma:

  • O cliente constrói um pedido PUT que contém os novos detalhes do recurso e da ETag para a versão atualmente em cache do recurso referenciado num cabeçalho If-Match HTTP. 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 Web obtém a ETag atual dos dados solicitados (encomenda 1 no exemplo acima) e compara-a com o valor no cabeçalho If-Match.

  • Se a ETag atual dos dados solicitados corresponder à ETag fornecida pelo pedido, o recurso não foi alterado e a API Web deverá realizar a atualização e devolver 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 da versão atualizada do recurso. A resposta deve incluir sempre o cabeçalho Location que referencia o URI do recurso recém-atualizado.

  • Se a ETag atual dos dados solicitados não corresponder à ETag fornecida pelo pedido, os dados foram alterados por outro utilizador desde que foram obtidos e a API Web deverá devolver uma resposta HTTP com um corpo de mensagem vazio e um código de estado 412 (Falha na Pré-condição).

  • Se os recursos a serem atualizados já não existirem, a API Web deverá devolver uma resposta HTTP com o código de estado 404 (Não Encontrado).

  • O cliente utiliza os cabeçalhos de código de estado e resposta para manter a cache. Se os dados tiverem sido atualizados (código de estado 204), o objeto poderá permanecer em cache (desde que o cabeçalho Cache-Control não especifique no-store), mas a ETag deverá ser atualizada. Se os dados foram alterados por outro usuário (código de status 412) ou não foram encontrados (código de status 404), o objeto armazenado 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();
        }
    }
    ...
}

Gorjeta

A utilização do cabeçalho If-Match é inteiramente opcional e, se for omitido, a API Web tentará sempre atualizar a encomenda especificada, possivelmente substituindo às cegas uma atualização realizada por outro utilizador. Para evitar problemas devido a atualizações perdidas, forneça sempre um cabeçalho If-Match.

Processamento de pedidos e respostas grandes

Pode haver ocasiões em que um aplicativo cliente precisa emitir solicitações que enviam ou recebem dados que podem ter vários megabytes (ou maiores) de tamanho. Se ficar a aguardar enquanto esta quantidade de dados é transmitida, tal pode fazer com que a aplicação cliente deixe de responder. Quando precisar de processar pedidos que incluam quantidades significativas de dados, considere os seguintes pontos:

Otimizar pedidos e respostas que envolvem grandes objetos

Alguns recursos podem ser objetos grandes ou incluir campos grandes, como imagens gráficas ou outros tipos de dados binários. Uma API Web deve suportar a transmissão em fluxo para permitir um carregamento e uma transferência otimizados destes recursos.

O protocolo HTTP fornece o mecanismo de codificação de transferência segmentada para transmitir em fluxo objetos de dados de grande dimensão de volta para um cliente. Quando o cliente envia um pedido HTTP GET para um objeto grande, a API Web pode enviar a resposta em segmentos por etapas através de uma ligação HTTP. O comprimento dos dados na resposta pode não ser conhecido inicialmente (pode ser gerado), portanto, o servidor que hospeda a API da Web deve enviar uma mensagem de resposta com cada parte que especifica o Transfer-Encoding: Chunked cabeçalho em vez de um cabeçalho Content-Length. A aplicação cliente pode receber cada um dos segmentos à vez para criar a resposta completa. A transferência de dados fica concluída quando o servidor envia um segmento final com tamanho igual a zero.

É concebível que um único pedido possa resultar num objeto enorme que consome recursos consideráveis. Se, durante o processo de streaming, a API da Web determinar que a quantidade de dados em uma solicitação excedeu alguns limites aceitáveis, ela poderá abortar a operação e retornar uma mensagem de resposta com o código de status 413 (Entidade de solicitação muito grande).

Pode minimizar o tamanho dos objetos grandes transmitidos através da rede com a compressão HTTP. Esta abordagem ajuda a reduzir a quantidade de tráfego de rede e a latência de rede associada, mas à custa da necessidade de um processamento adicional no cliente e no servidor a alojar a API Web. Por exemplo, um aplicativo cliente que espera receber dados compactados pode incluir um Accept-Encoding: gzip cabeçalho de solicitação (outros algoritmos de compactação de dados também podem ser especificados). Se o servidor suportar compressão, ele deve responder com o conteúdo mantido em formato gzip no corpo da mensagem e no cabeçalho da Content-Encoding: gzip resposta.

Pode combinar a compressão codificado com a transmissão em fluxo; comprima os dados primeiro antes da sua transmissão em fluxo e especifique a codificação de conteúdo gzip e a codificação da transferência segmentada nos cabeçalhos das mensagens. Tenha em atenção também que alguns servidores Web (por exemplo, o Internet Information Server) podem ser configurados para comprimir automaticamente as respostas HTTP, independentemente de a API Web comprimir ou não os dados.

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

Como alternativa à transmissão em fluxo assíncrona, uma aplicação cliente pode explicitamente solicitar dados de objetos grandes em segmentos, conhecidos como respostas parciais. A aplicação cliente envia um pedido HTTP HEAD para obter informações sobre o objeto. Se a API da Web oferecer suporte a respostas parciais, ela deverá responder à solicitação HEAD com uma mensagem de resposta que contenha um Accept-Ranges cabeçalho e um Content-Length cabeçalho que indique o tamanho total do objeto, mas o corpo da mensagem deverá estar vazio. A aplicação cliente pode utilizar estas informações para construir uma série de pedidos GET que especificam um intervalo de bytes a receber. A API da Web deve retornar uma mensagem de resposta com status HTTP 206 (Conteúdo Parcial), um cabeçalho Content-Length que especifica a quantidade real de dados incluídos no corpo da mensagem de resposta e um cabeçalho Content-Range que indica qual parte (como bytes 4000 para 8000) do objeto que esses dados representam.

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

Evitar o envio desnecessários de mensagens de estado 100-Continue nas aplicações cliente

Uma aplicação cliente prestes a enviar uma grande quantidade de dados para um servidor poderá determinar primeiro 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 um corpo de mensagem vazio. Se o servidor estiver disposto a processar o pedido, deverá responder com uma mensagem que especifica o estado HTTP 100 (Continuar). Assim, a aplicação cliente pode avançar e enviar o pedido completo, incluindo os dados no corpo da mensagem.

Se você hospedar um serviço usando o IIS, o driver de HTTP.sys detetará e manipulará automaticamente os cabeçalhos Expect: 100-Continue antes de passar solicitações para seu aplicativo Web. o que significa que é pouco provável ver estes cabeçalhos no código da aplicação e pode assumir que o IIS já filtrou todas as mensagens que considera serem inadequadas ou demasiado grandes.

Se você criar aplicativos cliente usando o .NET Framework, todas as mensagens POST e PUT enviarão primeiro mensagens com cabeçalhos Expect: 100-Continue por padrão. Tal como acontece com o lado do servidor, o processo é tratado de forma transparente pelo .NET Framework. No entanto, este processo faz com que cada pedido POST e PUT crie dois percursos de ida e volta ao servidor, mesmo para pedidos pequenos. Se a aplicação não estiver a enviar pedidos com grandes quantidades de dados, poderá desativar esta funcionalidade com a classe ServicePointManager para criar objetos ServicePoint na aplicação cliente. Um objeto ServicePoint processa as ligações que o cliente realiza para um servidor com base nos fragmentos de esquema e alojamento dos URIs que identificam os recursos do servidor. Pode, em seguida, definir a propriedade Expect100Continue do objeto ServicePoint como false. Todos os pedidos POST e PUT subsequentes realizados pelo cliente através de um URI que satisfaz os fragmentos de esquema e alojamento do objeto ServicePoint serão enviados sem cabeçalhos Expect: 100-Continue. O código seguinte mostra como configurar um objeto ServicePoint que configura todos os pedidos enviados para os URIs com um esquema http e um anfitrião 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 de paginação para pedidos que podem devolver grandes quantidades de objetos

Se uma coleção contiver um grande número de recursos, a emissão de um pedido GET para o URI correspondente poderá resultar num processamento significativo no servidor a alojar a API Web e afetar o desempenho e gerar uma quantidade significativa de tráfego de rede, resultando numa maior latência.

Para processar estes casos, a API Web deve suportar cadeias de consulta que ativam a aplicação cliente para refinar os pedidos ou obter dados em blocos (ou páginas) discretos e mais fáceis de gerir. O código a seguir mostra o GetAllOrders método no Orders controlador. Este método obtém os detalhes das encomendas. Se este método não tiver restrições, será concebível devolver uma grande quantidade de dados. Os parâmetros limit e offset destinam-se a reduzir o volume de dados para um subconjunto mais pequeno; neste caso, por predefiniçã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 obter 30 encomendas a começar no desvio 50 com o URI https://www.adventure-works.com/api/orders?limit=30&offset=50.

Gorjeta

Evite ativar aplicações cliente para especificar cadeias de consulta que resultam num URI com mais de 2000 caracteres de comprimento. Muitos clientes e servidores da Web não conseguem lidar com URIs tão longos.

Manter a capacidade de resposta, a escalabilidade e a disponibilidade

A mesma API da Web pode ser usada por muitos aplicativos cliente em execução em qualquer lugar do mundo. É importante confirmar se a API Web é implementada para manter a capacidade de resposta sob uma carga pesada, ser dimensionável para suportar uma carga de trabalho altamente variável e garantir a disponibilidade para clientes que executam operações críticas para a empresa. Quando determinar como cumprir estes requisitos, considere os seguintes pontos:

Fornecer suporte assíncrono para pedidos de execução longa

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

A API Web também deve fornecer um mecanismo para devolver os resultados do processamento à aplicação de cliente. Poderá conseguir isto ao fornecer um mecanismo de consulta para aplicações cliente para consultar periodicamente se o processamento foi concluído e obter o resultado ou permitir que a API Web envie uma notificação quando a operação for concluída.

Pode implementar um mecanismo de consulta simples ao fornecer uma consulta URI que funciona como um recurso virtual com a seguinte abordagem:

  1. A aplicação cliente envia o pedido inicial para a API 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. 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 Web regista 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. Tenha em atenção que, se a tarefa falhar, a API Web poderá também armazenar informações sobre a falha e definir o estado como Com Falhas.
    • Considere a aplicação de técnicas de repetição para resolver possíveis falhas transitórias.
  6. Enquanto a tarefa é executada, o cliente pode continuar a executar o seu próprio processamento. Ele pode enviar periodicamente uma solicitação para o URI recebido 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 tiver sido concluída ou falhar, a mensagem de resposta também poderá incluir os resultados do processamento ou quaisquer informações disponíveis 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.

As opções para implementar as notificações incluem:

  • Usando um hub de notificação para enviar respostas assíncronas para aplicativos cliente. Para obter mais informações, consulte Enviar notificações para usuários específicos usando os Hubs de Notificação do Azure.
  • Utilizar o modelo Comet para manter uma ligação de rede persistente entre o cliente e o servidor a alojar a API Web e utilizar esta ligação para enviar mensagens do servidor de volta para o cliente. O artigo da revista MSDN Building a Simple Comet Application in the Microsoft .NET Framework (Criar uma Aplicação Comet Simples no Microsoft .NET Framework) descreve uma solução de exemplo.
  • Usando o SignalR para enviar dados em tempo real do servidor web para o cliente através de uma conexão de rede persistente. SignalR está disponível para aplicações Web da ASP.NET como um pacote NuGet. Pode encontrar mais informações no site ASP.NET SignalR.

Confirmar que cada pedido está sem estado

Cada pedido deve ser considerado atómico. Não deve haver nenhuma dependência entre um pedido realizado por uma aplicação cliente e quaisquer pedidos subsequentes submetidos pelo mesmo cliente. Esta abordagem ajuda na escalabilidade; as instâncias do serviço Web podem ser implementadas em vários servidores. Os pedidos de cliente podem ser direcionados para qualquer uma destas instâncias e os resultados devem ser sempre iguais. Também melhora a disponibilidade por um motivo semelhante. Se um servidor Web falhar, os pedidos podem ser encaminhados para outra instância (com o Gestor de Tráfego do Azure) enquanto o servidor é reiniciado, sem que tal produza efeitos nocivos às aplicações cliente.

Rastreie clientes e implemente a limitação para reduzir as chances de ataques DoS

Se um cliente específico realizar um grande número de pedidos num determinado período de tempo, este poderá monopolizar o serviço e afetar o desempenho de outros clientes. Para atenuar este problema, uma API Web pode monitorizar as chamadas das aplicações cliente, através do controlo do endereço IP de todos os pedidos recebidos ou do registo de todos os acessos autenticados. Pode utilizar estas informações para limitar o acesso aos recursos. Se um cliente exceder um limite definido, a API Web poderá devolver uma mensagem de resposta com o estado 503 (Serviço Indisponível) e incluir um cabeçalho Retry-After que especificará quando é que o cliente pode enviar o pedido seguinte sem que este seja recusado. 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.

Gerir atentamente ligações HTTP persistentes

O protocolo HTTP suporta ligações HTTP persistentes quando estiverem disponíveis. A especificação HTTP 1.0 adicionou o cabeçalho Connection:Keep-Alive que permite que um aplicativo cliente indique ao servidor que ele pode usar a mesma conexão para enviar solicitações subsequentes em vez de abrir novas. A ligação será fechada automaticamente se o cliente não reutilizar a ligação dentro de um período de tempo definido pelo anfitrião. Este comportamento está predefinido no HTTP 1.1, tal como utilizado pelos serviços do Azure, pelo que não é necessário incluir cabeçalhos Keep-Alive nas mensagens.

Manter uma ligação aberta pode ajudar a melhorar a capacidade de resposta ao reduzir a latência e o congestionamento da rede, mas pode ser prejudicial para a escalabilidade ao manter ligações desnecessárias abertas por um período de tempo superior ao necessário, limitando a capacidade de outros clientes estabelecerem uma ligação em simultâneo. Poderá também afetar a autonomia da bateria se a aplicação cliente for executada num dispositivo móvel. Se a aplicação apenas realizar pedidos ocasionais ao servidor, manter uma ligação aberta poderá drenar a bateria mais rapidamente. Para garantir que uma ligação não se torna persistente com HTTP 1.1, o cliente pode incluir um cabeçalho Connection:Close com mensagens para substituir o comportamento predefinido. Da mesma forma, se um servidor estiver a processar um número muito elevado de clientes, poderá incluir um cabeçalho Connection: Close nas mensagens de resposta que devem fechar a ligação e guardar os recursos do servidor.

Nota

As ligações HTTP persistentes são uma funcionalidade puramente opcional para reduzir a sobrecarga de rede associada às repetidas tentativas de estabelecer um canal de comunicações. Nem a API Web nem a aplicação cliente devem depender da disponibilidade de uma ligação HTTP persistente. Não use conexões HTTP persistentes para implementar sistemas de notificação no estilo Comet; em vez disso, você deve usar soquetes (ou soquetes da Web, se disponíveis) na camada TCP. Por fim, tenha em atenção que os cabeçalhos Keep-Alive são de utilização limitada caso uma aplicação cliente comunique com um servidor através de um proxy; apenas a ligação com o cliente e o proxy vai ser persistente.

Publicar e gerir uma API Web

Para disponibilizar uma API Web para aplicações cliente, esta tem de ser implementada num ambiente anfitrião. Este ambiente é, geralmente, um servidor Web, embora possa ser outro tipo de processo anfitrião. Deve considerar os seguintes pontos ao publicar uma API Web:

  • Todos os pedidos têm de ser autenticados e autorizados e o nível adequado de controlo de acesso tem de ser imposto.
  • Uma API Web comercial pode ser sujeita a várias garantias de qualidade referentes aos tempos de resposta. É importante garantir que o ambiente do host seja escalável se a carga puder variar significativamente ao longo do tempo.
  • Pode ser necessário medir os pedidos para fins de rentabilização.
  • Pode ser necessário regular o fluxo de tráfego para a API Web e implementar a limitação para clientes específicos que tenham esgotado as respetivas quotas.
  • Os requisitos de regulamentação podem impor o registo e a auditoria de todos os pedidos e respostas.
  • Para garantir a disponibilidade, pode ser necessário monitorizar o estado de funcionamento do servidor a alojar a API Web e reiniciá-lo se necessário.

É útil ser capaz de dissociar esses problemas dos problemas técnicos relativos à implementação da API da Web. Por este motivo, considere criar uma fachada, em execução como um processo separado e que encaminha pedidos para a API Web. A fachada pode fornecer as operações de gestão e encaminhar pedidos validados para a API Web. A utilização de uma fachada também pode disponibilizar muitas vantagens funcionais, incluindo:

  • Funcionar como um ponto de integração para várias APIs Web.
  • Transformar mensagens e traduzir protocolos de comunicações para clientes criados com tecnologias variadas.
  • Colocar em cache pedidos e respostas para reduzir a carga no servidor a alojar a API Web.

Testar uma API Web

Uma API Web deve ser testada tão cuidadosamente quanto qualquer outro elemento de software. Você deve considerar a criação de testes de unidade para validar a funcionalidade.

A natureza de uma API da Web traz seus próprios requisitos adicionais para verificar se ela funciona corretamente. Deve prestar especial atenção aos seguintes aspetos:

  • Teste todas as rotas para verificar que invocam as operações corretas. Esteja especialmente atento ao código de estado HTTP 405 (Método Não Permitido) que está a ser devolvido inesperadamente pois tal pode ser indicativo de um erro entre uma rota e os métodos HTTP (GET, POST, PUT, DELETE) que podem ser enviados para essa rota.

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

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

    Nota

    Alguns aspetos de segurança, tal como a autenticação de utilizador, vão ser muito provavelmente da responsabilidade do ambiente anfitrião ao invés da API Web, mas continua a ser necessário incluir testes de segurança como parte do processo de implementação.

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

  • Verifique se as mensagens de pedido e resposta são bem estruturadas. Por exemplo, se um pedido HTTP POST contiver os dados para um novo recurso no formato x-www-form-urlencoded, confirme se a operação correspondente analisa corretamente os dados, cria os recursos e devolve uma resposta que contém os detalhes do novo recurso, incluindo o cabeçalho Location correto.

  • Verifique todas as ligações e URIs nas mensagens de resposta. Por exemplo, uma mensagem HTTP POST deve retornar o URI do recurso recém-criado. Todas as ligações HATEOAS devem ser válidas.

  • Confirme que cada operação devolve os códigos de estado corretos para diferentes combinações de entrada. Por exemplo:

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

Tenha atenção a códigos de estado de resposta inesperados no intervalo 5xx. Normalmente, estas mensagens são reportadas pelo servidor anfitrião para indicar que não conseguiu realizar um pedido válido.

  • Teste as diferentes combinações de cabeçalhos de pedidos que uma aplicação cliente pode especificar e confirme que a API Web devolve as informações esperadas nas mensagens de resposta.

  • Teste as cadeias de consulta. Se uma operação puder utilizar parâmetros opcionais (tal como pedidos de paginação), teste as diferentes combinações e a ordem dos parâmetros.

  • Verifique se as operações assíncronas foram concluídas com sucesso. Se a API Web suportar a transmissão em fluxo para pedidos que devolvem grandes objetos binários (tal como vídeo ou áudio), verifique se os pedidos de cliente não são bloqueados enquanto os dados são transmitidos. Se a API da Web implementar sondagem para operações de modificação de dados de longa duração, verifique se as operações relatam seu status corretamente à medida que prosseguem.

Também deverá criar e executar testes de desempenho para verificar se a API Web funciona de forma satisfatória em situações de pressão. Pode criar um teste de carga e desempenho da Web com o Visual Studio Ultimate.

Utilizar a Gestão de API do Azure

No Azure, considere usar o Gerenciamento de API do Azure para publicar e gerenciar uma API Web. Ao utilizar esta função, pode gerar um serviço que funciona como uma fachada para uma ou mais APIs Web. O serviço é em si um serviço Web escalável que você pode criar e configurar usando 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, no serviço cloud do Azure ou na máquina virtual do Azure.

  2. Ligue o serviço de gestão de API à API Web. Os pedidos enviados para o URL da API de gestão estão mapeados para os URIs na API Web. O mesmo serviço de gestão de API pode encaminhar pedidos para mais do que uma API Web. Tal operação permite-lhe agregar várias APIs Web num único serviço de gestão. Da mesma forma, a mesma API Web poderá ser referenciada a partir de mais do que um serviço de gestão de API se precisar de restringir ou particionar as funcionalidades disponíveis para diferentes aplicações.

    Nota

    Os URIs, nos links HATEOAS que são gerados como parte da resposta para solicitações HTTP GET, devem fazer referência à URL do serviço de gerenciamento de API e não ao servidor Web que está hospedando a API da Web.

  3. Para cada API Web, especifique as operações HTTP que expõem a API Web, juntamente com quaisquer parâmetros opcionais que uma operação pode receber como entrada. Também poderá configurar se o serviço de gestão de API deve colocar 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. Estas informações são utilizadas para gerar documentação para programadores, pelo que é importante que sejam exatas e completas.

    Você pode definir operações manualmente usando os assistentes fornecidos pelo portal do Azure ou importá-las de um arquivo que contém as definições no formato WADL ou Swagger.

  4. Configure as definições de segurança das comunicações entre o serviço de gestão de API e o servidor Web a alojar a API Web. Atualmente, o serviço de gestão de API suporta a autenticação Básica e a autenticação mútua com certificados, bem como a autorização de utilizador OAuth 2.0.

  5. Crie um produto. Um produto é a unidade de publicação. Deve adicionar APIs Web ligadas anteriormente ao serviço de gestão e ao produto. Quando o produto é publicado, as APIs Web ficam disponíveis para os programadores.

    Nota

    Antes de publicar um produto, também pode definir os grupos de utilizadores que podem aceder ao produto e adicionar utilizadores a estes grupos. Este procedimento dá-lhe controlo sobre os programadores e as aplicações que podem utilizar a API Web. Se uma API Web estiver sujeita a aprovação, antes de poder aceder à mesma, um programador terá de enviar um pedido ao administrador do produto. O administrador pode conceder ou negar o acesso ao programador. Os programadores existentes poderão também ser bloqueados se as circunstâncias forem alteradas.

  6. Configure políticas para cada API Web. As políticas permitem reger os aspetos como a permissão de chamadas entre domínios, a autenticação de clientes, a conversão entre formatos de dados XML e JSON de forma transparente, a restrição das chamadas a partir de um determinado intervalo IP, a utilização de quotas e a limitação da taxa de chamadas. As políticas podem ser aplicadas por todo o produto, a uma única API Web num produto ou a operações individuais numa API Web.

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

Gorjeta

O Azure disponibiliza o Gestor de Tráfego do Azure que permite implementar a ativação pós-falha e o balanceamento de carga, assim como reduzir a latência em várias instâncias de um site alojado em diferentes localizações geográficas. Pode utilizar o Gestor de Tráfego do Azure em conjunto com o Serviço de Gestão de API, o Serviço de Gestão de API pode encaminhar pedidos para instâncias de um site através do Gestor de Tráfego do Azure. Para obter mais informações, consulte Métodos de roteamento do Gerenciador de Tráfego.

Nessa estrutura, se você usar nomes DNS personalizados para seus sites, deverá configurar o registro CNAME apropriado para cada site para apontar para o nome DNS do site do Gerenciador de Tráfego do Azure.

Suporte a programadores do lado do cliente

Normalmente, os programadores que criam aplicações cliente necessitam de informações sobre como aceder à API Web e a documentação referente aos parâmetros, os tipos de dados, os tipos de retorno e os códigos de retorno que descrevem os diferentes pedidos e respostas entre o serviço Web e a aplicação cliente.

Documentar as operações REST de uma API Web

O Serviço de Gestão de API do Azure inclui um portal de programador, que descreve as operações REST expostas por uma API Web. Quando um produto é publicado, aparece neste portal. Os programadores podem inscrever-se neste portal para obter acesso; o administrador pode, em seguida, aprovar ou negar o pedido. Se o programador for aprovado, receberá uma chave de subscrição que é utilizada para autenticar as chamadas a partir das aplicações cliente que desenvolve. Esta chave tem de ser fornecida com cada chamada à API Web. Caso contrário, será rejeitada.

Este portal também fornece:

  • Documentação do produto, lista as operações que expõe, os parâmetros necessários e as respostas diferentes que podem ser devolvidas. Tenha em atenção que esta informação é gerada a partir dos detalhes fornecidos no passo 3 da lista na secção Publicar uma API Web com o Serviço de Gestão de API do Microsoft Azure.
  • Os fragmentos de código mostram como invocar operações de várias linguagens, incluindo JavaScript, C#, Java, Ruby, Python e PHP.
  • Uma consola de programadores que permite ao programador enviar um pedido HTTP para cada operação de teste no produto e ver os resultados.
  • Uma página onde o programador pode reportar quaisquer problemas ou questões encontrados.

O portal do Azure permite que você personalize o portal do desenvolvedor para alterar o estilo e o layout para corresponder à marca da sua organização.

Implementar um SDK do cliente

A criação de uma aplicação cliente que invoca pedidos REST para aceder a uma API Web precisa de uma quantidade significativa de código escrito para construir cada pedido e formatá-lo adequadamente, enviar o pedido para o servidor a alojar o serviço Web e analisar a resposta para saber se o pedido foi bem-sucedido ou falhou e extrair todos os dados devolvidos. Para proteger a aplicação cliente destas preocupações, pode fornecer um SDK que encapsula num wrapper a interface REST e deduz estes detalhes de baixo nível dentro de um conjunto de métodos mais funcional. Uma aplicação cliente utiliza estes métodos que convertem de forma transparente as chamadas em pedidos REST e, em seguida, convertem as respostas de volta para os valores que o método devolveu. Esta é uma técnica comum implementada por vários serviços, incluindo o SDK do Azure.

A criação de um SDK do lado do cliente é um projeto considerável, uma vez que tem de ser implementado de forma consistente e testado atentamente. No entanto, muito deste processo pode ser realizado mecanicamente e muitos fornecedores fornecem ferramentas que podem automatizar muitas destas tarefas.

Monitorizar uma API Web

Dependendo de como publicou e implementou a API Web, pode monitorizar a API Web diretamente ou pode recolher informações sobre a utilização e o estado de funcionamento ao analisar o tráfego que passa através do serviço de Gestão de API.

Monitorizar uma API Web diretamente

Se tiver implementado a API Web com o modelo da API Web da ASP.NET (como um projeto API Web ou como uma função da Web num serviço cloud do Azure) e o Visual Studio 2013, poderá recolher dados de utilização, desempenho e disponibilidade com o Application Insights da ASP.NET. O Application Insights é um pacote que controla e regista de forma transparente informações sobre os pedidos e as respostas quando a API Web é implementada na cloud. Depois de o pacote estar instalado e configurado, não precisa de corrigir qualquer código na API Web para utilizá-lo. Quando implementa a API Web num site do Azure, todo o tráfego é examinado e as estatísticas seguintes são recolhidas:

  • Tempo de resposta do servidor.
  • Número de pedidos do servidor e os detalhes de cada pedido.
  • Os pedidos mais lentos em termos de tempo de resposta médio.
  • Os detalhes de quaisquer pedidos falhados.
  • O número de sessões iniciadas por browsers e agentes de utilizador diferentes.
  • As páginas mais frequentemente visualizadas (principalmente útil para aplicações Web ao invés de APIs Web).
  • As diferentes funções de utilizador a aceder à API Web.

Você pode exibir esses dados em tempo real no portal do Azure. Você também pode criar testes da Web que monitoram a integridade da API da Web. Um teste da Web envia uma solicitação periódica para um URI especificado na API da Web e captura a resposta. Pode especificar a definição de uma resposta bem-sucedida (tal como o código de estado HTTP 200) e, se o pedido não devolver esta resposta, poderá definir o envio de um alerta para um administrador. Se necessário, o administrador poderá reiniciar o servidor que aloja a API Web se tiver falhado.

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

Monitorizar uma API Web através do Serviço de Gestão de API

Se você publicou sua API da Web usando o serviço de Gerenciamento de API, a página Gerenciamento de API no portal do Azure contém um painel que permite exibir o desempenho geral do serviço. A página de análise permite-lhe desagregar os detalhes da forma como o produto está a ser utilizado. Esta página contém os seguintes separadores:

  • Utilização. Este separador fornece informações sobre o número de chamadas da API realizadas e a largura de banda utilizada para processar essas chamadas ao longo do tempo. Pode filtrar os detalhes de utilização por produto, API e operação.
  • Estado de Funcionamento. Este separador permite-lhe ver o resultado dos pedidos da API (os códigos de estado HTTP devolvidos), a eficácia da política de colocação em cache, o tempo de resposta da API e o tempo de resposta do serviço. Novamente, pode filtrar os detalhes do cabeçalho por produto, API e operação.
  • Atividade. Este separador fornece um resumo de 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 apresenta o número de chamadas realizadas por cada programador.
  • Síntese. Este separador mostra um resumo dos dados de desempenho, incluindo os programadores responsáveis por verificar a maioria das chamadas à API e os produtos, as APIs Web e as operações que receberam estas chamadas.

Pode utilizar estas informações para determinar se uma determinada API Web ou operação está a provocar um estrangulamento e, se necessário, dimensionar o ambiente anfitrião e adicionar mais servidores. Também pode apurar se uma ou mais aplicações estão a utilizar um volume desproporcional de recursos e a aplicar as políticas adequadas para definir quotas e limitar as taxas de chamadas.

Nota

Pode alterar os detalhes de um produto publicado. As alterações são aplicadas de imediato. Por exemplo, pode adicionar ou remover uma operação de uma API Web sem ter de voltar a publicar o produto que contém a API Web.

Próximos passos

  • ASP.NET Web API OData (API Web OData da ASP.NET) contém exemplos e mais informações sobre como implementar uma API Web OData com a ASP.NET.
  • Introducing batch support in Web API and Web API OData descreve como implementar operações em lote em uma API da Web usando OData.
  • Os padrões de idempotência no blog de Jonathan Oliver fornecem uma visão geral da idempotência e como ela se relaciona com as operações de gerenciamento de dados.
  • As definições de código de status no site do W3C contêm uma lista completa de códigos de status HTTP e suas descrições.
  • Executar tarefas em segundo plano com o WebJobs fornece informações e exemplos sobre a utilização do WebJobs para realizar operações em segundo plano.
  • Os Hubs de Notificação do Azure notificam os usuários e mostram como usar um Hub de Notificação do Azure para enviar respostas assíncronas por push para aplicativos cliente.
  • Gestão de API descreve como publicar um produto que fornece acesso controlado e seguro a uma API Web.
  • A referência da API REST de Gerenciamento de API do Azure descreve como usar a API REST de Gerenciamento de API para criar aplicativos de gerenciamento personalizados.
  • Os métodos de roteamento do Gerenciador de Tráfego resumem como o Gerenciador de Tráfego do Azure pode ser usado para balancear a carga de solicitações em várias instâncias de um site que hospeda uma API Web.
  • Application Insights - Introdução à ASP.NET fornece informações detalhadas sobre a instalação e configuração do Application Insights num projeto API Web da ASP.NET.