Thursday, October 15, 2009

JAX-WS inside Jetty

Jetty is a nice and easy to use Web Server (see previous post), but it can it handle an open standards, heavy weight JAX-WS Web Service ?
With a bit of luck and glue code, it can.
So first we need a Service Provider Implementation (SPI) from Jetty: J2se6HttpServerSPI
This will make the JAX-WS endpoint use the Jetty server instead its default Sun HttpServer.
To plugin different SPI's you would define this new service in a META-INF/services file, but you can also set a system property, as described here. You can even do this in code which reduces the number of files you need to worry about when refactoring.
So here's an example of a Jetty Server handling a JAX-WS endpoint in combination with a File system directory.
      Server jettyServer = new Server(port);
     HandlerCollection handlerCollection = new HandlerCollection();
     jettyServer.setHandler(handlerCollection);
    
     /** 1) Publish WebService (JettyHttpServerProvider) */ 
     String context = "/web/ws";
             
     // 1.1) register THIS Jetty server with the JettyHttpServerProvider
     new JettyHttpServerProvider().setServer(jettyServer);
      
     // 1.2) make sure JAX-WS endpoint.publish will use our new service provider: JettyHttpServerProvider
     System.setProperty("com.sun.net.httpserver.HttpServerProvider",
           "org.mortbay.jetty.j2se6.JettyHttpServerProvider");
      
     // 1.3) add an empty HandlerCollection to by setup by this provider
     handlerCollection.addHandler( new HandlerCollection() );
      
     // 1.4) use JAX-WS API to publish the endpoint (will use a JettyHttpServerProvider)
     Endpoint endpoint = Endpoint.create(replayServiceImpl);
     endpoint.publish("http://localhost:" + port + "/web/ws", replayServiceImpl);
    
     /** 2) Publish WebGUI (Jetty) */       
     String context = "/gui";

     // 2.1) configure File Resource Handler
     ResourceHandler fileResourceHandler=new ResourceHandler();
     fileResourceHandler.setWelcomeFiles(new String[]{"index.html"});
     fileResourceHandler.setResourceBase(guiPath); // start here
       
     // 2.2) configure 'gui' Context
     ContextHandler guiHandler = new ContextHandler();
     guiHandler.setContextPath(context);
     guiHandler.setResourceBase(".");
     guiHandler.setClassLoader(Thread.currentThread().getContextClassLoader());
     guiHandler.setHandler(fileResourceHandler);
      
     // 2.3) add this context handler
     handlerCollection.addHandler(guiHandler);
 
     /** 3) start JETTY server */
     jettyServer.start();
     jettyServer.join();

19 comments:

  1. Thanks a lot for your post.

    I tried to port this to Jetty 7. This doesn't work though
    because I cannot register properly. There is no such setServer()
    method in Jetty 7 (?).

    Ask for code directly.

    ReplyDelete
  2. correction: there is doesn't seems to be a method setServer() in JettyHttpServerProvider, neither in Jetty 6 nor in Jetty 7.

    ReplyDelete
  3. Hi Axel,

    you need to download this service provider package separately, for example from:
    http://svn.codehaus.org/jetty-contrib/trunk/j2se6/src/main/java/org/mortbay/jetty/j2se6/JettyHttpServerProvider.java

    The current version does have a setServer() method.

    ReplyDelete
  4. If you just want to publish web services with Jetty and don't need servlets you can just copy those jars in your classpath:

    jetty-6.1.x.jar
    jetty-util-6.1.x.jar
    servlet-api-2.5-6.1.x.jar
    j2se6-6.1.x.jar

    and ou can then simply publish your endpoints using the JAX-WS API:

    Endpoint endpoint = Endpoint.create(replayServiceImpl);
    endpoint.publish("http://localhost:" + port + "/web/ws", replayServiceImpl);


    The JettyHttpServerProvider.setServer() call is only useful when you want to mix web applications with web service endpoints using the same server.

    ReplyDelete
  5. Hi Ludovic, thanks for feedback.

    Yes, my understanding is that the above construction is only necessary if have both: a web gui and a web service. But if you only have a web service to publish, you wouldn't even need Jetty. You could simply use what's available inside JDK1.6. Something as described in a previous post:

    http://tech-eureka.blogspot.com/2009/09/60-seconds-on-soap-based-web-services.html

    ReplyDelete
  6. Axel-- is the mixture of web pages with web services the ONLY reason to use Jetty?

    I'm looking for a simple, standalone way to expose some Java web services to .Net and the Endpoint.publish() method seems to work.

    ReplyDelete
  7. Well, yes JDK Endpoint publish does work, but if you are looking for a reliable, flexible mechanism to (maybe) add other things in the future, Jetty is the way to go.

    I really like the architecture of Connectors and the way it scales with complexity: If you want it simple, it is simply an API, 10 lines long. If you want it complex you can add xml configs and such. Also, I think it will be faster and more reliable. Off hand I remember a bug with JDK's HttpServer and some very long byte stream responses over HTTP. Unfortunately I can't find that link anymore ...

    Reading books and examples from others, I found that people usually use the Sun HTTP server to show concepts but move on to something else for 'real deployment'.

    ReplyDelete
  8. Hi Ludovic, thanks for this post.
    I'm trying to publish a standalone JAX-WS service using Jetty, as you describe above.
    Jetty seems to start, but every time I'm trying to access the service wsdl, I get this:

    HTTP ERROR: 500
    INTERNAL_SERVER_ERROR
    RequestURI=/TestWs/TestWSService
    java.lang.IllegalArgumentException: fromIndex(1) > toIndex(0)
    at java.util.SubList.(AbstractList.java:604)
    at java.util.RandomAccessSubList.(AbstractList.java:758)
    at java.util.AbstractList.subList(AbstractList.java:468)
    at org.mortbay.jetty.j2se6.J2SE6ContextHandler.invokeHandler(J2SE6ContextHandler.java:119)
    at org.mortbay.jetty.j2se6.J2SE6ContextHandler.handle(J2SE6ContextHandler.java:86)
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:547)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)

    Looks like some filters (whatever it is) are not configured by default. Do you have an idea, why this happens? May be I need some sort of a config file?

    ReplyDelete
  9. Sorry, I'm a not very experienced Java user. I have problems building the Jetty SPI. Could someone provide step by step instructions on how to build it please?
    After executing mvn I get the following output (J2SE_HOME is set):

    C:\WINDOWS\Temp\j2se6>mvn clean install
    [INFO] Scanning for projects...
    [INFO] ------------------------------------------------------------------------
    [ERROR] FATAL ERROR
    [INFO] ------------------------------------------------------------------------
    [INFO] Error building POM (may not be this project's POM).


    Project ID: org.mortbay.jetty:jetty-j2se6:jar:null

    Reason: Cannot find parent: org.mortbay.jetty:project for project: org.mortbay.jetty:jetty-j2se6:jar:null for project org.mortbay.jetty:jetty-j2se6:jar:null


    [INFO] ------------------------------------------------------------------------
    [INFO] Trace
    org.apache.maven.reactor.MavenExecutionException: Cannot find parent: org.mortbay.jetty:project for project: org.mortbay.jetty:jetty-j2se6:jar:null for project org.mortbay.jetty:jetty-j2se6:jar:null
    at org.apache.maven.DefaultMaven.getProjects(DefaultMaven.java:404)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:272)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:138)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:362)
    at org.apache.maven.cli.compat.CompatibleMain.main(CompatibleMain.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
    at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
    at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
    at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
    ...
    ------------------------------------------------
    [INFO] Total time: < 1 second
    [INFO] Finished at: Mon May 24 21:59:55 CEST 2010
    [INFO] Final Memory: 1M/4M
    [INFO]

    ReplyDelete
  10. Hi Mike, I'm not sure what this problem is, but it looks like a maven POM version? issue.
    See for example http://mail-archives.apache.org/mod_mbox/maven-dev/200608.mbox/%3Cecv4eq$orm$1@sea.gmane.org%3E

    Were you able to compile and use Jetty inside Eclipse or so ? Once that works, you could try and setup the Maven POM through an Eclipse maven plugin if you really want to use Maven. Or simply use an Ant build.xml to build it. In many cases Ant is more suitable and straight-forward, I think.

    Also consider the Jetty or Maven sites to get Jetty/Maven support. (This is just a Java bloke's blog)

    ReplyDelete
  11. Thanks for sharing this! I tried to do the same for Jetty 7. To be sure I was using Jetty and not Sun's built-in server, I monitored HTTP traffic with a sniffer. I made a strange observation: the moment I put jetty-j2sehttpspi-7.1.6.v20100715.jar on my classpath (in addition to jetty-all-7.1.6.v20100715.jar and servlet-api.jar), Jetty seems to be used, see the response headers I got when asking for my wsdl:

    HTTP/1.1 200 OK
    Content-type: text/xml;charset="utf-8"
    Transfer-Encoding: chunked
    Server: Jetty(7.1.6.v20100715)

    I don't need to set System properties, create a new jettyServer, add handlers, call start/join on the server object, etc. I used just 1 line of code:

    Endpoint.publish("http://localhost:8080/ts", new TimeServerImpl());

    Isn't that weird? Any idea how this is possible?

    ReplyDelete
  12. Like someone mentioned before, I get an error when I use the J2se6HttpServerSPI.

    I get the following error:

    HTTP ERROR: 500

    INTERNAL_SERVER_ERROR

    RequestURI=/hw

    java.lang.IllegalArgumentException: fromIndex(1) > toIndex(0)
    at java.util.SubList.(AbstractList.java:604)
    at java.util.RandomAccessSubList.(AbstractList.java:758)
    at java.util.AbstractList.subList(AbstractList.java:468)
    at org.mortbay.jetty.j2se6.J2SE6ContextHandler.invokeHandler(J2SE6ContextHandler.java:119)
    at org.mortbay.jetty.j2se6.J2SE6ContextHandler.handle(J2SE6ContextHandler.java:86)
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:926)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:549)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)


    Anyone who can solve the mystery about why I get this?
    How to fix it(if it can)?

    ReplyDelete
  13. Hmm, sorry no idea.
    Please note that you can browse the source code here:

    http://grepcode.com/file/repo1.maven.org/maven2/org.mortbay.jetty/jetty-j2se6/6.1.21/org/mortbay/jetty/j2se6/J2SE6ContextHandler.java

    ReplyDelete
  14. You wouldn't happen to have a complete sample project, where you use this?

    ReplyDelete
  15. Well, actually I do.

    Send me an email to axel.podehl@gmail.com and I'll send you an example...

    ReplyDelete
  16. Thanks a lot for the code Axel, it really helped me out :)

    ReplyDelete
  17. Ok, I would like to Address the issue mentioned by "Daniel B" and others:

    HTTP ERROR: 500

    INTERNAL_SERVER_ERROR

    RequestURI=/hw

    java.lang.IllegalArgumentException: fromIndex(1) > toIndex(0)
    at java.util.SubList.(AbstractList.java:604)
    at java.util.RandomAccessSubList.(AbstractList.java:758)
    at java.util.AbstractList.subList(AbstractList.java:468)
    at org.mortbay.jetty.j2se6.J2SE6ContextHandler.invokeHandler(J2SE6ContextHandler.java:119)
    at org.mortbay.jetty.j2se6.J2SE6ContextHandler.handle(J2SE6ContextHandler.java:86)
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:926)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:549)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)

    I was able to fix this problem by modifying the J2SE6 module. The file J2SE6ContextHandler.java needs the following fix on line 117

    Original: if (filters != null)
    Change to: if (filters != null && (filters.size() > 0))

    What was happening is on line 119 there is a call to filters.sublist(1, filters.size()). The only problem is that this assumes filters will always have a size() > 0. This is not the case. Now this could be because I am not using Jetty properly. However, only after the fix above was I able to get this example to work.

    ReplyDelete
  18. how do I configure https

    ReplyDelete
  19. I think you would use an org.mortbay.jetty.security.SslSelectChannelConnector and configure that one.

    ReplyDelete