I have a Rest Client in a Quarkus (1.8.1) service defined like this:
#RegisterRestClient
#Path("/")
#Produces("application/json")
#Consumes("application/json")
public interface MyClient {
#POST
#Path("/{entity}")
Response postEntity(#HeaderParam(value = "Authorization") String auth,
#PathParam("entity") String entity, Object payload) throws MyException;
}
And I have implemented ResponseExceptionMapper in the same package like this:
public class MyExceptionMapper implements ResponseExceptionMapper<MyException> {
#Override
public MyException toThrowable(Response r) {
return new DynamicsException(r.getStatus() + " - " + r.readEntity(String.class));
}
}
When I call the service it is currently returning a 404 error, and I expected the code in the MyExceptionMapper class to be called. However it doesn't and instead a javax.ws.rs.WebApplicationException is thrown. The stack trace includes a call to the DefaultResponseExceptionMapper. It's seems that my mapper has not been registered.
How can I register my handler for invalid responses from calls to the service?
You need to register MyExceptionMapper as provider to the rest client with #RegisterProvider(MyExceptionMapper.class).
#RegisterRestClient
#RegisterProvider(MyExceptionMapper.class)
#Path("/")
#Produces("application/json")
#Consumes("application/json")
public interface MyClient {
Each implementation provides a default ResponseExceptionMapper implementation that will map and invoke a response to javax.ws.rs.WebApplicationException when the response status code is >= 400. It has a priority of Integer.MAX_VALUE, and is meant to be
used as a fallback whenever an error is encountered. This mapper will be registered bydefault to all client interfaces, but this can be disabled by setting an MP Config property,
microprofile.rest.client.disable.default.mapper, to true.
RestClientBuilder.newBuilder().property("microprofile.rest.client.disable.default.mapper",true)
Related
I'm using Quarkus 1.2.0.Final.
I have the following REST client:
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#RegisterRestClient(configKey = "<some key>")
public interface SomeClient {
#POST
#Path("<some path>")
SomeResponse someMethod(SomeRequest request);
}
This bean is used in my other beans as a dependency.
And I have the following test case:
#QuarkusTest
class SomeTest {
#Test
void testGetTransactions() { }
}
class SomeClientImpl implements SomeClient {
#Override
public SomeResponse someMethod(SomeRequest request) {
// <the implementation doesn't matter>
return null;
}
}
The test is failing with the following exception:
Suppressed: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.example.client.SomeClient and qualifiers [#RestClient]
...
Why the test is failing? If I remove the class class SomeClientImpl implements SomeClient {...} the test is passing. So implementing an interface leads to test failing, which is weird.
Update 1:
I tried the next code and I'm getting the same exception:
#QuarkusTest
class TransactionServiceImplTest {
#Test
void testGetTransactions() {
new SomeClient() {
#Override
public SomeResponse someMethod(SomeRequest request) {
// <the implementation doesn't matter>
return null;
}
};
}
}
Quarkus REST client is based on MicroProfile REST Client. With MP REST Client you are not supposed to implement the REST Client interface -- it will be automatically generated for you.
For example, if you leave the interface as-is like this:
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#RegisterRestClient(configKey = "<some key>")
public interface SomeClient {
#POST
#Path("<some path>")
SomeResponse someMethod(SomeRequest request);
}
You can use the REST Client like this in your application:
#Inject
#RestClient
SomeClient client;
public void doSomething() {
SomeRequest req = // ...
SomeResponse resp = someMethod(req);
}
When your application code calls someMethod(req) what happens is your application makes an HTTP POST request to whatever URL SomeClient.someMethod is configured for, and then the JSON response of that HTTP POST requests is converted into the SomeResponse object using JSON-B or Jackson.
For more information, I would suggest going through the Quarkus REST client guide
If your goal is mock the external REST service that SomeClient normally talks to, I would suggest using a library like MockServer to mock responses to requests by your application via SomeClient. In your test, configure the URL for SomeClient to point to the MockServer that you start as part of the test.
I have a RESTful web service with an #POST annotated method that throws an exception when the request content type cannot be consumed. I would like to see what exactly went wrong. For this I would like to access all the details of the failed POST call, such as the body content. For this I created an #Provider catching NotSupportedException. I still have problems finding a way to get the desired details though. How would I do that?
The #POST annotated method:
#Path("/language")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class LanguageResource {
#POST
public Response postLanguages(Session session) {
return Response.status(Response.Status.OK)
.entity(Mock.getLanguages())
.build();
}
}
The #Provider:
#Provider
public class NotSupportedMapper implements ExceptionMapper<NotSupportedException> {
#Override
public Response toResponse(NotSupportedException exception) {
System.out.println(exception.toString());
return Response.status(Response.Status.NOT_IMPLEMENTED)
.build();
}
}
Using Jetty with Jersey embedded.
I was wondering if it is possible for one of my resources to have 2 method with #POST annotation on the same path but use the parameters to distinguish which method to call.
For example, lets say I have /api/v1/MyResource. And the MyResource class look like this:
// The /api is set in my context.
#Path("/v1/MyResource")
public class MyResource {
private final MyService service;
public MyResource(MyService service){
this.service = service;
}
#POST
#PermitAll
#Consumes(MediaType.APPLICATION_JSON)
public Response doPost(MyParam param){
this.service.doStuffWith(param);
}
#POST
#PermitAll
#Consumes(MediaType.APPLICATION_JSON)
public Response doPost(MyParamOfADiffType param){
this.service.doStuffWith(param);
}
}
Since my service has two implementation of the doStuffWith() function, I expected Jersey to try to serialize the request body into a MyParam object and if its not working try into a MyParamOfADiffType object. Then if its not working I expected a 500 Internal error.
But I get a ModelValidationException instead. Anyone know if what I am trying is possible?
I am trying to implement a simple client in rest easy, but I am getting an error saying "You must use at least one, but no more than one http method annotation". In my server implementation, I have added a http annotation on my method.
#Path("/")
public class TestResource
{
#GET
#Path("/domain/{value}")
public String get(#PathParam("value") final String value) {
return "Hello" + value;
}
}
I debugged it through, the first time it is not hitting the runtime exception, However, it is making a second call to it and failing, not sure why and how.
My client as junit test:
#Test
public void testPerformRestEasy() {
ResteasyClient client = new ResteasyClientBuilder().build();
ResteasyWebTarget target = client.target("http://localhost:8080/");
TestResource proxy = target.proxy(TestResource.class);
String response = proxy.get("user");
Assert.assertEquals("Hellouser", response);
}
The code where it is failing
private static <T> ClientInvoker createClientInvoker(Class<T> clazz, Method method, ResteasyWebTarget base, ProxyConfig config)
{
Set<String> httpMethods = IsHttpMethod.getHttpMethods(method);
if (httpMethods == null || httpMethods.size() != 1)
{
throw new RuntimeException("You must use at least one, but no more than one http method annotation on: " + method.toString());
}
ClientInvoker invoker = new ClientInvoker(base, clazz, method, config);
invoker.setHttpMethod(httpMethods.iterator().next());
return invoker;
}
Error:
java.lang.RuntimeException: You must use at least one, but no more than one http method annotation on: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
at org.jboss.resteasy.client.jaxrs.ProxyBuilder.createClientInvoker(ProxyBuilder.java:76)
at org.jboss.resteasy.client.jaxrs.ProxyBuilder.proxy(ProxyBuilder.java:52)
at org.jboss.resteasy.client.jaxrs.ProxyBuilder.build(ProxyBuilder.java:120)
at org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.proxy(ClientWebTarget.java:72)
Does anyone know what the issue is here?
The Resteasy JAXRS 2 client does not seem to accept implementation classes directly. To make it work, you have to create a properly annotated interface. It is used by Resteasy to generate a client proxy and your server must implement exactly the same interface.
So in your case, you have to split your code into an interface and a separate implementation class:
#Path("/")
public interface TestResource {
#GET
#Path("/domain/{value}")
String get(#PathParam("value") final String value);
}
public class TestResourceImpl implements TestResource {
#Override String get(final String value) {
return "Hello" + value;
}
}
I'm not sure if this is Resteasy-specific or required by the specification, but solved the same issue for me. You can find the section that gave me the hint here in the documentation.
You have to define the MIME media type resource representation of resource(#Produces/#Consumes) from client. Like -
#Path("/")
public class TestResource
{
#GET
#Produces("text/plain")
#Path("/domain/{value}")
public String get(#PathParam("value") final String value) {
return "Hello" + value;
}
}
The Jboss Client framework Doc will help you more.
In my case the developer of the Rest Client Interface had wrongly extended RestEasyClientProxy. It wasn't the methods in the Rest Interface that were missing the http annotations, but the inherited methods.
Removing extends RestEasyClientProxy from the Rest Client Interface code fixed the issue.
I am developing a RESTlet API (JAVA), and I've created a custom authorization filter that I run all requests through before passing it to my router. In my requests I always pass the session ID as a request attribute, e.g.
http://localhost:8080/myAPI/{sid}/someResource/
Now, in my functions that extends ServerResource, I can do something like this to easily extract that {sid}:
String sid = (getRequestAttributes().containsKey("sid")) ? getRequestAttributes().get("sid").toString() : "";
My problem is, in my authorization function, which extends Filter (the authorization function is not called via a router, but is called in my main createInboundRoot() function), I cannot use the same method to extract the {sid}. I've created a workaround using string manipulation of request.getResourceRef().getSegments(), but there must be a better way?
Any help will be appreciated!
Thanks
You can create a common parent class for any child of ServerResource. like this:
public class CommonParentResource extends ServerResource
{
// class definition
}
And then override the doInit() method of the ServerResource class in it.
public class CommonParentResource extends ServerResource
{
public void doInit()
{
boolean authorized=false;
String sid = getRequestAttributes().containsKey("sid") ? (String)getRequestAttributes().get("sid") : StringUtils.EMPTY;
// Authorization logic here.
if(!authorized)//after authorization process completed.
{
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
getResponse().setEntity(/*Representation carrrying message for unauthorized user*/);
}
}
}
Now any new child class of ServerResource that you want to perform this authorization check, must extend this CommonParentResource class. Like this:
public class FriendsListResource extends CommonParentResource
{
#Get
//......
}
Two points are important here:
doInit() of any child class of ServerResource is called before calling any method annotated with #Get / #Post / ...
(Caution) If you do not use this statement:
getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
i.e. if you do not set status of response to an error, then methods annotated with #Get / #Post / #Put / ... will get called ! But if your program sets the status of the response to an error-status, then the #Get / #Post / #Put / ... will not get executed, and the end user will see the error message represented by:
getResponse().setEntity(/*Representation carrrying message for unauthorized user*/);