杰西2:如何创建自定义HTTP参数绑定

17
我正在尝试为我的RESTful服务创建自定义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注释。我的目的是除了标准的javax.ws.rs.*Param之外,拥有自定义的http参数绑定。

我尝试实现org.glassfish.hk2.api.InjectionResolver,它基本上从http头中提取值:

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

    return "Hello World";
}
...

}

当我调用RESTful服务时,服务器出现以下异常。这表明框架无法解析函数签名中的参数:

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

请帮忙。感谢任何建议。我在谷歌上进行了大量搜索,但无法使其正常工作。Jersey 2.*. 如何替换Jersey 1.*的InjectableProvider和AbstractHttpContextInjectable可能是类似的问题。

--更新: 我使用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");

}

}

谢谢!


@Service在哪里? - Mingtao Zhang
@MingtaoZhang 我注册了一个AbstractBinder。请编辑我的问题以添加这些细节。 - yzandrew
你使用的是哪个版本的Jersey jar?你是在任何应用服务器或Grizzly上运行它吗? - Dhana Krishnasamy
@DhanaKrishnasamy 我正在使用带有Grizzly的Jersey 2.0。 - yzandrew
1
@yzandrew 为什么你想要创建一个自定义的 Http 参数?也许你可以使用 @HeaderParam 并通过 HTTP 标头将 UserAuth 值传递到你的 REST API 中? - herau
@herau 感谢您的评论。我实际需要的是将所有的HTTP头绑定到一个对象中。任何实现此目标的解决方案都将不胜感激。 - yzandrew
4个回答

15

如果你只是想将值直接从请求头传递给方法,那么你不需要创建自定义注解。假设你有一个叫做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 Provider(这是您将访问标头参数的位置 - 请参阅我的评论):

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

以及一个配置文件的Binder

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

我希望这能帮到你。


谢谢,非常有用的帖子!只有一个问题,我在Jersey2.6中找不到AbstractHttpContextValueFactory。你知道我们可以用什么替代吗?再次感谢! - Christian Sisti
@ChristianSisti 谢谢,它实际上是Jersey的一个内部类,所以在我的示例代码中使用它可能不是最明智的想法。您可以查看原始Jersey代码,并自己编写这样的类 - 它相当简单。 - lpiepiora
@lpiepiora 我明白了,谢谢。我成功地使用了AbstractContainerRequestValueFactory使我的代码工作,但是它是Jersey的内部类。提供我们自己的实现会更好。 - Christian Sisti
@ChristianSisti 我的意思是让你提供自己的实现,我只是链接了另一个类,这样你就可以看到它是如何完成的。 - lpiepiora
类UserAuthHeaderParamValueFactoryProvider的导入包是什么? - Ahmet Karakaya
显示剩余4条评论

2
我不知道如何解决你的异常。但是,我可以向你提出另一种做同样事情的不同方法。希望它能有所帮助。
我也遇到了完全相同的问题:我需要在http头中添加额外的参数(顺便说一下,这也与身份验证有关)。此外,由于我想要进行“典型的”rest实现,而不维护会话,因此我需要在每次调用时发送它们。
我正在使用Jersey 2.7-但我想它应该在2.0中工作。我遵循了他们的文档。 https://jersey.java.net/documentation/2.0/filters-and-interceptors.html 那里非常清楚,但无论如何,我在下面复制粘贴了我的实现。它运行良好。确实还有其他保护rest服务的方法,例如这是一个很好的方法: 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

    }

}

希望能有所帮助!

谢谢你的解决方案!我必须坚持使用自定义绑定。请查看我的评论以获取详细原因。然而,你的解决方案可能会帮助其他遇到类似问题的人。 - yzandrew

1
如果您需要检索绑定到一个对象的所有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);
   }
}

谢谢您的回复。我认为自定义绑定对我来说是更好的选择。我可以将HTTP头/查询参数/路径参数绑定到Protobuff对象中,然后将其作为业务逻辑中模型对象的一部分使用。 - yzandrew

0

这是我的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");
                }

            };
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接