1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2004-2006 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 * $Id: TunnelServlet.java,v 1.3 2007/03/10 12:42:13 tanderson Exp $
44 */
45 package org.exolab.jms.net.tunnel;
46
47 import javax.servlet.ServletException;
48 import javax.servlet.http.HttpServlet;
49 import javax.servlet.http.HttpServletRequest;
50 import javax.servlet.http.HttpServletResponse;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.InterruptedIOException;
54 import java.io.OutputStream;
55 import java.io.PrintWriter;
56 import java.net.Socket;
57
58
59 /***
60 * HTTP Tunnel servlet.
61 *
62 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
63 * @version $Revision: 1.3 $ $Date: 2007/03/10 12:42:13 $
64 */
65 public class TunnelServlet extends HttpServlet {
66
67 /***
68 * The host that the server is running on.
69 */
70 private String _host;
71
72 /***
73 * The port that the connector is listening on.
74 */
75 private int _port;
76
77 /***
78 * The socket timeout, in milliseconds. A value of <code>0</code> indicates
79 * to block indefinitely.
80 */
81 private int _timeout;
82
83 /***
84 * The connection manager.
85 */
86 private static final SocketManager _manager = new SocketManager();
87
88 /***
89 * Initialisation property name for the host that the server is running on.
90 */
91 private static final String SERVER_HOST = "host";
92
93 /***
94 * Initialisation property name for the port that the connector is listening
95 * on.
96 */
97 private static final String SERVER_PORT = "port";
98
99 /***
100 * Initialisation property name for the maximum time a connection will
101 * block waiting for data.
102 */
103 private static final String READ_TIMEOUT = "readTimeout";
104
105 /***
106 * The default read timeout in seconds.
107 */
108 private static final int DEFAULT_READ_TIMEOUT = 30;
109
110 /***
111 * Initialisation property name for the time a connection must be idle for,
112 * before it may be reaped.
113 */
114 private static final String IDLE_PERIOD = "idlePeriod";
115
116 /***
117 * The default idle period in seconds.
118 */
119 private static final int DEFAULT_IDLE_PERIOD = 60 * 5;
120
121
122 /***
123 * Initialise the servlet.
124 *
125 * @throws ServletException if the servlet can't be initialised
126 */
127 public void init() throws ServletException {
128 _host = getString(SERVER_HOST);
129 _port = getInt(SERVER_PORT);
130
131 int readTimeout = getInt(READ_TIMEOUT, DEFAULT_READ_TIMEOUT);
132 int idlePeriod = getInt(IDLE_PERIOD, DEFAULT_IDLE_PERIOD);
133
134 _timeout = readTimeout * 1000;
135 _manager.setIdlePeriod(idlePeriod);
136
137 log("OpenJMS tunnel accepting requests (timeout=" + readTimeout
138 + ", idle=" + idlePeriod);
139 }
140
141 /***
142 * Handle a GET request. This method always sets the response code to
143 * <code>HttpServletResponse.SC_BAD_REQUEST</code>.
144 *
145 * @param request the client request
146 * @param response the response to the request
147 * @throws IOException for any I/O error
148 */
149 protected void doGet(HttpServletRequest request,
150 HttpServletResponse response) throws IOException {
151 response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
152 }
153
154 /***
155 * Handle a POST request.
156 *
157 * @param request the client request
158 * @param response the response to the request
159 * @throws IOException for any I/O error
160 */
161 protected void doPost(HttpServletRequest request,
162 HttpServletResponse response) throws IOException {
163
164 String action = request.getHeader("action");
165
166 if (action == null) {
167 response.sendError(HttpServletResponse.SC_BAD_REQUEST,
168 "Invalid action");
169 } else if (action.equals("open")) {
170 open(response);
171 } else {
172 String id = request.getHeader("id");
173 if (id == null) {
174 response.sendError(HttpServletResponse.SC_BAD_REQUEST,
175 "Invalid connection");
176 } else if (action.equals("read")) {
177 read(id, response);
178 } else if (action.equals("write")) {
179 write(id, request, response);
180 } else if (action.equals("close")) {
181 close(id, response);
182 } else {
183 response.sendError(HttpServletResponse.SC_BAD_REQUEST,
184 "Invalid action");
185 }
186 }
187 }
188
189 /***
190 * Handle an open request. A connection is established to the server and the
191 * identifier written to the client.
192 *
193 * @param response the response to the request
194 * @throws IOException for any I/O error
195 */
196 private void open(HttpServletResponse response) throws IOException {
197 response.setContentType("text/plain");
198 PrintWriter out = new PrintWriter(response.getWriter());
199
200 try {
201 String id = _manager.open(_host, _port);
202 out.println("OPEN " + id);
203 response.setStatus(HttpServletResponse.SC_OK);
204 } catch (Exception exception) {
205 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
206 exception.getMessage());
207 log("open failed", exception);
208 }
209 }
210
211 /***
212 * Handles a read request. Data is read from the endpoint and written to the
213 * client.
214 *
215 * @param id the endpoint identifier
216 * @param response the response to the client
217 * @throws IOException for any I/O error
218 */
219 private void read(String id, HttpServletResponse response)
220 throws IOException {
221 Socket socket = _manager.getSocket(id);
222 if (socket == null) {
223 log("Connection not found, id=" + id);
224 response.sendError(HttpServletResponse.SC_BAD_REQUEST,
225 "Connection not found");
226 } else {
227 byte[] data = new byte[1024];
228 try {
229 socket.setSoTimeout(_timeout);
230 InputStream in = socket.getInputStream();
231 int count = 0;
232 try {
233 count = in.read(data);
234 } catch (InterruptedIOException ignore) {
235 }
236
237 if (count != -1) {
238 response.setContentLength(count);
239 response.setStatus(HttpServletResponse.SC_OK);
240 OutputStream out = response.getOutputStream();
241 out.write(data, 0, count);
242 out.flush();
243 } else {
244 remove(id);
245 response.setStatus(
246 HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
247 }
248 } catch (IOException exception) {
249 log("read failed", exception);
250 remove(id);
251 response.sendError(
252 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
253 exception.getMessage());
254 }
255 }
256 }
257
258 /***
259 * Handles a write request. Data from the client is written to the endpoint
260 *
261 * @param id the endpoint identifier
262 * @param request the client request
263 * @param response the response to the client
264 * @throws IOException for any I/O error
265 */
266 private void write(String id, HttpServletRequest request,
267 HttpServletResponse response) throws IOException {
268 Socket endpoint = _manager.getSocket(id);
269 if (endpoint == null) {
270 response.sendError(HttpServletResponse.SC_BAD_REQUEST,
271 "Connection not found");
272 } else {
273 try {
274
275
276
277 InputStream in = request.getInputStream();
278 OutputStream out = endpoint.getOutputStream();
279 byte[] data = new byte[1024];
280 int count = 0;
281 while (count != -1) {
282 count = in.read(data);
283 if (count > 0) {
284 out.write(data, 0, count);
285 }
286 }
287 in.close();
288 out.flush();
289 response.setStatus(HttpServletResponse.SC_OK);
290 } catch (IOException exception) {
291 log("write failed", exception);
292 remove(id);
293 response.sendError(
294 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
295 exception.getMessage());
296 }
297 }
298 }
299
300 /***
301 * Handle a close request.
302 *
303 * @param id the endpoint identifier
304 * @param response the response to the client
305 * @throws IOException for any I/O error
306 */
307 private void close(String id, HttpServletResponse response)
308 throws IOException {
309
310 try {
311 log("close(id=" + id + ")");
312 _manager.close(id);
313 response.setStatus(HttpServletResponse.SC_OK);
314 } catch (IOException exception) {
315 log("close failed", exception);
316 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
317 exception.getMessage());
318 }
319 }
320
321 /***
322 * Removes a socket.
323 *
324 * @param id the socket identifier
325 */
326 private void remove(String id) {
327 try {
328 _manager.close(id);
329 } catch (IOException ignore) {
330 }
331 }
332
333 /***
334 * Helper to get an initialisation property.
335 *
336 * @param name the property name
337 * @return the value corresponding to <code>name</code>
338 * @throws ServletException if the property doesn't exist
339 */
340 private String getString(String name) throws ServletException {
341 String value = getInitParameter(name);
342 if (value == null) {
343 throw new ServletException("Property not defined: " + name);
344 }
345 return value;
346 }
347
348 /***
349 * Helper to get an initialisation property.
350 *
351 * @param name the property name
352 * @return the value corresponding to <code>name</code>
353 * @throws ServletException if the property doesn't exist
354 */
355 private int getInt(String name) throws ServletException {
356 int result;
357 String value = getString(name);
358 try {
359 result = Integer.parseInt(value);
360 } catch (NumberFormatException exception) {
361 throw new ServletException("Invalid " + name + ": " + value);
362 }
363 return result;
364 }
365
366 /***
367 * Helper to get an initialisation property.
368 *
369 * @param name the property name
370 * @param defaultValue the default value to use
371 * @return the value corresponding to <code>name</code>
372 * @throws ServletException if the value is invalid
373 */
374 private int getInt(String name, int defaultValue) throws ServletException {
375 int result;
376 String value = getInitParameter(name);
377 if (value == null) {
378 result = defaultValue;
379 } else {
380 try {
381 result = Integer.parseInt(value);
382 } catch (NumberFormatException exception) {
383 throw new ServletException("Invalid " + name + ": " + value);
384 }
385 }
386 return result;
387 }
388 }