Giter Site home page Giter Site logo

bootique-jersey-client's Introduction

build test deploy Maven Central

Bootique is a minimally opinionated java launcher and integration technology. It is intended for building container-less runnable Java applications. With Bootique you can create REST services, webapps, jobs, DB migration tasks, etc. and run them as if they were simple commands. No JavaEE container required! Among other things Bootique is an ideal platform for Java microservices, as it allows you to create a fully-functional app with minimal setup.

Each Bootique app is a collection of modules interacting with each other via dependency injection. This GitHub project provides Bootique core. Bootique team also develops a number of important modules. A full list is available here.

Quick Links

Support

You have two options:

  • Open an issue on GitHub with a label of "help wanted" or "question" (or "bug" if you think you found a bug).
  • Post a question on the Bootique forum.

TL;DR

For the impatient, here is how to get started with Bootique:

  • Declare the official module collection:
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.bootique.bom</groupId>
            <artifactId>bootique-bom</artifactId>
            <version>3.0-M4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency> 
    </dependencies>
</dependencyManagement>
  • Include the modules that you need:
<dependencies>
    <dependency>
        <groupId>io.bootique.jersey</groupId>
        <artifactId>bootique-jersey</artifactId>
    </dependency>
    <dependency>
        <groupId>io.bootique.logback</groupId>
        <artifactId>bootique-logback</artifactId>
    </dependency>
</dependencies>
  • Write your app:
package com.foo;

import io.bootique.Bootique;

public class Application {
    public static void main(String[] args) {
        Bootique
            .app(args)
            .autoLoadModules()
            .exec()
            .exit();
    }
}

It has main() method, so you can run it!

For a more detailed tutorial proceed to this link.

Upgrading

See the "maven-central" badge above for the current production version of bootique-bom. When upgrading, don't forget to check upgrade notes specific to your version.

bootique-jersey-client's People

Contributors

aarrsseni avatar andrus avatar const1993 avatar elena-bondareva avatar irus avatar stariy95 avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bootique-jersey-client's Issues

Support for configuring trust store

Often we have to read from HTTPS connections that have self-signed certificates (or certificates that are signed by a proper authority, that is unfortunately not known to Java). In this case we need to explicitly tell our clients about those certificates by providing a "trust store". Let's add local trust store to jerseyclient config:

jerseyclient:
  trustStore: classpath:ts.jks
  trustStorePassword: changeit # make this default

Java 10+: explicit activation and jaxb dependencies are required

Somehow we overlooked bootique-jersey-client when doing Java 10+ upgrade (lack of tests?). Just like other Jersey modules, it throws on Java 10+ because of the lack of a few libs. Here is an exception (on Java 12) :

java.lang.NoClassDefFoundError: javax/activation/DataSource
at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3142)
at java.base/java.lang.Class.getDeclaredConstructors(Class.java:2362)
at org.jvnet.hk2.internal.Utilities$3.run(Utilities.java:1378)
at org.jvnet.hk2.internal.Utilities$3.run(Utilities.java:1374)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:310)
at org.jvnet.hk2.internal.Utilities.getAllConstructors(Utilities.java:1374)
at org.jvnet.hk2.internal.Utilities.findProducerConstructor(Utilities.java:1317)
at org.jvnet.hk2.internal.DefaultClassAnalyzer.getConstructor(DefaultClassAnalyzer.java:83)
at org.glassfish.jersey.inject.hk2.JerseyClassAnalyzer.getConstructor(JerseyClassAnalyzer.java:148)
at org.jvnet.hk2.internal.Utilities.getConstructor(Utilities.java:180)
at org.jvnet.hk2.internal.ClazzCreator.initialize(ClazzCreator.java:129)
at org.jvnet.hk2.internal.ClazzCreator.initialize(ClazzCreator.java:180)
at org.jvnet.hk2.internal.SystemDescriptor.internalReify(SystemDescriptor.java:740)
at org.jvnet.hk2.internal.SystemDescriptor.reify(SystemDescriptor.java:694)
at org.jvnet.hk2.internal.ServiceLocatorImpl.reifyDescriptor(ServiceLocatorImpl.java:464)
at org.jvnet.hk2.internal.ServiceLocatorImpl.narrow(ServiceLocatorImpl.java:2310)
at org.jvnet.hk2.internal.ServiceLocatorImpl.access$1200(ServiceLocatorImpl.java:128)
at org.jvnet.hk2.internal.ServiceLocatorImpl$9.compute(ServiceLocatorImpl.java:1395)
at org.jvnet.hk2.internal.ServiceLocatorImpl$9.compute(ServiceLocatorImpl.java:1390)
at org.glassfish.hk2.utilities.cache.internal.WeakCARCacheImpl.compute(WeakCARCacheImpl.java:128)
at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetAllServiceHandles(ServiceLocatorImpl.java:1452)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getAllServiceHandles(ServiceLocatorImpl.java:1377)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getAllServiceHandles(ServiceLocatorImpl.java:1366)
at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.getAllServiceHolders(AbstractHk2InjectionManager.java:158)
at org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager.getAllServiceHolders(ImmediateHk2InjectionManager.java:54)
at org.glassfish.jersey.internal.inject.Providers.getServiceHolders(Providers.java:337)
at org.glassfish.jersey.internal.inject.Providers.getCustomProviders(Providers.java:175)
at org.glassfish.jersey.message.internal.MessageBodyFactory.initialize(MessageBodyFactory.java:238)
at org.glassfish.jersey.message.internal.MessageBodyFactory$MessageBodyWorkersConfigurator.postInit(MessageBodyFactory.java:136)
at org.glassfish.jersey.client.ClientConfig$State.lambda$initRuntime$2(ClientConfig.java:470)
at java.base/java.util.Arrays$ArrayList.forEach(Arrays.java:4411)
at org.glassfish.jersey.client.ClientConfig$State.initRuntime(ClientConfig.java:470)
at org.glassfish.jersey.internal.util.collection.Values$LazyValueImpl.get(Values.java:341)
at org.glassfish.jersey.client.ClientConfig.getRuntime(ClientConfig.java:826)
at org.glassfish.jersey.client.ClientRequest.getConfiguration(ClientRequest.java:285)
at org.glassfish.jersey.client.JerseyInvocation.validateHttpMethodAndEntity(JerseyInvocation.java:143)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:112)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:108)
at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:99)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:419)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:319)

Logging client requests

Currently instrumentation framework outputs client HTTP request boundaries, status and time:

INFO i.b.j.c.i.ClientTimingFilter: Client request started
INFO i.b.j.c.i.ClientTimingFilter: Client request finished. Status: 200, time: 5 ms.

Would be cool to also provide (probably in the base client module) logging of all requests upon completion (kind of like bootique-jetty does, but for the client instead of server requests).

Named truststores, Client builder

The apps occasionally need more than one truststore. Or a typical situation when a client for one endpoint needs a custom trust store (because the server is using self-signed certificates), and all others are fine with the default JVM truststore. So going to replace (with deprecation) the current singleton truststore with a new config tree:

jerseyclient:
   trustStore:
     ts1:
       location: "classpath:ts1"
       password: "changeit" # "changeit"  is the default of course and can be omitted
    ts2:
      location: ....

The use of it will require adding builder method to HttpClientFactory:

clientFactory.newClientBuilder().auth("a1").trustStore("ts1").build();

Response time healthcheck

Per #23 we created health checks for remote HTTP services that validate that the remote service is alive and responds. I guess it would make sense to create a similar health check triggered by slow responses, with a WARNING and CRITICAL threshold. (Or maybe it will be the same HttpHealthCheck with extra config options)

"apiKeyHeader" and "apiKeyParameter" authenticators

We currently support "oauth2" and "basic" auth out of the box for clients. Here is another common type of authentication - API key. While to the best of my knowledge it is not formally specified anywhere, I've seen a number of popular services using it. According to that Swagger link above there may be three common varieties:

  • Appending API Key as URL parameter
  • Passing it as a header
  • Passing it in a cookie

We'll implement two types of authenticators - "apiKeyParameter" and "apiKeyHeader" with the following configuration (Cookie is a kind of a header; also yet to see a service that uses cookies for this) :

jerseyclient:
  auth:
    myservice1:
      type: "apiKeyHeader" 
      # Header name. If missing, "X-Api-Key" is assumed
      name: "X-MyService-Api-Key" 
      # API Key value. Required
      key: "abc12345"
    myservice2:
      type: "apiKeyParameter" 
      # Query param name. If missing, "api_key" is assumed
      name: "my_key" 
      # API Key value. Required
      key: "abc12345"

Support Gzip compression by default

It turns out Jersey HTTP client doesn't support compression out of the box. Configuring it is dirt simple:

client.register(GZipEncoder.class)

but still I feel like it would be beneficial to just make it a default and allow turning it off via YAML (kind of similar to Jetty server feature).

StackOverflowError in Oauth2AuthenticatorFactory or we should not reuse Configuration in AuthenticatorFactory

An attempt to reuse parent Configuration in AuthenticatorFactory (specifically Oauth2AuthenticatorFactory) may cause StackOverflowErrors in some cases. I don't have a reproducible test case, but I've seen it happen in my apps. Even cloning the config doesn't help. So we need to change the signature of AuthenticatorFactory.createAuthFilter(..) to remove Configuration parameter, so that factory implementors don't run into this issue. If implementors require access to some http client parameters (e.g. proxy settings), they may pass them via YAML properties or store them in injector.

Allow parameterized templates in mapped target "url"

Change WebTargetFactory.url to String from URI to support templates like http://127.0.0.1:8080/r1/{p1}?name={p2}. This is useful in various scenarios, such as bootique/bootique-linkmove#40

Such change of the type of the factory parameter should not break any existing configurations, and short of people subclassing WebTargetFactory , should be backwards compatible.

Exception on JDK11

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /home/travis/build/bootique/bootique-jersey-client/bootique-jersey-client/src/main/java/io/bootique/jersey/client/auth/OAuth2TokenDAO.java:[30,33] package javax.xml.bind.annotation does not exist
[ERROR] /home/travis/build/bootique/bootique-jersey-client/bootique-jersey-client/src/main/java/io/bootique/jersey/client/auth/BasicAuthenticatorFactory.java:[30,22] package javax.xml.bind does not exist
[ERROR] /home/travis/build/bootique/bootique-jersey-client/bootique-jersey-client/src/main/java/io/bootique/jersey/client/auth/OAuth2TokenDAO.java:[124,10] cannot find symbol
  symbol:   class XmlAttribute
  location: class io.bootique.jersey.client.auth.OAuth2TokenDAO.TokenDTO
[ERROR] /home/travis/build/bootique/bootique-jersey-client/bootique-jersey-client/src/main/java/io/bootique/jersey/client/auth/OAuth2TokenDAO.java:[127,10] cannot find symbol
  symbol:   class XmlAttribute
  location: class io.bootique.jersey.client.auth.OAuth2TokenDAO.TokenDTO
[ERROR] /home/travis/build/bootique/bootique-jersey-client/bootique-jersey-client/src/main/java/io/bootique/jersey/client/auth/BasicAuthenticatorFactory.java:[87,51] cannot find symbol
  symbol:   variable DatatypeConverter
  location: class io.bootique.jersey.client.auth.BasicAuthenticatorFactory.BasicAuthenticator

Client requests logger

Jersey provides org.glassfish.jersey.filter.LoggingFilter. I guess we can create our own logging SLF delegate and if it is active at the "debug" level, install LoggingFilter with this delegate via HttpClientFactoryFactory.createConfig(..). This way log configuration will stay centralized under Logback, and no additional user-visible config is needed.

Support for OAuth 2 authenticator

In addition to the BASIC auth supported already, let's add an OAuth 2 athenticator that would support "client credentials" auth flow , returning a Client with cached Bearer token that will be used for API requests. Possible config structure:

jerseyclient:
  auth:
    twitter:
      type: oauth2
      tokenUrl: https://api.twitter.com/oauth2/token
      username: xyz
      password: 123

This should be enough to access APIs like Twitter, Github, Google, etc. out of the box with no custom security code.

Upgrading Jersey to 2.27

Since bootique-jersey got upgraded, let's upgrade Jersey on the client as well to make sure they align.

AuthenticatorFactory should have access to Injector

since we don't know what kind of authenticators users would write, and what kind of dependencies those might have, it probably makes sense to add Guice Injector to the AuthenticatorFactor list of parameters:

ClientRequestFilter createAuthFilter(Configuration clientConfig, Injector injector)

In fact I already have a few client-specific authenticators that absolutely require access to container.

HttpTargets : Configuring named targets

Suggested feature would allow to configure individual URL targets. E.g.:

jerseyclient:
   compression: true
   auth:
    compression: false
     a1: 
        type: basic
   trustStores:
      ts1:
         location: classpath:embedded_truststore.jks
   targets:
     t1:
       url: "https://bootique.io"
       compression: true # this overrides parent compression settings
       trustStore: ts1 # this is a reference to the named trustStore
       auth: a1 # this is a reference to the named auth
// This object is injectable
interface HttpTargets {
   WebTarget newTarget(String name);
}

This is trying to address a number of practical problems:

  1. The users really don't care about the Client object. They want WebTarget with some base URL in most cases.
  2. Keeping the base URL outside of the client configuration requires the apps to write their own factories with URL configuration.
  3. Current singleton configuration does not allow to override trust store per client, which is often problematic (e.g. some targets have self-signed certs and require a custom trust store, others are using proper certificates and require the default JVM trust store). (scratching this. It will be addressed per #28)

TODO: we should test registering Features for named targets... and that different features for different targets do not interfere with each other.

RuntimeException in WebTarget that use authentication with type "oauth"

Use case:

  1. YML-config - WebTarget with authentication; authentication with type "oauth2"
jerseyclient:
  auth:
    myauth:
      type: oauth2
      tokenUrl: "..."
      username: "..."
      password: "..."
  targets:
    mytarget:
      auth: "apptoken"
  1. As result, WebTarget with client request filter OAuth2TokenAuthenticator is registered
  2. When WebTarget is used, filter OAuth2TokenAuthenticator tries to get token value with help of POST request to "tokenUrl" with basic authorization header
  3. If username/password is invalid then POST request in p.3 returns response with 401 status.
  4. But filter OAuth2TokenAuthenticator translates this response to RuntimeException:
    Class io.bootique.jersey.client.auth.OAuth2TokenDAO:
protected OAuth2Token readToken(Response response) {
       if (response.getStatus() != Response.Status.OK.getStatusCode()) {
           String json = response.readEntity(String.class);
           String message = String.format(“Error reading token: %s ... %s”, response.getStatus(), json);
           throw new RuntimeException(message);
       }
….
}

Maybe is it needed to translate status 401 (and other Http statuses) of POST request in p.3 to corresponded ClientErrorException, not to RuntimeException

Flip 'followRedirects' config default to true, clarify other defaults

Currently there are a number of client properties that are modeled as Java primitives in configuration with no clarity on the default values. Let's flip the default for one of them, and document the rest:

  • followRedirects currently "false" if omitted. Should really be "true". Just makes sense to follow redirects by default.
  • readTimeoutMs no change... keeping the default as '0', but need to document the fact that zero == infinity (per JAX RS ClientProperties.READ_TIMEOUT)
  • connectTimeoutMs no change... keeping the default as '0', but need to document the fact that zero == infinity (per JAX RS ClientProperties.CONNECT_TIMEOUT)
  • connectTimeoutMs no change... keeping the default as '0', but need to document the fact that values that are <= 0 are ignored (per JAX RS ClientProperties.ASYNC_THREADPOOL_SIZE)

UPGRADE NOTES

  • If you implicitly relied on jerseyclient.followRedirects default to be "false", you will need to reconfigure your app to set it to false explicitly:
jerseyclient:
   followRedirects: false

Ability to register healthchecks for remote web services

We need a way to check that a remote REST service is alive. Unlike say bootique-jdbc, we don't have a registry of all clients, so we can't install health checks automatically. So we'll simply provide a few REST-oriented health check class(es) that the user may register via vanilla health check API from metrics.

class HttpHealthCheck {
    static HttpHealthCheck checkHEAD(WebTarget) {}
    static HttpHealthCheck checkGET(WebTarget) {}
    static HttpHealthCheck checkOPTIONS(WebTarget) {}
    // other types of health checks require more complex interaction with the server 
    // and can be handcoded without using HttpHealthCheck
}

Implement token refresh capability for OAuth2 authentication

Should implement the ability to refresh expiring OAuth2 tokens. We need to check the token expiration time before using it, and get a fresh token if needed.

Note that per https://tools.ietf.org/html/rfc6749#section-6 "refresh_token" should not be used to re-login the client since we are using "client_credentials" auth scheme.

TODO: Also would be nice to brainstorm token validation or reset approach (e.g. the server may have forcefully logged out the user and requests would start failing. How can we reset the client to make a fresh login attempt?

Use a different Set<Feature> from bootique-jersey

Looks like we have a room for potential conflict between server-side and client-side JAXRS features, as both bootique-jersey and bootique-jersey-client inject Set<Feature>. Let's use a client-specific annotation for client features.

Oauth2AuthenticatorFactory should not get token on startup

Currently using of Oauth2AuthenticatorFactory results in an HTTP call to the token endpoint on startup. This may be undesirable in many applications. The token should only be obtained when the first request is issued and not earlier.

Support for authentication filters

Let's support transparent configuration-driven authentication options for the client, starting with BASIC. Sample YAML:

jerseyclient:
  auth:
    name1:
       type: basic
       username: u1
       password: p1

HttpClientFactory can be extended to get an authenticated client referencing named auth:

Client newAuthenticatedClient(String authName)

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.