View Javadoc

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                 // log("read(id=" + id + "), [length=" + count + "]");
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                 // log("write(id=" + id + "), [length="
275                 //    + request.getContentLength()
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 }