Реализация веб-API

Тщательно спроектированный веб-API RESTful определяет ресурсы, связи и схемы навигации, которые доступны для клиентских приложений. При реализации и развертывании веб-API следует учитывать физические требования среды размещения веб-API и способ создания веб-API, а не логическую структуру данных. В этом руководстве рассматриваются советы и рекомендации по реализации веб-API и его публикации, чтобы сделать API доступными для клиентских приложений. Подробные сведения о разработке веб-API см. в статье Проектирование веб-API.

Обработка запросов

При реализации кода для обработки запросов следует учитывать перечисленные ниже аспекты.

Идемпотентность действий GET, PUT, DELETE, HEAD и PATCH

Код, который реализует эти запросы, не должен включать никаких побочных эффектов. Один и тот же запрос, выполненный повторно через тот же ресурс, должен возвращать результат в том же состоянии. Например, отправка нескольких запросов DELETE в один URI должна иметь одинаковый эффект, хотя код состояния HTTP в сообщениях ответа может отличаться. Например, первый запрос DELETE вернет код состояния 204 (нет содержимого), а все последующие аналогичные запросы DELETE — код состояния 404 (не найдено).

Примечание.

В статье, посвященной шаблонам идемпотентности, в блоге Джонатана Оливера (Jonathan Oliver) представлен обзор идемпотентности и ее связи с операциями по управлению данными.

Отсутствие несвязанных побочных эффектов у действий POST, которые создают новые ресурсы

Если запрос POST предназначен для создания нового ресурса, последствия запроса должны быть ограничены новым ресурсом (и, возможно, любые прямые связанные ресурсы, если связана какая-то связь). Например, в системе электронной коммерции запрос POST, который создает новый заказ для клиента, может также изменять уровни инвентаризации и создавать сведения о выставлении счетов, но не должен изменять информацию, не связанную с заказом, или иметь другие побочные эффекты на общее состояние системы.

Нежелательность реализации множественных операции POST, PUT и DELETE

Поддержка запросов POST, PUT и DELETE через коллекции ресурсов. Запрос POST может содержать сведения о нескольких новых ресурсах и добавить их все в одну и ту же коллекцию, запрос PUT может заменить весь набор ресурсов в коллекции, а запрос DELETE может удалить всю коллекцию.

Поддержка OData, включенная в веб-API ASP.NET 2, позволяет создавать пакеты запросов. Клиентское приложение может упаковать несколько запросов веб-API и отправлять их на сервер в одном HTTP-запросе, а также получать один ответ HTTP, который содержит ответы на каждый запрос. Дополнительные сведения см. в разделе "Введение пакетной поддержки" в веб-API и OData веб-API.

Соблюдение спецификаций протокола HTTP при отправке ответов

Веб-интерфейс API должен возвращать сообщения, содержащие правильный код состояния HTTP, чтобы клиент мог определить способ обработки результатов, соответствующие заголовки HTTP, чтобы клиент мог понять характер результата, и подходящим образом форматированный текст, чтобы клиент мог проанализировать результат.

Однако в этом случае операция POST должна возвращать код состояния 201 (создано), а ответное сообщение должно содержать URI созданного ресурса в заголовке Location.

Поддержка согласования содержимого

Текст ответного сообщения может содержать данные в различных форматах. Например, HTTP-запрос GET может возвращать данные в формате JSON или XML. Когда клиент отправляет запрос, он может включить заголовок Accept, указывающий форматы данных, которые он может обработать. Эти форматы задаются в виде типов мультимедиа. Например, клиент, отправляющий запрос GET, который извлекает изображения, может указать заголовок Accept, перечисляющий типы мультимедиа, которые может обработать клиент, например image/jpeg, image/gif, image/png. Когда веб-API возвращает результат, ему следует форматировать данные с помощью одного из этих типов мультимедиа и указать формат в заголовке Content-Type ответа.

Если клиент не указывает заголовок Accept, то используйте формат по умолчанию для текста ответа. Например, платформа веб-API ASP.NET по умолчанию использует для текстовых данных формат JSON.

Методика HATEOAS позволяет клиенту обнаружить ресурсы и перейти к ним из исходной стартовой точки. Это достигается за счет использования ссылок, содержащих URI; когда клиент отправляет запрос HTTP GET, чтобы получить доступ к ресурсу, ответ должен содержать URI, которые позволяют клиентскому приложению быстро найти все непосредственно связанные ресурсы. Например, в веб-API с поддержкой решения электронной коммерции заказчик может разместить много заказов. Когда клиентское приложение получает сведения о заказчике, ответ должен содержать ссылки, позволяющие первому отправлять запросы HTTP GET для извлечения этих заказов. Кроме того, ссылки в стиле HATEOAS должны содержать описания других операций (POST, PUT, DELETE и т. д.), которые поддерживает каждый связанный ресурс, а также соответствующий URI для выполнения каждого запроса. Более подробно этот подход описан в руководстве по проектированию API.

В настоящее время отсутствуют стандарты, определяющие реализацию HATEOAS, однако в следующем примере показан один из возможных способов. В этом примере запрос HTTP GET, который находит сведения о заказчике, возвращает ответ, включающий ссылки HATEOAS на его заказы:

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"]}
]}

В этом примере данные заказчика, представленные классом Customer , показаны в следующем фрагменте кода. Ссылки HATEOAS находятся в свойстве 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; }
}

Операция HTTP GET получает данные заказчика из хранилища и создает объект Customer, а затем заполняет коллекцию Links. Результат представляется в формате ответного сообщения JSON. Каждая ссылка содержит следующие поля:

  • Связь (Rel) между возвращаемым объектом и объектом, описанным ссылкой. в этом случае self указывает, что ссылка является ссылкой на сам объект (аналогично указателю this во многих языках объектно-ориентированного программирования), а orders — это имя коллекции со сведениями, связанными с заказом;
  • гиперссылка (Href) для объекта, описываемого в ссылке в виде URI;
  • тип HTTP-запроса (Action), который можно отправлять на этот URI;
  • формат данных (Types), который необходимо предоставить в HTTP-запросе или которые могут быть возвращены в ответе, в зависимости от типа запроса.

Ссылки HATEOAS, показанные в примере HTTP-ответа, указывают на то, что клиентское приложение может выполнять следующие операции:

  • Запрос HTTP GET к URI https://adventure-works.com/customers/2 для получения сведений о заказчике (повторно). данные могут быть возвращены в формате XML или JSON;
  • Запрос HTTP PUT к URI https://adventure-works.com/customers/2 для изменения сведений о заказчике. новые данные должны быть представлены в сообщении запроса в формате x-www-form-urlencoded;
  • Запрос HTTP DELETE к URI https://adventure-works.com/customers/2 для удаления заказчика. запрос не ожидает никаких дополнительных сведений или возврата данных в теле ответного сообщения;
  • Запрос HTTP GET к URI https://adventure-works.com/customers/2/orders для поиска всех заказов заказчика. данные могут быть возвращены в формате XML или JSON;
  • Запрос HTTP POST к URI https://adventure-works.com/customers/2/orders для создания нового заказа для заказчика. данные должны быть представлены в сообщении запроса в формате x-www-form-urlencoded.

Обработка исключений

Если операция создает неперехваченное исключение, учитывайте следующие аспекты.

Перехват всех исключений и возвращение пользователям информативных сообщений

Код, который реализует операцию HTTP, должен полностью обрабатывать все исключения и не оставлять их обработку платформе. Если исключение не позволяет успешно завершить операцию, его можно передать обратно в ответное сообщение, однако при этом оно должно включать значимое описание ошибки, вызвавшей исключение. Исключение также должно включать соответствующий код состояния HTTP, а не просто возвращаемый код состояния 500 для каждого случая. Например, если запрос пользователя приводит к обновлению базы данных, что нарушает ограничение (например, при попытке удалить заказчика, имеющего необработанные заказы), следует возвращать код состояния 409 (конфликт) и текст сообщения с указанием причины конфликта кода. Если же запрос определяется как невыполнимый по иной причине, можно возвратить код состояния 400 (неправильный запрос). Полный список кодов состояния HTTP представлен на странице определений кодов состояния на веб-сайте консорциума W3C.

Следующий пример кода перехватывает разные условия и возвращает соответствующие ответы.

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

Совет

Не включайте сведения, которые могут быть полезны злоумышленнику, пытающемуся проникнуть в API.

Многие веб-серверы перехвата ошибки сами до попадания в веб-API. Например, если вы настроили проверку подлинности на веб-сайте и пользователь указал неправильные сведения для проверки подлинности, то веб-сервер должен предоставить ответ с кодом состояния 401 (отсутствует авторизация). После проверки подлинности клиента код может выполнить собственные проверки, чтобы убедиться, что клиенту можно предоставить доступ к запрашиваемому ресурсу. В случае сбоя проверки подлинности следует возвращать код состояния 403 (запрещено).

Согласованная обработка исключений и внесение в журнал сведений об ошибках

Для обработки исключений согласованным способом рассмотрите возможность применения глобальной стратегии обработки ошибок в рамках всего веб-API. Кроме того, следует включить ведение журнала для записи подробных сведений о каждом исключении; этот журнал ошибок может содержать подробные сведения до тех пор, пока он не станет доступен клиентам через Интернет.

Раздельная обработка ошибок на стороне клиента и на стороне сервера

Протокол HTTP позволяет различать ошибки, возникающие из-за клиентского приложения (коды состояния HTTP 4xx), и ошибки, вызванные неполадками на сервере (коды состояния HTTP 5xx). Обязательно учитывайте это при обработке ответных сообщений об ошибках.

Оптимизация доступа к данным на стороне клиента

В распределенной среде, например, включающей веб-сервер и клиентские приложения, одним из основных источников проблем является сеть. Это может оказаться значительным узким местом, особенно в том случае, если клиентское приложение часто отправляет запросы или получает данные. Поэтому следует стремиться к тому, чтобы свести к минимуму объем трафика, проходящего через сеть. При реализации кода для получения и хранения данных необходимо учитывать перечисленные ниже моменты.

Поддержка кэширования на стороне клиента

Протокол HTTP 1.1 поддерживает кэширование в клиентах и на промежуточных серверах, через которые направляется запрос, с помощью заголовка Cache-Control. Когда клиентское приложение отправляет в веб-API запрос HTTP GET, ответ может включать заголовок Cache-Control. Этот заголовок указывает, могут ли данные в теле ответа безопасно кэшироваться клиентом или промежуточным сервером, через который был направлен запрос, и как долго следует кэшировать данные, прежде чем срок их действия истечет и они будут считаться устаревшими.

В следующем примере показан запрос HTTP GET и соответствующий ответ, который включает заголовок 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}

В этом примере заголовок Cache-Control указывает, что срок действия возвращенных данных составляет 600 секунд, а сами данные предназначены только для одного клиента и не должны храниться в общем кэше, используемом другими клиентами (определяется параметром private). В заголовке Cache-Control должен быть указан параметр public вместо private, чтобы данные хранились в общем кэше. Также может быть указан параметр no-store, чтобы данные не кэшировались клиентом. В следующем примере кода показано, как создать заголовок Cache-Control в ответном сообщении:

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

В этом коде используется пользовательский класс IHttpActionResult с именем OkResultWithCaching. Этот класс позволяет контроллеру задавать содержимое заголовка кэша:

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

Примечание.

Протокол HTTP также определяет директиву no-cache для заголовка Cache-Control. Директива может ввести в заблуждение, поскольку означает не "не следует кэшировать", а "повторно проверить кэшированные данные на сервере перед их возвращением"; данные по-прежнему могут кэшироваться, но каждый раз, когда они используются, данные проверяются на предмет своей актуальности.

Управление кэшем — это задача клиентского приложения или промежуточного сервера, однако при правильной реализации это позволяет сэкономить пропускную способность и повысить производительность за счет устранения необходимости в выборке данных, которые уже были получены недавно.

Значение max-age в заголовке Cache-Control носит исключительно информационный характер. Оно не гарантирует неизменность соответствующих данных в течение указанного времени. Веб-API следует задать для параметра max-age подходящее значение в зависимости от ожидаемой изменчивости данных. По истечении этого периода клиент удаляет объект из кэша.

Примечание.

Большинство современных веб-браузеров поддерживают кэширование на стороне клиента за счет добавления соответствующих заголовков cache-control в запросы и проверки заголовков результатов, как описано в этой статье. Однако некоторые устаревшие браузеры не будут кэшировать значения, возвращаемые из URL-адреса, который включает строку запроса. Обычно это не является проблемой для пользовательских клиентских приложений, реализующих собственные стратегии управления кэшем на основе протокола, описанного здесь.

Некоторые устаревшие прокси-серверы демонстрируют такое же поведение и могут не кэшировать запросы на основе URL-адреса со строками запросов. Это может оказаться проблемой для пользовательских клиентских приложений, подключающихся к веб-серверу через такой прокси-сервер.

Применение тегов сущностей для оптимизации обработки запросов

Когда клиентское приложение получает объект, ответное сообщение также может содержать элемент ETag (тег сущности). ETag представляет собой непрозрачную строку, в которой указывается версия ресурса; при каждом изменении ресурса изменяется и его ETag. Этот ETag должен кэшироваться клиентским приложением как часть данных. В следующем примере кода показано, как добавить ETag в ответ на запрос HTTP GET. Этот код использует метод GetHashCode объекта для формирования числового значения, идентифицирующего объект (при необходимости этот метод можно переопределить и создать собственный хэш с помощью такого алгоритма, как 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;
    }
    ...
}

Ответное сообщение, отправленное веб-API, выглядит следующим образом:

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}

Совет

По соображениям безопасности не разрешать кэшировать конфиденциальные данные или данные, возвращаемые через прошедшее проверку подлинности (HTTPS).

Клиентское приложение может в любое время выдать последующий запрос GET для получения того же ресурса, и если ресурс был изменен (имеет разные теги ETag), кэшированную версию следует удалить и добавить в кэш новую версию. Если ресурс большой и для передачи его обратно клиенту требуется значительный объем пропускной способности, повторная отправка запросов для получения тех же данных может оказаться неэффективной. Для решения этой проблемы протокол HTTP определяет следующий процесс оптимизации запросов GET, которые должны поддерживать в веб-API:

  • Клиент создает запрос GET, содержащий ETag для кэшированной в настоящее время версии ресурса, указанный в заголовке HTTP If-None-Match:

    GET https://adventure-works.com/orders/2 HTTP/1.1
    If-None-Match: "2147483648"
    
  • Операция GET в веб-API получает текущий ETag для запрошенных данных (заказ 2 в примере выше) и сравнивает его со значением заголовка If-None-Match.

  • Если текущий ETag для запрошенных данных соответствует тегу ETag, предоставленному в запросе, то это означен, что ресурс не был изменен, а веб-API должен возвратить ответ HTTP с пустым текстом и кодом состояния 304 (не изменено).

  • Если текущий ETag для запрошенных данных не соответствует тегу ETag, предоставленному в запросе, то это означен, что данные были изменены, и веб-API должен возвратить ответ HTTP с новыми данными в теле сообщения и кодом состояния 200 (ОК).

  • Если запрошенные данные больше не существуют, веб-API должен возвратить ответ HTTP с кодом состояния 404 (не найдено).

  • Клиент использует код состояния для поддержания кэша в актуальном состоянии. Если данные не изменились (код состояния 304), то объект может оставаться в кэше и клиентское приложение будет использовать эту версию объекта. Если данные изменились (код состояния 200), то кэшированный объект следует удалить и вставить новый. В случае отсутствия данных (код состояния 404) объекта следует удалить из кэша.

Примечание.

Если заголовок ответа содержит заголовок Cache-Control с параметром no-store, то объект следует всегда удалять из кэша, независимо от кода состояния HTTP.

В следующем коде показан метод, расширенный FindOrderByID для поддержки заголовка If-None-Match. Обратите внимание, если заголовок If-None-Match опущен, то извлечение всегда выполняется в заданном порядке:

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

Этот пример включает дополнительный настраиваемый класс IHttpActionResult с именем EmptyResultWithCaching. Этот класс выступает в роли простой оболочки объекта HttpResponseMessage , которая не содержит текст ответа:

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

Совет

В этом примере значение ETag для данных формируется путем хэширования данных, полученных из базового источника данных. Если ETag можно вычислить другим способом, можно дополнительно оптимизировать процесс, и данные необходимо извлекать из источника данных только, если они изменились. Этот подход особенно полезен, если имеется большой объем данных или доступ к источнику данных может привести к значительным задержкам (например, если источником данных является удаленная база данных).

Применение тегов сущностей для поддержки оптимистической блокировки

Чтобы включить обновление ранее кэшированных данных, протокол HTTP поддерживает стратегию оптимистичного параллелизма. Если после получения и кэширования ресурса клиентское приложение впоследствии отправляет запрос PUT или DELETE для изменения или удаления ресурса, он должен содержать заголовок If-Match, ссылающийся на ETag. Затем веб-API может использовать эту информацию для определения того, был ли ресурс уже изменен другим пользователем после его получения и отправки соответствующего ответа обратно в клиентское приложение, следующим образом:

  • Клиент создает запрос PUT, содержащий сведения о новом ресурсе и ETag для кэшированной в настоящее время версии ресурса, который указан в заголовке HTTP If-Match. В следующем примере показан запрос PUT, который обновляет заказ:

    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
    
  • Операция PUT в веб-API получает текущий ETag для запрошенных данных (заказ 1 в примере выше) и сравнивает его со значением заголовка If-Match.

  • Если текущий ETag для запрошенных данных соответствует ETag, предоставленному в запросе, то это означает, что ресурс не был изменен и веб-API следует выполнить обновление и возвратить сообщение с кодом состояния HTTP 204 (нет содержимого) при успешном выполнении. Ответ может включать заголовки Cache-Control и ETag для обновленной версии ресурса. Ответ всегда должен содержать заголовок Location, указывающий URI обновленного ресурса.

  • Если текущий ETag для запрошенных данных не соответствует ETag, предоставленному в запросе, то это означает, что данные были изменены другим пользователем, поскольку была выполнена выборка, и веб-API должен вернуть ответ HTTP с пустым текстом и кодом состояния 412 (необходимое условие не выполнено).

  • Если ресурс, который требуется обновить, больше не существует, веб-API должен возвратить ответ HTTP с кодом состояния 404 (не найдено).

  • Клиент использует код состояния и заголовки ответа для поддержания кэша в актуальном состоянии. Если данные были обновлены (код состояния 204), то объект может оставаться в кэше (при условии, что в заголовке Cache-Control отсутствует параметр no-store), однако ETag следует обновить. Если данные были изменены другим пользователем (код состояния 412) или не найдены (код состояния 404), то кэшированный объект должен быть отключен карта.

В следующем примере кода показана реализация операции PUT для контроллера Orders:

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

Совет

Использование заголовка If-Match вовсе необязательно, и если он не указан, веб-API будет всегда пытаться обновить указанный заказ, возможно, путем машинальной перезаписи обновления, внесенного другим пользователем. Чтобы избежать проблем, связанных с потерей обновлений, всегда указывайте заголовок If-Match.

Обработка больших запросов и ответов

Иногда может возникнуть ситуация, когда клиентское приложение должно выдавать запросы, отправляющие или получающие данные, которые могут иметь размер нескольких мегабайт (или больше). Ожидание завершения передачи такого объема данных может привести к тому, что клиентское приложение перестанет отвечать на запросы. Если необходимо обрабатывать запросы, включающие значительные объемы данных, обязательно учитывайте указанные ниже моменты.

Оптимизация запросов и ответов, имеющих отношение к большим объектам

Некоторые ресурсы могут быть большими объектами или включать большие поля, такие как графические изображения или другие типы двоичных данных. Веб-API должен поддерживать потоковую передачу для оптимизации отправки и загрузки этих ресурсов.

Протокол HTTP обеспечивает механизм поблочного кодирования для потоковой передачи больших объектов данных обратно клиенту. Когда клиент отправляет HTTP-запрос GET для крупного объекта, веб-API может отправить ответ через подключение HTTP поэтапно в виде фрагментов. Длина данных в ответе может быть не известна изначально (она может быть создана), поэтому сервер, на котором размещен веб-API, должен отправлять ответное сообщение с каждым блоком, указывающим Transfer-Encoding: Chunked заголовок, а не заголовок Content-Length. В свою очередь, клиентское приложение может получать каждый блок для создания ответа. Когда сервер отправляет обратно последний фрагмент данных с нулевым размером, передача данных завершается.

Один запрос может вернуть огромный объект, на обработку которого потребуются значительные ресурсы. Если в процессе потоковой передачи веб-API определяет, что объем данных в запросе превысил некоторые допустимые ограничения, можно отменить операцию и вернуть ответное сообщение с кодом состояния 413 (слишком большой объект запроса).

Можно свести к минимуму размер больших объектов, передаваемых по сети, с помощью сжатия HTTP. Такой подход позволяет сократить объем сетевого трафика и связанные с этим сетевые задержки, но для этого требуется дополнительная обработка на стороне клиента и на сервере, где размещен веб-API. Например, клиентское приложение, которое ожидает получения сжатых данных, может включать заголовок запроса (другие алгоритмы сжатия данных также можно указать Accept-Encoding: gzip ). Если сервер поддерживает сжатие, оно должно отвечать с содержимым, которое хранится в формате gzip в тексте сообщения и заголовке Content-Encoding: gzip ответа.

Можно объединять закодированное сжатие с потоковой передачей; перед потоковой передачей данные сначала необходимо сжать, а затем указать в заголовках сообщений кодировку содержимого GZIP и поблочное кодирование. Кроме того, некоторые веб-серверы (такие как Internet Information Server) можно настроить на автоматическое сжатие HTTP-ответов, независимо от того, сжимает веб-API данные или нет.

Реализация частичных ответов для клиентов, которые не поддерживают асинхронные операции

В качестве альтернативы асинхронной потоковой передаче клиентское приложение может явно запросить данные для больших объектов в виде фрагментов, которые также называются частичными ответами. Клиентское приложение отправляет запрос HTTP HEAD для получения сведений об объекте. Если веб-API поддерживает частичные ответы, он должен отвечать на запрос HEAD с сообщением ответа, содержащим Accept-Ranges заголовок и Content-Length заголовок, указывающий общий размер объекта, но текст сообщения должен быть пустым. Клиентское приложение может использовать эти сведения для создания ряда запросов GET, которые указывают диапазон байтов для получения. Веб-API должен возвращать ответное сообщение с состоянием HTTP 206 (частичное содержимое), заголовок Content-Length, указывающий фактический объем данных, включенных в текст сообщения ответа, и заголовок Content-Range, указывающий, какая часть (например, байты 4000 к 8000) объекта представляет.

Более подробно запросы HTTP HEAD и частичные ответы описаны в руководстве по проектированию API.

Отказ от отправки клиентским приложениям ненужных сообщений с кодом 100 (продолжение)

Клиентское приложение, которое собирается передать на сервер большой объем данных, может сначала определить, желает ли сервер фактически принять такой запрос. Перед отправкой данных клиентское приложение может отправить HTTP-запрос с заголовком Expect: 100-Continue, заголовком Content-Length, который указывает размер данных, но с пустым текстом сообщения. Если сервер готов обработать запрос, он должен отправить ответное сообщение с кодом состояния HTTP 100 (продолжить). Клиентское приложение затем может продолжить выполнение операции и отправить полный запрос, включая данные в теле сообщения.

Если вы размещаете службу с помощью IIS, драйвер HTTP.sys автоматически обнаруживает и обрабатывает ожидаемые заголовки: 100-Continue перед передачей запросов в веб-приложение. Это означает, что существует небольшая вероятность увидеть эти заголовки в коде приложения, и можно предположить, что IIS уже отсортировали любые сообщения, которые они посчитали неподходящими или слишком большими.

Если вы создаете клиентские приложения с помощью платформа .NET Framework, по умолчанию все сообщения POST и PUT будут отправлять сообщения с помощью ожиданий: 100-Продолжить заголовки по умолчанию. Так же, как и на стороне сервера, этот процесс обрабатывается прозрачно платформой .NET Framework. Однако этот процесс приводит к тому, что каждый запрос POST и PUT проходит два цикла обработки на сервере, даже если запрос небольшой. Если приложение не отправляет запросы с большими объемами данных, можно отключить эту функцию с помощью класса ServicePointManager для создания объектов ServicePoint в клиентском приложении. Объект ServicePoint обрабатывает соединение, которое клиент устанавливает с сервером, на основе схемы и фрагментов узла из кодов URI, которые определяют ресурсы на сервере. Затем можно присвоить свойству Expect100Continue объекта ServicePoint значение false. Все последующие запросы POST и PUT, сделанные клиентом через URI, который соответствует схеме и фрагментам узла объекта ServicePoint , будут отправляться без заголовков Expect: 100-Continue. В примере кода ниже показано, как настроить объект ServicePoint, который настраивает все запросы, отправленные на URI со схемой http и узлом www.contoso.com.

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

Можно также задать статическое свойство Expect100Continue класса ServicePointManager и указать для этого свойства значение по умолчанию для всех создаваемых в последующем объектов ServicePoint.

Поддержка разбиения на страницы для запросов, которые могут возвращать большое количество объектов

Если коллекция содержит большое количество ресурсов, отправка запроса GET на соответствующий URI может привести значительной обработки на сервере, на котором размещен веб-API, что влияет на производительность и создает значительный объем сетевого трафика. В свою очередь это приводит к увеличению времени задержки.

В таких случаях веб-API должен поддерживать строки запроса, которые позволяют клиентскому приложению уточнять запросы или делать выборку данных в виде более управляемых, отдельных блоках (или страницах). В следующем коде показан GetAllOrders метод в контроллере Orders . Этот метод получает сведения о заказах. В случае отсутствия ограничений он может возвратить большой объем данных. Параметры limit и offset предназначены для сокращения объема данных до небольшого подмножества, в этом случае — только первые 10 заказов по умолчанию:

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

Клиентское приложение может выдать запрос на получение 30 заказов, начиная с позиции смещения 50, используя такой URI: https://www.adventure-works.com/api/orders?limit=30&offset=50.

Совет

Избегайте предоставления клиентским приложениям возможности задавать строки запроса, которые приводят к созданию URI длиной более 2000 символов. Многие веб-клиенты и серверы не могут обрабатывать URI, которые являются длительными.

Обеспечение доступности, масштабируемости и скорости реагирования

Один и тот же веб-API может использоваться несколькими клиентскими приложениями, работающими в любой точке мира. Очень важно убедиться, что веб-API реализован так, что обеспечивает одинаковую скорость реагирования при интенсивной нагрузке, масштабируемость для поддержки непостоянной рабочей нагрузки, а также гарантирует доступность для клиентов, выполняющих критически важные бизнес-операции. При определении того, как обеспечить соответствие этим требованиям, необходимо учитывать указанные ниже моменты.

Поддержка асинхронных операций для длительных запросов

Запрос, обработка которого может занять много времени, следует выполнять без блокирования клиента, отправившего этот запрос. Веб-API может выполнить некоторую начальную проверку запроса, инициировать отдельную задачу для выполнения работы, а затем вернуть ответное сообщение с кодом HTTP 202 (принято). Такую задачу можно обработать асинхронно в самой службе веб-API или передать на выполнение в фоновый процесс.

В веб-API также должен быть реализован механизм для возвращения результатов обработки обратно в клиентское приложение. Добиться этого можно за счет предоставления механизма опроса для клиентских приложений с целью периодически запрашивать у них сведения о завершении обработки и получить результат. Либо веб-API можно настроить на отправку уведомлений по завершении операции.

Реализовать простой механизм опроса можно, предоставив URI polling (действует как виртуальный ресурс) в рамках следующего подхода.

  1. Клиентское приложение отправляет первоначальный запрос в веб-API.
  2. Веб-API хранит сведения о запросе в таблице, которая хранится в таблице Azure служба хранилища или кэше Microsoft Azure, и создает уникальный ключ для этой записи, возможно, в виде GUID. Кроме того, сообщение, содержащее сведения о запросе и уникальный ключ, также можно отправить через Служебная шина Azure.
  3. Веб-API инициирует обработку как отдельную задачу или библиотеку, например Hangfire. Веб-API записывает состояние задачи в таблице как Running.
    • Если вы используете Служебная шина Azure, обработка сообщений будет выполнена отдельно от API, возможно, с помощью Функции Azure или AKS.
  4. Веб-API возвращает ответное сообщение с кодом состояния HTTP 202 (принято) и универсальный код ресурса (URI), содержащий уникальный ключ, созданный - что-то подобное /polling/{guid}.
  5. После завершения задачи веб-API сохраняет результаты в таблице и задает состояние задачи "Завершить". Обратите внимание: если задача завершается сбоем, веб-API может сохранить сведения об ошибке и установить состояние Failed.
  6. Пока выполняется задача, клиент может продолжать выполнять собственную обработку. Он может периодически отправлять запрос в URI, полученный ранее.
  7. Веб-API в URI запрашивает состояние соответствующей задачи в таблице и возвращает ответное сообщение с кодом состояния HTTP 200 (ОК), содержащее это состояние (выполнение, завершение или сбой). Если задача завершена успешно или с ошибкой, ответное сообщение также может включать результаты обработки или любые доступные сведения о причине сбоя.
    • Если длительный процесс имеет более промежуточные состояния, лучше использовать библиотеку, которая поддерживает шаблон сага, например NServiceBus или MassTransit.

Ниже описаны несколько возможных вариантов для реализации уведомлений.

  • Использование центра уведомлений для отправки асинхронных ответов клиентским приложениям. Дополнительные сведения см. в статье Отправка уведомлений определенным пользователям с помощью службы "Центры уведомлений Azure".
  • Использование модели Comet для сохранения постоянного сетевого соединения между клиентом и сервером, на котором размещен веб-API, и использование этого подключения для отправки сообщения от сервера обратно клиенту. В статье Создание простого Comet-приложения в Microsoft .NET Framework в журнале MSDN описывается пример решения.
  • Использование SignalR для передачи данных в режиме реального времени с веб-сервера клиенту через постоянное сетевое подключение. SignalR доступен для веб-приложений ASP.NET в виде пакета NuGet. Дополнительные сведения см.на веб-сайте ASP.NET SignalR .

Реализация всех запросов без учета состояния

Каждый запрос следует считать атомарным. Должны отсутствовать зависимости между одним запросом от клиентского приложения и всеми последующими запросами, отправленными от того же клиента. Такой подход способствует масштабируемости; экземпляры веб-службы можно развернуть на нескольких серверах. Клиентские запросы можно направить в любой из этих экземпляров, и результаты при этом всегда должны быть одинаковыми. По этой же причине повышается и доступность; в случае сбоя веб-сервера запросы можно передать в другой экземпляр (с помощью диспетчера трафика Azure), пока сервер перезапускается, что не оказывает никакого влияния на клиентские приложения.

Отслеживание клиентов и реализация регулирования, чтобы снизить вероятность атак DoS

Если определенный клиент выполняет большое количество запросов в течение заданного периода времени, он может монополизировать службу и повлиять на производительность других клиентов. Чтобы устранить эту проблему, веб-API может отслеживать вызовы от клиентских приложений с помощью отслеживания IP-адреса для всех входящих запросов или путем регистрации в журнале каждой операции доступа с проверкой подлинности. Эти сведения можно использовать для ограничения доступа к ресурсам. Если клиент превышает заданный предел, веб-API может вернуть ответное сообщение с состоянием 503 (служба недоступна) и включить заголовок Retry-After, указывающий, когда клиент сможет отправить следующий запрос, который не будет отклонен. Эта стратегия может помочь снизить вероятность атаки типа "отказ в обслуживании" (DoS) от набора клиентов, застопоряющих систему.

Осторожность при управлении постоянными HTTP-подключениями

Протокол HTTP поддерживает постоянные HTTP-подключения, где они доступны. В спецификации HTTP 1.0 был добавлен заголовок Connection:Keep-Alive, который позволяет клиентскому приложению сообщить серверу, что последний может использовать то же подключение для отправки последующих запросов вместо открытия новых. Подключение закрывается автоматически, если клиент не использовал подключение повторно в течение промежутка времени, определенного узлом. Это поведение применяется по умолчанию в HTTP 1.1 и используется службами Azure, поэтому нет необходимости включать в сообщения заголовки Keep-Alive.

Постоянно открытое подключение может помочь повысить скорость реагирования за счет снижения задержки и перегрузки сети, однако это может помешать масштабируемости, так как остается больше открытых подключений, чем требуется, а это ограничивает возможность параллельного подключения других клиентов. Это также может повлиять на время работы от батареи, если клиентское приложение выполняется на мобильном устройстве; если приложение выполняет лишь редкие запросы к серверу, постоянно открытое подключение может привести к более быстрому разряду батареи. Для указания того, чтобы подключение по HTTP 1.1 не было постоянным, клиент может включить в сообщения заголовок Connection:Close. Это позволит переопределить поведение по умолчанию. Аналогично, если сервер обрабатывает большое количество клиентов, он может включить в ответные сообщения заголовок Connection:Close, что позволяет закрыть подключение и сэкономить ресурсы сервера.

Примечание.

Постоянные HTTP-подключения абсолютно необязательны. Они призваны уменьшить нагрузку на сеть, связанную с многократным установлением канала связи. Ни веб-API, ни клиентское приложение не должно зависеть от постоянного HTTP-подключения, которое доступно. Не используйте постоянные HTTP-подключения для реализации систем уведомлений в стиле Comet; Вместо этого следует использовать сокеты (или веб-сокеты, если они доступны) на уровне TCP. Наконец, обратите внимание, что возможности использования заголовков Keep-Alive ограничены, если клиентское приложение подключается к серверу через прокси-сервер; постоянным в этом случае будет только соединение между клиентом и прокси-сервером.

Публикация веб-API и управление им

Чтобы сделать веб-API доступным для клиентских приложений, веб-API следует развернуть в среде узла. Такой средой обычно является веб-сервер, хотя это может быть и другой тип хост-процесса. При публикации веб-API следует учитывать следующие моменты.

  • Все запросы должны пройти проверку подлинности и авторизацию, а также необходимо обеспечить соответствующий уровень контроля доступа.
  • В отношении коммерческого веб-API могут применяться различные гарантии качества, касающиеся времени ответа. Важно убедиться, что среда узла масштабируется, если загрузка может значительно отличаться с течением времени.
  • Возможно, запросы потребуется отслеживать для целей тарификации.
  • Может потребоваться направлять поток трафика веб-API, а также реализовать регулирование для определенных клиентов, исчерпавших свои квоты.
  • Согласно нормативным требованиям может оказаться обязательным вести журнал и аудит всех запросов и ответов.
  • Чтобы обеспечить доступность, может потребоваться отслеживать работоспособностью сервера, на котором размещен веб-API, и при необходимости перезапускать сервер.

Полезно разделить эти проблемы с техническими проблемами, связанными с реализацией веб-API. По этой причине рассмотрите возможность создания оболочки, которая запущена как отдельный процесс и направляет запросы к веб-API. Оболочка может предоставить операции управления и переадресовывать проверенные запросы к веб-API. Использование оболочки обеспечивает множество функциональных преимуществ, включая приведенные ниже.

  • Выступает в качестве точки интеграции для нескольких веб-API.
  • Преобразование сообщений и протоколов связи для клиентов, созданных с использованием различных технологий.
  • Кэширование запросов и ответов для снижения нагрузки на сервер, на котором размещен веб-API.

Тестирование веб-API

Веб-API следует тестировать так же тщательно, как и любой другой компонент программного обеспечения. Рекомендуется создать модульные тесты для проверки работы операций.

Характер веб-API привносит свои определенные требования к проверке правильности его работы. Следует уделять особое внимание указанным ниже моментам.

  • Проверьте все маршруты и убедитесь, что они вызывают все необходимые операции. Особое внимание следует уделять коду состояния HTTP 405 (метод запрещен), который может быть неожиданно возвращен, поскольку это может означать несоответствие маршрута и методов HTTP (GET, POST, PUT, DELETE), которые можно передавать по этому маршруту.

    Отправьте HTTP-запросы на маршруты, которые не поддерживают их, например отправку запроса POST в определенный ресурс (запросы POST должны отправляться только в коллекции ресурсов). В этих случаях единственным допустимым ответом должен быть код состояния 405 ("Запрещено").

  • Убедитесь, что все маршруты защищены надлежащим образом и используют необходимые процедуры проверки подлинности и авторизации.

    Примечание.

    Некоторые аспекты безопасности, такие как проверка подлинности пользователя, вероятнее всего реализуются средой размещения, а не веб-API, однако вам по-прежнему необходимо включить в процесс развертывания тесты безопасности.

  • Проверьте обработку исключений, выполняемую каждой операцией, и убедитесь, что в клиентское приложение обратно передается соответствующий и значимый ответ HTTP.

  • Убедитесь, что запросы и ответные сообщения имеют правильный формат. Например, если запрос HTTP POST содержит данные для нового ресурса в формате x-www-form-urlencoded, убедитесь, что соответствующая операция правильно анализирует данные, создает ресурсы и возвращает ответ, содержащий сведения о новом ресурсе, включая правильный заголовок Location.

  • Проверяйте все ссылки и URI в ответных сообщениях. Например, в сообщении HTTP POST должен возвращаться URI нового созданного ресурса. Все ссылки HATEOAS должно быть действительными.

  • Убедитесь, что каждая операция возвращает правильные коды состояния для различных сочетаний входных данных. Например:

    • Если запрос выполнен успешно, она должна возвращать код состояния 200 (ОК)
    • Если ресурс не найден, то операция должна возвращать код состояния HTTP "404 (не найдено)".
    • Если клиент отправляет запрос, который успешно удаляет ресурс, код состояния должен быть 204 (нет содержимого).
    • Если клиент отправляет запрос, который создает ресурс, код состояния должен быть 201 (создано).

Следует внимательно отслеживать неожиданные ответы с кодами состояния в диапазоне 5xx. Обычно эти сообщения отправляются сервером размещения для указания того, что ему не удалось выполнить допустимый запрос.

  • Проверьте различные сочетания заголовков запроса, которые клиентское приложение может указывать, и убедитесь, что веб-API возвращает в ответных сообщениях ожидаемые данные.

  • Проверьте строки запроса. Если операция может принимать необязательные параметры (такие как запросы разбивки на страницы), протестируйте различные комбинации параметров и порядок их указания.

  • Убедитесь, что асинхронные операции завершаются успешно. Если веб-API поддерживает потоковую передачу запросов, которые возвращают большие двоичные объекты (например, видео или аудио), убедитесь, что клиентские запросы не блокируются во время потоковой передачи данных. Если веб-API реализует опрос для длительных операций изменения данных, убедитесь в том, что операции надлежащим образом сообщают о своем состоянии во время выполнения.

Необходимо также создать и запустить тесты производительности для проверки того, что веб-API работает удовлетворительно в принудительном режиме. С помощью Visual Studio Ultimate можно создать проект нагрузочного теста и веб-теста производительности.

Использование управления API Azure

В Azure рекомендуется использовать службу "Управление API" Azure для публикации и управления веб-API. С помощью этого средства вы можете создать службу, которая выступает в роли оболочки для одного или нескольких веб-API. Сама служба представляет собой масштабируемую веб-службу, которую можно создать и настроить с помощью портала Azure. Эту службу можно использовать для публикации и управления веб-API, как указано ниже.

  1. Разверните веб-API для веб-сайта, облачной службы Azure или виртуальной машины Azure.

  2. Подключите службу управления API к веб-API. Запросы, отправленные на URL-адрес API управления, сопоставляются с URI в веб-API. Эта же служба управления API может направить запросы в несколько веб-API. Это позволяет объединить несколько веб-API в рамках одной службы управления. Аналогичным образом, на один и тот же веб-API можно ссылаться из нескольких служб управления API, если необходимо ограничить или разделить функциональные возможности, доступные для различных приложений.

    Примечание.

    URI в ссылках HATEOAS, созданных в рамках ответа на HTTP-запросы GET, должны ссылаться на URL-адрес службы управления API, а не веб-сервер, на котором размещается веб-API.

  3. Для каждого веб-API укажите операции HTTP, предоставляемые веб-API вместе с любыми дополнительными параметрами, которые операция может принимать в качестве входных. Можно также указать, следует ли службе управления API кэшировать ответ, полученный от веб-API, для оптимизации повторных запросов тех же данных. Запишите сведения об ответах HTTP, которые может создавать каждая операция. Эта информация используется для создания документации для разработчиков, поэтому важно, чтобы она была точной и полной.

    Операции можно задать либо вручную с помощью мастеров, имеющихся на портале Azure, либо импортировать их из файла, содержащего определения в формате WADL или Swagger.

  4. Настройте параметры безопасности для взаимодействия службы управления API с веб-сервером, на котором размещен веб-API. Служба управления API в настоящее время поддерживает обычную проверку подлинности и взаимную проверку подлинности с использованием сертификатов, а также проверку подлинности пользователей OAuth 2.0.

  5. Создайте продукт. Продукт — это единица публикации; можно добавить веб-API, ранее подключенные вами к службе управления для продукта. После публикации продукта веб-API становятся доступными для разработчиков.

    Примечание.

    Перед публикацией продукта можно также определить группы пользователей, которые могут получить доступ к продукту, и добавить пользователей в эти группы. Это позволит вам получить контроль над разработчиками и приложениями, использующими веб-API. Если веб-API необходимо утвердить, то прежде чем получить доступ к нему разработчик должен отправить запрос администратору продукта. Администратор может предоставить или запретить доступ для разработчика. В случае если обстоятельства изменились, можно также заблокировать существующих разработчиков.

  6. Настройте политики для каждого веб-API. Политики управляют такими аспектами, как выполнение междоменных вызовов, способ проверки подлинности клиентов, выполнение прозрачного преобразования между форматами данных XML и JSON, ограничение вызовов из заданного диапазона IP-адресов, квоты на использование, а также ограничение скорости вызова. Политики могут применяться глобально для всего продукта, для одного веб-API в продукте или для отдельных операций в веб-API.

Дополнительные сведения см. в документации по Управлению API.

Совет

Azure предоставляет диспетчер трафика Azure, который позволяет реализовать отказоустойчивость и балансировку нагрузки, а также уменьшить задержку между несколькими экземплярами веб-сайта, размещенного в различных географических регионах. Диспетчер трафика Azure можно использовать в сочетании со службой управления API; последняя может направлять запросы в экземпляры веб-сайта через диспетчер трафика Azure. Дополнительные сведения см. в статье Методы маршрутизации диспетчера трафика.

В этой структуре, если вы используете пользовательские DNS-имена для веб-сайтов, необходимо настроить соответствующую запись CNAME для каждого веб-сайта, чтобы указать DNS-имя веб-сайта Диспетчер трафика Azure.

Поддержка разработчиков на стороне клиента

Для разработчиков, занимающихся созданием клиентских приложений, обычно требуются сведения о том, как получить доступ к веб-API, и документация, касающаяся параметров, типов данных, возвращаемых типов и кодов возврата, которые описывают различные запросы и ответы между веб-службой и клиентским приложением.

Документирование операций REST для веб-API

В состав службы управления API Azure входит портал для разработчиков, на котором представлены описания операций REST, предоставляемых веб-API. Если продукт был опубликован, он отображается на этом портале. Разработчики могут использовать этот портал для регистрации доступа; после чего администратор может утвердить или отклонить запрос. Если разработчик утвержден, ему назначается ключ подписки, который используется для проверки подлинности вызовов из разрабатываемых им клиентских приложений. Этот ключ необходимо предоставлять при каждом вызове веб-API, в противном случае вызов будет отклонен.

На этом портале также предоставлено следующее:

  • документация по продукту, список операций, которые он предоставляет, необходимые параметры и разные ответы, которые могут быть возвращены; обратите внимание, что эта информация основывается на данных, указанных на шаге 3 в списке в разделе "Публикация и управление веб-API с помощью службы управления API Azure";
  • фрагменты кода, которые показывают, как вызывать операции на нескольких языках программирования, включая JavaScript, C#, Java, Ruby, Python и PHP;
  • консоль разработчика, с помощью которой разработчики могут отправлять запрос HTTP для проверки каждой операции в продукте и просмотра результатов;
  • страница, где разработчик может сообщить об обнаруженных ошибках и проблемах.

На портале Azure можно настроить портал разработчика: изменить стиль оформления и макет с учетом фирменной символики организации.

Реализация клиентского пакета SDK

Для создания клиентского приложения, которое вызывает запросы REST на доступ к веб-API, требуется значительный объем кода для написания каждого запроса и форматирования его соответствующим образом, отправки запроса на сервер, на котором размещена веб-службы, синтаксического анализа ответа, вычисляющего то, успешно ли был выполнен запрос или завершился сбоем, а также извлечения всех возвращенных данных. Чтобы исключить в клиентском приложении эти проблемы, можно предоставить пакет SDK, который служит оболочкой для интерфейса REST и абстрагирует эти низкоуровневые элементы внутри более функционального набора методов. Клиентское приложение использует эти методы, которые прозрачно преобразовывают вызовы в запросы REST, а ответы — обратно в возвращаемые методом значения. Это распространенный метод, реализуемый многими службами, включая пакет Azure SDK.

Создание клиентского пакета SDK — довольно сложная задача, поскольку его следует согласованно и тщательно реализовывать и тестировать. Однако большая часть этого процесса может выполняться механически, а многие поставщики предлагают средства, позволяющие автоматизировать выполнение многих из этих задач.

Наблюдение за веб-API

В зависимости от способа публикации и развертывания веб-API можно отслеживать веб-API напрямую или организовать сбор сведений об использовании и работоспособности путем анализа трафика, проходящего через службу управления API.

Наблюдение за веб-API напрямую

При реализации веб-API с помощью шаблона веб-API ASP.NET (либо в качестве проекта веб-API, либо в виде веб-роли в облачной службе Azure) и Visual Studio 2013 вы можете организовать сбор данных о доступности, производительности и использовании данных с помощью ASP.NET Application Insights. Application Insights — это пакет, который прозрачно отслеживает и записывает сведения о запросах и ответах, когда веб-API развернут в облаке. После установки и настройки пакета вам не нужно вносить изменения в код веб-API, чтобы использовать его. При развертывании веб-API на веб-сайте Azure проверяется весь трафик, и собирается следующая статистика:

  • время ответа от сервера;
  • число запросов к серверу и сведения о каждом запросе;
  • самые медленные запросы по среднему времени ответа;
  • сведения обо всех неудачных запросах;
  • число сеансов, инициализированных в различных браузерах и различными агентами пользователя;
  • наиболее часто просматриваемые страницы (в основном полезно для веб-приложений, а не для веб-API);
  • сведения о различных ролях пользователей, работающих с веб-API.

Эти данные можно просматривать в реальном времени на портале Azure. Можно также создать веб-тесты, наблюдающие за работоспособностью веб-API. Веб-тест периодически отправляет запрос на указанный URI в веб-API и записывает ответ. Можно указать определение успешного ответа (например, код состояния HTTP 200), и если запрос не возвращает этот ответ, можно настроить оповещение для отправки администратору. При необходимости администратор может перезапустить сервер, на котором размещен веб-API, если произошел сбой.

Дополнительные сведения см. в статье о начале работы с ASP.NET в Application Insights.

Наблюдение за веб-API через службу управления API

Если веб-API был опубликован с помощью службы "Управление API", то на странице "Управление API" на портале Azure имеется панель мониторинга, с помощью которой можно ознакомиться с общей картиной производительности службы. На странице аналитики можно детализировать подробные сведения об использовании продукта. На этой странице представлены следующие вкладки:

  • Использование. На этой вкладке содержатся сведения о количестве вызовов API и пропускной способности, необходимой для обработки этих вызовов по времени. Можно отфильтровать сведения об использовании по продуктам, API и операциям.
  • Работоспособность. На этой вкладке вы можете просмотреть результаты запросов API (возвращенные коды состояния HTTP), сведения об эффективности политики кэширования, о времени ответа API и времени ответа службы. На этой вкладке можно так же фильтровать данные работоспособности по продуктам, API и операциям.
  • Действие . На этой вкладке представлена сводная информация в текстовом виде о количестве успешных вызовов, неудачных вызовов и заблокированных вызовов, о среднем времени отклика и времени отклика для каждого продукта, веб-API и каждой операции. На этой странице также указано количество вызовов, совершенных каждым разработчиком.
  • Краткий обзор. На этой вкладке представлена сводка по данным производительности, включая сведения о разработчиках, ответственных за совершение большинства вызовов API, а также информация о продуктах, веб-API и операциях, которые получили эти вызовы.

Эти сведения можно использовать для определения того, является ли определенный веб-API или операция узким местом, чтобы при необходимости можно было масштабировать среду размещения и добавить дополнительные серверы. Можно также получить представление о том, имеются ли приложения, которые используют неограниченный объем ресурсов, и применить соответствующие политики для установления квот и ограничений на скорость вызова.

Примечание.

Можно изменить данные для опубликованных продуктов. Изменения при этом вступают в силу сразу же. Например, можно добавить или удалить операции из веб-API без необходимости повторной публикации продукта, который содержит этот веб-API.

Следующие шаги