source: josm/trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java@ 5837

Last change on this file since 5837 was 5837, checked in by Don-vip, 11 years ago

fix #8586 - Remote Control: allow imagery handler to work without any mapFrame + add wiki link in HTTP 400 error message

  • Property svn:eol-style set to native
File size: 12.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.remotecontrol;
3
4import java.io.BufferedInputStream;
5import java.io.BufferedOutputStream;
6import java.io.IOException;
7import java.io.InputStreamReader;
8import java.io.OutputStream;
9import java.io.OutputStreamWriter;
10import java.io.Reader;
11import java.io.Writer;
12import java.net.Socket;
13import java.util.Arrays;
14import java.util.Date;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.StringTokenizer;
18import java.util.TreeMap;
19
20import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler;
21import org.openstreetmap.josm.io.remotecontrol.handler.AddWayHandler;
22import org.openstreetmap.josm.io.remotecontrol.handler.ImageryHandler;
23import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler;
24import org.openstreetmap.josm.io.remotecontrol.handler.LoadAndZoomHandler;
25import org.openstreetmap.josm.io.remotecontrol.handler.LoadObjectHandler;
26import org.openstreetmap.josm.io.remotecontrol.handler.OpenFileHandler;
27import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
28import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException;
29import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerErrorException;
30import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerForbiddenException;
31import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler;
32import org.openstreetmap.josm.tools.Utils;
33
34/**
35 * Processes HTTP "remote control" requests.
36 */
37public class RequestProcessor extends Thread {
38 /**
39 * RemoteControl protocol version. Change minor number for compatible
40 * interface extensions. Change major number in case of incompatible
41 * changes.
42 */
43 public static final String PROTOCOLVERSION = "{\"protocolversion\": {\"major\": " +
44 RemoteControl.protocolMajorVersion + ", \"minor\": " +
45 RemoteControl.protocolMinorVersion +
46 "}, \"application\": \"JOSM RemoteControl\"}";
47
48 /** The socket this processor listens on */
49 private Socket request;
50
51 /**
52 * Collection of request handlers.
53 * Will be initialized with default handlers here. Other plug-ins
54 * can extend this list by using @see addRequestHandler
55 */
56 private static Map<String, Class<? extends RequestHandler>> handlers = new TreeMap<String, Class<? extends RequestHandler>>();
57
58 /**
59 * Constructor
60 *
61 * @param request A socket to read the request.
62 */
63 public RequestProcessor(Socket request) {
64 super("RemoteControl request processor");
65 this.setDaemon(true);
66 this.request = request;
67 }
68
69 /**
70 * Spawns a new thread for the request
71 */
72 public static void processRequest(Socket request) {
73 RequestProcessor processor = new RequestProcessor(request);
74 processor.start();
75 }
76
77 /**
78 * Add external request handler. Can be used by other plug-ins that
79 * want to use remote control.
80 *
81 * @param command The command to handle.
82 * @param handler The additional request handler.
83 */
84 static void addRequestHandlerClass(String command,
85 Class<? extends RequestHandler> handler) {
86 addRequestHandlerClass(command, handler, false);
87 }
88
89 /**
90 * Add external request handler. Message can be suppressed.
91 * (for internal use)
92 *
93 * @param command The command to handle.
94 * @param handler The additional request handler.
95 * @param silent Don't show message if true.
96 */
97 private static void addRequestHandlerClass(String command,
98 Class<? extends RequestHandler> handler, boolean silent) {
99 if(command.charAt(0) == '/')
100 {
101 command = command.substring(1);
102 }
103 String commandWithSlash = "/" + command;
104 if (handlers.get(commandWithSlash) != null) {
105 System.out.println("RemoteControl: ignoring duplicate command " + command
106 + " with handler " + handler.getName());
107 } else {
108 if (!silent) {
109 System.out.println("RemoteControl: adding command \"" +
110 command + "\" (handled by " + handler.getSimpleName() + ")");
111 }
112 handlers.put(commandWithSlash, handler);
113 }
114 }
115
116 /** Add default request handlers */
117 static {
118 addRequestHandlerClass(LoadAndZoomHandler.command, LoadAndZoomHandler.class, true);
119 addRequestHandlerClass(LoadAndZoomHandler.command2, LoadAndZoomHandler.class, true);
120 addRequestHandlerClass(ImageryHandler.command, ImageryHandler.class, true);
121 addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true);
122 addRequestHandlerClass(AddWayHandler.command, AddWayHandler.class, true);
123 addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true);
124 addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
125 addRequestHandlerClass(LoadObjectHandler.command, LoadObjectHandler.class, true);
126 addRequestHandlerClass(OpenFileHandler.command, OpenFileHandler.class, true);
127 }
128
129 /**
130 * The work is done here.
131 */
132 public void run() {
133 Writer out = null;
134 try {
135 OutputStream raw = new BufferedOutputStream(
136 request.getOutputStream());
137 out = new OutputStreamWriter(raw);
138 Reader in = new InputStreamReader(new BufferedInputStream(
139 request.getInputStream()), "ASCII");
140
141 StringBuffer requestLine = new StringBuffer();
142 while (requestLine.length() < 1024 * 1024) {
143 int c = in.read();
144 if (c == -1 || c == '\r' || c == '\n')
145 break;
146 requestLine.append((char) c);
147 }
148
149 System.out.println("RemoteControl received: " + requestLine);
150 String get = requestLine.toString();
151 StringTokenizer st = new StringTokenizer(get);
152 if (!st.hasMoreTokens()) {
153 sendError(out);
154 return;
155 }
156 String method = st.nextToken();
157 if (!st.hasMoreTokens()) {
158 sendError(out);
159 return;
160 }
161 String url = st.nextToken();
162
163 if (!method.equals("GET")) {
164 sendNotImplemented(out);
165 return;
166 }
167
168 String command = null;
169 int questionPos = url.indexOf('?');
170 if(questionPos < 0)
171 {
172 command = url;
173 }
174 else
175 {
176 command = url.substring(0, questionPos);
177 }
178
179 // find a handler for this command
180 Class<? extends RequestHandler> handlerClass = handlers.get(command);
181 if (handlerClass == null) {
182 // no handler found
183 StringBuilder usage = new StringBuilder(1024);
184 for (Entry<String, Class<? extends RequestHandler>> handler : handlers.entrySet()) {
185 String[] mandatory = handler.getValue().newInstance().getMandatoryParams();
186 usage.append("<li>");
187 usage.append(handler.getKey());
188 if (mandatory != null) {
189 usage.append("<br/>mandatory parameter: ").append(Utils.join(", ", Arrays.asList(mandatory)));
190 }
191 usage.append("</li>");
192 }
193 String websiteDoc = "http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl";
194 String help = "No command specified! The following commands are available:<ul>"
195 + usage.toString()
196 + "</ul>" + "See <a href=\""+websiteDoc+"\">"+websiteDoc+"</a> for complete documentation.";
197 sendBadRequest(out, help);
198 } else {
199 // create handler object
200 RequestHandler handler = handlerClass.newInstance();
201 try {
202 handler.setCommand(command);
203 handler.setUrl(url);
204 handler.handle();
205 sendHeader(out, "200 OK", handler.getContentType(), false);
206 out.write("Content-length: " + handler.getContent().length()
207 + "\r\n");
208 out.write("\r\n");
209 out.write(handler.getContent());
210 out.flush();
211 } catch (RequestHandlerErrorException ex) {
212 sendError(out);
213 } catch (RequestHandlerBadRequestException ex) {
214 sendBadRequest(out, ex.getMessage());
215 } catch (RequestHandlerForbiddenException ex) {
216 sendForbidden(out, ex.getMessage());
217 }
218 }
219
220 } catch (IOException ioe) {
221 } catch (Exception e) {
222 e.printStackTrace();
223 try {
224 sendError(out);
225 } catch (IOException e1) {
226 }
227 } finally {
228 try {
229 request.close();
230 } catch (IOException e) {
231 }
232 }
233 }
234
235 /**
236 * Sends a 500 error: server error
237 *
238 * @param out
239 * The writer where the error is written
240 * @throws IOException
241 * If the error can not be written
242 */
243 private void sendError(Writer out) throws IOException {
244 sendHeader(out, "500 Internal Server Error", "text/html", true);
245 out.write("<HTML>\r\n");
246 out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n");
247 out.write("</HEAD>\r\n");
248 out.write("<BODY>");
249 out.write("<H1>HTTP Error 500: Internal Server Error</h2>\r\n");
250 out.write("</BODY></HTML>\r\n");
251 out.flush();
252 }
253
254 /**
255 * Sends a 501 error: not implemented
256 *
257 * @param out
258 * The writer where the error is written
259 * @throws IOException
260 * If the error can not be written
261 */
262 private void sendNotImplemented(Writer out) throws IOException {
263 sendHeader(out, "501 Not Implemented", "text/html", true);
264 out.write("<HTML>\r\n");
265 out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
266 out.write("</HEAD>\r\n");
267 out.write("<BODY>");
268 out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n");
269 out.write("</BODY></HTML>\r\n");
270 out.flush();
271 }
272
273 /**
274 * Sends a 403 error: forbidden
275 *
276 * @param out
277 * The writer where the error is written
278 * @throws IOException
279 * If the error can not be written
280 */
281 private void sendForbidden(Writer out, String help) throws IOException {
282 sendHeader(out, "403 Forbidden", "text/html", true);
283 out.write("<HTML>\r\n");
284 out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n");
285 out.write("</HEAD>\r\n");
286 out.write("<BODY>");
287 out.write("<H1>HTTP Error 403: Forbidden</h2>\r\n");
288 if (help != null) {
289 out.write(help);
290 }
291 out.write("</BODY></HTML>\r\n");
292 out.flush();
293 }
294
295 /**
296 * Sends a 403 error: forbidden
297 *
298 * @param out
299 * The writer where the error is written
300 * @throws IOException
301 * If the error can not be written
302 */
303 private void sendBadRequest(Writer out, String help) throws IOException {
304 sendHeader(out, "400 Bad Request", "text/html", true);
305 out.write("<HTML>\r\n");
306 out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n");
307 out.write("</HEAD>\r\n");
308 out.write("<BODY>");
309 out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n");
310 if (help != null) {
311 out.write(help);
312 }
313 out.write("</BODY></HTML>\r\n");
314 out.flush();
315 }
316
317 /**
318 * Send common HTTP headers to the client.
319 *
320 * @param out
321 * The Writer
322 * @param status
323 * The status string ("200 OK", "500", etc)
324 * @param contentType
325 * The content type of the data sent
326 * @param endHeaders
327 * If true, adds a new line, ending the headers.
328 * @throws IOException
329 * When error
330 */
331 private void sendHeader(Writer out, String status, String contentType,
332 boolean endHeaders) throws IOException {
333 out.write("HTTP/1.1 " + status + "\r\n");
334 Date now = new Date();
335 out.write("Date: " + now + "\r\n");
336 out.write("Server: JOSM RemoteControl\r\n");
337 out.write("Content-type: " + contentType + "\r\n");
338 out.write("Access-Control-Allow-Origin: *\r\n");
339 if (endHeaders)
340 out.write("\r\n");
341 }
342}
Note: See TracBrowser for help on using the repository browser.