Quarkus: Setup reactive and blocking and non-blocking routes the same way

Created on 3 Jun 2020  路  20Comments  路  Source: quarkusio/quarkus

I a using Quarkus and reading about it, and to my understanding, it seems to favor non-blocking code over blocking code. In light of that, it seems weird that creating a blocking http endpoint is much simpler than a reactive route.

This is an example: https://github.com/oscarfh/code-with-quarkus-reactive-routes
This project has a simple edpoint that, given a string passed as query parameter, you get a "user" object with the string as its name. If you send the name "fail", you will get a 400 back.

To create the blocking endpoint, all I have to do for blocking code is:

@GET
    @Produces(MediaType.APPLICATION_JSON)
    public User hello(@QueryParam("name") String name) {
        return service.createUser(name);
    }

And to map my exception to an HTTP response:

@Provider
public class MyExceptionHandler implements ExceptionMapper<MyCustomException>
{
    @Override
    public Response toResponse(MyCustomException exception)
    {
        return Response.status(Response.Status.BAD_REQUEST).build();
    }
}

I got it covered in 2 "real" lines of code (the rest is boilerplate), and it took me something like 10 minutes.

This is the code for a similar non-blocking behavior:

@Route(path = "/reactive-hello", methods = HttpMethod.GET)
    void greetings(RoutingExchange ex) {
        String name = ex.getParam("name").get();

        ObjectMapper objectMapper = new ObjectMapper();

        service.rxCreateUser(name)
                .onItem().apply(user -> {
            try {
                return objectMapper.writeValueAsString(user);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        })
                .onItem().invoke(userAsString -> {
            ex.response().putHeader("Content-type", MediaType.APPLICATION_JSON);
            ex.ok(userAsString);
        })
                .subscribe().with(s -> ex.response().close(),
                throwable -> {
                    if (throwable instanceof MyCustomException) {
                        ex.response().setStatusCode(400).setStatusMessage("BAD REQUEST").end();
                    } else {
                        ex.response().setStatusCode(500).setStatusMessage("INTERNAL SERVER ERROR").end();
                    }
                });
    }

(I have inlined everything, so it is easier to see the difference. The format is the standard format in my Intellij - for some reason it is called "Quarkus IDE")
One extra point is that in the blocking variant, all exceptions will map to a 404, while this is not provided in the non-blocking version.

It would be great if both variants were virtually identical when setting things up and offered the same type of resources (exception mapping, for instance).

This seems to be possible: there is another framework that will decide based on the return type whether the code is blocking or not and act accordingly. Would that be possible in Quarkus?

arereactive arevertx kinenhancement

All 20 comments

We have improved the reactive routes UX recently. If your service returns a Mutiny type, you should be able to do somethign like (in the master branch):

@Route(path = "/reactive-hello", methods = HttpMethod.GET, produces = "application/json")
Uni<User> greetings(HttpServerRequest request) {
   return service.rxCreateUser(request.getParam("name"));
}

I'm not sure about the exception handling though... Maybe @cescoffier has some idea?

Exception handling (whether your Uni produced a failure or if the method throws an exception) would produce a 500 response (using the routing context fail method).

If you register a route of type FAILURE then you can handle the failure and produce the response you want.

This is very cool! I will take a look as soon as I can.
I will close this ticket for now. Thanks a lot!

Hi @mkouba, if I return a Uni<Response>, the whole response is serialized, instead of just its body. Is there a way to customize the http response code using this approach?

We have improved the reactive routes UX recently. If your service returns a Mutiny type, you should be able to do somethign like (in the master branch):

@Route(path = "/reactive-hello", methods = HttpMethod.GET, produces = "application/json")
Uni<User> greetings(HttpServerRequest request) {
   return service.rxCreateUser(request.getParam("name"));
}

You can find more details on what I want to do in this question: https://stackoverflow.com/questions/64509316/returning-uniresponse-from-reactive-endpoint-causes-response-to-be-serialized

@rober710 Well, you can probably transform the Uni<Response> to a Uni that only holds the body, i.e. with Uni.map(). @cescoffier WDYT?

@mkouba Yes, but that would be equivalent to return a Uni of the body's class, and I wouldn't be able to change the response's status code. Maybe I'm missing something with the .map() approach?

For example, if I'm creating a user, It makes sense to return a Uni<User>, but how do I set the HTTP response code to 201? With the current approach, returning a Uni<User> always yields an HTTP 200 response.

I don't believe we support returning a Uni<Response> at the moment. However, you can do something like (not totally sure of the API:

void greeting(HttpServerRequest request, RoutingContext rc) {
   // ...
   Uni<User> user = ...
   user.subscribe().with(
      u -> {
        rc.getResponse().setStatusCode(201); // Whatever code
        rc.getResponse().end(Json.encode(user));
      },
      f -> {
        rc.getResponse().setStatusCode(500).end();      
      }
   );
}

@cescoffier Thanks! That looks like working with promises in JS. I'll do it that way.

However, it would be a lot cleaner if we could return a Uni<Response>, let the framework deal with serializing the response's body to json, and set the response's http status and headers according to what's set in the Response object. Do you think it's worth implementing this for reactive routes?

If you point me to where the framework inspect's the method's return type and handles the response, I may be able to help implementing this feature and submit a PR.

Yes, it would be a nice addition.

Note that... this part is generated in bytecode. So be warned, it can be a spicy experience:
https://github.com/quarkusio/quarkus/blob/master/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java#L716-L723

Exception handling (whether your Uni produced a failure or if the method throws an exception) would produce a 500 response (using the routing context fail method).

If you register a route of type FAILURE then you can handle the failure and produce the response you want.

Hello @cescoffier , would you be able to give an example on how to do this?
I have a code simillar to:

         .flatMap(resp -> {
                            if (resp.statusCode() == 200) {
                                return Uni.createFrom().item(resp.bodyAsJsonObject());
                            } else {
                                return Uni.createFrom().failure(new AuthorizationException("x","y"));
                            }

Authorization exception is a custom exception mapper, however whenever I return that exception I don't get the response body i expect.

Did you register a reactive route handling the exception?

@Route(type = HandlerType.FAILURE)
void onFailure(AuthorizationException e, HttpServerResponse response) {
  response.setStatusCode(501).end(e.getMessage());
}

@cescoffier
I am getting this error
Build step io.quarkus.vertx.web.deployment.VertxWebProcessor#validateBeanDeployment threw an exception: java.lang.IllegalStateException: No parameter injector found for parameter 0 of route method void onFailure

g this error
Build step io.quarkus.vertx.web.deployment.VertxWebProcessor#validateBeanDeployment threw an exception: java.lang.IllegalStateException: No parameter injector found for parameter 0 of route method void onFailure

That's very likely because of an old version of quarkus used. Failure handlers can only inject exception parameters in 1.9.0+.

@mkouba
You were right, I upgraded an now i do not get that error, however the route does not get triggered when i throw the exception.

@ApplicationScoped
public class ExceptionHandler {
    @Route(type = Route.HandlerType.FAILURE)
    void onFailure(AuthorizationException e, HttpServerResponse response) {
        log.info("im here");
        response.setStatusCode(501).end(e.getMessage());
    }
}

When i try to trigger the exception i get :
RESTEASY002020: Unhandled asynchronous exception, sending back 500
which is what i always got

RESTEASY002020: Unhandled asynchronous exception, sending back 500

@mohhef Does your app use JAX-RS as well? I think that we'll need a reproducer.

@mkouba Yes it does use JAX-RS,
I need to handle the exceptions thrown and return it as a response from a custom exception handler, is there another way to do that?

I'm not sure how reactive routes and resteasy play together in this area. My guess would be that resteasy handles exceptions separately, i.e. the failure handler will not be used for anything JAX-RS...

@mkouba
I have changed the application to use reactive routes only, now the failure route is being detected, but now when i return any response entity I get a bunch of attributes that I dont need:
and the response code in swagger is always 200, even if the "status" attribute in the response body is otherwise

{
  "entity": {
    "objectType": null,
    "status": null,
    "responseStatusDetails": [
      {
        "code": "SERVER_ERROR",
        "description": "Invalid Status code 400"
      }
    ]
  },
  "status": 500,
  "metadata": {},
  "annotations": null,
  "entityClass": "confidential.model.ModelApiResponse",
  "genericType": null,
  "length": -1,
  "location": null,
  "language": null,
  "date": null,
  "lastModified": null,
  "stringHeaders": {},
  "cookies": {},
  "allowedMethods": [],
  "entityTag": null,
  "closed": false,
  "mediaType": null,
  "links": [],
  "statusInfo": "INTERNAL_SERVER_ERROR",
  "reasonPhrase": "Internal Server Error",
  "headers": {}
}


However what i want to receive is something like this, which is what i normally receive without reactive routes:

{
    "responseStatusDetails": [
      {
        "code": "SERVER_ERROR",
        "description": "Invalid Status code 400"
      }
    ]
  }

now when i return any response entity I get a bunch of attributes that I dont need:

The content of the response is in your hands, i.e. it depends on the failure handler impl.

and the response code in swagger is always 200,

I have no idea how swagger is integrated..

Like I said we really need a reproducer/minimal app to get further. And pls create a separate issue to discuss your problem - it's unrelated to this issue.

Hi @mkouba, if I return a Uni<Response>, the whole response is serialized, instead of just its body. Is there a way to customize the http response code using this approach?

@mkouba Yes, but that would be equivalent to return a Uni of the body's class, and I wouldn't be able to change the response's status code. Maybe I'm missing something with the .map() approach?

For example, if I'm creating a user, It makes sense to return a Uni<User>, but how do I set the HTTP response code to 201? With the current approach, returning a Uni<User> always yields an HTTP 200 response.

My issue is pretty much these quoted comments above, ill create a new issue for that.
@rober710 did you find a way to deal serializing the whole Response and the consistent return of the same status code
https://stackoverflow.com/questions/64509316/returning-uniresponse-from-reactive-endpoint-causes-response-to-be-serialized

Was this page helpful?
0 / 5 - 0 ratings