JAL-1645 source formatting and organise imports
[jalview.git] / src / jalview / httpserver / HttpServer.java
1 package jalview.httpserver;
2
3 import jalview.rest.RestHandler;
4
5 import java.net.BindException;
6 import java.net.URI;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.Map;
10
11 import javax.servlet.http.HttpServletRequest;
12 import javax.servlet.http.HttpServletResponse;
13
14 import org.eclipse.jetty.server.Handler;
15 import org.eclipse.jetty.server.Server;
16 import org.eclipse.jetty.server.ServerConnector;
17 import org.eclipse.jetty.server.handler.ContextHandler;
18 import org.eclipse.jetty.server.handler.HandlerCollection;
19 import org.eclipse.jetty.util.thread.QueuedThreadPool;
20
21 /**
22  * An HttpServer built on Jetty. To use it
23  * <ul>
24  * <li>call getInstance() to create and start the server</li>
25  * <li>call registerHandler to add a handler for a path (below /jalview)</li>
26  * <li>when finished, call removedHandler</li>
27  * </ul>
28  * 
29  * @author gmcarstairs
30  * @see http://eclipse.org/jetty/documentation/current/embedding-jetty.html
31  */
32 public class HttpServer
33 {
34   /*
35    * 'context root' - actually just prefixed to the path for each handler for
36    * now - see registerHandler
37    */
38   private static final String JALVIEW_PATH = "jalview";
39
40   /*
41    * Singleton instance of this server
42    */
43   private static HttpServer instance;
44
45   /*
46    * The Http server
47    */
48   private Server server;
49
50   /*
51    * Registered handlers for context paths
52    */
53   private HandlerCollection contextHandlers;
54
55   /*
56    * Lookup of ContextHandler by its wrapped handler
57    */
58   Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
59
60   /*
61    * The context root for the server
62    */
63   private URI contextRoot;
64
65   /**
66    * Returns the singleton instance of this class.
67    * 
68    * @return
69    * @throws BindException
70    */
71   public static HttpServer getInstance() throws BindException
72   {
73     synchronized (HttpServer.class)
74     {
75       if (instance == null)
76       {
77         instance = new HttpServer();
78       }
79       return instance;
80     }
81   }
82
83   /**
84    * Private constructor to enforce use of singleton
85    * 
86    * @throws BindException
87    *           if no free port can be assigned
88    */
89   private HttpServer() throws BindException
90   {
91     startServer();
92
93     /*
94      * Provides a REST server by default; add more programmatically as required
95      */
96     registerHandler(RestHandler.getInstance());
97   }
98
99   /**
100    * Start the http server
101    * 
102    * @throws BindException
103    */
104   private void startServer() throws BindException
105   {
106     try
107     {
108       /*
109        * Create a server with a small number of threads; jetty will allocate a
110        * free port
111        */
112       QueuedThreadPool tp = new QueuedThreadPool(4, 1); // max, min
113       server = new Server(tp);
114       // 2 selector threads to handle incoming connections
115       ServerConnector connector = new ServerConnector(server, 0, 2);
116       // restrict to localhost
117       connector.setHost("localhost");
118       server.addConnector(connector);
119
120       /*
121        * HttpServer shuts down with Jalview process
122        */
123       server.setStopAtShutdown(true);
124
125       /*
126        * Create a mutable set of handlers (can add handlers while the server is
127        * running). Using vanilla handlers here rather than servlets
128        */
129       // TODO how to properly configure context root "/jalview"
130       contextHandlers = new HandlerCollection(true);
131       server.setHandler(contextHandlers);
132       server.start();
133       // System.out.println(String.format(
134       // "HttpServer started with %d threads", server.getThreadPool()
135       // .getThreads()));
136       contextRoot = server.getURI();
137     } catch (Exception e)
138     {
139       System.err.println("Error trying to start HttpServer: "
140               + e.getMessage());
141       try
142       {
143         server.stop();
144       } catch (Exception e1)
145       {
146         e1.printStackTrace();
147       }
148     }
149     if (server == null)
150     {
151       throw new BindException("HttpServer failed to allocate a port");
152     }
153   }
154
155   /**
156    * Returns the URI on which we are listening
157    * 
158    * @return
159    */
160   public URI getUri()
161   {
162     return server == null ? null : server.getURI();
163   }
164
165   /**
166    * For debug - write HTTP request details to stdout
167    * 
168    * @param request
169    * @param response
170    */
171   protected void dumpRequest(HttpServletRequest request,
172           HttpServletResponse response)
173   {
174     for (String hdr : Collections.list(request.getHeaderNames()))
175     {
176       for (String val : Collections.list(request.getHeaders(hdr)))
177       {
178         System.out.println(hdr + ": " + val);
179       }
180     }
181     for (String param : Collections.list(request.getParameterNames()))
182     {
183       for (String val : request.getParameterValues(param))
184       {
185         System.out.println(param + "=" + val);
186       }
187     }
188   }
189
190   /**
191    * Stop the Http server.
192    */
193   public void stopServer()
194   {
195     if (server != null)
196     {
197       if (server.isStarted())
198       {
199         try
200         {
201           server.stop();
202         } catch (Exception e)
203         {
204           System.err.println("Error stopping Http Server on "
205                   + server.getURI() + ": " + e.getMessage());
206         }
207       }
208     }
209   }
210
211   /**
212    * Register a handler for the given path and set its URI
213    * 
214    * @param handler
215    * @return
216    * @throws IllegalStateException
217    *           if handler path has not been set
218    */
219   public void registerHandler(AbstractRequestHandler handler)
220   {
221     String path = handler.getPath();
222     if (path == null)
223     {
224       throw new IllegalStateException(
225               "Must set handler path before registering handler");
226     }
227
228     // http://stackoverflow.com/questions/20043097/jetty-9-embedded-adding-handlers-during-runtime
229     ContextHandler ch = new ContextHandler();
230     ch.setAllowNullPathInfo(true);
231     ch.setContextPath("/" + JALVIEW_PATH + "/" + path);
232     ch.setResourceBase(".");
233     ch.setClassLoader(Thread.currentThread().getContextClassLoader());
234     ch.setHandler(handler);
235
236     /*
237      * Remember the association so we can remove it later
238      */
239     this.myHandlers.put(handler, ch);
240
241     /*
242      * A handler added to a running server must be started explicitly
243      */
244     contextHandlers.addHandler(ch);
245     try
246     {
247       ch.start();
248     } catch (Exception e)
249     {
250       System.err.println("Error starting handler for " + path + ": "
251               + e.getMessage());
252     }
253
254     handler.setUri(this.contextRoot + ch.getContextPath().substring(1));
255     System.out.println("Jalview " + handler.getName()
256             + " handler started on " + handler.getUri());
257   }
258
259   /**
260    * Removes the handler from the server; more precisely, remove the
261    * ContextHandler wrapping the specified handler
262    * 
263    * @param handler
264    */
265   public void removeHandler(AbstractRequestHandler handler)
266   {
267     /*
268      * Have to use this cached lookup table since there is no method
269      * ContextHandler.getHandler()
270      */
271     ContextHandler ch = myHandlers.get(handler);
272     if (ch != null)
273     {
274       contextHandlers.removeHandler(ch);
275       myHandlers.remove(handler);
276       System.out.println("Stopped Jalview " + handler.getName()
277               + " handler on " + handler.getUri());
278     }
279   }
280 }