Giter VIP home page Giter VIP logo

netty-rest's Introduction

Netty RESTful Server

Netty-rest is a high performance HTTP and WebSocket server implementation based on Netty. It uses javax.ws.rs annotations and generates Java bytecode at runtime in order to provide best performance. It basically maps the java methods to endpoints and takes care of validation, serialization / deserialization, authentication and designed for minimum overhead and maximum performance. It can generate Swagger specification automatically so that you can generate client libraries, API documentation easily.

Here is a simple example:

import org.rakam.server.http.HttpServer;
import org.rakam.server.http.HttpServerBuilder;
import org.rakam.server.http.HttpService;
import org.rakam.server.http.annotations.*;

import javax.ws.rs.Path;
import java.util.Arrays;
import java.util.HashSet;

public class HttpServerTest {
    public static void main(String[] args) throws Exception {
        HttpServer build = new HttpServerBuilder()
                .setHttpServices(new HashSet<>(Arrays.asList(new CustomHttpServer()))).build();

        build.bindAwait("127.0.0.1", 7847);
    }

    @Path("/")
    public static class CustomHttpServer extends HttpService {
        @JsonRequest
        @ApiOperation(value = "Parameter demo endpoint")
        @Path("/parameter")
        public String testJsonParameter(@ApiParam("param1") String param1, @ApiParam("param2") int param2) {
            return param1 + param2;
        }
    }
}

And then run the following CURL command:

curl -X POST http://127.0.0.1:7847/parameter \
    -H 'content-type: application/json' \
    -d '{"param1": "Hello", "param2": 2}'

If you don't pass one of the parameters, the server will return 400 response, you can also use complex java beans in parameters and method return signature. The library uses Jackson for serialization of the object that you passed and deserialization of the JSON attributes. It will be mapped to the parameters and the method will be invoked for the API calls.

Here is the complete list of examples for basic operations:

import org.rakam.server.http.HttpServer;
import org.rakam.server.http.HttpServerBuilder;
import org.rakam.server.http.HttpService;
import org.rakam.server.http.annotations.*;

import javax.ws.rs.Path;
import java.util.Arrays;
import java.util.HashSet;

public class HttpServerTest {
    public static void main(String[] args) throws Exception {
        HttpServer build = new HttpServerBuilder()
                .setHttpServices(new HashSet<>(Arrays.asList(new SimpleHttpService()))).build();

        build.bindAwait("127.0.0.1", 7847);
    }

    @Path("/")
    public static class SimpleHttpService extends HttpService {
        @JsonRequest
        @ApiOperation(value = "Bean Demo endpoint")
        @Path("/bean")
        public String testJsonBean(@BodyParam DemoBean demo) {
            return demo.toString();
        }

        @JsonRequest
        @ApiOperation(value = "Parameter demo endpoint")
        @Path("/parameter")
        public String testJsonParameter(@ApiParam("param1") String param1, @ApiParam("param2") int param2) {
            return param1 + param2;
        }

        // You can also use CompletableFuture for async operations
        @JsonRequest
        @ApiOperation(value = "Parameter demo endpoint")
        @Path("/future-parameter")
        public CompletableFuture<String> futureTestJsonParameter(@ApiParam("param1") String param1, @ApiParam("param2") Integer param2, @ApiParam(value = "param3", required = false) Long param3) {
            CompletableFuture<String> future = new CompletableFuture<>();
            future.complete(param1 + param2 + param3);
            return future;
        }

        @JsonRequest
        @ApiOperation(value = "Parameter demo endpoint")
        @Path("/header-cookie-parameter")
        public CompletableFuture<String> futureTestJsonParameter(@HeaderParam("my-custom-header") String param1, @CookieParam("my-cookie-param") String param2) {
            CompletableFuture<String> future = new CompletableFuture<>();
            future.complete(param1 + param2);
            return future;
        }

        @JsonRequest
        @ApiOperation(value = "Raw demo endpoint")
        @Path("/raw")
        public void testJsonParameter(RakamHttpRequest request) {
            request.response("cool").end();
        }

        public static class DemoBean {
            public final String test;

            @JsonCreator
            public DemoBean(@JsonProperty("test") String test) {
                this.test = test;
            }
        }
    }
}

Authentication

You can implement API key based authentification easily with custom parameters. Here is a simple example:

public class HttpServerTest {
    public static void main(String[] args) throws Exception {
        HttpServer build = new HttpServerBuilder()
                .setCustomRequestParameters(ImmutableMap.of("projectId", method -> (node, request) -> {
                    String apiKey = request.headers().get("api_key");
                    try {
                        return apiKeyService.findProject(apiKey);
                    } catch (NotFoundException e) {
                        throw new HttpRequestException("API key is invalid", HttpResponseStatus.FORBIDDEN);
                    }
                })).build();
                .setHttpServices(new HashSet<>(Arrays.asList(new CustomHttpServer()))).build();

        build.bindAwait("127.0.0.1", 7847);
    }

    @Path("/")
    public static class CustomHttpServer extends HttpService {
        @JsonRequest
        @ApiOperation(value = "Parameter demo endpoint")
        @Path("/list")
        public List<String> testJsonParameter(@Named("projectId") int id) {
            return db.getItemsForProject(id);
        }
    }
}

Request hooks

You can add hooks to API calls before the methods are executed and also after they're executed. Here is an example:

HttpServer build = new HttpServerBuilder()
    .setHttpServices(new HashSet<>(Arrays.asList(new SimpleHttpService())))
    .addJsonPreprocessor(new RequestPreprocessor() {
        @Override
        public void handle(RakamHttpRequest request) {
            System.out.println(request.getUri());
        }
    }, (method) -> true)
    .addPostProcessor(new ResponsePostProcessor() {
        @Override
        public void handle(FullHttpResponse response) {
            System.out.println(response.getStatus());
        }
    }, (method) -> true).build();

Websockets

Although the library is designed for RESTFul APIs, it also has support for websockets:

public class HttpServerTest {
    public static void main(String[] args) throws Exception {
        HttpServer build = new HttpServerBuilder()
                .setWebsocketServices(new HashSet<>(Arrays.asList(new SimpleWebhookService()))).build();

        build.bindAwait("127.0.0.1", 7847);
    }
    
    public class SimpleWebhookService extends WebSocketService {
        private String id;

        @Override
        public void onOpen(WebSocketRequest request) {
            id = UUID.randomUUID().toString();
            System.out.println(String.format("%s: started", id));
        }

        @Override
        public void onMessage(ChannelHandlerContext ctx, String message) {
            System.out.println(String.format("%s: sent %s", id, message));
        }

        @Override
        public void onClose(ChannelHandlerContext ctx) {
            System.out.println(String.format("%s: closed", id));
        }
    }
}

Exception handling

Exception hooks are particularly useful for logging them to your API Exception tracker. If you throw HttpRequestException in your code, you can set the API call status code and error message but if the Exception is not an instance of HttpRequestException, the server will return 500 status code.

HttpServer build = new HttpServerBuilder()
      .setHttpServices(new HashSet<>(Arrays.asList(new SimpleHttpService())))
      .setExceptionHandler(new HttpServerBuilder.ExceptionHandler() {
          @Override
          public void handle(RakamHttpRequest request, Throwable e) {

          }
      }).build();

Swagger

The library automatically generates the Swagger spec for you. You can see the specification in /api/swagger.json path. Here is a real example. I also maintaion a Slate documentation generator from Swagger specification. This library is compatible with the API documentation generator, here is an example.

You can set your Swagger instance using HttpServerBuilder.setSwagger. Here is an example:

Info info = new Info()
        .title("My API Documentation")
        .version("0.1")
        .description("My great API")
        .contact(new Contact().email("[email protected]"))
        .license(new License()
                .name("Apache License 2.0")
                .url("http://www.apache.org/licenses/LICENSE-2.0.html"));

Swagger swagger = new Swagger().info(info)
        .host("app.myapp.io")
        .basePath("/")
        .tags(ImmutableList.copyOf(tags))
        .securityDefinition("api_key", new ApiKeyAuthDefinition().in(In.HEADER).name("api_key"));

new HttpServerBuilder().setSwagger(swagger).build()

Misc

If you run the library on Linux, it will try to use Epoll but you can disable it with HttpServerBuilder.setUseEpollIfPossible

You can also use your own Jackson mapper with HttpServerBuilder.setMapper if you have custom JSON serializers / deserializers.

We also support Proxy Protocol if you run the HTTP server behind the load balancer. You can enable it with HttpServerBuilder.setProxyProtocol.

You can take a look at examples in Rakam which heavily uses netty-rest: https://github.com/rakam-io/rakam/blob/master/rakam/src/main/java/org/rakam/plugin/user/UserHttpService.java

Profiling

The library exposes an MBean called org.rakam.server.http:name=SHttpServer. If you attach the JVM instance you can call getActiveClientCount and getActiveRequests for the list of active request the execution time of each request.

To be done

  • Javadocs
  • API usage monitoring tool

netty-rest's People

Contributors

buremba avatar hazkaz avatar hellococooo 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

netty-rest's Issues

Dependency conflicts on com.fasterxml.jackson.core:jackson-databind:jar, leading to inconsistent semantic behaviors

Hi, in netty-rest-netty-rest-0.106, there are mulptiple versions of com.fasterxml.jackson.core:jackson-databind:jar. However, according to Maven's "nearest wins" strategy, only com.fasterxml.jackson.core:jackson-databind:jar:2.4.5 can be loaded, and com.fasterxml.jackson.core:jackson-databind:jar:2.8.9 will be shadowed.

As shown in the following dependency tree, io.swagger:swagger-core:jar:1.5.16 expects to reference com.fasterxml.jackson.core:jackson-databind:jar:2.8.9. But due to dependency conflicts, Maven actually loads com.fasterxml.jackson.core:jackson-databind:jar:2.4.5. As a result, io.swagger:swagger-core:jar:1.5.16 has to invoke the methods included in the unexpected version com.fasterxml.jackson.core:jackson-databind:jar:2.4.5, which may cause inconsistent semantic behaviors.

In total, there are 12 conflicting API pairs between these two library version.

For instance, method <org.rakam.server.http.ModelConverters.readAsProperty(java.lang.reflect.Type)> actually references method <com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithExternalTypeId(JsonParser, DeserializationContext)> in the unexpected version com.fasterxml.jackson.core:jackson-databind:jar:2.4.5 via the following invocation path:

<org.rakam.server.http.ModelConverters: io.swagger.models.properties.Property readAsProperty(java.lang.reflect.Type)> C:\Users\Flipped\Desktop\project\netty-rest-netty-rest-0.106\.\target\classes
<io.swagger.converter.ModelConverterContextImpl: io.swagger.models.properties.Property resolveProperty(java.lang.reflect.Type,java.lang.'annotation'.Annotation[])> C:\Users\Flipped\.m2\repository\io\swagger\swagger-core\1.5.16\swagger-core-1.5.16.jar
<com.fasterxml.jackson.databind.MappingIterator: java.lang.Object next()> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.MappingIterator: java.lang.Object nextValue()> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.deser.BeanDeserializer: java.lang.Object deserialize(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.deser.BeanDeserializer: java.lang.Object deserializeFromObject(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.deser.BeanDeserializer: java.lang.Object deserializeWithExternalTypeId(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext)>

By further analyzing, the expected callee <com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithExternalTypeId(JsonParser, DeserializationContext)>, have different implementations from the actual callees with the same signatures (same method names, same paremeters) included in the unexpected (but actual loaded) version com.fasterxml.jackson.core:jackson-databind:jar:2.4.5, which leads to different behaviors.

Solution:
Use the newer version com.fasterxml.jackson.core:jackson-databind:jar:2.8.9 to keep the version consistency.

The detailed informantion of the remaining 11 conflicting API pairs can be found in the following attachment.

12 conflicting API pairs in project netty-rest.txt

Dependency tree----

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ netty-rest ---
[INFO] org.rakam:netty-rest:jar:0.106
[INFO] +- javax.ws.rs:javax.ws.rs-api:jar:2.0.1:compile
[INFO] +- com.fasterxml.jackson.core:jackson-core:jar:2.4.5:compile
[INFO] +- io.airlift:log:jar:0.117:compile
[INFO] | - javax.inject:javax.inject:jar:1:compile
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.4.5:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.4.0:compile
[INFO] | - (com.fasterxml.jackson.core:jackson-core:jar:2.4.5:compile - omitted for duplicate)
[INFO] +- com.jcraft:jzlib:jar:1.1.2:compile
[INFO] +- io.netty:netty-all:jar:4.0.33.Final:compile
[INFO] +- com.thoughtworks.paranamer:paranamer:jar:2.8:compile
[INFO] +- io.swagger:swagger-core:jar:1.5.16:compile
[INFO] | +- org.apache.commons:commons-lang3:jar:3.2.1:compile
[INFO] | +- org.slf4j:slf4j-api:jar:1.7.22:compile
[INFO] | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.8.9:compile - omitted for conflict with 2.4.0)
[INFO] | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.8.9:compile - omitted for conflict with 2.4.5)
[INFO] | +- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.8.9:compile
[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.8.9:compile - omitted for conflict with 2.4.5)
[INFO] | | - org.yaml:snakeyaml:jar:1.17:compile
[INFO] | +- io.swagger:swagger-models:jar:1.5.16:compile
[INFO] | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.8.9:compile - omitted for conflict with 2.4.0)
[INFO] | | +- (org.slf4j:slf4j-api:jar:1.7.22:compile - omitted for duplicate)
[INFO] | | - io.swagger:swagger-annotations:jar:1.5.16:compile
[INFO] | +- com.google.guava:guava:jar:20.0:compile
[INFO] | - javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] +- org.jetbrains.kotlin:kotlin-stdlib:jar:1.2.31:provided (scope not updated to compile)
[INFO] | - org.jetbrains:annotations:jar:13.0:provided
[INFO] +- org.jetbrains.kotlin:kotlin-reflect:jar:1.2.31:compile
[INFO] | - (org.jetbrains.kotlin:kotlin-stdlib:jar:1.2.31:compile - omitted for duplicate)
[INFO] - junit:junit:jar:4.12:test
[INFO] - org.hamcrest:hamcrest-core:jar:1.3:test

Thanks!
Best regards,
Coco

Handle Expect 100 header properly

Curl doesn't work without empty Expect header since it first sends an empty body and requests the proper body size before sending the actual body.

Got 404 when run your sample code.

I tested with browser and curl, both returned 404 code.
And i checked in RouteMatcher, near
final HttpRequestHandler handler = routes.get(new PatternBinding(method, path));

and i got a null hadler.
then the prefixRoutes is empty. nothing in it.
so
noMatch.handle(request);
return the '404' (not found)

i guess there is something wrong in your sample code? or did i make something wrong?
help me please.

Referencing unexpected methods, due to dependency conflicts on com.fasterxml.jackson.core:jackson-core:jar

An issue similar to #8.

Mulptiple versions of com.fasterxml.jackson.core:jackson-core:jar exist in netty-rest-netty-rest-0.106 . Based on Maven's "nearest wins" strategy, only com.fasterxml.jackson.core:jackson-core:jar:2.4.5 can be loaded, and com.fasterxml.jackson.core:jackson-core:jar:2.8.9 will be shadowed.

Similarly, as shown in the following dependency tree, io.swagger:swagger-core:jar:1.5.16 expects to reference com.fasterxml.jackson.core:jackson-core:jar:2.8.9. However, due to dependency conflicts, Maven actually loads com.fasterxml.jackson.core:jackson-core:jar:2.4.5. Consequenctly, io.swagger:swagger-core:jar:1.5.16 has to invoke the methods included in the unexpected version com.fasterxml.jackson.core:jackson-core:jar:2.4.5, which may cause inconsistent semantic behaviors.

In total, there are 9 conflicting API pairs between these two library version.

Likewise , method org.rakam.server.http.SwaggerReader.isPrimitive(Type) actually references method com.fasterxml.jackson.core.util.JsonParserSequence.setnamesSet(Hashset) in the unexpected version ccom.fasterxml.jackson.core:jackson-core:jar:2.4.5 via the following invocation path:

<org.rakam.server.http.SwaggerReader: boolean isPrimitive(java.lang.reflect.Type)> C:\Users\Flipped\Desktop\project\netty-rest-netty-rest-0.106\.\target\classes
<org.rakam.server.http.ModelConverters: io.swagger.models.properties.Property readAsProperty(java.lang.reflect.Type)> C:\Users\Flipped\Desktop\project\netty-rest-netty-rest-0.106\.\target\classes
<io.swagger.converter.ModelConverterContextImpl: io.swagger.models.properties.Property resolveProperty(java.lang.reflect.Type,java.lang.'annotation'.Annotation[])> C:\Users\Flipped\.m2\repository\io\swagger\swagger-core\1.5.16\swagger-core-1.5.16.jar
<com.fasterxml.jackson.databind.MappingIterator: java.lang.Object next()> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.MappingIterator: java.lang.Object nextValue()> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.deser.impl.BeanAsArrayDeserializer: java.lang.Object deserialize(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.deser.impl.InnerClassProperty: void deserializeAndSet(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext,java.lang.Object)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.deser.std.EnumMapDeserializer: java.lang.Object deserializeWithType(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext,com.fasterxml.jackson.databind.jsontype.TypeDeserializer)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer: java.lang.Object deserializeTypedFromObject(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer: java.lang.Object _deserializeTypedForId(com.fasterxml.jackson.core.JsonParser,com.fasterxml.jackson.databind.DeserializationContext,com.fasterxml.jackson.databind.util.TokenBuffer)> C:\Users\Flipped\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.4.5\jackson-databind-2.4.5.jar
<com.fasterxml.jackson.core.util.JsonParserSequence: com.fasterxml.jackson.core.util.JsonParserSequence.setnamesSet(Hashset)>

By further analyzing, the expected callee com.fasterxml.jackson.core.util.JsonParserSequence.setnamesSet(Hashset), have different implementations from the actual callees with the same signatures (same method names, same paremeters) included in the unexpected (but actual loaded) version com.fasterxml.jackson.core:jackson-core:jar:2.4.5, which leads to different behaviors.

Solution:
Use the newer version com.fasterxml.jackson.core:jackson-core:jar:2.8.9 to keep the version consistency.

The detailed informantion of the remaining 8 conflicting API pairs can be found in the following attachment.

9 conflicting API pairs in project netty-rest.txt

Dependency tree-----
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ netty-rest ---
[INFO] org.rakam:netty-rest:jar:0.106
[INFO] +- javax.ws.rs:javax.ws.rs-api:jar:2.0.1:compile
[INFO] +- com.fasterxml.jackson.core:jackson-core:jar:2.4.5:compile
[INFO] +- io.airlift:log:jar:0.117:compile
[INFO] | - javax.inject:javax.inject:jar:1:compile
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.4.5:compile
[INFO] | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.4.0:compile
[INFO] | - (com.fasterxml.jackson.core:jackson-core:jar:2.4.5:compile - omitted for duplicate)
[INFO] +- com.jcraft:jzlib:jar:1.1.2:compile
[INFO] +- io.netty:netty-all:jar:4.0.33.Final:compile
[INFO] +- com.thoughtworks.paranamer:paranamer:jar:2.8:compile
[INFO] +- io.swagger:swagger-core:jar:1.5.16:compile
[INFO] | +- org.apache.commons:commons-lang3:jar:3.2.1:compile
[INFO] | +- org.slf4j:slf4j-api:jar:1.7.22:compile
[INFO] | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.8.9:compile - omitted for conflict with 2.4.0)
[INFO] | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.8.9:compile - omitted for conflict with 2.4.5)
[INFO] | +- com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.8.9:compile
[INFO] | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.8.9:compile - omitted for conflict with 2.4.5)
[INFO] | | - org.yaml:snakeyaml:jar:1.17:compile
[INFO] | +- io.swagger:swagger-models:jar:1.5.16:compile
[INFO] | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.8.9:compile - omitted for conflict with 2.4.0)
[INFO] | | +- (org.slf4j:slf4j-api:jar:1.7.22:compile - omitted for duplicate)
[INFO] | | - io.swagger:swagger-annotations:jar:1.5.16:compile
[INFO] | +- com.google.guava:guava:jar:20.0:compile
[INFO] | - javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] +- org.jetbrains.kotlin:kotlin-stdlib:jar:1.2.31:provided (scope not updated to compile)
[INFO] | - org.jetbrains:annotations:jar:13.0:provided
[INFO] +- org.jetbrains.kotlin:kotlin-reflect:jar:1.2.31:compile
[INFO] | - (org.jetbrains.kotlin:kotlin-stdlib:jar:1.2.31:compile - omitted for duplicate)
[INFO] - junit:junit:jar:4.12:test
[INFO] - org.hamcrest:hamcrest-core:jar:1.3:test

Thanks!
Best regards,
Coco

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.