Code Monkey home page Code Monkey logo

vertx-rest's Introduction

vertx-rest

Continuous Integration Code Coverage License

Overview

  • Abstraction over resteasy-vertx to simplify writing a vertx REST application based on JAX-RS annotations
  • Provides a backpressure enabled HTTP server to drop requests on high load
  • Exception mappers to standardise error response
  • Timeout annotation for closing long-running requests
  • Uses rxjava3

Setup

Add the following dependency to the dependencies section of your build descriptor:

  • Maven (in your pom.xml):
  <dependency>
    <groupId>com.dream11</groupId>
    <artifactId>vertx-rest</artifactId>
    <version>x.y.z</version>
  </dependency>
  • Gradle (in your build.gradle file):
  dependencies {
   compile 'com.dream11:vertx-rest:x.y.z'
  }

Note: Replace x.y.z above with one of the released versions

Usage

Create Rest Verticle

Create the application REST Verticle by extending com.dream11.rest.AbstractRestVerticle class. The AbstractRestVerticle here does the following:

  • Finds all the resources(i.e, classes annotated with @Path) in the package and adds an instance of each of the resource classes to the resteasy-deployment registry
  • Adds all the Filters and ExceptionMappers and any other custom Providers to the resteasy-deployment
  • Starts a backpressure enabled HTTP server and dispatches each request to the handler registered with the resteasy-deployment
public class RestVerticle extends AbstractRestVerticle {
    
  public RestVerticle() {
    // starts http server with default options
    // All classes with @Path annotation in the specified package are registered with the router
    super("com.dream11.package"); 
  }
  
  @Override
  protected ClassInjector getInjector() {
    // Add your implmentation of injector
    return null;
  }
}

Use the following constructor to pass custom HTTP Server Options

public RestVerticle() {
  super("com.dream11.package", new HttpServerOptions().setPort(8080)); // starts http server with provided options
}

Create a Rest Resource

@Path("/test")
public class TestRoute {

  @GET
  @Consumes(MediaType.WILDCARD)
  @Produces(MediaType.APPLICATION_JSON)
  @ApiResponse(content = @Content(schema = @Schema(implementation = String.class)))
  public CompletionStage<String> test() {
    return Single.just("Hello World")
            .toCompletionStage();
  }
}

Validations

  • Use all the jakarta constraints given here
  • You can use @TypeValidationError(code=<errorCode>, message=<errorMessage>) on DTO parameters to send custom error code and message when parsing of parameter fails
  • @TypeValidationError can also be used for Integer, Long, Float or Double types in @HeaderParam, @QueryParam etc. If you want to use @TypeValidationError on a parameter of type other than these types you can create a custom converter similar to IntegerParamConverter and a provider extending ParamConverterProvider. Reference

Exception Handling

  • Provides exceptions and exception mappers to standardize error response
  • Implement RestError as an enum to specify error codes, messages and http status codes to be returned in response for your exceptions
    • Throw RestException with the restError to return error response in the following format
    {
      "error": {
        "code": "UNKNOWN_EXCEPTION",
        "message": "Something went wrong",
        "cause": "Cannot connect to mysql database"
      } 
    }
  • Refer to the package com.dream11.rest.exception.mapper for mappers provided by default
  • Implement your own exception mappers if you need some other error response

Timeouts

  • Configure request timeout by annotating your JAX-RS resource classes with @Timeout(value = <timeoutMillis>, httpStatusCode = <httpStausCode>)
  • Default timeout for each JAX-RS route is 20 seconds
  • Since there is no official HTTP Status code for origin server timeout, it has been kept configurable. By default 500 status code is returned in case of timeouts.

Dependency Injection

  • Implement abstract method getInjector of AbstractRestVerticle to return an implementation of the ClassInjector interface
  • This will be used for injecting instances of the REST resource classes
  • Refer to com.dream11.rest.injector.GuiceInjector for an example

Custom Providers

  • Annotate your provider classes with @Provider to automatically register them with resteasy deployment

  • Alternatively, if your provider class necessitates a constructor with parameters, you can override the following method in AbstractRestVerticle to register the provider object

public class RestVerticle extends AbstractRestVerticle {

  @Override
  protected List<Object> getProviderObjects() {
    List<Object> providerObjects = super.getProviderObjects();
    // add your provider object with custom constructor to the list
    return providerObjects;
  }
}

Custom Json Provider

You can create custom json providers by

public class RestVerticle extends AbstractRestVerticle {
  
  @Override
  protected JsonProvider getJsonProvider() {
    // return your custom json provider
  }
}

Examples

Please refer tests for an example application

Swagger Specification

Follow this doc to generate swagger specification.

References

vertx-rest's People

Contributors

akshaypatidar1999 avatar dependabot[bot] avatar nikhilt-d11 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

vertx-rest's Issues

HTTP request body resolution fails in case of extra keys

Summary

When using AbstractRestVerticle, for handling REST paths with request bodies, client requests encounter a 500 status code if the request body contains additional keys. This behavior is inconsistent with the expected response, which should return a 400 status code in such cases.

Steps To Reproduce

  1. Create a rest verticle and add a POST route usage ref
@Path("/test")
public class TestRoute {

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  @ApiResponse(content = @Content(schema = @Schema(implementation = String.class)))
  public CompletionStage<String> test() {
    return Single.just("Hello World").toCompletionStage();
  }
}
  1. Create a sample class and edit the POST route handle to use it as request body
@Data
@NoArgsConstructor
public class TestRequestBody {
   private String requestId;
}
public CompletionStage<String> test(TestRequestBody requestBody) {
  1. Start the application, and make a sample request, where there are extra keys in the request body
curl -i --location 'localhost:8080/test' --header 'Content-Type: application/json' --data '{"requestId": "12345", "requestedAt": 1711610520000}'
HTTP/1.1 500 Internal Server Error
transfer-encoding: chunked
Content-Type: application/json

{"error":{"message":"Something went wrong","cause":"requestedAt","code":"UNKNOWN_EXCEPTION"}}

Additional Details & Logs

Application Error Logs 2024-03-28 11:44:31,686 ERROR [vert.x-eventloop-thread-2] [GenericExceptionMapper.java:15] [] Error: UNKNOWN_EXCEPTION 500 Something went wrong java.lang.NoSuchFieldException: requestedAt at java.base/java.lang.Class.getDeclaredField(Class.java:2610) at com.dream11.rest.provider.JsonProvider.readFrom(JsonProvider.java:41) at org.jboss.resteasy.core.interception.jaxrs.AbstractReaderInterceptorContext.readFrom(AbstractReaderInterceptorContext.java:101) at org.jboss.resteasy.core.interception.jaxrs.ServerReaderInterceptorContext.readFrom(ServerReaderInterceptorContext.java:63) at org.jboss.resteasy.core.interception.jaxrs.AbstractReaderInterceptorContext.proceed(AbstractReaderInterceptorContext.java:80) at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:213) at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:95) at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:128) at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524) at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:370) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:408) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:69) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161) at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:370) at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247) at org.jboss.resteasy.plugins.server.vertx.RequestDispatcher.service(RequestDispatcher.java:96) at org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler.lambda$handle$0(VertxRequestHandler.java:68) at io.vertx.core.impl.future.FutureImpl$1.onSuccess(FutureImpl.java:91) at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54) at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:86) at io.vertx.core.impl.DuplicatedContext.execute(DuplicatedContext.java:184) at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:51) at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:211) at io.vertx.core.impl.future.PromiseImpl.tryComplete(PromiseImpl.java:23) at io.vertx.core.http.impl.HttpEventHandler.handleEnd(HttpEventHandler.java:79) at io.vertx.core.http.impl.Http1xServerRequest.onEnd(Http1xServerRequest.java:595) at io.vertx.core.http.impl.Http1xServerRequest.lambda$pendingQueue$1(Http1xServerRequest.java:133) at io.vertx.core.streams.impl.InboundBuffer.handleEvent(InboundBuffer.java:255) at io.vertx.core.streams.impl.InboundBuffer.write(InboundBuffer.java:134) at io.vertx.core.http.impl.Http1xServerRequest.handleEnd(Http1xServerRequest.java:576) at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:76) at io.vertx.core.impl.DuplicatedContext.execute(DuplicatedContext.java:174) at io.vertx.core.http.impl.Http1xServerConnection.onEnd(Http1xServerConnection.java:221) at io.vertx.core.http.impl.Http1xServerConnection.onContent(Http1xServerConnection.java:209) at io.vertx.core.http.impl.Http1xServerConnection.handleOther(Http1xServerConnection.java:189) at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:176) at io.vertx.core.net.impl.ConnectionBase.read(ConnectionBase.java:159) at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:153) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93) at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:88) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:61) at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:38) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) at io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:544) at io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.readReady(AbstractKQueueChannel.java:387) at io.netty.channel.kqueue.KQueueEventLoop.processReady(KQueueEventLoop.java:218) at io.netty.channel.kqueue.KQueueEventLoop.run(KQueueEventLoop.java:296) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:833)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.