We are using Java GRPC for one of our internal services and we have a server side interceptor that we use to grab information from the headers and set them up in a logging context that that uses a ThreadLocal internally.
So in our interceptor we do something similar to this:
LogMessageBuilder.setServiceName("some-service");
final String someHeaderWeWant = headers.get(HEADER_KEY);
final LoggerContext.Builder loggingContextBuilder = new LoggerContext.Builder()
.someFieldFromHeaders(someHeaderWeWant);
LoggerContext.setContext(loggingContextBuilder.build());
Then in our service call we access it like this:
LoggingContext loggingContext = LoggingContext.getCurrent()
However the current context is null some of the time.
We then tried to use the GRPC Context class like below:
LogMessageBuilder.setServiceName("some-service");
final String someHeaderWeWant = headers.get(HEADER_KEY);
final LoggerContext.Builder loggingContextBuilder = new LoggerContext.Builder()
.someFieldFromHeaders(someHeaderWeWant);
Context.current().withValue(LOGGING_CONTEXT_KEY, loggingContextBuilder.build()).attach()
Then accessing it in the service call like:
LoggingContext context = LOGGING_CONTEXT_KEY.get(Context.current())
However that is also sometimes null and if I print out the memory addresses it appears that early on the context is always the ROOT context regardless of me attaching in the interceptor, but after a few calls the contexts are correct and the logger data is there like it should.
So if anyone has any ideas or better ways to propagate data from an interceptor to the service call I would love to hear it.
Each callback can be called on a different thread, so the thread-local has to be set for each callback. It seems you may accidentally be getting Contexts intended for other RPCs.
grpc-java 0.12.0 should be released this week. Context has been partially integrated in 0.12.0, and we also added Contexts.interceptCall() which is exactly what you need: it attaches and detaches the context for each callback.
In 0.12.0, you should now see new contexts being created for each server call (instead of ROOT) and contexts propagated from client calls to StreamObserver callbacks.
As another note, unlike ThreadLocal Context is intended to be tightly scoped: after attach(), you should generally have a try-finally to detach().
Related
I'm working on a REST API of a TomEE 7 based web app, which uses Shiro 1.3.2 for security. When an API request comes in, a SecurityManager and a Subject are created, and the latter is bound to a SubjectThreadState. I can call SecurityUtils.getSubject() anywhere in the endpoint code and the subject is always available.
However, problems arise when I try to do the same inside my custom JSON serialiser. It only serialises specific fields in some classes, so I register it on a per-field basis using this annotation:
#JsonSerialize(using = MySerialiser.class)
Long myRelatedItemId;
I wrote my serialiser based on the example code on this page under "2.7. #JsonSerialize". The serialiser needs to perform a cache lookup, and for that it has to have a Shiro subject. There is none because, thanks to the annotation above, I don't call the serialiser manually; instead Jersey calls it. This exception gets thrown (clarification: when I try to run SecurityUtils.getSubject() from the serialiser code):
org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
at org.apache.shiro.subject.Subject$Builder.<init>(Subject.java:627)
at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)
I have confirmed that everything works if I call something like ObjectMapper().writeValueAsString() manually from the API endpoint code. However, that is definitely not the proper way to do it, because then the endpoint would effectively send and receive strings instead of the objects they are meant to handle.
I don't understand much about the inner workings of Shiro or Jackson, but it seems like the serialisation is being performed inside another thread, where Shiro's SubjectThreadState doesn't exist. Although if threading really is the cause, then I cannot see why Thread.currentThread().getName() returns the same value both inside and outside the serialiser, as does Thread.currentThread().getId().
I have tried a vast number of things to no avail, including:
Upgrading to Shiro 1.4.0.
Upgrading Jackson from 2.7.5 to 2.9.7.
Saving the SecurityManager instance that is created at the start of the API call inside a static ThreadLocal variable of the serialiser class.
Writing my own implementation of MessageBodyWriter which, not surprisingly, is called in exactly the same fashion.
Setting the staticSecurityManagerEnabled parameter to true in the ShiroFilter configuration in my web.xml.
Can anyone suggest how I could make the SecurityManager (or Subject) visible to the serialiser, when it's running in a thread not started by my code (clarification: or, otherwise running in parallel and started by Jersey, as far as I can tell)? Thanks in advance.
Update:
This stack trace was taken inside the serialiser:
<mypackage>.MySerializer.serialize()
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField()
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields()
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize()
com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue()
com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize()
com.fasterxml.jackson.databind.ObjectWriter.writeValue()
com.fasterxml.jackson.jaxrs.base.ProviderBase.writeTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed()
org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed()
org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo()
org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed()
org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo()
org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse()
org.glassfish.jersey.server.ServerRuntime$Responder.processResponse()
org.glassfish.jersey.server.ServerRuntime$Responder.process()
org.glassfish.jersey.server.ServerRuntime$2.run()
This one was taken in our interceptor class where the Subject is created and bound:
<mypackage>.MySecurityInterceptor.createSession()
sun.reflect.NativeMethodAccessorImpl.invoke0()
sun.reflect.NativeMethodAccessorImpl.invoke()
sun.reflect.DelegatingMethodAccessorImpl.invoke()
java.lang.reflect.Method.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext$Invocation.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext.proceed()
org.apache.openejb.monitoring.StatsInterceptor.record()
org.apache.openejb.monitoring.StatsInterceptor.invoke()
sun.reflect.GeneratedMethodAccessor111.invoke()
sun.reflect.DelegatingMethodAccessorImpl.invoke()
java.lang.reflect.Method.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext$Invocation.invoke()
org.apache.openejb.core.interceptor.ReflectionInvocationContext.proceed()
org.apache.openejb.core.interceptor.InterceptorStack.invoke()
org.apache.openejb.core.stateless.StatelessContainer._invoke()
org.apache.openejb.core.stateless.StatelessContainer.invoke()
org.apache.openejb.core.ivm.EjbObjectProxyHandler.synchronizedBusinessMethod()
org.apache.openejb.core.ivm.EjbObjectProxyHandler.businessMethod()
org.apache.openejb.core.ivm.EjbObjectProxyHandler._invoke()
org.apache.openejb.core.ivm.BaseEjbProxyHandler.invoke()
com.sun.proxy.$Proxy279.getEntity()
org.openapitools.api.impl.MyApiServiceImpl.getEntity()
org.openapitools.api.MyApi.getEntity()
sun.reflect.NativeMethodAccessorImpl.invoke0()
sun.reflect.NativeMethodAccessorImpl.invoke()
sun.reflect.DelegatingMethodAccessorImpl.invoke()
java.lang.reflect.Method.invoke()
org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke()
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run()
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke()
org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch()
org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch()
org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke()
org.glassfish.jersey.server.model.ResourceMethodInvoker.apply()
org.glassfish.jersey.server.model.ResourceMethodInvoker.apply()
org.glassfish.jersey.server.ServerRuntime$2.run()
There are 46 more calls that are identical in both traces after that last line, so I excluded them. They contain a bunch of org.apache.catalina.core and org.glassfish.jersey.
Take a look at Shiro's Subject Thread Association doc
I needed to add an authorisation header to all outgoing requests from the client, and I added a ClientRequestFilter as instructed in this answer.
Now I want to change the properties of the filter I created. I tried to re-register a new copy of the filter but that didn't work. I tried to look through the available methods of the Client object but didn't find anything like de-register or remove.
Sample Code:
Client client = ClientBuilder.newClient().register(new ClientObjectMapperProvider());
AuthTokenFilter authTokenFilter = new AuthTokenFilter(authTokenService, authConfig);
client.register(authTokenFilter);
//make some requests
//In another method in another class
AuthTokenFilter newFilter = new AuthTokenFilter(authTokenService, newConfig);
client.register(newFilter);
//make more requests
When I send requests after registering the new filter, I want the auth header to be different according to the new config, but that's not happening.
On further investigation it seems that the client object will not register an object if another of the same type is already registered. Still haven't found a way to de-register though.
Creating Client instances is quite heavyweight so you don't want to do that very often, hence it makes sense that you are caching and reusing the client instance.
If you simply wish to have a separate authentication filter per targeted endpoint then registration doesn't have to be done on the client instance. Before you can actually talk to anything you need to create a WebTarget (which represents a concrete endpoint to which you are to communicate) Once you've created your WebTarget you can register your filter with that.
Configuring webtarget's can also be fairly heavy weight (see here)
but it does mean that you can spawn (and cache) different authentication filters per targeted host.
If you are wanting to dynamically reconfigure your client/webtarget based on some edited configuration (e.g. properties file/configuration REST endpoint) then you
can simply have a cache of WebTarget/Client instances which you regenerate from scratch when the configuration changes. (You need to remember to call client.close() before discarding any old client instance)
I have two instances of clients with different configs that I am creating (timeout, threadpool, etc...), and would like to leverage Dropwizard's metric on both of the clients.
final JerseyClientBuilder jerseyClientBuilder = new JerseyClientBuilder(environment)
.using(configuration.getJerseyClientConfiguration());
final Client config1Client = jerseyClientBuilder.build("config1Client");
environment.jersey().register(config1Client);
final Client config2Client = jerseyClientBuilder.build("config2Client");
environment.jersey().register(config2Client);
However, I am getting
org.glassfish.jersey.internal.Errors: The following warnings have been detected:
HINT: Cannot create new registration for component type class org.glassfish.jersey.client.JerseyClient:
Existing previous registration found for the type.
And only one client's metric shows up.
How do I track both clients' metrics or is it not common to have 2 clients in a single dropwizard app?
Never mind, turned out I was an idiot (for trying to save some resource on the ClientBuilder).
2 Things that I did wrong with my original code:
1. You don't need to register Jersey clients, just the resource is enough... somehow I missed the resource part in my code and just straight up trying to register the client
2. You need to explicitly build each JerseyClientBuilder and then build your individually configured clients, then dropwizard will fetch by each JerseyClientBuilder's metrics
In the end, I just had to change my code to the following:
final Client config1Client = new JerseyClientBuilder(environment)
.using(configuration.getJerseyClientConfiguration()).build("config1Client");
final Client config2Client = new JerseyClientBuilder(environment)
.using(configuration.getJerseyClientConfiguration()).build("config2Client");
Doh.
environment.jersey().register() has a javadoc listing of Adds the given object as a Jersey singleton component meaning that the objects registered become part of the jersey dependency injection framework. Specifically this method is used to add resource classes to the jersey context, but any object with an annotation or type that Jersey looks for can be added this way. Additionally, since they are singletons you can only have one of them per any concrete type (which is why you are getting a "previous registration" error from Jersey).
I imagine that you want to have two Jersey clients to connect to two different external services via REST/HTTP. Since your service needs to talk to these others to do its work, you'll want to have the clients accessible wherever the "work" or business logic is being performed.
For example, this guide creates a resource class that requires a client to an external http service to do currency conversions. I'm not saying this is a great example (just a top google result for dropwizard external client example). In fact, I think this not a good to structure your application. I'd create several internal objects that hide from the resource class how the currency information is fetched, like a business object (BO) or data access object (DAO), etc.
For your case, you might want something like this (think of these as constructor calls). JC = jersey client, R = resource object, BO = business logic object
JC1()
JC2()
B1(JC1)
B2(JC2)
R1(B1)
R2(B2)
R3(B1, B2)
environment.jersey().register(R1)
environment.jersey().register(R2)
environment.jersey().register(R3)
The official Dropwizard docs are somewhat helpful. They at least explain how to create a jersey client; they don't explain how to structure your application.
If you're using the Jersey client builder from dropwizard, each of the clients that you create should be automatically registered to record metrics. Make sure you're using the client builder from the dropwizard-client artifact and package io.dropwizard.client. (Looks like you are because you have the using(config) method.)
How to inject a parameter in the client of an Ejb?
Something like that:
final Hashtable<String, String> jndiProperties = new Hashtable<String, String>();
jndiProperties.put("java.naming.factory.initial", "org.ow2.carol.jndi.spi.MultiOrbInitialContextFactory");
jndiProperties.put("java.naming.factory.url.pkgs", "org.ow2.jonas.naming");
jndiProperties.put("java.naming.provider.url", "rmi://localhost:1099");
final Context context = new InitialContext(jndiProperties);
Object obj = context.lookup("MyEjbTest");
context.addToEnvironment("user", new Object());
In the server side, using an Interceptor get the parameter injected by the client:
public Object intercept(InvocationContext ctx) throws Exception {
Object o = ctx.getContextData().get("user");
if (o != null) {
LOG.info("Exists " + o.toString());
return ctx.proceed();
} else {
return null;
}
}
The parameter user is never injected in the context and in the server side o is always null. Is there any way to handle that?
No, there is no standard way to implicitly pass data to an EJB from a client. You must explicitly pass data to the EJB via a method argument.
If you're using RMI-IIOP, then you could write your own interceptor to transfer context data to the server and then store it in a thread local. If you're using WebSphere Application Server, you could use application context work areas (this was attempted to be standardized by JSR 149, but it was not deemed portable enough). These options are likely too niche or too cumbersome, so you're likely better off just explicitly passing the data via a method argument.
A complete example for sending additional context data using RMI-IIOP is quite extensive, but the general steps are:
Start by registering an ORBInitializer. See the javadoc therein, but since ORB configuration is usually tightly controlled by the application server, you should read your application server documentation, particularly for how (or if it's even supported at all) you can add an ORB interceptor and how the class loading works.
In the client, your ORBInitializer should call ORBInitInfo.add_client_request_interceptor. In your implementation of the send_request method, call ClientRequestInfo.add_request_service_context.
Normally, you would reserve a vendor prefix with the OMG for the service context ID, but if it's local to your environment (i.e., you're not providing your application to third parties), then you can probably pick one that doesn't conflict with any other products in your environment.
The bytes you send are your choosing. Your client would probably set some data in a thread local, and then your implementation of the send_request method would serialize the data to a byte[] to be added to the ServiceContext.
In the server, your ORBInitializer should call add_server_request_interceptor. Your implementation of this interceptor would decode the service context sent by the client and probably set a thread local variable for the duration of the request and remove it at the end.
Background
I have a java.lang.Thread that runs inside an application on a web server (JBoss or WebSphere) at a specific time, without human interaction, and all it does is send out an email. The contents of the email are similar to the contents of a JSP (/jsp/Report.jsp) we use as a display in a web view.
Instead of duplicating the same work or changing the JSP to a static class both can access, I would like to grab the contents of the run of the JSP from inside the thread and place it in the email for sending.
I have the current ServletContext from using a listener in the "web.xml". My current JSP call in the thread is like:
servletContext.getRequestDispatcher("/jsp/Report.jsp").include(dummyRequest, dummyResponse);
And the request/response classes are basically created like this:
final HttpServletRequest dummyRequest = new HttpServletRequest() { .... }
final HttpServletResponse dummyResponse = new HttpServletResponse() { .... }
I was going to set additional attributes (Classes) to the JSP via the dummyRequest like "dummyRequest.setAttribute(name, value)".
Whenever I make the call, I get exceptions because the dummy request/response is an anonymous class of HttpServletResponse/HttpServletRequest.
WebSphere Application Server 7.0.0.17:
java.lang.RuntimeException: SRV.8.2: RequestWrapper objects must extend ServletRequestWrapper or HttpServletRequestWrapper
JBoss AS 7.1.1:
java.lang.ClassCastException: my.test.thread$1$2 incompatible with javax.servlet.ServletRequestWrapper
And I can't create a HttpServletResponseWrapper/HttpServletRequestWrapper without an original request/response.
Question
So.... Is it possible to grab the contents of a JSP from inside a Thread on a web application using the context?
If so, how do I go about doing it?
Update
Here is the code I am using for my test: link
Research
I've now started diving into the server's source code to try and get a clue what is going on.
JBoss
In ApplicationDispatcher, "forward" does nothing since the "DISPATCHER_TYPE" attribute isn't set in the request (seen in the method processRequest). This isn't required for "include".
The problem I get with "incude" about the incompatible type is inside "ApplicationFilterFactory.createFilterChain". The Request object isn't the right class it is looking for, which in JBoss' case is either "org.apache.catalina.connector.Request" or "org.apache.catalina.connector.RequestFacade". It won't continue at all unless the request matches one of these types.
So when I use the following request:
final HttpServletRequest dummyRequest = new org.apache.catalina.connector.RequestFacade(new org.apache.catalina.connector.Request() { ... });
It successfully runs and returns the results of the JSP from inside the thread.
Websphere
I have not been able to produce the same results on Websphere.
Websphere requires an instance of "com.ibm.ws.webcontainer.srt.SRTConnectionContextImpl" and then manipulating the ServletContext to its original class "com.ibm.wsspi.webcontainer.facade.ServletContextFacade", but then I get stuck on an "access$200" null pointer exception inside "com.ibm.ws.webcontainer.srt.SRTServletRequest$SRTServletRequestHelper", which makes it seem like I am breaking Java somehow.
java.lang.NullPointerException at com.ibm.ws.webcontainer.srt.SRTServletRequest$SRTServletRequestHelper.access$200(SRTServletRequest.java:2629)
This is my current code:
SRTConnectionContext n = new SRTConnectionContextImpl();
(((SRTServletRequest) (n.getRequest())).getRequestContext())
.setCurrWebAppBoundary((WebApp) ((ServletContextFacade) context)
.getIServletContext());
servletContext.getRequestDispatcher("/jsp/Report.jsp").include(n.getRequest(), n.getResponse());
The End
Hopefully someone can find a way to accomplish this on Websphere.
From my viewing of the source, unless there is a side method I am missing, you cannot run a include/forward without the server's own specific class files for the request. Even request wrappers are unwrapped to their base classes, and that is why I was always getting the Class Cast Exception with and without a wrapper.
If there isn't a cleaner, not server specific with server classes, method of getting the results of a JSP from inside a thread, than this may be the answer to my original question, regardless of how messy it seems.
because the dummy request/response is an anonymous class of
HttpServletResponse/HttpServletRequest.
No. you get classcast exception b/c they are different container expects wrapper.. your code is providing request/response.
It is not very clear to us where exactly you are making call to create dummy HttpServletRequest/Response.. looks like from where you are calling... you actually need to instantiate ServletRequestWrapper/responseWapper object, set you request on it if you have a handle, and work with it.
May be this can help
http://hc.apache.org/ to read the html out of your jsp/response and create email out of it.