bootique / bootique-jersey-client Goto Github PK
View Code? Open in Web Editor NEWAn HTTP client integrated with Bootique, built on top of Jersey
Home Page: https://bootique.io
License: Apache License 2.0
An HTTP client integrated with Bootique, built on top of Jersey
Home Page: https://bootique.io
License: Apache License 2.0
Renaming metrics per bootique/bootique-metrics#30 :
io.bootique.jersey.client.instrumented.InstrumentedFeature.client-request-timer
renamed to bq.JerseyClient.Client.RequestTimer
Client-side Features will be possible per #5 . Lets make sure we support injection into services that are registered via features.
this would allow us to write cleaner tests that require running Jetty or a full client app.
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.
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)
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.
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:
WebTarget
with some base URL in most cases.TODO: we should test registering Features for named targets... and that different features for different targets do not interfere with each other.
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.
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();
There are some breaking change in 0.20 ... Upgrading all standard modules
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
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
}
Currently OAuth2TokenAuthenticator
uses JAXRS API directly to create token API client. Let's use HttpClientFactory
for this purpose, or otherwise we can't pass truststores and such.
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).
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).
stay up to date
Since bootique-jersey
got upgraded, let's upgrade Jersey on the client as well to make sure they align.
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)
Implies an upgrade to the latest bootique (0.19), bootique-metrics (0.7)
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.
Part of the overall deprecated API cleanup per bootique/bootique#214
Create an "instrumented" version of jersey client. First thing to track is request timing similar to what Jetty TimingFilter does.
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)
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.
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.
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?
Upgrading Jackson in Bootique core. Need to upgrade bootique-jersey-client
to a compatible version. bootique/bootique#189
A configurable client implementation with configs for the underlying jersey-grizzly-connector
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:
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"
[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
Use case:
jerseyclient:
auth:
myauth:
type: oauth2
tokenUrl: "..."
username: "..."
password: "..."
targets:
mytarget:
auth: "apptoken"
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
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
)jerseyclient.followRedirects
default to be "false", you will need to reconfigure your app to set it to false explicitly:jerseyclient:
followRedirects: false
Once bootique/bootique#14 becomes available, switch to dynamic polymorphic configs for AuthenticatorFactory, removing @jsontype annotations.
Per bootique/bootique#27 we can now annotate config factories. Let's upgrade to BQ 0.21 and annotate HttpClientFactoryFactory .
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.
Support for client-side Feature contributions similar to how Jersey server does.
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.