是否可以使用类似于服务器端的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无关:
答案 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请求。因此,您无法从资源类中读取注释。
我不确定这对您是否有用,但由于您希望从客户端访问服务器资源类,因此提及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是否提供类似的内容。
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服务实现带注释的接口,然后重用相同的接口来创建在客户端调用的客户端代理。
由于您已经在使用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);