zandero / rest.vertx Goto Github PK
View Code? Open in Web Editor NEWA JAX-RS like annotation processor for vert.x verticals and more
License: Apache License 2.0
A JAX-RS like annotation processor for vert.x verticals and more
License: Apache License 2.0
Hi! Thanks for that awesome library!
When writing Controller methods with no parameters I get:
Error java.lang.IllegalArgumentException: HV000116: The method parameter array cannot not be null
package com.infloop.auth.controllers;
import com.zandero.rest.annotation.ResponseWriter;
import com.zandero.rest.writer.FileResponseWriter;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/")
@Api(value = "/", description = "Swagger")
@Produces({MediaType.APPLICATION_JSON})
public class SwaggerController {
@GET
@Path("/swagger/swagger.json")
@ApiOperation(value = "Get swagger documentation",
notes = "Returns a swagger.json"
)
@ResponseWriter(FileResponseWriter.class)
public String get() { // <<== here
return "swagger.json";
}
}
Workaround for this:
package com.infloop.auth.controllers;
import com.zandero.rest.annotation.ResponseWriter;
import com.zandero.rest.writer.FileResponseWriter;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/")
@Api(value = "/", description = "Swagger")
@Produces({MediaType.APPLICATION_JSON})
public class SwaggerController {
@GET
@Path("/swagger/swagger.json")
@ApiOperation(value = "Get swagger documentation",
notes = "Returns a swagger.json"
)
@ResponseWriter(FileResponseWriter.class)
public String get(final @DefaultValue("") String empty) { // <<== here
return "swagger.json";
}
}
Possible solution:
when ArgumentProvider.getArguments
returns null - do not validate parameters.
in RestRouter.java
...
try {
Object[] args = ArgumentProvider.getArguments(method, definition, context, readers, providers, injectionProvider);
validate(method, definition, validator, toInvoke, args);
fut.complete(method.invoke(toInvoke, args));
}
...
I'm using the following dependencies:
compile group: 'com.zandero', name: 'rest.vertx', version: '0.8.6'
compile group: 'io.swagger', name: 'swagger-jaxrs', version: '1.5.21'
compile group: 'io.swagger', name: 'swagger-core', version: '1.5.21'
compile group: 'io.swagger', name: 'swagger-parser', version: '1.0.28'
compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
compile group: 'javax.el', name: 'javax.el-api', version: '3.0.0'
compile group: 'org.glassfish.web', name: 'javax.el', version: '2.2.4'
compile group: 'org.hibernate', name: 'hibernate-validator', version: '6.0.13.Final'
compile group: 'org.hibernate', name: 'hibernate-validator-annotation-processor', version: '6.0.13.Final'
I am using RestRouter mounted as a subRouter like this:
final Router restRouter = createRestRoutes(vertx);
mainRouter.mountSubRouter("/prod/rest", restRouter);
But when one of the rest paths are using regular expressions, the argument number is not taking this into account, which results in invalid data being parsed.
I am working on adding Spring Validation to the REST Controllers so I have created an interface
@Path("/user")
public interface LoginControllerContract {
@Path("/login")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Future<UserInfo> login(@Valid Credentials credentials);
}
And an implementation class
@Component
@Slf4j
@Validated
public class LoginController implements LoginControllerContract {
private final JDBCClient jdbcClient;
@Autowired
public LoginController(JDBCClient jdbcClient) {
this.jdbcClient = jdbcClient;
}
@Override
public Future<UserInfo> login(@Valid Credentials credentials) {
In my bootstrap verticle where I declare an @Autowired to LoginControllerContract, I register the Controller with rest.vertx
@Autowired
private LoginControllerContract loginController;
/**
* Sets up the router with the login controller
* @param router the vertx web router to set up
*/
private void setupLoginController(Router router) {
new RestBuilder(router)
.register(loginController)
.errorHandler(HttpStatusExceptionHandler.class)
.build();
}
When I disable the validation AOP, I get an actual object to LoginController and the registration works fine and I can reach the endpoint /user/login. However when I enable the validation AOP, Spring proxies the LoginController class which makes the registration not do anything and I get a "Resource Not Found" error. I tried following the code but it seems the method AnnotationProcessor::isRestCompatible is not correctly identifying the methods which are rest compatible.
/**
* @param method to check if REST compatible
* @return true if REST method, false otherwise
*/
private static boolean isRestCompatible(Method method) {
return (!method.getDeclaringClass().isInstance(Object.class) &&
!isNative(method) && !isFinal(method) &&
(isPublic(method) || isInterface(method) || isAbstract(method)));
}
Within the proxy is choosing all the methods which makes the RouteDefinition itself to not be built correctly.
private List<Object> apis = new ArrayList<>();
private Router getRouter() {
if (vertx == null) {
return RestRouter.register(router, apis);
}
return RestRouter.register(vertx, apis);
}
public static Router register(Vertx vertx, Object... restApi) {
Assert.notNull(vertx, "Missing vertx!");
Router router = Router.router(vertx);
return register(router, restApi);
}
calling
return new RestBuilder(vertx)
.register(new MyObject())
.errorHandler(WebApplicationExceptionHandler.class)
.build();
@Path("/the")
@Slf4j
public class MyObject {
@Path("/path")
@GET
@Produces(MediaType.APPLICATION_JSON)
public void login() {
From Intellij debugger
restApi:java.lang.Object[1] = {java.lang.Object[1]@5307}
->0: {java.util.ArrayList@5248} size = 1
-->0: {MyObject@2245}
which leads to an empty Map<RouteDefinition, Method> definitions in
public static Router register(Router router, Object... restApi) {
// TODO: split into smaller chucks
Assert.notNull(router, "Missing vertx router!");
Assert.isTrue(restApi != null && restApi.length > 0, "Missing REST API class object!");
assert restApi != null;
for (Object api : restApi) {
// check if api is an instance of a class or a class type
if (api instanceof Class) {
Class inspectApi = (Class) api;
try {
api = ClassFactory.newInstanceOf(inspectApi, injectionProvider, null);
}
catch (ClassFactoryException | ContextException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
Map<RouteDefinition, Method> definitions = AnnotationProcessor.get(api.getClass());
boolean bodyHandlerRegistered = false;
boolean cookieHandlerRegistered = false;
for (RouteDefinition definition : definitions.keySet()) {
because Object api
here is not really MyObject
but java.util.ArrayList
which is not annotated
For the moment, all the services I wrote had the "Blocking" annotation.
It works pretty well, but its is not an ideal solution for an asynchronous framework like vertx.
It would be interesting to have a way of writing asynchronous methods, either with the Jax-RS 2.0 AsyncResponse (which is quite complex) or by letting the methods have a way of calling a "resume" method with the result as a parameter.
i want implement a async controller method like vertx.web
here is my code
@GET
@Path("/create")
@Produces(MediaType.APPLICATION_JSON)
@ResponseWriter(VertxResponseWriter.class)
public Handler<Void> create(@Context RoutingContext context,
@Context HttpServerResponse response,
@Context Vertx vertx) {
System.out.println(Thread.currentThread().getName() + "create start");
context.request().getParam("id");
SpaceDO spaceDO = new SpaceDO();
Handler<Void> result = res -> {
System.out.println("really return");
response.setStatusCode(200)
.putHeader("content-type", "application/json")
.end("vapsce return");
};
spaceService.create(spaceDO, dbResult -> {
System.out.println(Thread.currentThread().getName() + "async complete");
result.handle(dbResult.result());
});
System.out.println(Thread.currentThread().getName() + "create method end");
return result;
}
but the com.zandero.rest.RestRouter#produceResponse method end end response mandatory,and system invoke error: java.lang.IllegalStateException: Response has already been written
if (!response.ended()) {
response.end();
}
why do this, and could i comment that statement in some fork?
TestRest rest = new TestRest();
Router router = RestRouter.register(vertx, rest);
TemplateEngine engine = ThymeleafTemplateEngine.create();
TemplateHandler templateHandler = TemplateHandler.create(engine);
router.route().handler(templateHandler);
TestRest .java
@GET
@Path("/echo1")
@Produces(MediaType.TEXT_HTML)
public void echo1(@Context RoutingContext routingContext, @Context Vertx vertx) {
routingContext.put("text","welcome!").reroute("index.html");
}
Html display right, but console have exception :
java.lang.IllegalStateException: Response has already been written
at io.vertx.core.http.impl.HttpServerResponseImpl.checkWritten(HttpServerResponseImpl.java:572)
at io.vertx.core.http.impl.HttpServerResponseImpl.end0(HttpServerResponseImpl.java:407)
at io.vertx.core.http.impl.HttpServerResponseImpl.end(HttpServerResponseImpl.java:325)
at io.vertx.core.http.impl.HttpServerResponseImpl.end(HttpServerResponseImpl.java:314)
at com.zandero.rest.exception.GenericExceptionHandler.write(GenericExceptionHandler.java:14)
at com.zandero.rest.exception.GenericExceptionHandler.write(GenericExceptionHandler.java:9)
at com.zandero.rest.RestRouter.handleException(RestRouter.java:428)
at com.zandero.rest.RestRouter.lambda$getHandler$11(RestRouter.java:375)
at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:223)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:101)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:120)
at io.vertx.ext.web.impl.RouterImpl.accept(RouterImpl.java:79)
at io.vertx.core.http.impl.ServerConnection.processMessage(ServerConnection.java:444)
at io.vertx.core.http.impl.ServerConnection.handleMessage(ServerConnection.java:156)
at io.vertx.core.http.impl.HttpServerImpl$ServerHandlerWithWebSockets.handleMessage(HttpServerImpl.java:661)
at io.vertx.core.http.impl.HttpServerImpl$ServerHandlerWithWebSockets.handleMessage(HttpServerImpl.java:614)
at io.vertx.core.net.impl.VertxHandler.lambda$channelRead$1(VertxHandler.java:150)
at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:342)
at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:200)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:148)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.vertx.core.http.impl.HttpServerImpl$Http2UpgradeHandler.channelRead(HttpServerImpl.java:929)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:49)
at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:27)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
at java.lang.Thread.run(Thread.java:745)
according to JAX-RS doc, the type of a query parameter can:
ok, that's a lot to implement... maybe the 2 first one would be nice :-)
Note : I tried to use a RequestReader, but it does'nt seem to work with QueryParam. For the moment I'll stick with string parameters
Option to place not found handler on specific point before next handler is invoked.
For instance:
not found /api/* handler placed after all other /api/ handler but before /* handler.
Hi, that will be very handy to be able to use simple POJO as the parameter's aggregator. Please, see the following Javadoc https://docs.oracle.com/javaee/7/api/javax/ws/rs/BeanParam.html
I follow the instruction in "Injection" section, but got exception, the same exception happened in your example project.
java.lang.IllegalArgumentException: Failed to instantiate class of type: com.sc.application.rest.TestRest3, class needs empty constructor!
package com.sc.application.rest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.google.inject.Guice;
import com.google.inject.Inject;
import io.vertx.core.json.JsonObject;
@Path("/test3")
public class TestRest3 {
private MyService service;
// public TestRest3() {
//
// service = Guice
// .createInjector(new GuiceInjectionProvider())
// .getInstance(MyService.class);
// }
@Inject
public TestRest3(MyService someService) {
service = someService;
}
@GET
@Path("/test")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject echo() {
JsonObject message = new JsonObject();
message.put("collection", "mycollection").put("document", new JsonObject().put("name", this.service.call()));
return message;
}
}
package com.sc.application.rest;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.zandero.rest.injection.InjectionProvider;
public class GuiceInjectionProvider extends AbstractModule implements InjectionProvider {
private Injector injector;
public GuiceInjectionProvider() {
injector = Guice.createInjector(this);
}
@Override
protected void configure() {
bind(MyService.class).to(MyServiceImpl.class);
}
@Override
public Object getInstance(Class clazz) {
return injector.getInstance(clazz);
}
}
package com.sc.application.rest;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import java.util.HashMap;
import java.util.Map;
import com.sc.application.service.SomeDatabaseService;
import com.zandero.rest.RestBuilder;
import com.zandero.rest.RestRouter;
public class App extends AbstractVerticle {
@Override
public void start() {
Router router = new RestBuilder(vertx)
.injectWith(GuiceInjectionProvider.class)
.register(TestRest3.class)
.build();
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sc</groupId>
<artifactId>sc-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sc-service</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>3.5.2</vertx.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-sync</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
<version>${vertx.version}</version>
<classifier>processor</classifier>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-docgen</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
<version>${vertx.version}</version>
<scope>provided</scope>
</dependency>
<!-- <dependency> -->
<!-- <groupId>io.advantageous.qbit</groupId> -->
<!-- <artifactId>qbit-core</artifactId> -->
<!-- <version>2.0.0</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>io.advantageous.qbit</groupId> -->
<!-- <artifactId>qbit-vertx</artifactId> -->
<!-- <version>2.0.0</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>io.advantageous.qbit</groupId> -->
<!-- <artifactId>qbit-admin</artifactId> -->
<!-- <version>2.0.0</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>io.advantageous.qbit</groupId> -->
<!-- <artifactId>qbit-service-discovery</artifactId> -->
<!-- <version>2.0.0</version> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>io.advantageous.qbit</groupId> -->
<!-- <artifactId>qbit-guice</artifactId> -->
<!-- <version>1.1.0.RELEASE</version> -->
<!-- </dependency> -->
<!-- https://mvnrepository.com/artifact/com.google.inject/guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>25.1-jre</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.zandero/rest.vertx -->
<dependency>
<groupId>com.zandero</groupId>
<artifactId>rest.vertx</artifactId>
<version>0.8.4</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Starting App get exception
SEVERE: Failed in deploying verticle
java.lang.IllegalArgumentException: Failed to instantiate class of type: com.sc.application.rest.TestRest3, class needs empty constructor!
at com.zandero.rest.RestRouter.register(RestRouter.java:118)
at com.zandero.rest.RestRouter.register(RestRouter.java:80)
at com.zandero.rest.RestBuilder.getRouter(RestBuilder.java:328)
at com.zandero.rest.RestBuilder.build(RestBuilder.java:360)
at com.sc.application.rest.App.start(App.java:70)
at io.vertx.core.AbstractVerticle.start(AbstractVerticle.java:106)
at io.vertx.core.impl.DeploymentManager.lambda$doDeploy$8(DeploymentManager.java:483)
at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:339)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:463)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
I have created custom Authentication Filter:
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter
As this filter should invoked for each rest API call, I am trying to register it in router using:
RestRouter.register(vertx, AuthenticationFilter.class)
but this filter is not invoked.
in case of other J2EE Container it is working fine as it uses:
register of ResourceConfig .
Please suggest any Solution or alternative for this Issue.
in JAX-RS, methods annotated with @GET
, @POST
, @PUT
, or @DELETE
don't need a @PATH
annotation if class already has one (they will get the path from the class).
For the moment, such methods are not added to the router by RestRoute.register
, you need to add a @Path("/")
.
I downloaded rest.vertx.example and run it, exception throw when I access /echo
If I create a new empty constructor, guice could not inject MyService into the rest
SEVERE: Failed to instantiate class of type: com.zandero.rest.example.rest.EchoRest, class needs empty constructor!
java.lang.IllegalArgumentException: Failed to instantiate class of type: com.zandero.rest.example.rest.EchoRest, class needs empty constructor!
at com.zandero.rest.RestRouter.register(RestRouter.java:118)
at com.zandero.rest.RestRouter.register(RestRouter.java:80)
at com.zandero.rest.RestBuilder.getRouter(RestBuilder.java:328)
at com.zandero.rest.RestBuilder.build(RestBuilder.java:360)
at com.zandero.rest.example.ServerVerticle.start(ServerVerticle.java:58)
at io.vertx.core.AbstractVerticle.start(AbstractVerticle.java:111)
at io.vertx.core.impl.DeploymentManager.lambda$doDeploy$8(DeploymentManager.java:434)
at io.vertx.core.impl.ContextImpl.lambda$wrapTask$2(ContextImpl.java:337)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:403)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:445)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
at java.lang.Thread.run(Thread.java:748)
Good evening,
I've seen the issue has been closed, but i'm seeing some odd behavior.
Using vertx web 3.8.0, updated rest.vertx to 0.8.9.
I'm having one path with a GET and a DELETE
1:
@get
@path("tokens/:tokenReferenceId")
2:
@delete
@path("tokens/:tokenReferenceId")
I'm noticing after the update the DELETE annotated method is results in a 404. But after adding an empty body to my delete request, the DELETE annotated method is found again.
It looks like DELETE now requires a body to be able to be identified.
For now for me it's not a problem because my calls are made generic in my front-end, so adding an empty object is not that big of a deal. But it was unexpected.
You want me to open a new report?
Originally posted by @jsirach in #42 (comment)
Currently one can set only one HTTP method for a route. While in other frameworks like Spring Web you can set multiple HTTP methods for the same route. Being able to set multiple HTTP methods lets the developer migrate a route's HTTP method over time from one to another or simply to respond to the same requests with different HTTP methods.
Example code:
@Path("/test")
public class TestResource{
@GET
@POST
public String getName(){
return "Hello Java";
}
}
At first thank you for your project. I really like it because it is a good alternative to the official Vert.x Web Contract library and enables me to start a project rapidly.
I use rest.vertx with Kotlin and would like to have support for Kotlin coroutines. At the moment it is not possible to change some behaviours of rest.vertx without forking your project. I suggest to change the structure a bit so that support for Kotlin coroutines or Quasar fibers will be possible as an optional external library.
I would like to support something like this (this will not work at the moment):
@POST @Path("/echo") suspend fun echoFunction(echo: Echo): Echo { // ... do something in an asynchronous way but in synchronous coroutine style ... return echo }
But at the moment I can only do this (with some code around the coroutine):
@POST @Path("/echo") fun echoFunction(echo: Echo): Future<Echo> { Future future = Future.future() // launch coroutine GlobalScope.launch(vertx.dispatcher) { // ... do something in an asynchronous way but in synchronous coroutine style ... future.complete(echo) } return future }
The first example is much more concise. In order to enable it from external code I need to open rest.vertx a bit:
Both would be members of RestRouter. The RestRouter would have a possibilty to overwrite the AnnotationProcessor by something like a sub class and a new RouterHandlerFactory can be added that would for example support Kotlin coroutines or Quasar fibers.
What do you think about such a change? I think rest.vertx would be even better if we made it a bit more extensible.
The HTTP method "DELETE" is allowed to have a body. But rest.vertx does not allow.
Exception in thread "vert.x-eventloop-thread-0" java.lang.IllegalArgumentException: one.two.three.MyController.delete(Payload arg0) - Missing argument annotation (@PathParam, @QueryParam, @FormParam, @HeaderParam, @CookieParam or @Context) for: arg0!
at com.zandero.utils.Assert.isFalse(Assert.java:38)
at com.zandero.utils.Assert.isTrue(Assert.java:51)
at com.zandero.rest.AnnotationProcessor.get(AnnotationProcessor.java:65)
at com.zandero.rest.RestRouter.register(RestRouter.java:124)
at com.zandero.rest.RestBuilder.getRouter(RestBuilder.java:406)
at com.zandero.rest.RestBuilder.build(RestBuilder.java:451)
When a method is annotated with
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
And the request is sent with the "Accept: application/xml", the response is returned as JSON.
If it is called with "Accept: application/json", the response works ok and returns json.
It looks like this code is the main problem:
for (MediaType produces : definition.getProduces()) {
response.putHeader(HttpHeaders.CONTENT_TYPE, produces.toString());
}
By default ALL elements in Produces are added. This means the last will always win. Instead it should look at what is in Accept header on the request.
Hi again,
After I registered a Validator I started to get the following exception when the RestRouter tries to validate a function that takes no parameter. The validator library that I am using is hibernate-validators 6.0.13.
The exception I get is the following:
java.lang.IllegalArgumentException: HV000116: The method parameter array cannot not be null.
at org.hibernate.validator.internal.util.Contracts.assertNotNull(Contracts.java:45) ~[hibernate-validator-6.0.13.Final.jar:6.0.13.Final]
at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:222) ~[hibernate-validator-6.0.13.Final.jar:6.0.13.Final]
at com.zandero.rest.RestRouter.validate(RestRouter.java:533) [rest.vertx-0.8.7.jar:?]
And below you will find the signature of one of the method that cause the problem:
@GET
@RolesAllowed(Scope.CATEGORIES_LIST_PUBLIC)
fun listCategories(): Future<List<CategoryUserResource>>
A simple fix may be to check if there is at least one parameters before trying to call validateParameters.
Best,
Hi,
We are using in our application versioned path like the following:
@Path("/api/admin/1.0/categories")
Unfortunately, the ValidatingUtils.isRegEx is picking 1.0 as a valid regex and is creating a path param named "param0" for it. Therefore we need to add a parameter @PathParam("param0") in all of our methods otherwise we get this error:
Invalid parameter type for: @PathParam("param0") for: /api/admin/1.0/categories
We have found multiple ways to overcome the issue but it could be good if in the future we can enable/disable which type of detection we want for path params.
Best,
How to implement websocket function with rest.vertx?
One of my functions inside a Controller throws an Exception, and it is already caught by an exception handler (defined by RestRouter.getExceptionHandlers().register()), but it still shows up as an error
[vert.x-eventloop-thread-0] ERROR RestRouter - Handling exception:
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.zandero.rest.RestRouter.lambda$null$2(RestRouter.java:472)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$2(ContextImpl.java:272)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
the front page doc uses the word vertical - when it should be verticle. Minor - but good to fix.
When i have a method returning a javax.ws.rs.core.Response, it is not possible to have the data returned as Json, due to the condition below in JaxResponseWriter class. Is there any reason for this?
if (writer != null && !(writer instanceof JsonResponseWriter)) {
writer.write(jax.getEntity(), request, response);
}
else {
response.end(jax.getEntity().toString());
}
@GET
@Path("/h1")
@Produces({MediaType.APPLICATION_XML})
public Response h1(){
User u = new User();
u.setPassword("p");
u.setName("a");
return Response.ok(u).build();
}
This page contains the following errors:
error on line 1 at column 1: Document is empty
Below is a rendering of the page up to the first error.
Why this happen?
Hi!
I think it would be terribly cool to add support for javax.validation and @Valid
parameter annotation, which would automatically check for violations in deserialized entities and throw ConstraintViolationException in case of found violations.
When there are exceptions raised by the body handler (for example JSON description not conform to the body type), they are rendered as plain text, not JSON, although the content-type is application/json
as expected
it would be interesting to have a way to customize the output for exceptions.
(tested on 0.2 - Great project by the way!)
As I use Enunciate for documentation, the web services classes are divided in interface and implementation. In that case, according to JAX-RS specs, I should'nt repeat the annotations on implementation. But rest.vertx doesn't find the method in that case, as it use only getAnnotation()
on the method without climbing to the superclass. I assume the same happens for parameter annotations.
see https://stackoverflow.com/questions/25916796/inheritance-with-jax-rs for the link to the spec, and https://stackoverflow.com/questions/10082619/how-do-java-method-annotations-work-in-conjunction-with-method-overriding/10082663 for an example of climbing to the superclass to read method annotations.
TestRest rest = new TestRest();
Router router = RestRouter.register(vertx, rest);
router.route().handler(CookieHandler.create());
SessionHandler handler = SessionHandler.create(LocalSessionStore.create(vertx));
router.route().handler(handler);
TestRest.java
@GET
@Path("/echo")
@Produces(MediaType.TEXT_HTML)
public String echo(@Context RoutingContext routingContext) {
Session session = routingContext.session();
return "Hello world!";
}
Session session = routingContext.session();
session == null , something wrong?
If i'm overlooking something, please correct me.
Currently i'm building a router with
for (Collection api : Collection.values()) { apiList.add(api.getResource()); } return new RestBuilder(vertx) .register(apiList.toArray()) .errorHandler(RestFallbackExceptionHandler.class) .build();
Which correctly builds the router.
I have created an error handler with:
`
@produces("application/json")
public class RestFallbackExceptionHandler implements ExceptionHandler {
/**
* Logger for the handler.
*/
Logger LOG = LogManager.getLogger(RestFallbackExceptionHandler.class);
/**
* Writes the error to the output.
*
* @param exception The exception to get the data from.
* @param request The http request made.
* @param response The response to be send back to the user.
*/
@Override
public void write(DefaultRestException exception, HttpServerRequest request, HttpServerResponse response) {
response.setStatusCode(exception.getStatus());
try {
response.end(Serialization.getDefaultObjectMapper().writeValueAsString(exception));
} catch (JsonProcessingException ex) {
LOG.error("Could not serialize default REST error exception");
response.end(exception.getMessage());
}
}
}
`
Following the example on the github page.
When i access a REST endpoint for which the user is unauthorized i'm getting the following result:
"HTTP 401 Unauthorized"
With in my log file:
"Resolving to generic exception handler: com.zandero.rest.exception.GenericExceptionHandler"
I was expecting my exception handler "RestFallbackExceptionHandler" to be used.
The Serialization.getDefaultObjectMapper() is a jackson objectmapper
Following RegEx path is not recognized:
{path:^(?!\\/api\\/).*}
Need to improve code searching for regular expressions inside { }
In my application, web service are multi-tenant, so they all look the same
@POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Blocking @Path("/")
public ServiceResult dosomething(myClass param, @Context User user) {
ServiceResult res = new ServiceResult();
doAsTenant(user, () -> {
... some logic here
});
return res;
}
doAsTenant
sets a thread local with the tenant id to protect access to resources.
Do you think it would be possible to add a common logic directly in the rest handlers, in order to keep only the business logic in the web service class ? For example, have a pre and post step for each service, set in RestRouter options ?
CORS handler via RestBuilder is bound to late.
CORS handler should be bound before RESTs
Hi good evening!
Do you think there is a possibility to add the http PATCH method to your library?
Thanks and cheers,
John.
Thank you for reply and close the last issue I have! There is something like this in Vertx-Web I can not do with rest.vertx:
In Vertx-Web I call async method like below and it is not blocking io, it doesn't need to run under worker thread:
public void test(Vertx vertx) {
Router.router(vertx).post("/product/save").handler(ctx -> {
Product product = new Product();
CompletableFuture<Product> result = productService.saveProduct(product);
result.whenComplete((p, e) -> vertx.runOnContext(none -> {
ctx.response().end(Json.encodePrettily(p));
}));
});
}
And this above code work fine, async and not blocking any thread.
In rest.vertx, I do this:
@POST
@Path("/save")
@Consumes(MediaType.APPLICATION_JSON)
public HttpServerResponse save(@Context HttpServerResponse response, Product product) throws InterruptedException, ExecutionException {
CompletableFuture<Product> result = productService.saveProduct(product);
result.whenComplete((p, e) -> {
System.out.println("Product: " + p);
response.putHeader("content-type", "application/json; charset=utf-8")
.end(Json.encodePrettily(p));
});
return response;
}
If I put break point in return response, when I hit /product/save, it return me the json result. However if I release break point, empty result produced.
I also try return void, but it return empty:
@POST
@Path("/save")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public void save(@Context HttpServerResponse response, Product product) throws InterruptedException, ExecutionException {
CompletableFuture<Product> result = productService.saveProduct(product);
result.whenComplete((p, e) -> {
System.out.println("Product: " + p);
response.putHeader("content-type", "application/json; charset=utf-8")
.end(Json.encodePrettily(p));
});
}
The problem now is how to have response.end( result) in whenComplete() of Future, but still produce the result like Vertx-Web.
Thanks!
Verticle mutil instance, bug ExceptionHandlerFactory has shared handlers...
when checkIfAlreadyRegistered will throw Exception
Hi
When i try to inject a Context like below, this is not made available. It works fine on method level
public class SomeRest {
@context Vertx vertx;
}
Hi,
I was having the following exception on my POST endpoints with a @RoleAllowed annotation:
java.lang.IllegalStateException: Request has already been read
at io.vertx.core.http.impl.HttpServerRequestImpl.checkEnded(HttpServerRequestImpl.java:438)
at io.vertx.core.http.impl.HttpServerRequestImpl.handler(HttpServerRequestImpl.java:203)
at io.vertx.ext.web.impl.HttpServerRequestWrapper.handler(HttpServerRequestWrapper.java:39)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl.handle(BodyHandlerImpl.java:73)
After investigation I found that all route checking the roles are registered with the order 0 because of the following line:
However for the route serving the content there is a check avoiding this case:
rest.vertx/src/main/java/com/zandero/rest/RestRouter.java
Lines 170 to 172 in 8925ffd
I found a workarround by adding the following lines before calling RestRouter.register which make sure that parsing the body is done before anything else:
router.route().order(Integer.MIN_VALUE)
.handler(BodyHandler.create())
Best,
When adding the rest.vertx dependency through maven, it add 3Mb+ dependencies that seem not useful.
At the moment, I import it with a lot of exclusions, but it would be better to have theses dependencies marked as optional in the project:
<dependency>
<groupId>com.zandero</groupId>
<artifactId>rest.vertx</artifactId>
<version>0.8.1</version>
<exclusions>
<exclusion>
<groupId>io.vertx</groupId>
<artifactId>vertx-rx-java</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
</exclusion>
<exclusion>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
</exclusion>
</exclusions>
</dependency>
This is not quite a bug, but when I use the example of return Future, the return is actually the future object, not the Dummy content, I have modified this way, it is working and return the the json. However, I don't know whether I am blocking vertx main thread or not. Could you please advice?
@POST
@Path("/save")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Product save(Product product) throws InterruptedException, ExecutionException {
CompletableFuture<Product> result = productService.saveProduct(product);
result.thenAccept(p -> {
System.out.println("Product: " + p);
});
return result.get();
}
The exception handling is very good but I think It'd be great if one could register ExceptionHandlers for specific exceptions. A proposal is to add a method to the ExceptionHandler interface that returns the list of exceptions it handles. If the list returned is null/empty, then it would handle any exception, otherwise the ExceptionHandler would handle the specific exception.
public interface ExceptionHandler<T extends Throwable> extends HttpResponseWriter<T> {
default List<Throwable> getHandledExceptions() {
return Collections.emptyList();
}
}
For example, I need to raise a RuntimeException (BadRequestException) that I'd like the BadRequestExceptionHandler to handle. I can also raise another RuntimeException (UnauthorizedException) that I'd like the UnauthorizedExceptionHandler to handler. The BadRequestExceptionHandler would return a 400 HTTP status whereas UnauthorizedExceptionHandler would return a 401 HTTP status.
In some cases, we need to use template engines even in Rest services. For example, if we want to redirect the user to a third-party server with x-www-form-urlencoded
body payload we need to use Javascript for doing this.
When registering exception handler instances (not class types) the matching algorithm doesn't find the correct exception handler instance.
Hi
I'm trying out rest.vertx (latest version, 0.8.6), so far I like it very much. There's just a small issue related to service methods with QueryParam.
Observation:
Service methods which use @QueryParam receive the URL encoded param, for example "Hello%20World"
Expected:
Standard behaviour would be to get the URL decoded query param, i.e. "Hello World"
Cheers
Thomas
Would be great to have a base class with eg: @ApplicationPath("v1") so any subclasses will be routed relative to the base class path, eg: /v1/resource1 etc..
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.