1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleHttpServer.java,v 1.6 2004/05/11 20:43:55 olegk Exp $
3    * $Revision: 1.6 $
4    * $Date: 2004/05/11 20:43:55 $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   * [Additional notices, if required by prior licensing conditions]
29   *
30   */
31  
32  package org.apache.commons.httpclient.server;
33  
34  import java.io.IOException;
35  import java.net.InetAddress;
36  import java.net.ServerSocket;
37  import java.net.Socket;
38  import java.util.HashSet;
39  import java.util.Iterator;
40  import java.util.Set;
41  
42  import org.apache.commons.httpclient.Header;
43  import org.apache.commons.httpclient.HttpStatus;
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  /***
48   * A simple, but extensible HTTP server, mostly for testing purposes.
49   * 
50   * @author Christian Kohlschuetter
51   * @author Oleg Kalnichevski
52   */
53  public class SimpleHttpServer implements Runnable {
54      private static final Log LOG = LogFactory.getLog(SimpleHttpServer.class);
55      
56      private ServerSocket server = null;
57      private Thread t;
58      private ThreadGroup tg;
59      private boolean stopped = false;
60  
61      private Set connections = new HashSet();
62  
63      private HttpRequestHandler requestHandler = null;
64      private HttpService serivce = null;
65  
66      /***
67       * Creates a new HTTP server instance, using an arbitrary free TCP port
68       * 
69       * @throws IOException  if anything goes wrong during initialization
70       */
71      public SimpleHttpServer() throws IOException {
72          this(0);
73      }
74  
75      /***
76       * Creates a new HTTP server instance, using the specified TCP port
77       * 
78       * @param   port    Desired TCP port
79       * @throws IOException  if anything goes wrong during initialization
80       */
81      public SimpleHttpServer(int port) throws IOException {
82          server = new ServerSocket(port);
83          if(LOG.isInfoEnabled()) {
84              LOG.info("New SimpleHttpServer on port " + getLocalPort());
85          }
86          tg = new ThreadGroup("SimpleHttpServer group");
87          t = new Thread(tg, this, "SimpleHttpServer connection handler");
88          t.setDaemon(true);
89          t.start();
90      }
91  
92      /***
93       * Returns the TCP port that this HTTP server instance is bound to.
94       *
95       * @return  TCP port, or -1 if not running
96       */
97      public int getLocalPort() {
98          return server.getLocalPort();
99      }
100     
101     /***
102      * Returns the IP address that this HTTP server instance is bound to.
103      * @return String representation of the IP address or <code>null</code> if not running
104      */
105     public String getLocalAddress() {
106         InetAddress address = server.getInetAddress();
107         // Ugly work-around for older JDKs
108         byte[] octets = address.getAddress();
109         if ((octets[0] == 0) 
110          && (octets[1] == 0) 
111          && (octets[2] == 0) 
112          && (octets[3] == 0)) {
113             return "localhost"; 
114         } else {
115             return address.getHostAddress();
116         }
117     }
118 
119     /***
120      * Checks if this HTTP server instance is running.
121      * 
122      * @return  true/false
123      */
124     public boolean isRunning() {
125         if(t == null) {
126             return false;
127         }
128         return t.isAlive();
129     }
130 
131     /***
132      * Stops this HTTP server instance.
133      */
134     public void destroy() {
135         if (stopped) {
136             return;
137         }
138 
139         stopped = true;
140         if(LOG.isInfoEnabled()) {
141             LOG.info("Stopping SimpleHttpServer on port " + getLocalPort());
142         }
143 
144         tg.interrupt();
145 
146         if (server != null) {
147             try {
148                 server.close();
149             } catch(IOException e) {
150                 
151             }
152         }
153 
154         for (Iterator it = connections.iterator(); it.hasNext();) {
155             SimpleHttpServerConnection conn =
156                 (SimpleHttpServerConnection) it.next();
157             conn.destroy();
158         }
159     }
160 
161     /***
162      * Returns the currently used HttpRequestHandler by this SimpleHttpServer
163      * 
164      * @return The used HttpRequestHandler, or null.
165      */
166     public HttpRequestHandler getRequestHandler() {
167         return requestHandler;
168     }
169 
170     /***
171      * Sets the HttpRequestHandler to be used for this SimpleHttpServer.
172      * 
173      * @param rh    Request handler to be used, or null to disable.
174      */
175     public void setRequestHandler(HttpRequestHandler rh) {
176         this.requestHandler = rh;
177     }
178 
179     public void setHttpService(HttpService service) {
180         this.serivce = service;
181     }
182 
183     public void removeConnection(SimpleHttpServerConnection conn) {
184         connections.remove(conn);
185     }
186 
187     public void processRequest(
188         final SimpleHttpServerConnection conn,
189         final SimpleRequest request) throws IOException
190     {
191         if (conn == null) {
192             throw new IllegalArgumentException("Connection may not be null");
193         }
194         if (request == null) {
195             throw new IllegalArgumentException("Request may not be null");
196         }
197     	boolean complete = false;
198         if (this.requestHandler != null) {
199             complete = requestHandler.processRequest(conn, request);
200             if (complete) {
201                 return;
202             }
203         }
204         SimpleResponse response = null;
205         if (this.serivce != null) {
206             response = new SimpleResponse();
207             complete = this.serivce.process(request, response);
208         }
209         if (!complete) {
210             response = ErrorResponse.getInstance().
211                 getResponse(HttpStatus.SC_SERVICE_UNAVAILABLE);
212             conn.connectionClose();
213         }
214         writeResponse(conn, response);
215     }
216 
217     public void writeResponse(
218         final SimpleHttpServerConnection conn, 
219         final SimpleResponse response) throws IOException 
220    {
221         if (response == null) {
222             return;
223         }
224         ResponseWriter out = conn.getWriter();
225         if (!response.containsHeader("Content-Length")) {
226             int len = 0;
227             if (response.getBodyString() != null) {
228                 len = response.getBodyString().length();
229             }
230             response.addHeader(
231                 new Header("Content-Length", Integer.toString(len), true)); 
232         }
233         if (!response.containsHeader("Content-Type")) {
234             StringBuffer buffer = new StringBuffer();
235             if (response.getContentType() != null) {
236                 buffer.append(response.getContentType());
237                 if (out.getEncoding() != null) {
238                     buffer.append("; charset=");
239                     buffer.append(out.getEncoding());
240                 }
241             }
242             response.addHeader(
243                 new Header("Content-Type", buffer.toString(), true)); 
244         }
245         // @TODO implement HTTP/1.1 persistent connections
246         if (!conn.isKeepAlive()) {
247             response.setHeader(
248                 new Header("Connection", "close", true)); 
249         }
250         out.println(response.getStatusLine());
251         Iterator item = response.getHeaderIterator();
252         while (item.hasNext()) {
253             Header header = (Header) item.next();
254             out.print(header.toExternalForm());
255         }
256         out.println();
257         if (response.getBodyString() != null) {
258             out.print(response.getBodyString());   
259         }
260         out.flush();
261     }
262 
263     public void run() {
264         try {
265             while (!Thread.interrupted()) {
266                 Socket socket = server.accept();
267                 try {
268 
269                     SimpleHttpServerConnection conn =
270                         new SimpleHttpServerConnection(this, socket);
271 
272                     connections.add(conn);
273 
274                     Thread t =
275                         new Thread(tg, conn, "SimpleHttpServer connection");
276                     t.setDaemon(true);
277                     t.start();
278                 } catch (IOException e) {
279                     LOG.error("I/O error: " + e.getMessage());
280                 }
281                 Thread.sleep(100);
282             }
283         } catch (InterruptedException accept) {
284         } catch (IOException e) {
285             if (!stopped) {
286                 LOG.error("I/O error: " + e.getMessage());
287             }
288         } finally {
289             destroy();
290         }
291     }
292 }