Джерси Асинхронный ContainerRequestFilter

У меня есть REST API Джерси, и я использую ContainerRequestFilter для авторизации. Я также использую @ManagedAsync на всех конечных точках, чтобы мой API мог обслуживать тысячи одновременных запросов.

Мой фильтр авторизации обращается к удаленной службе, но когда фильтр запущен, Джерси еще не добавил текущий поток к своему внутреннему ExecutorService, поэтому я полностью теряю преимущества асинхронности.

Могу ли я сказать Джерси, что хочу, чтобы этот ContainerRequestFilter был асинхронным?

@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter
{
    @Inject
    private AuthorizationService authSvc;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException
    {
        String authToken = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

        // HITS A REMOTE SERVER
        AuthorizationResponse authResponse = authSvc.authorize(authToken);

        if (!authResponse.isAuthorized())
        {
            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                    .entity("unauthorized!")
                    .build());
        }
    }
}

И вот пример ресурса:

@Path("/stuff")
@Produces(MediaType.APPLICATION_JSON)
public class StuffResource
{
    @GET
    @Path("/{id}")
    @ManagedAsync
    public void getById(@PathParam("id") long id, @Suspended final AsyncResponse ar)
    {
        Stuff s;

        // HIT THE DATABASE FOR STUFF

        ar.resume(s);
    }
}

ОБНОВЛЕНИЕ Только что получил ответ от ребят из Джерси, и это невозможно с версии 2.7. Асинхронно вызывается только сам метод ресурса, а не фильтры. Любые предложения по продолжению приветствуются.


person Alden    schedule 12.12.2013    source источник
comment
Ребята из Джерси сказали, будут ли в будущем поддерживаться асинхронные фильтры?   -  person Paul Bellora    schedule 19.12.2013
comment
Они сказали добавить запрос функции и что потребуется некоторый рефакторинг :(. В краткосрочной перспективе я просто вызываю метод для авторизации в верхней части каждой конечной точки ресурса, что действительно уродливо, но выполняет свою работу. Я Я рассматриваю возможность использования Guice и АОП, но, очевидно, хотел бы как-то сделать это на Джерси. Я удивлен, что это не появилось раньше, в основном @ManagedAsync бесполезен, если у вас есть какие-либо фильтры...   -  person Alden    schedule 19.12.2013
comment
@Alden ContainerRequestFilter все еще плохо работает с @ManagedAsync? Я искал это все и не могу найти никакой информации, связанной с этим. Думаю, HK2 по-прежнему лучший подход, верно? 5 лет спустя...   -  person igracia    schedule 12.04.2018
comment
@igracia я не уверен. Примерно через год после публикации этого вопроса мы смогли значительно улучшить производительность API, переработав настройку Guice, и решили полностью прекратить использование @ManagedAsync. С тех пор прошло почти 4 года, и мы не оглядывались назад — теперь у нас есть много API-интерфейсов Джерси с высокой пропускной способностью, которые бесперебойно работают в производстве.   -  person Alden    schedule 03.08.2018


Ответы (3)


Это не встроено в Джерси с версии 2.7.

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

ОБНОВЛЕНИЕ - есть и другие способы...

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

Guice AOP – ошибка

Я использую Guice для DI (внедрение Guice для работы с Джерси — это подвиг сам по себе!), поэтому я решил, что могу использовать Guice AOP, чтобы обойти эту проблему. Хотя внедрение Guice работает, невозможно заставить Guice создавать классы ресурсов с помощью Джерси 2, поэтому Guice AOP не может работать с методами классов ресурсов. Если вы отчаянно пытаетесь заставить Guice создавать классы ресурсов с помощью Jersey 2, не тратьте время понапрасну, потому что это не сработает. Это хорошо известная проблема.

HK2 AOP — РЕКОМЕНДУЕМОЕ РЕШЕНИЕ

HK2 недавно выпустила функцию AOP, см. этот вопрос, чтобы узнать, как заставить ее работать.

Мониторинг — тоже работает

Это не для слабонервных, и это совершенно не рекомендуется в документах по Джерси. . Вы можете зарегистрировать и ApplicationEventListener и переопределить onRequest, чтобы вернуть RequestEventListener, который прослушивает RESOURCE_METHOD_START и вызывает службу аутентификации/авторизации. Это событие запускается из потока @ManagedAsync, что и является здесь всей целью. Одно предостережение: метод abortWith не работает, поэтому он не будет работать так же, как обычный ContainerRequestFilter. Вместо этого вы можете создать исключение, если аутентификация не удалась, и зарегистрировать ExceptionMapper для обработки вашего исключения. Если кто-то достаточно смелый, чтобы попробовать, дайте мне знать, и я опубликую код.

person Alden    schedule 22.12.2013

Я не уверен, что это то, что вы искали, но заглядывали ли вы в Spring OncePerRequestFilter? В настоящее время я использую его для своего уровня авторизации, где каждый запрос проходит через некоторый фильтр, который расширяет этот OncePerRequestFilter в зависимости от того, как мои фильтры сопоставляются с URL-адресами. Вот краткий обзор того, как я его использую:

Аутентификация/авторизация ресурс в Dropwizard

Я не очень хорошо разбираюсь в частях асинхронной отправки этих фильтров, но я надеюсь, что эта ссылка хотя бы прольет свет на то, чего вы пытаетесь достичь!

person shahshi15    schedule 19.12.2013
comment
Мне это не кажется асинхронным. Вы когда-нибудь использовали OncePerRequestFilter асинхронно со Spring? - person Alden; 20.12.2013
comment
Я еще немного прочитал, и мне было интересно, можете ли вы прояснить некоторые вещи? Согласен, Джерси еще не добавил текущий поток в свой внутренний ExecutorService. Но по умолчанию каждый HTTP-запрос уже обрабатывается в отдельном потоке (конечно, из заранее определенного пула потоков) самим веб-сервером. Посмотрите здесь: ссылка. Если вы не хотите, чтобы jax-rs запускал каждый запрос в отдельном потоке (из собственного пула потоков), я не вижу в этом прямой необходимости. Я думаю, настройка вашего веб-сервера должна быть достаточно хорошей. - person shahshi15; 20.12.2013
comment
И почему вы вообще хотите, чтобы ваш фильтр авторизации был асинхронным? Что бы вы вернули в качестве ответа до фактического завершения авторизации? Вызов фильтра должен быть синхронизирован. Вам нужно подождать, пока служба аутентификации вернет Accepted или Denied, и на основе этого вы можете перенаправить вызов асинхронного ресурса или вернуть 401 Unauthorized. Короче говоря, асинхронный вызов ресурса имеет смысл (если выполнение этого вызова занимает много времени), но вызов асинхронного фильтра не имеет смысла. Просто мои 2 цента. - person shahshi15; 20.12.2013
comment
вы правы в том, что веб-сервер обслуживает каждый запрос в другом потоке из своего пула потоков. Стандартная проблема заключается в том, что ваш веб-сервер имеет в своем распоряжении, скажем, 20 потоков. если приходит запрос, используйте поток из пула для его обслуживания (теперь осталось 19), а когда он будет выполнен, добавьте поток обратно в пул. Но если этот запрос занимает много времени, а куча других запросов занимает много времени, ваш веб-сервер теперь тратит много ресурсов на их обслуживание и имеет ограниченный пул потоков. смысл отдельного ExecutorService именно в том, что веб-сервер освобождается для обслуживания многих - person Alden; 20.12.2013
comment
Запросы. Я говорю о более чем 1000 одновременных запросов. Я не хочу вдаваться в подробности того, как может работать асинхронная аутентификация, но в основном вы говорите веб-серверу, что он может продолжать и обслуживать больше запросов, но будьте осторожны, потому что позже я буду обслуживать ответ на этот конкретный запрос. TLDR; для этого есть очень веские причины, прочитайте об этом, если вам интересно. - person Alden; 20.12.2013

Мы используем безопасность Spring для аутентификации/авторизации. Я решил проблему, используя локатор подресурсов с пустым путем, как показано ниже:

@Path("/customers")
public class CustomerResource {
    @Inject
    private CustomerService customerService;

    @Path("")
    public CustomerSubResource delegate() {
        final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return new CustomerSubResource(auth);
    }

    public class CustomerSubResource {
        private final Authentication auth;           

        public CustomerSubResource(final Authentication auth) {
            this.auth = auth;
        }

        @POST
        @Path("")
        @Produces(MediaType.APPLICATION_JSON)
        @Consumes(MediaType.APPLICATION_JSON)
        @ManagedAsync
        public void createCustomer(final Customer customer, @Suspended final AsyncResponse response) {
            // Stash the Spring security context into the Jersey-managed thread
            SecurityContextHolder.getContext().setAuthentication(this.auth);

            // Invoke service method requiring pre-authorization
            final Customer newCustomer = customerService.createCustomer(customer);

            // Resume the response
            response.resume(newCustomer);
        }
    }
}
person Savitha Ganapathi    schedule 31.12.2013