KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hsqldb > WebServerConnection


1 /* Copyright (c) 2001-2005, The HSQL Development Group
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of the HSQL Development Group nor the names of its
15  * contributors may be used to endorse or promote products derived from this
16  * software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */

30
31
32 package org.hsqldb;
33
34 import java.io.BufferedOutputStream JavaDoc;
35 import java.io.DataInputStream JavaDoc;
36 import java.io.File JavaDoc;
37 import java.io.FileInputStream JavaDoc;
38 import java.io.IOException JavaDoc;
39 import java.io.InputStream JavaDoc;
40 import java.io.OutputStream JavaDoc;
41 import java.net.HttpURLConnection JavaDoc;
42 import java.net.Socket JavaDoc;
43
44 import org.hsqldb.lib.ArrayUtil;
45 import org.hsqldb.lib.InOutUtil;
46 import org.hsqldb.persist.HsqlDatabaseProperties;
47 import org.hsqldb.resources.BundleHandler;
48 import org.hsqldb.rowio.RowInputBinary;
49 import org.hsqldb.rowio.RowOutputBinary;
50
51 // fredt@users 20021002 - patch 1.7.1 - changed notification method
52
// unsaved@users 20021113 - patch 1.7.2 - SSL support
53
// boucherb@users 20030510 - patch 1.7.2 - SSL support moved to factory interface
54
// boucherb@users 20030510 - patch 1.7.2 - general lint removal
55
// boucherb@users 20030514 - patch 1.7.2 - localized error responses
56
// fredt@users 20030628 - patch 1.7.2 - new protocol, persistent sessions
57

58 /**
59  * A web server connection is a transient object that lasts for the duration
60  * of the SQL call and its result. This class uses the notification
61  * mechanism in WebServer to allow cleanup after a SHUTDOWN.<p>
62  *
63  * The POST method is used for login and subsequent remote calls. In 1.7.2
64  * The initial login establishes a persistent Session and returns its handle
65  * to the client. Subsequent calls are executed in the context of this
66  * session.<p>
67  * (fredt@users)
68  *
69  * Rewritten in version HSQLDB 1.7.2, based on original Hypersonic code.
70  *
71  * @author Thomas Mueller (Hypersonic SQL Group)
72  * @author fredt@users
73  * @version 1.7.2
74  * @since Hypersonic SQL
75  */

76 class WebServerConnection implements Runnable JavaDoc {
77
78     static final String JavaDoc ENCODING = "8859_1";
79     private Socket JavaDoc socket;
80     private WebServer server;
81     private static final int REQUEST_TYPE_BAD = 0;
82     private static final int REQUEST_TYPE_GET = 1;
83     private static final int REQUEST_TYPE_HEAD = 2;
84     private static final int REQUEST_TYPE_POST = 3;
85     private static final String JavaDoc HEADER_OK = "HTTP/1.0 200 OK";
86     private static final String JavaDoc HEADER_BAD_REQUEST =
87         "HTTP/1.0 400 Bad Request";
88     private static final String JavaDoc HEADER_NOT_FOUND = "HTTP/1.0 404 Not Found";
89     private static final String JavaDoc HEADER_FORBIDDEN = "HTTP/1.0 403 Forbidden";
90     static final int BUFFER_SIZE = 256;
91     private RowOutputBinary rowOut = new RowOutputBinary(BUFFER_SIZE);
92     private RowInputBinary rowIn = new RowInputBinary(rowOut);
93
94     //
95
static final byte[] BYTES_GET = "GET".getBytes();
96     static final byte[] BYTES_HEAD = "HEAD".getBytes();
97     static final byte[] BYTES_POST = "POST".getBytes();
98     static final byte[] BYTES_CONTENT = "Content-Length: ".getBytes();
99     static final byte[] BYTES_WHITESPACE = new byte[] {
100         (byte) ' ', (byte) '\t'
101     };
102
103     // default mime type mappings
104
private static final int hnd_content_types =
105         BundleHandler.getBundleHandle("content-types", null);
106
107     /**
108      * Creates a new WebServerConnection to the specified WebServer on the
109      * specified socket.
110      *
111      * @param socket the network socket on which WebServer communication
112      * takes place
113      * @param server the WebServer instance to which the object
114      * represents a connection
115      */

116     WebServerConnection(Socket JavaDoc socket, WebServer server) {
117         this.server = server;
118         this.socket = socket;
119     }
120
121     /**
122      * Retrieves a best-guess mime-type string using the file extension
123      * of the name argument.
124      *
125      * @return a best-guess mime-type string using the file extension
126      * of the name argument.
127      */

128     private String JavaDoc getMimeTypeString(String JavaDoc name) {
129
130         int pos;
131         String JavaDoc key;
132         String JavaDoc mimeType;
133
134         if (name == null) {
135             return ServerConstants.SC_DEFAULT_WEB_MIME;
136         }
137
138         pos = name.lastIndexOf('.');
139         key = null;
140         mimeType = null;
141
142         // first search user-specified mapping
143
if (pos >= 0) {
144             key = name.substring(pos).toLowerCase();
145             mimeType = server.serverProperties.getProperty(key);
146         }
147
148         // if not found, search default mapping
149
if (mimeType == null && key.length() > 1) {
150             mimeType = BundleHandler.getString(hnd_content_types,
151                                                key.substring(1));
152         }
153
154         return mimeType == null ? ServerConstants.SC_DEFAULT_WEB_MIME
155                                 : mimeType;
156     }
157
158     /**
159      * Causes this WebServerConnection to process its HTTP request
160      * in a blocking fashion until the request is fully processed
161      * or an exception occurs internally.
162      *
163      * This method reads the Request line then delegates action to subroutines.
164      */

165     public void run() {
166
167         try {
168             DataInputStream JavaDoc inStream =
169                 new DataInputStream JavaDoc(socket.getInputStream());
170             int count;
171             String JavaDoc request;
172             String JavaDoc name = null;
173             int method = REQUEST_TYPE_BAD;
174             int len = -1;
175
176             // read line, ignoring any leading blank lines
177
do {
178                 count = InOutUtil.readLine(inStream, rowOut);
179
180                 if (count == 0) {
181                     throw new Exception JavaDoc();
182                 }
183             } while (count < 2);
184
185             byte[] byteArray = rowOut.getBuffer();
186             int offset = rowOut.size() - count;
187
188             if (ArrayUtil.containsAt(byteArray, offset, BYTES_POST)) {
189                 method = REQUEST_TYPE_POST;
190                 offset += BYTES_POST.length;
191             } else if (ArrayUtil.containsAt(byteArray, offset, BYTES_GET)) {
192                 method = REQUEST_TYPE_GET;
193                 offset += BYTES_GET.length;
194             } else if (ArrayUtil.containsAt(byteArray, offset, BYTES_HEAD)) {
195                 method = REQUEST_TYPE_HEAD;
196                 offset += BYTES_HEAD.length;
197             } else {
198                 throw new Exception JavaDoc();
199             }
200
201             count = ArrayUtil.countStartElementsAt(byteArray, offset,
202                                                    BYTES_WHITESPACE);
203
204             if (count == 0) {
205                 throw new Exception JavaDoc();
206             }
207
208             offset += count;
209             count = ArrayUtil.countNonStartElementsAt(byteArray, offset,
210                     BYTES_WHITESPACE);
211             name = new String JavaDoc(byteArray, offset, count, ENCODING);
212
213             switch (method) {
214
215                 case REQUEST_TYPE_BAD :
216                     processError(REQUEST_TYPE_BAD);
217                     break;
218
219                 case REQUEST_TYPE_GET :
220                     processGet(name, true);
221                     break;
222
223                 case REQUEST_TYPE_HEAD :
224                     processGet(name, false);
225                     break;
226
227                 case REQUEST_TYPE_POST :
228                     processPost(inStream, name);
229                     break;
230             }
231
232             inStream.close();
233             socket.close();
234         } catch (Exception JavaDoc e) {
235             server.printStackTrace(e);
236         }
237     }
238
239     /**
240      * POST is used only for database access. So we can assume the strings
241      * are those generated by HTTPClientConnection
242      */

243     private void processPost(InputStream JavaDoc inStream,
244                              String JavaDoc name) throws HsqlException, IOException JavaDoc {
245
246         // fredt - parsing in this block is not actually necessary
247
try {
248
249             // read the Content-Type line
250
InOutUtil.readLine(inStream, rowOut);
251
252             // read and parse the Content-Length line
253
int count = InOutUtil.readLine(inStream, rowOut);
254             int offset = rowOut.size() - count;
255
256             // get buffer always after reading into rowOut, else old buffer may
257
// be returned
258
byte[] byteArray = rowOut.getBuffer();
259
260             if (!ArrayUtil.containsAt(byteArray, offset, BYTES_CONTENT)) {
261                 throw new Exception JavaDoc();
262             }
263
264             count -= BYTES_CONTENT.length;
265             offset += BYTES_CONTENT.length;
266
267             // omit the last two characters
268
String JavaDoc lenStr = new String JavaDoc(byteArray, offset, count - 2);
269             int length = Integer.parseInt(lenStr);
270
271             InOutUtil.readLine(inStream, rowOut);
272         } catch (Exception JavaDoc e) {
273             processError(HttpURLConnection.HTTP_BAD_REQUEST);
274
275             return;
276         }
277
278         processQuery(inStream);
279     }
280
281     /**
282      * Processes a database query in HSQL protocol that has been
283      * tunneled over HTTP protocol.
284      *
285      * @param inStream the incoming byte stream representing the HSQL protocol
286      * database query
287      */

288     void processQuery(InputStream JavaDoc inStream) {
289
290         try {
291             Result resultIn = Result.read(rowIn,
292                                           new DataInputStream JavaDoc(inStream));
293
294             //
295
Result resultOut;
296
297             if (resultIn.mode == ResultConstants.SQLCONNECT) {
298                 try {
299                     int dbID = server.getDBID(resultIn.subSubString);
300                     Session session = DatabaseManager.newSession(dbID,
301                         resultIn.getMainString(), resultIn.getSubString());
302
303                     resultOut = new Result(ResultConstants.UPDATECOUNT);
304                     resultOut.databaseID = dbID;
305                     resultOut.sessionID = session.getId();
306                 } catch (HsqlException e) {
307                     resultOut = new Result(e, null);
308                 } catch (RuntimeException JavaDoc e) {
309                     resultOut = new Result(e, null);
310                 }
311             } else {
312                 int dbID = resultIn.databaseID;
313                 Session session = DatabaseManager.getSession(dbID,
314                     resultIn.sessionID);
315
316                 resultOut =
317                     session == null
318                     ? new Result(Trace.error(Trace.DATABASE_NOT_EXISTS), null)
319                     : session.execute(resultIn);
320             }
321
322 //
323
rowOut.reset();
324             resultOut.write(rowOut);
325
326             OutputStream JavaDoc outStream = socket.getOutputStream();
327             String JavaDoc header = getHead(HEADER_OK, false,
328                                     "application/octet-stream",
329                                     rowOut.size());
330
331             outStream.write(header.getBytes(ENCODING));
332             outStream.write(rowOut.getOutputStream().getBuffer(), 0,
333                             rowOut.getOutputStream().size());
334             outStream.flush();
335             outStream.close();
336         } catch (Exception JavaDoc e) {
337             server.printStackTrace(e);
338         }
339     }
340
341     /**
342      * Processes an HTTP GET request
343      *
344      * @param name the name of the content to get
345      * @param send whether to send the content as well, or just the header
346      */

347     private void processGet(String JavaDoc name, boolean send) {
348
349         try {
350             String JavaDoc hdr;
351             OutputStream JavaDoc os;
352             InputStream JavaDoc is;
353             int b;
354
355             if (name.endsWith("/")) {
356                 name = name + server.getDefaultWebPage();
357             }
358
359             // traversing up the directory structure is forbidden.
360
if (name.indexOf("..") != -1) {
361                 processError(HttpURLConnection.HTTP_FORBIDDEN);
362
363                 return;
364             }
365
366             name = server.getWebRoot() + name;
367
368             if (File.separatorChar != '/') {
369                 name = name.replace('/', File.separatorChar);
370             }
371
372             is = null;
373
374             server.printWithThread("GET " + name);
375
376             try {
377                 File JavaDoc file = new File JavaDoc(name);
378
379                 is = new DataInputStream JavaDoc(new FileInputStream JavaDoc(file));
380                 hdr = getHead(HEADER_OK, true, getMimeTypeString(name),
381                               (int) file.length());
382             } catch (IOException JavaDoc e) {
383                 processError(HttpURLConnection.HTTP_NOT_FOUND);
384
385                 if (is != null) {
386                     is.close();
387                 }
388
389                 return;
390             }
391
392             os = new BufferedOutputStream JavaDoc(socket.getOutputStream());
393
394             os.write(hdr.getBytes(ENCODING));
395
396             if (send) {
397                 while ((b = is.read()) != -1) {
398                     os.write(b);
399                 }
400             }
401
402             os.flush();
403             os.close();
404             is.close();
405         } catch (Exception JavaDoc e) {
406             server.printError("processGet: " + e.toString());
407             server.printStackTrace(e);
408         }
409     }
410
411     /**
412      * Retrieves an HTTP protocol header given the supplied arguments.
413      *
414      * @param responseCodeString the HTTP response code
415      * @param addInfo true if additional header info is to be added
416      * @param mimeType the Content-Type field value
417      * @param length the Content-Length field value
418      * @return an HTTP protocol header
419      */

420     String JavaDoc getHead(String JavaDoc responseCodeString, boolean addInfo,
421                    String JavaDoc mimeType, int length) {
422
423         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(128);
424
425         sb.append(responseCodeString).append("\r\n");
426
427         if (addInfo) {
428             sb.append("Allow: GET, HEAD, POST\nMIME-Version: 1.0\r\n");
429             sb.append("Server: ").append(
430                 HsqlDatabaseProperties.PRODUCT_NAME).append("\r\n");
431         }
432
433         if (mimeType != null) {
434             sb.append("Content-Type: ").append(mimeType).append("\r\n");
435             sb.append("Content-Length: ").append(length).append("\r\n");
436         }
437
438         sb.append("\r\n");
439
440         return sb.toString();
441     }
442
443     /**
444      * Processess an HTTP error condition, sending an error response to
445      * the client.
446      *
447      * @param code the error condition code
448      */

449     private void processError(int code) {
450
451         String JavaDoc msg;
452
453         server.printWithThread("processError " + code);
454
455         switch (code) {
456
457             case HttpURLConnection.HTTP_BAD_REQUEST :
458                 msg = getHead(HEADER_BAD_REQUEST, false, null, 0);
459                 msg += BundleHandler.getString(WebServer.webBundleHandle,
460                                                "BAD_REQUEST");
461                 break;
462
463             case HttpURLConnection.HTTP_FORBIDDEN :
464                 msg = getHead(HEADER_FORBIDDEN, false, null, 0);
465                 msg += BundleHandler.getString(WebServer.webBundleHandle,
466                                                "FORBIDDEN");
467                 break;
468
469             case HttpURLConnection.HTTP_NOT_FOUND :
470             default :
471                 msg = getHead(HEADER_NOT_FOUND, false, null, 0);
472                 msg += BundleHandler.getString(WebServer.webBundleHandle,
473                                                "NOT_FOUND");
474                 break;
475         }
476
477         try {
478             OutputStream JavaDoc os =
479                 new BufferedOutputStream JavaDoc(socket.getOutputStream());
480
481             os.write(msg.getBytes(ENCODING));
482             os.flush();
483             os.close();
484         } catch (Exception JavaDoc e) {
485             server.printError("processError: " + e.toString());
486             server.printStackTrace(e);
487         }
488     }
489
490     /**
491      * Retrieves the thread name to be used when
492      * this object is the Runnable object of a Thread.
493      *
494      * @return the thread name to be used when
495      * this object is the Runnable object of a Thread.
496      */

497     String JavaDoc getConnectionThreadName() {
498         return "HSQLDB HTTP Connection @" + Integer.toString(hashCode(), 16);
499     }
500 }
501
Popular Tags