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.
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.PrintWriter;
27 import java.lang.reflect.InvocationTargetException;
28 import java.net.BindException;
29 import java.util.HashMap;
32 import javax.servlet.ServletInputStream;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
36 import org.eclipse.jetty.server.handler.ContextHandler;
38 import jalview.bin.Console;
39 import jalview.httpserver.AbstractRequestHandler;
40 import jalview.httpserver.HttpServer;
43 * A simple handler to process (or delegate) HTTP requests on /jalview/rest
45 public class RestHandler extends AbstractRequestHandler
50 STARTED, IN_PROGRESS, FINISHED, ERROR, NOT_RUN
53 public interface EndpointI
55 public String getPath();
57 public String getName();
59 public String getParameters();
61 public String getDescription();
63 public void processEndpoint(HttpServletRequest request,
64 HttpServletResponse response);
68 private static final String MY_PATH = "rest";
70 private static final String MY_NAME = "Rest";
72 private String missingEndpointMessage = null;
74 private boolean init = false;
76 // map of method names and method handlers
77 private Map<String, AbstractEndpoint> endpoints = null;
79 protected Map<String, AbstractEndpoint> getEndpoints()
84 protected AbstractEndpoint getNewEndpoint(String name)
86 if (getEndpoints() == null)
92 return getEndpoints().get(name).getClass()
93 .getDeclaredConstructor(API.class).newInstance(this);
94 } catch (InstantiationException | IllegalAccessException
95 | IllegalArgumentException | InvocationTargetException
96 | NoSuchMethodException | SecurityException e)
98 Console.debug("Could not instantiate new endpoint '" + name + "'", e);
104 * Singleton instance of this class
106 private static RestHandler instance = null;
109 * Returns the singleton instance of this class
112 * @throws BindException
114 public static RestHandler getInstance() throws BindException
116 synchronized (RestHandler.class)
118 if (instance == null)
120 instance = new RestHandler();
127 * Private constructor enforces use of singleton
129 * @throws BindException
131 protected RestHandler() throws BindException
136 * We don't register the handler here - this is done as a special case in
137 * HttpServer initialisation; to do it here would invite an infinite loop of
138 * RestHandler/HttpServer constructor
143 * Handle a jalview/rest request
145 * @throws IOException
148 protected void processRequest(HttpServletRequest request,
149 HttpServletResponse response) throws IOException
152 * Currently just echoes the request; add helper classes as required to
155 // This "pointless" call to request.getInputStream() seems to preserve the
156 // InputStream for use later in getRequestBody.
157 request.getInputStream();
159 String remoteAddr = request.getRemoteAddr();
160 if (!("127.0.0.1".equals(remoteAddr) || "localhost".equals(remoteAddr)))
162 returnError(request, response, "Not authorised: " + remoteAddr,
163 HttpServletResponse.SC_UNAUTHORIZED);
166 if (getEndpoints() == null)
168 final String queryString = request.getQueryString();
169 final String reply = "REST not yet implemented; received "
170 + request.getMethod() + ": " + request.getRequestURL()
171 + (queryString == null ? "" : "?" + queryString);
172 Console.error(reply);
174 response.setHeader("Cache-Control", "no-cache/no-store");
175 response.setHeader("Content-type", "text/plain");
176 final PrintWriter writer = response.getWriter();
181 String endpointName = getRequestedEndpointName(request);
182 Console.debug(endpointName);
184 if (!getEndpoints().containsKey(endpointName)
185 || getEndpoints().get(endpointName) == null)
188 "RestHandler did not find endpoint '" + endpointName + "'");
190 response.setHeader("Cache-Control", "no-cache/no-store");
191 response.setHeader("Content-type", "text/plain");
192 PrintWriter writer = response.getWriter();
193 writer.write(missingEndpointMessage == null
194 ? "REST endpoint '" + endpointName + "' not defined"
195 : missingEndpointMessage);
197 writer.write("Available endpoints are:\n");
198 ContextHandler ch = HttpServer.getInstance().getContextHandler(this);
199 String base = HttpServer.getInstance().getUri().toString();
200 String contextPath = ch == null ? "" : ch.getContextPath();
201 for (String key : getEndpoints().keySet())
203 writer.write(base + contextPath + "/" + key + "\n");
205 response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
209 response.setHeader("Cache-Control", "no-cache/no-store");
210 response.setHeader("Content-type", "text/plain");
211 EndpointI ep = getNewEndpoint(endpointName);
212 ep.processEndpoint(request, response);
218 * Returns a display name for this service
221 public String getName()
229 * @throws BindException
231 protected void init() throws BindException
236 protected void init(String path) throws BindException
239 // Override this in extended class
240 // e.g. registerHandler and addEndpoints
243 protected void addEndpoint(AbstractEndpoint ep)
245 if (getEndpoints() == null)
247 endpoints = new HashMap<>();
249 endpoints.put(ep.getPath(), ep);
250 Console.debug("REST API, added endpoint '" + ep.getPath() + "'");
253 protected String getRequestedEndpointName(HttpServletRequest request)
255 String pathInfo = request.getPathInfo();
256 int slashpos = pathInfo.indexOf('/', 1);
257 return slashpos > 1 ? pathInfo.substring(1, slashpos)
258 : pathInfo.substring(1);
261 protected void returnError(HttpServletRequest request,
262 HttpServletResponse response, String message)
264 returnError(request, response, message,
265 HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
268 protected void returnError(HttpServletRequest request,
269 HttpServletResponse response, String message, int statusCode)
271 response.setStatus(statusCode);
272 String endpointName = getRequestedEndpointName(request);
273 Console.error(getName() + " error: endpoint " + endpointName
274 + " failed: '" + message + "'");
277 PrintWriter writer = response.getWriter();
278 writer.write("Endpoint " + endpointName + ": " + message);
279 } catch (IOException e)
281 Console.debug("Could not write to REST response for endpoint "
286 protected void returnStatus(HttpServletResponse response, String id,
291 PrintWriter writer = response.getWriter();
293 writer.write("id=" + id + "\n");
295 writer.write("status=" + status.toString() + "\n");
296 } catch (IOException e)
298 Console.debug("Could not write status to REST response for id:" + id,
303 protected String getRequestBody(HttpServletRequest request,
304 HttpServletResponse response) throws IOException
306 StringBuilder sb = new StringBuilder();
307 BufferedReader reader = null;
308 Console.debug("REQUEST=" + request.toString());
309 Console.debug("REQUEST.Content-Length=" + request.getContentLength());
312 reader = request.getReader();
313 Console.debug("Using getReader()");
314 } catch (IllegalStateException e)
316 ServletInputStream is = request.getInputStream();
317 reader = new BufferedReader(new InputStreamReader(is));
318 Console.debug("Using getInputStream()");
325 while ((line = reader.readLine()) != null)
327 sb.append(line).append('\n');
336 returnError(request, response, "Error reading body of HTTP request");
338 return sb.toString();