View Javadoc

1   /*
2    * Copyright (c) 2005-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.http;
14  
15  import java.io.EOFException;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.net.Socket;
20  import java.text.SimpleDateFormat;
21  import java.util.Date;
22  
23  import org.abstracthorizon.danube.connection.Connection;
24  import org.abstracthorizon.danube.connection.ConnectionHandler;
25  import org.abstracthorizon.danube.http.util.ErrorConnectionHandler;
26  import org.abstracthorizon.danube.http.util.MultiStringMap;
27  import org.abstracthorizon.danube.service.server.ServerConnectionHandler;
28  import org.abstracthorizon.danube.support.RuntimeIOException;
29  
30  /**
31   * This class is entry point for HTTP server.
32   * It extends {@link org.abstracthorizon.danube.http.Selector} class adding
33   * "Server" header and handles HTTP/1.1 "Connection: keep-alive"
34   * (multiple requests over singe socket connection.
35   *
36   * @author Daniel Sendula
37   */
38  public class HTTPServerConnectionHandler extends ServerConnectionHandler {
39  
40      /** Version string */
41      public static final String VERSION_STRING = "1.0";
42  
43      /** Full version string */
44      public static final String FULL_VERSION_STRING = "Danube/" + VERSION_STRING;
45  
46      /** RFC822 date format */
47      public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
48  
49      /** Default buffer size of 8Kb */
50      public static final int DEFAULT_BUFFER_SIZE = 1024*8; // 8Kb
51  
52      /** Error handler */
53      protected ConnectionHandler errorHandler;
54  
55      /** Default buffer size */
56      protected int defaultBufferSize = DEFAULT_BUFFER_SIZE;
57  
58      /** Constructor */
59      public HTTPServerConnectionHandler() {
60      }
61  
62      /**
63       * Returns generic error handler
64       * @return generic error handler
65       */
66      public ConnectionHandler getErrorHandler() {
67          if (errorHandler == null) {
68              errorHandler = new ErrorConnectionHandler();
69          }
70          return errorHandler;
71      }
72  
73      /**
74       * Sets generic error handler
75       * @param errorHandler generic error handler
76       */
77      public void setErrorHandler(ConnectionHandler errorHandler) {
78          this.errorHandler = errorHandler;
79      }
80  
81      /**
82       * Returns default buffer size
83       * @return default buffer size
84       */
85      public int getDefaultBufferSize() {
86          return defaultBufferSize;
87      }
88  
89      /**
90       * Sets default buffer size
91       * @param defaultBufferSize default buffer size
92       */
93      public void setDefaultBufferSize(int defaultBufferSize) {
94          this.defaultBufferSize = defaultBufferSize;
95      }
96  
97      /**
98       * Processes connection
99       *
100      * @param httpConnection http connection to be processed
101      * @throws IOException
102      */
103     protected void processConnection(Connection connection) {
104         HTTPConnectionImpl httpConnection = (HTTPConnectionImpl)connection;
105         try {
106             try {
107                 httpConnection.processRequest();
108             } catch (EOFException eof) {
109                 throw eof;
110             } catch (IOException e) {
111                 throw new EOFException(e.getMessage());
112             }
113 
114             MultiStringMap headers = httpConnection.getResponseHeaders();
115 
116             headers.putOnly("Server", FULL_VERSION_STRING);
117 
118             // This header should be present so presetting it to text/html should not make any harm
119             headers.putOnly("Content-Type", "text/html");
120 
121             // Mandatory field
122             headers.putOnly("Date", DATE_FORMAT.format(new Date()));
123 
124             try {
125                 connectionHandler.handleConnection(httpConnection);
126                 // Ensure that all output to the user is commited
127                 OutputStream out = (OutputStream)httpConnection.adapt(OutputStream.class);
128                 try {
129                     out.close();
130                 } catch (IOException ignore) {
131                 }
132             } catch (Throwable e) {
133                 Socket socket = (Socket)httpConnection.adapt(Socket.class);
134                 if ((socket != null) && (socket.isConnected() && !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown())) {
135                     httpConnection.setResponseStatus(Status.INTERNAL_SERVER_ERROR);
136                     ConnectionHandler connectionHandler = getErrorHandler();
137                     httpConnection.getAttributes().put("_exception", e);
138                     connectionHandler.handleConnection(httpConnection);
139                 } else {
140                    throw new EOFException();
141                 }
142             } finally {
143                 InputStream in = (InputStream)httpConnection.adapt(InputStream.class);
144                 in.close();
145             }
146         } catch (IOException e) {
147             throw new RuntimeIOException(e);
148         }
149     }
150 
151     protected Connection decorateConnection(Connection connection) {
152         return new HTTPConnectionImpl(connection, this, getDefaultBufferSize());
153     }
154 
155     protected boolean postProcessing(Connection connection, boolean persistConnection) {
156         HTTPConnectionImpl httpConnection = (HTTPConnectionImpl)connection;
157         MultiStringMap headers = httpConnection.getResponseHeaders();
158 
159         if (persistConnection) {
160             String code = httpConnection.getResponseStatus().getCode();
161             // If response doesn't start with 1xx and is not 204 and 304
162             // and there is no Content-Length present then only way to determine size of
163             // message is for server to drop connection.
164 
165             // TODO Transfer-Encoding has different way of defining size of the message
166             // it is not implemented here!
167             if (!code.startsWith("1")
168                     && !"204".equals(code)
169                     && !"304".equals(code)
170                     && !headers.containsKey("Content-Length")
171                     && !"chunked".equals(headers.getOnly("Transfer-Encoding"))) {
172 
173                 persistConnection = false;
174             }
175         }
176 
177         if (persistConnection) {
178             if ("HTTP/1.1".equals(httpConnection.getRequestProtocol())) {
179                 try {
180                     if ("Close".equalsIgnoreCase(headers.getOnly("Connection"))) {
181                         persistConnection = false;
182                     }
183                 } catch (IllegalStateException ignore) {
184                 }
185             } else {
186                 try {
187                     if (!"Keep-Alive".equalsIgnoreCase(headers.getOnly("Connection"))) {
188                         persistConnection = false;
189                     }
190                 } catch (IllegalStateException ignore) {
191                 }
192             }
193         }
194         return persistConnection;
195     }
196 
197     protected void finishConnection(Connection connnection) {
198     }
199 
200 }