2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.httpserver;
23 import java.net.BindException;
25 import java.util.Collections;
26 import java.util.HashMap;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
32 import org.eclipse.jetty.server.Connector;
33 import org.eclipse.jetty.server.Handler;
34 import org.eclipse.jetty.server.Server;
35 import org.eclipse.jetty.server.ServerConnector;
36 import org.eclipse.jetty.server.handler.ContextHandler;
37 import org.eclipse.jetty.server.handler.HandlerCollection;
38 import org.eclipse.jetty.util.thread.QueuedThreadPool;
40 import jalview.rest.RestHandler;
43 * An HttpServer built on Jetty. To use it
45 * <li>call getInstance() to create and start the server</li>
46 * <li>call registerHandler to add a handler for a path (below /jalview)</li>
47 * <li>when finished, call removedHandler</li>
51 * @see http://eclipse.org/jetty/documentation/current/embedding-jetty.html
53 public class HttpServer
56 * 'context root' - actually just prefixed to the path for each handler for
57 * now - see registerHandler
59 private static final String JALVIEW_PATH = "jalview";
62 * Singleton instance of this server
64 private static HttpServer instance;
69 private Server server;
72 * Registered handlers for context paths
74 private HandlerCollection contextHandlers;
77 * Lookup of ContextHandler by its wrapped handler
79 Map<Handler, ContextHandler> myHandlers = new HashMap<Handler, ContextHandler>();
82 * The context root for the server
84 private URI contextRoot;
87 * The port of the server. This can be set before starting the instance
88 * as a suggested port to use (it is not guaranteed).
89 * The value will be set to the actual port being used after the instance
92 private static int PORT = 0;
95 * Returns the singleton instance of this class.
98 * @throws BindException
100 public static HttpServer getInstance() throws BindException
102 synchronized (HttpServer.class)
104 if (instance == null)
106 instance = new HttpServer();
113 * Private constructor to enforce use of singleton
115 * @throws BindException
116 * if no free port can be assigned
118 private HttpServer() throws BindException
123 * Provides a REST server by default; add more programmatically as required
125 registerHandler(RestHandler.getInstance());
129 * Start the http server
131 * @throws BindException
133 private void startServer() throws BindException
138 * Create a server with a small number of threads;
139 * If PORT has been set then jetty will try and use this, otherwise
140 * jetty will allocate a free port
142 QueuedThreadPool tp = new QueuedThreadPool(4, 1); // max, min
143 server = new Server(tp);
144 // 2 selector threads to handle incoming connections
145 ServerConnector connector = new ServerConnector(server, 0, 2);
146 // restrict to localhost
147 connector.setHost("localhost");
150 connector.setPort(PORT);
152 server.addConnector(connector);
155 * HttpServer shuts down with Jalview process
157 server.setStopAtShutdown(true);
160 * Create a mutable set of handlers (can add handlers while the server is
161 * running). Using vanilla handlers here rather than servlets
163 // TODO how to properly configure context root "/jalview"
164 contextHandlers = new HandlerCollection(true);
165 server.setHandler(contextHandlers);
167 Connector[] cs = server.getConnectors();
170 if (cs[0] instanceof ServerConnector)
172 ServerConnector c = (ServerConnector) cs[0];
176 // System.out.println(String.format(
177 // "HttpServer started with %d threads", server.getThreadPool()
179 contextRoot = server.getURI();
180 } catch (Exception e)
183 "Error trying to start HttpServer: " + e.getMessage());
187 } catch (Exception e1)
189 e1.printStackTrace();
194 throw new BindException("HttpServer failed to allocate a port");
199 * Returns the URI on which we are listening
205 return server == null ? null : server.getURI();
209 * For debug - write HTTP request details to stdout
214 protected void dumpRequest(HttpServletRequest request,
215 HttpServletResponse response)
217 for (String hdr : Collections.list(request.getHeaderNames()))
219 for (String val : Collections.list(request.getHeaders(hdr)))
221 System.out.println(hdr + ": " + val);
224 for (String param : Collections.list(request.getParameterNames()))
226 for (String val : request.getParameterValues(param))
228 System.out.println(param + "=" + val);
234 * Stop the Http server.
236 public void stopServer()
240 if (server.isStarted())
245 } catch (Exception e)
247 System.err.println("Error stopping Http Server on "
248 + server.getURI() + ": " + e.getMessage());
255 * Register a handler for the given path and set its URI
259 * @throws IllegalStateException
260 * if handler path has not been set
262 public void registerHandler(AbstractRequestHandler handler)
264 String path = handler.getPath();
267 throw new IllegalStateException(
268 "Must set handler path before registering handler");
271 // http://stackoverflow.com/questions/20043097/jetty-9-embedded-adding-handlers-during-runtime
272 ContextHandler ch = new ContextHandler();
273 ch.setAllowNullPathInfo(true);
274 ch.setContextPath("/" + JALVIEW_PATH + "/" + path);
275 ch.setResourceBase(".");
276 ch.setClassLoader(Thread.currentThread().getContextClassLoader());
277 ch.setHandler(handler);
280 * Remember the association so we can remove it later
282 this.myHandlers.put(handler, ch);
285 * A handler added to a running server must be started explicitly
287 contextHandlers.addHandler(ch);
291 } catch (Exception e)
294 "Error starting handler for " + path + ": " + e.getMessage());
297 handler.setUri(this.contextRoot + ch.getContextPath().substring(1));
298 System.out.println("Jalview " + handler.getName()
299 + " handler started on " + handler.getUri());
303 * Removes the handler from the server; more precisely, remove the
304 * ContextHandler wrapping the specified handler
308 public void removeHandler(AbstractRequestHandler handler)
311 * Have to use this cached lookup table since there is no method
312 * ContextHandler.getHandler()
314 ContextHandler ch = myHandlers.get(handler);
317 contextHandlers.removeHandler(ch);
318 myHandlers.remove(handler);
319 System.out.println("Stopped Jalview " + handler.getName()
320 + " handler on " + handler.getUri());
325 * This sets the "suggested" port to use. It can only be called once before
326 * starting the HttpServer instance. After the server has actually started the
327 * port is set to the actual port being used and cannot be changed.
330 * @return successful change
332 public static boolean setSuggestedPort(int port)
334 if (port < 1 || PORT > 0)
342 public static int getPort()