How to use multiple servlet injectors correctly?

880 views
Skip to first unread message

Peter

unread,
Mar 9, 2012, 4:35:11 AM3/9/12
to google-guice
For testing, I want to set up two web servers on different ports
programmatically, each configured differently. However there seems to
be contamination of object instances between both servers, and I don't
understand where it's coming from. I am using Guice, Jersey and
GrizzlyWebServer. Guice gives me a warning "Multiple Servlet injectors
detected", which is intentional since I have two different servers. I
understand that singletongs (@Singleton) in Guice are per injector,
and not per JVM, so each server should have its own singleton
instance.

I've written a stand-alone program to illustrate the problem. It
starts up two servers on localhost on different ports. When pointing a
browser at "http://localhost:9991/context/" I expect "9991" in the
body of the response, and analogously for port 9992. However, _both_
servers respond with "9992". I've also printed out the object hashes
in the body of the response, to illustrate that both servers are
sharing the same object, which is not what I want.

Can someone explain what I am doing wrong?

---
package systemtest;

import com.google.inject.*;
import com.google.inject.servlet.GuiceFilter;
import com.google.inject.servlet.GuiceServletContextListener;
import com.sun.grizzly.http.embed.GrizzlyWebServer;
import com.sun.grizzly.http.servlet.ServletAdapter;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;

import javax.servlet.ServletContextEvent;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;

/**
* @author Peter De Maeyer
*/
public class MyWebServer {

private final int port;

private final GrizzlyWebServer webServer;

public MyWebServer( int aPort ) {
port = aPort;
ServletAdapter adapter = new ServletAdapter();
adapter.addServletListener( MyGuiceConfig.class.getName() );
adapter.addFilter( new GuiceFilter(), "Guice filter", null );
adapter.setServletPath( "/" );
adapter.setContextPath( "/context" );
adapter.addContextParameter( "port", String.valueOf( port ) );
webServer = new GrizzlyWebServer( port );
webServer.addGrizzlyAdapter( adapter, null );
}

public static void main( String[] aRgs ) throws IOException {
MyWebServer server9991 = new MyWebServer( 9991 );
try {
server9991.start();
MyWebServer server9992 = new MyWebServer( 9992 );
try {
server9992.start();
System.out.println( "Press any key to stop..." );
System.in.read();
}
finally {
server9992.stop();
}
}
finally {
server9991.stop();
}
}

public MyWebServer start() throws IOException {
System.out.printf( "Starting web server on port %d",
port ).println();
webServer.start();
return this;
}

public MyWebServer stop() {
System.out.printf( "Stopping web server on port %d",
port ).println();
webServer.stop();
return this;
}

public static class MyGuiceConfig extends
GuiceServletContextListener {

private volatile String home;

@Override protected Injector getInjector() {
return Guice.createInjector( new MyModule( home ), new
MyServletModule() );
}

@Override public void contextInitialized( ServletContextEvent
aEvent ) {
home = aEvent.getServletContext().getInitParameter( "port" );
super.contextInitialized( aEvent );
}
}

public static class MyModule implements Module {

private final String port;

public MyModule( String aPort ) {
port = aPort;
}

@Override public void configure( Binder aBinder ) {
aBinder.bind( MyService.class );
}

@Provides
@Singleton
public MyPort providePort() {
return new MyPort( port );
}
}

public static class MyPort {

private final String port;

public MyPort( String aPort ) {
port = aPort;
}

@Override public String toString() {
return port + " (port#" + System.identityHashCode( this ) + ")";
}
}

@Path( "/" )
public static class MyService {

private final MyPort port;

@Inject
public MyService( MyPort aPort ) {
port = aPort;
}

@GET
@Produces( MediaType.TEXT_HTML )
public Response get() {
String content = "<html><body>" + port + " (service#" +
System.identityHashCode( this ) + ") </body></html>";
return Response.ok( content ).build();
}
}

public static class MyServletModule extends JerseyServletModule {

@Override protected void configureServlets() {
serve( "/*" ).with( GuiceContainer.class );
}
}
}
---

Alen Vrečko

unread,
Mar 10, 2012, 9:31:06 AM3/10/12
to google...@googlegroups.com
GuiceFilter has static state. Normally that is not a problem since you'd have one GuiceFilter per WebAppClassLoader.

In your case you have GuiceFilter defined in the same ClassLoader as both of your servers. What happens is:

The second server overwrites the GuiceFilter#pipeline field (with warning). This makes guice servlet behave the same for both servers.

A couple of ideas
o) refactor GuiceFilter not to use static state ;)
o) use custom classloaders to force isolation (don't forget to set the Thread.currentThread().setContextClassLoader(server1cl))

What I'd probably do
o) don't use embedded server

I think it is best to test on the same server and same setup as you are using in production so there are no surprises. Your test environment should be a close copy of production.

I don't like embedded servers for testing in general case. What I'd probably use is Arquillian with Managed Container for Tomcat 7 (no embedded stuff). See here for usage


You have many options. I am sure you will figure some more options if you haven't already.

Cheers
Alen

Dne petek, 09. marec 2012 10:35:11 UTC+1 je oseba Peter napisala:

Peter

unread,
Mar 13, 2012, 4:54:07 AM3/13/12
to google-guice
Hmm, I find embedded servers really useful for testing the network
protocol. But opinions on embedded servers aside, I found some more
pointers:

http://stackoverflow.com/questions/9074704/embedded-jetty-different-ports-for-internally-and-externally-visible-endpoints
http://code.google.com/p/google-guice/issues/detail?id=635

I haven't gotten it to work yet, but I'm getting closer...

Peter

On Mar 10, 3:31 pm, Alen Vrečko <alen.vre...@gmail.com> wrote:
> GuiceFilter has static state. Normally that is not a problem since you'd
> have one GuiceFilter per WebAppClassLoader.
>
> In your case you have GuiceFilter defined in the same ClassLoader as both
> of your servers. What happens is:
>
> The second server overwrites the GuiceFilter#pipeline field (with warning).
> This makes guice servlet behave the same for both servers.
>
> A couple of ideas
> o) refactor GuiceFilter not to use static state ;)
> o) use custom classloaders to force isolation (don't forget to set the
> Thread.currentThread().setContextClassLoader(server1cl))
>
> What I'd probably do
> o) don't use embedded server
>
> I think it is best to test on the same server and same setup as you are
> using in production so there are no surprises. Your test environment should
> be a close copy of production.
>
> I don't like embedded servers for testing in general case. What I'd
> probably use is Arquillian with Managed Container for Tomcat 7 (no embedded
> stuff). See here for usage
>
> https://github.com/arquillian/arquillian-container-tomcat/blob/master...https://github.com/arquillian/arquillian-container-tomcat/blob/master...https://docs.jboss.org/author/display/ARQ/Multiple+Containers?_sscc=t
Reply all
Reply to author
Forward
0 new messages