可以使用ResteasyClient(代理框架)进行自定义方法处理吗?

时间:2015-11-25 08:13:18

标签: jax-rs resteasy

是否可以使用类似于服务器端的ResteasyClient (Proxy Framework)注册DynamicFeature?

类似于此:

final ResteasyClient client = new ResteasyClientBuilder().build();
client.register(new MyDynamicFeature());

MyDynamicFeature实现DynamicFeature的地方

我试图弄清楚如何让ClientResponseFilter检查http返回状态,具体取决于资源方法中存在的注释,而DynamicFeature似乎是最有希望获得对ResourceInfo的访问权限的导致

基本上,我想做这样的事情:

@POST
@Path("some/path/user")
@ExpectedHttpStatus(201) // <- this would have to be passed on somehow as expectedStatus
User createUser(User request);

然后在ClientResponseFilter(或任何其他解决方案)中使用以下内容:

@Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
    if (responseContext.getStatus() != expectedStatus) {
        // explode
    }
}

因为在ClientResponseFilter中,我没有看到任何方法来了解定义过滤器当前正在分析的REST调用的资源方法是什么。

现在的问题是,框架现在只检查响应状态是否成功,它不会检查它是200还是201,我们还想改进它。

以下是一些似乎解释非常相似的文章,但这似乎与使用ClientResponseFilter / ResteasyClient无关:

2 个答案:

答案 0 :(得分:0)

无法在您的客户端注册DynamicFeature

请参阅DynamicFeature documentation

  

用于动态注册后匹配的JAX-RS元提供程序   在部署时设置JAX-RS应用程序期间的提供程序。   JAX-RS运行时使用动态功能来注册提供者   应适用于特定的资源类和方法   覆盖在any上定义的任何基于注释的绑定定义   注册资源过滤器或拦截器实例。

     

实现此接口的提供者可以使用@Provider进行注释   注释,以便在扫描时由JAX-RS运行时发现   对于资源和提供者。 仅支持此提供程序类型   Server API的一部分

JAX-RS Client API可用于使用在HTTP协议之上公开的任何Web服务,并且不限于使用JAX-RS实现的服务。

请注意,JAX-RS客户端API 不会直接调用资源类。相反,它会向服务器生成HTTP请求。因此,您无法从资源类中读取注释。

更新1

我不确定这对您是否有用,但由于您希望从客户端访问服务器资源类,因此提及Jersey提供proxy-based client API({{ {3}}包)。

基本思想是你可以附加org.glassfish.jersey.client.proxy,然后通过服务器端的资源类实现该接口,同时通过使用{{3动态生成该实现,在客户端重用相同的接口调用正确的低级客户端API方法。

此示例摘自standard JAX-RS annotations to an interface

  

考虑在http://localhost:8080公开资源的服务器。可以通过以下界面描述资源:

     
@Path("myresource")
public interface MyResourceIfc {

    @GET
    @Produces("text/plain")
    String get();

    @POST
    @Consumes("application/xml")
    @Produces("application/xml")
    MyBean postEcho(MyBean bean);

    @GET
    @Path("{id}")
    @Produces("text/plain")
    String getById(@PathParam("id") String id);
}
     

您可以使用java.lang.reflect.Proxy中定义的Jersey documentation类来使用此接口访问服务器端资源。这是一个例子:

     
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080/");
MyResourceIfc resource = WebResourceFactory.newResource(MyResourceIfc.class, target);

String responseFromGet = resource.get();
MyBean responseFromPost = resource.postEcho(myBeanInstance);
String responseFromGetById = resource.getById("abc");

我不确定RESTEasy是否提供类似的内容。

更新2

RESTEasy还提供WebResourceFactory。请参阅this package

  

RESTEasy有一个客户端代理框架,允许您使用JAX-RS注释在远程HTTP资源上调用。它的工作方式是编写Java接口并在方法和接口上使用JAX-RS注释。例如:

     
public interface SimpleClient {

    @GET
    @Path("basic")
    @Produces("text/plain")
    String getBasic();

    @PUT
    @Path("basic")
    @Consumes("text/plain")
    void putBasic(String body);

    @GET
    @Path("queryParam")
    @Produces("text/plain")
    String getQueryParam(@QueryParam("param") String param);

    @GET
    @Path("matrixParam")
    @Produces("text/plain")
    String getMatrixParam(@MatrixParam("param") String param);

    @GET
    @Path("uriParam/{param}")
    @Produces("text/plain")
    int getUriParam(@PathParam("param") int param);
}
     

RESTEasy有一个基于Apache HttpClient的简单API。您生成代理然后您可以调用代理上的方法。调用的方法根据您对方法进行注释并发布到服务器的方式转换为HTTP请求。以下是您的设置方法:

     
Client client = ClientFactory.newClient();
WebTarget target = client.target("http://example.com/base/uri");
ResteasyWebTarget rtarget = (ResteasyWebTarget) target;

SimpleClient simple = rtarget.proxy(SimpleClient.class);
simple.putBasic("hello world");
     

或者,您可以直接使用RESTEasy客户端扩展接口:

     
ResteasyClient client = new ResteasyClientBuilder().build();
ResteasyWebTarget target = client.target("http://example.com/base/uri");

SimpleClient simple = target.proxy(SimpleClient.class);
simple.putBasic("hello world");
     

[...]

     

该框架还支持JAX-RS定位器模式,但在客户端。因此,如果您的方法仅使用proxy framework进行注释,则该代理方法将返回该方法返回的接口的新代理。

     

[...]

     

通常可以在客户端和服务器之间共享接口。在这种情况下,您只需要让JAX-RS服务实现带注释的接口,然后重用相同的接口来创建在客户端调用的客户端代理。

更新3

由于您已经在使用documentation,假设您的服务器资源实现了用于创建客户端代理的相同接口,因此以下解决方案应该可以正常工作。

来自Spring AOP的@Path已经打包了RESTEasy客户端。基本上,此解决方案创建代理的代理,以拦截正在调用的方法。

以下类存储RESTEasy Proxy Framework实例:

public class MethodWrapper {

    private Method method;

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }
}

以下代码实现了神奇:

ResteasyClient client = new ResteasyClientBuilder().build();
ResteasyWebTarget target = client.target("http://example.com/api");
ExampleResource resource = target.proxy(ExampleResource.class);

MethodWrapper wrapper = new MethodWrapper();

ProxyFactory proxyFactory = new ProxyFactory(resource);
proxyFactory.addAdvice(new MethodInterceptor() {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        wrapper.setMethod(invocation.getMethod());
        return invocation.proceed();
    }
});

ExampleResource resourceProxy = (ExampleResource) proxyFactory.getProxy();
Response response = resourceProxy.doSomething("Hello World!");

Method method = wrapper.getMethod();
ExpectedHttpStatus expectedHttpStatus = method.getAnnotation(ExpectedHttpStatus.class);

int status = response.getStatus();
int expectedStatus = annotation.status();

有关详细信息,请查看文档:

答案 1 :(得分:0)

首先,我不能真正理解这个解决方案,但我会在这里粘贴答案。

另外,你可以问为什么我们这样做?因为我们需要/想要测试服务返回正确的http状态,但遗憾的是我们正在测试的服务并不总是为同一http方法返回相同的http状态。

E.g。在下面的示例中,post返回HttpStatus.OK,同一服务的另一个post方法可以返回HttpStatus.CREATED。

以下是我们最终得到的解决方案,ClientResponseFilter:

的组合
import java.io.IOException;
import java.util.UUID;

import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;

/**
 * {@link ClientResponseFilter} which will handle setting the HTTP StatusCode property for use with
 * {@link HttpStatusResponseInterceptor}
 */
public class HttpStatusResponseFilter implements ClientResponseFilter {

    public static final String STATUS_CODE = "StatusCode-" + UUID.randomUUID();

    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        requestContext.setProperty(STATUS_CODE, responseContext.getStatusInfo());
    }
}

和ReaderInterceptor:

import java.io.IOException;
import java.lang.annotation.Annotation;

import javax.ws.rs.ServerErrorException;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;

/**
 * {@link ReaderInterceptor} which will verify the success HTTP status code returned from the server against the
 * expected successful HTTP status code {@link SuccessStatus}
 *
 * @see HttpStatusResponseFilter
 */
public class HttpStatusResponseInterceptor implements ReaderInterceptor {

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext interceptorContext) throws ServerErrorException, IOException {
        Status actualStatus = (Status) interceptorContext.getProperty(HttpStatusResponseFilter.STATUS_CODE);
        if (actualStatus == null) {
            throw new IllegalStateException("Property " + HttpStatusResponseFilter.STATUS_CODE + " does not exist!");
        }

        Status expectedStatus = null;
        for (Annotation annotation : interceptorContext.getAnnotations()) {
            if (annotation.annotationType() == SuccessStatus.class) {
                expectedStatus = ((SuccessStatus) annotation).value();
                break;
            }
        }

        if (expectedStatus != null && expectedStatus != actualStatus) {
            throw new ServerErrorException(String.format("Invalid status code returned. Expected %d, but got %d.",
                    expectedStatus.getStatusCode(), actualStatus.getStatusCode()), actualStatus);
        }

        return interceptorContext.proceed();
    }
}

我们在创建客户端时注册了这两个:

    final ResteasyClient client = new ResteasyClientBuilder().disableTrustManager().build();
    client.register(new HttpStatusResponseFilter());
    client.register(new HttpStatusResponseInterceptor());

SuccessStatus是一个注释,我们用它来注释我们想要专门检查的方法,例如那样:

@POST
@Path("some/foobar")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@SuccessStatus(Status.OK)
Foobar createFoobar(Foobar foobar);