JAL-1725 jetty libraries added to classpath
[jalview.git] / src / jalview / httpserver / HttpServer.java
1 package jalview.httpserver;
2
3 import java.net.BindException;
4 import java.net.URI;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.Map;
8
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11
12 import org.eclipse.jetty.server.Handler;
13 import org.eclipse.jetty.server.Server;
14 import org.eclipse.jetty.server.handler.ContextHandler;
15 import org.eclipse.jetty.server.handler.HandlerCollection;
16 import org.eclipse.jetty.util.thread.QueuedThreadPool;
17
18 /**
19  * An HttpServer built on Jetty
20  * 
21  * @author gmcarstairs
22  * @see http://eclipse.org/jetty/documentation/current/embedding-jetty.html
23  */
24 public class HttpServer
25 {
26   private static final String JALVIEW_PATH = "/jalview";
27
28   /*
29    * Singleton instance of this server
30    */
31   private static HttpServer instance;
32
33   /*
34    * The Http server
35    */
36   private Server server;
37
38   /*
39    * Registered handlers for context paths
40    */
41   private HandlerCollection contextHandlers;
42
43   /*
44    * Lookup of ContextHandler by its wrapped handler
45    */
46   Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
47
48   /*
49    * The context root for the server
50    */
51   private URI contextRoot;
52
53   /**
54    * Returns the singleton instance of this class.
55    * 
56    * @return
57    * @throws BindException
58    */
59   public static HttpServer getInstance() throws BindException
60   {
61     synchronized (HttpServer.class)
62     {
63       if (instance == null) {
64         instance = new HttpServer();
65       }
66       return instance;
67     }
68   }
69
70   /**
71    * Private constructor to enforce use of singleton
72    * 
73    * @throws BindException
74    *           if no free port can be assigned
75    */
76   private HttpServer() throws BindException
77   {
78     startServer();
79   }
80
81   /**
82    * Start the http server with a default thread pool size of 1
83    * 
84    * @throws BindException
85    */
86   private void startServer() throws BindException
87   {
88     try
89     {
90       // problem: how to both limit thread pool size and
91       // pick a random free port - two alternative constructors
92       QueuedThreadPool tp = new QueuedThreadPool(1, 1);
93       // server = new Server(tp); // ? fails with URI null
94       server = new Server(0); // pick a free port number
95
96       /*
97        * HttpServer shuts down with Jalview process
98        */
99       server.setStopAtShutdown(true);
100
101       /*
102        * Create a mutable set of handlers (can add handlers while the server is
103        * running)
104        */
105       // TODO how to combine this with context root "/jalview"
106       contextHandlers = new HandlerCollection(true);
107       server.setHandler(contextHandlers);
108
109       server.start();
110       contextRoot = server.getURI();
111       System.out.println("Jalview endpoint " + contextRoot);
112     } catch (Exception e)
113     {
114       System.err.println("Error trying to start HttpServer: "
115               + e.getMessage());
116       try
117       {
118         server.stop();
119       } catch (Exception e1)
120       {
121         e1.printStackTrace();
122       }
123     }
124     if (server == null)
125     {
126       throw new BindException("HttpServer failed to allocate a port");
127     }
128   }
129
130   /**
131    * Returns the URI on which we are listening
132    * 
133    * @return
134    */
135   public URI getUri()
136   {
137     return server == null ? null : server.getURI();
138   }
139
140   /**
141    * For debug - write HTTP request details to stdout
142    * 
143    * @param request
144    * @param response
145    */
146   protected void dumpRequest(HttpServletRequest request,
147           HttpServletResponse response)
148   {
149     for (String hdr : Collections.list(request.getHeaderNames()))
150     {
151       for (String val : Collections.list(request.getHeaders(hdr)))
152       {
153         System.out.println(hdr + ": " + val);
154       }
155     }
156     for (String param : Collections.list(request.getParameterNames()))
157     {
158       for (String val : request.getParameterValues(param))
159       {
160         System.out.println(param + "=" + val);
161       }
162     }
163   }
164
165   /**
166    * Stop the Http server.
167    */
168   public void stopServer()
169   {
170     if (server != null)
171     {
172       if (server.isStarted())
173       {
174         try
175         {
176           server.stop();
177         } catch (Exception e)
178         {
179           System.err.println("Error stopping Http Server on "
180                   + server.getURI() + ": " + e.getMessage());
181         }
182       }
183     }
184   }
185
186   /**
187    * Register a handler for the given path and returns its URI
188    * 
189    * @param path
190    *          a path below the context root (without leading or trailing
191    *          separator)
192    * @param handler
193    * @return
194    */
195   public String registerHandler(String path, AbstractRequestHandler handler)
196   {
197     // http://stackoverflow.com/questions/20043097/jetty-9-embedded-adding-handlers-during-runtime
198     ContextHandler ch = new ContextHandler();
199     ch.setContextPath("/" + path);
200     ch.setResourceBase(".");
201     ch.setClassLoader(Thread.currentThread()
202             .getContextClassLoader());
203     ch.setHandler(handler);
204
205     /*
206      * Remember the association so we can remove it later
207      */
208     this.myHandlers.put(handler, ch);
209
210     /*
211      * A handler added to a running server must be started explicitly
212      */
213     contextHandlers.addHandler(ch);
214     try
215     {
216       ch.start();
217     } catch (Exception e)
218     {
219       System.err.println("Error starting handler for " + path + ": "
220               + e.getMessage());
221     }
222
223     return this.contextRoot + ch.getContextPath().substring(1);
224   }
225
226   /**
227    * Removes the handler from the server; more precisely, remove the
228    * ContextHandler wrapping the specified handler
229    * 
230    * @param handler
231    */
232   public void removeHandler(Handler handler)
233   {
234     /*
235      * Have to use this cached lookup table since there is no method
236      * ContextHandler.getHandler()
237      */
238     ContextHandler ch = myHandlers.get(handler);
239     if (ch != null)
240     {
241       contextHandlers.removeHandler(ch);
242       myHandlers.remove(handler);
243     }
244   }
245 }