Нужен ли метод OPTIONS в REST-сервисах?

Автор: admin от 16-11-2017, 06:05, посмотрело: 400

Согласно стандарту HTTP/1.1 метод OPTIONS может быть использован клиентом для определения параметров или требований, связанных с ресурсом. Сервер также может отправлять документацию в удобочитаемом формате. Ответ на запрос OPTIONS может содержать список допустимых методов для данного ресурса в хедере Allow.



То есть этот метод мог бы стать отличным средством для документирования наших REST-сервисов с одной стороны, и быть существенным дополнением к архитектурному ограничению HATEOAS с другой.



А теперь давайте отвлечёмся от страшных слов типа “HATEOAS” и зададимся вопросом: а есть ли какая-нибудь практическая польза от использования метода OPTIONS в веб-приложениях?

HTTP Message Handlers.



Унаследуем наш новый обработчик от класса DelegatingHandler, перегрузим метод SendAsync и добавим новую функциональность как продолжение таска. Это важно, поскольку мы хотим запустить вначале базовый механизм маршрутизации. В таком случае переменная request будет содержать все необходимые свойства.



protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    return await base.SendAsync(request, cancellationToken).ContinueWith(
        task =>
        {
            var response = task.Result;
            if (request.Method == HttpMethod.Options)
            {
                var methods = new Actionselector(request).GetSupportedMethods();
                if (methods != null)
                {
                    response = new HttpResponseMessage(HttpStatusCode.OK)
                    {
                        Content = new StringContent(string.Empty)
                    };

                    response.Content.Headers.Add("Allow", methods);
                    response.Content.Headers.Add("Allow", "OPTIONS");
                }
            }

            return response;
        }, cancellationToken);
}


Класс Actionselector пытается найти подходящий контроллер для запроса в конструкторе. Если контроллер не найден, метод GetSupportedMethods вернёт null. Функция IsMethodSupported подменяет текущий запрос в контексте для того, чтобы найти action-метод по заданному имени. Блок finally восстанавливает RouteData, поскольку вызов _apiSelector.SelectAction может изменить это свойство контекста.



private class Actionselector
{
    private readonly HttpRequestMessage _request;
    private readonly HttpControllerContext _context;
    private readonly ApiControllerActionselector _apiSelector;
    private static readonly string[] Methods =
        { "GET", "PUT", "POST", "PATCH", "DELETE", "HEAD", "TRACE" };

    public Actionselector(HttpRequestMessage request)
    {
        try
        {
            var configuration = request.GetConfiguration();
            var requestContext = request.GetRequestContext();

            var controllerDescriptor =
                new DefaultHttpControllerSelector(configuration)
                    .SelectController(request);

            _context = new HttpControllerContext
            {
                Request = request,
                RequestContext = requestContext,
                Configuration = configuration,
                ControllerDescriptor = controllerDescriptor
            };
        }
        catch
        {
            return;
        }

        _request = _context.Request;
        _apiSelector = new ApiControllerActionselector();
    }

    public IEnumerable<string> GetSupportedMethods()
    {
        return _request == null ? null : Methods.Where(IsMethodSupported);
    }

    private bool IsMethodSupported(string method)
    {
        _context.Request = new HttpRequestMessage(
            new HttpMethod(method), _request.RequestUri);
        var routeData = _context.RouteData;

        try
        {
            return _apiSelector.SelectAction(_context) != null;
        }
        catch
        {
            return false;
        }
        finally
        {
            _context.RouteData = routeData;
        }
    }
}


Финальный шаг – это добавление нашего обработчика в конфигурацию в startup-коде:



configuration.MessageHandlers.Add(new OptionsHandler());


Чтобы всё корректно работало, необходимо явно указывать типы параметров. Если используется атрибут Route, тип надо указывать прямо в нём:



[Route("Books/{id:long}", Name = "GetBook")]


Без явного указания типа путь Books/abcd будет рассматриваться как корректный.



Итак, теперь мы имеем реализацию OPTIONS для всех поддерживаемых Uri в сервисе. Однако, это всё ещё не идеальное решение. Описанный подход никак не учитывает авторизацию. Если мы используем маркеры доступа и каждый вызов сервиса предусматривает наличие текущего принципала, то его права никак не учитываются и ценность метода OPTIONS резко снижается. Для любого пользователя клиент всегда будет получать один и тот же список допустимых HTTP методов независимо от его прав.



Кроме того, сервис будет отвечать на запрос с кодом 200 OK даже если Uri содержит неверный идентификатор ресурса, например, /books/0.



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



Финальный код





Авторский перевод

Источник: Хабрахабр

Категория: Информационная безопасность

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
  • bowtiesmilelaughingblushsmileyrelaxedsmirk
    heart_eyeskissing_heartkissing_closed_eyesflushedrelievedsatisfiedgrin
    winkstuck_out_tongue_winking_eyestuck_out_tongue_closed_eyesgrinningkissingstuck_out_tonguesleeping
    worriedfrowninganguishedopen_mouthgrimacingconfusedhushed
    expressionlessunamusedsweat_smilesweatdisappointed_relievedwearypensive
    disappointedconfoundedfearfulcold_sweatperseverecrysob
    joyastonishedscreamtired_faceangryragetriumph
    sleepyyummasksunglassesdizzy_faceimpsmiling_imp
    neutral_faceno_mouthinnocent