Jersey 2: Как создать настраиваемую привязку параметров HTTP

Я пытаюсь создать настраиваемое связывание параметров http для моей службы. См. Пример ниже.

@POST
@Path("/user/{userId}/orders")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(@PathParam("userId") String someString, @UserAuthHeaderParam String authString){

}

Вы можете видеть, что в сигнатуре функции есть аннотация UserAuthHeaderParam. То, что я хочу сделать, - это привязка к параметру http, отличная от стандартного javax.ws.rs. * Param.

Я попытался реализовать org.glassfish.hk2.api.InjectionResolver, которые в основном извлекают значение из заголовка http:

public class ProtoInjectionResolver implements InjectionResolver<UserAuthHeaderParam>{
...
@Override
public Object resolve(Injectee injectee, ServiceHandle< ? > root)
{

    return "Hello World";
}
...

}

Когда я звоню в службу поддержки, сервер получает исключения. Это указывает на то, что структура не может разрешить параметр в сигнатуре функции:

org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyResource,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2136594195), 

java.lang.IllegalArgumentException: While attempting to resolve the dependencies of rs.server.MyResource errors were found

Пожалуйста, помогите. Любые рекомендации приветствуются. Я делаю много поиска в google, но не могу заставить его работать. Джерси 2. *. Как заменить InjectableProvider и AbstractHttpContextInjectable of Jersey 1. * может быть похожий вопрос.

- ОБНОВЛЕНИЯ: Я использую AbstractBinder для привязки своего резольвера к UserAuthHeaderParam:

public class MyApplication extends ResourceConfig
{

public MyApplication()
{
    register(new AbstractBinder()
    {
        @Override
        protected void configure()
        {
            // bindFactory(UrlStringFactory.class).to(String.class);
            bind(UrlStringInjectResolver.class).to(new TypeLiteral<InjectionResolver<UrlInject>>()
            {
            }).in(Singleton.class);
        }
    });
    packages("rs");

}

}

Спасибо!

Ответы

Ответ 1

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

@GET
public String authFromHeader(@HeaderParam("Authorization") String authorization) {
    return "Header Value: " + authorization + "\n";
}

Вы можете протестировать его, вызвав curl, например.

$ curl --header "Authorization: 1234" http://localhost:8080/rest/resource
Header Value: 1234

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

Сначала вы должны объявить свою аннотацию следующим образом:

@java.lang.annotation.Target(PARAMETER)
@java.lang.annotation.Retention(RUNTIME)
@java.lang.annotation.Documented
public @interface UserAuthHeaderParam {
}

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

@Singleton
public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {

    @Inject
    protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
        super(mpep, locator, Parameter.Source.UNKNOWN);
    }

    @Override
    protected Factory<?> createValueFactory(Parameter parameter) {
        Class<?> classType = parameter.getRawType();

        if (classType == null || (!classType.equals(String.class))) {
            return null;
        }

        return new AbstractHttpContextValueFactory<String>() {
            @Override
            protected String get(HttpContext httpContext) {
                // you can get the header value here
                return "testString";
            }
        };
    }
}

Теперь объявите преобразователь впрыска

public class UserAuthHeaderParamResolver extends ParamInjectionResolver<UserAuthHeaderParam> {
    public UserAuthHeaderParamResolver() {
        super(UserAuthHeaderParamValueFactoryProvider.class);
    }
}

и связующее для вашей конфигурации

public class HeaderParamResolverBinder extends AbstractBinder {

    @Override
    protected void configure() {
        bind(UserAuthHeaderParamValueFactoryProvider.class)
                .to(ValueFactoryProvider.class)
                .in(Singleton.class);

        bind(UserAuthHeaderParamResolver.class)
                .to(new TypeLiteral<InjectionResolver<UserAuthHeaderParam>>() {})
                .in(Singleton.class);
    }
}

теперь последнее, в вашем ResourceConfig добавьте register(new HeaderParamResolverBinder()), как этот

@ApplicationPath("rest")
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        register(new HeaderParamResolverBinder());
        packages("your.packages");
    }
}

Учитывая, что теперь вы можете использовать значение, которое вам нужно:

@GET
public String getResult(@UserAuthHeaderParam String param) {
    return "RESULT: " + param;
}

Надеюсь, это поможет.

Ответ 2

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

Я столкнулся с одной и той же проблемой: мне нужны дополнительные параметры в заголовке http (кстати, также связанные с аутентификацией). Кроме того, мне нужно отправить их при каждом вызове, так как я хочу выполнить "типичную" реализацию отдыха, не поддерживая сеанс.

Я использую Jersey 2.7 - но я бы сказал, что он должен работать в версии 2.0. Я следил за их документацией https://jersey.java.net/documentation/2.0/filters-and-interceptors.html

Здесь совершенно ясно, но в любом случае я копирую мою реализацию ниже. Он работает нормально. Правда есть и другие способы обеспечения службы отдыха, например, это хорошо: http://www.objecthunter.net/tinybo/blog/articles/89

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

Копировать-вставить: я определил фильтр для аутентификации, который применяется к каждому вызову и выполняется перед службой (благодаря @PreMatching).

@PreMatching
public class AuthenticationRequestFilter implements ContainerRequestFilter {

    @Override
    public void filter(final ContainerRequestContext requestContext) throws IOException {
        final MultivaluedMap<String, String> headers = requestContext.getHeaders();
        if (headers == null) {
            throw new...
        }

        // here I get parameters from the header, via headers.get("parameter_name")
        // In particular, I get the profile, which I plan to use as a Jersey role
        // then I authenticate
        // finally, I inform the Principal and the role in the SecurityContext object, so that I can use @RolesAllowed later
        requestContext.setSecurityContext(new SecurityContext() {

            @Override
            public boolean isUserInRole(final String arg0) {
                //...
            }

            @Override
            public boolean isSecure() {
                //...
            }

            @Override
            public Principal getUserPrincipal() {
                //...
            }

            @Override
            public String getAuthenticationScheme() {
                //...
            }
        });

    }

}

Вы должны включить этот класс фильтра в свою реализацию ResourceConfig,

public class MyResourceConfig extends ResourceConfig {

    public MyResourceConfig() {

        // my init
        // my packages
        register(AuthenticationRequestFilter.class); // filtro de autenticación
        // other register

    }

}

Надеюсь, что это поможет!

Ответ 3

Если вам нужно получить все привязки заголовков HTTP к одному объекту, решение может заключаться в использовании аннотации @Context для получения javax.ws.rs.core.HttpHeaders; который содержит список всех заголовков запросов.

@POST
@Path("/user/{userId}/orders")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public MyResult foo(@PathParam("userId") String someString, @Context HttpHeaders headers){
 // You can list all available HTTP request headers via following code :
   for(String header : headers.getRequestHeaders().keySet()){
     System.out.println(header);
   }
}

Ответ 4

вот моя фактическая реализация класса UserAuthHeaderParamValueFactoryProvider

import javax.inject.Inject;
import javax.inject.Singleton;

import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;

    import org.glassfish.jersey.server.model.Parameter;

    @Singleton
    public class UserAuthHeaderParamValueFactoryProvider extends AbstractValueFactoryProvider {

        @Inject
        protected UserAuthHeaderParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) {
            super(mpep, locator, Parameter.Source.UNKNOWN);
        }

        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
            Class<?> classType = parameter.getRawType();

            if (classType == null || (!classType.equals(String.class))) {
                return null;
            }

            return new AbstractContainerRequestValueFactory<String>() {
                @Override
                public String provide() {
                    //you can use get any header value.
                    return getContainerRequest().getHeaderString("Authorization");
                }

            };
        }