5

I have an spring app which is using tomcat with websockets. I would like to use the DelegatingSecurityContextRunnable to be executed every time when tomcat creates a new thread, i.e. warp the tomcat thread. Does anyone know how this is done. The reason for the question can be found.here

Maybe this can be done with using AOP and some advice?

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
Tito
  • 2,234
  • 6
  • 31
  • 65
  • You can't do this with Spring AOP since the thread are create by Tomcat not Spring. You could resort to AspectJ weaving, but I think that would give you a ton of other problems. The issue you link to is related to Mock testing, something I strongly discourage, since it is rather easy to include the Spring SecurityFilterChain in a MockMVC test, which means you only mock the HttpLayer. – Klaus Groenbaek Jun 16 '17 at 10:35
  • Klaus i do not see why this is related to Mock testing only. The problem, the way i see it, is that when tomcat creates a new sibling thread the security context is not available there since the thread itself is a siblig thread. As mentioned there the first thread has the security context but all following threads do not. In this case i od not need the spring security filter chain since my authentication is part of the websocket and not part of the http exchange and since all those filters are targeting http i am not quite sure why i would need them? – Tito Jun 16 '17 at 11:23
  • I am following the idea to override the tomcat executor there and plug the DelegatingSecurityContextRunnable there but so far without any sucess – Tito Jun 16 '17 at 11:25

1 Answers1

1

In Spring boot you can configure a Wrapper by hooking into the Tomcat connector. See this as an example:

@Bean
    public EmbeddedServletContainerFactory servletContainerFactory() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();

        factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {

            @Override
            public void customize(Connector connector) {
                AbstractProtocol protocolHandler = (AbstractProtocol) connector.getProtocolHandler();
                TaskQueue taskqueue = new TaskQueue() {
                    @Override
                    public boolean offer(Runnable e, long timeout, TimeUnit unit) throws InterruptedException {
                        return super.offer(new MyRunnable(e), timeout, unit);
                    }

                    @Override
                    public boolean offer(Runnable o) {
                        return super.offer(new MyRunnable(o));
                    }
                };
                TaskThreadFactory tf = new TaskThreadFactory("artur-" + "-exec-", false, 0);
                ThreadPoolExecutor e = new ThreadPoolExecutor(10, 10, 1000, TimeUnit.SECONDS, taskqueue);
                taskqueue.setParent(e);
                protocolHandler.setExecutor(e);
            }
        });
        return factory;
    }

And here is my custom Runable (this can be any wrapper, i did not bother implementing exactly yours):

static class MyRunnable implements Runnable {

        private Runnable r;

        public MyRunnable(Runnable r) {
            this.r = r;
        }

        @Override
        public void run() {
            System.out.println("Custom runable");
            runInner();
        }

        void runInner() {
            r.run();
        }

    }

And here are my imports:

import java.util.concurrent.TimeUnit;

import org.apache.catalina.connector.Connector;
import org.apache.coyote.AbstractProtocol;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.PropertySource;

What this does:

The Tomcat connector initialises itself. You can set the executor to use, in which case Tomcat will stop creating its own configuration and instead use yours.

By overwriting the offer methods in the queue, you have the chance to wrap your Runnable in any custom Runnable. In my case, for testing, I simply added a Sysout to see that everything is working correctly.

The Threadpool implementation I used is an exact copy of the tomcat default (minus the properties). This way, behaviour stays the same, except that any Runnable is now your delegating wrapper.

When I test that, my console prints:

 Custom runable

I hope this is what you were looking for.

I use spring boot, but this is essentially a tomcat issue not a spring issue. You can adapt the solution to your specific scenario.

-- Artur

pandaadb
  • 6,306
  • 2
  • 22
  • 41
  • Arthur many thanks for this ,, can you please tell me the ThreadPoolExecutor in this example is that the ThreadPoolExecutor from the tomcat i.e. not the one from Spring right? i.e. i mean package org.apache.tomcat.util.threads.ThreadPoolExecutor. – Tito Jun 16 '17 at 12:02
  • @Tito yes. That one is just implementing the same interface though, so it shouldn't matter. In any case, i will update my post with the relevant imports. – pandaadb Jun 16 '17 at 12:03
  • @Arthur if I understand this correctly this is wrapping my runnable but should not that me the other way around i.e. wrapping the tomcat runnable? I am testing this right now. – Tito Jun 16 '17 at 12:07
  • The offer method gets the `Runnable` that tomcat is creating. You wrap that one ( `new MyRunnable(e)`) by providing your own implementation. Inside that `MyRunnable` you just delegate to the original one. The offer is simply adding it to the queue. The Executor picks that up eventually and executes it – pandaadb Jun 16 '17 at 12:16
  • Mind you, it sounds like you want to pass a security context around. At the time a Runnable is created, it has no security context. That is rather way-down-the-line and the request has not yet been processed. Your question was unclear on intention for me, so I just demonstrate on how to hook your own code in there, i don't know what the DelegateSecurityRunable does – pandaadb Jun 16 '17 at 12:37
  • @Arthur ,, i think you got my question right however what i do not understand is where is TaskThreadFactory used in your code sample it is not bind anywhere, am i missing something – Tito Jun 16 '17 at 12:53
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146879/discussion-between-pandaadb-and-tito). – pandaadb Jun 16 '17 at 12:59