View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.transport.http;
8   
9   import org.mule.RequestContext;
10  import org.mule.api.transformer.TransformerException;
11  import org.mule.api.transport.Connector;
12  import org.mule.api.transport.OutputHandler;
13  import org.mule.util.SystemUtils;
14  
15  import java.io.DataOutputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.io.UnsupportedEncodingException;
20  import java.net.Socket;
21  import java.net.SocketException;
22  import java.util.Iterator;
23  
24  import org.apache.commons.httpclient.ChunkedOutputStream;
25  import org.apache.commons.httpclient.Header;
26  import org.apache.commons.httpclient.HttpParser;
27  import org.apache.commons.httpclient.StatusLine;
28  import org.apache.commons.io.IOUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /** A connection to the SimpleHttpServer. */
33  public class HttpServerConnection
34  {
35      private static final Log logger = LogFactory.getLog(HttpServerConnection.class);
36  
37      private Socket socket;
38      private final InputStream in;
39      private final OutputStream out;
40      // this should rather be isKeepSocketOpen as this is the main purpose of this flag
41      private boolean keepAlive = false;
42      private final String encoding;
43  
44      public HttpServerConnection(final Socket socket, String encoding, HttpConnector connector) throws IOException
45      {
46          super();
47  
48          if (socket == null)
49          {
50              throw new IllegalArgumentException("Socket may not be null");
51          }
52  
53          this.socket = socket;
54          setSocketTcpNoDelay(connector.isSendTcpNoDelay());
55          this.socket.setKeepAlive(connector.isKeepAlive());
56          
57          if (connector.getReceiveBufferSize() != Connector.INT_VALUE_NOT_SET
58              && socket.getReceiveBufferSize() != connector.getReceiveBufferSize())
59          {
60              socket.setReceiveBufferSize(connector.getReceiveBufferSize());            
61          }
62          if (connector.getServerSoTimeout() != Connector.INT_VALUE_NOT_SET
63              && socket.getSoTimeout() != connector.getServerSoTimeout())
64          {
65              socket.setSoTimeout(connector.getServerSoTimeout());
66          }
67          
68          this.in = socket.getInputStream();
69          this.out = new DataOutputStream(socket.getOutputStream());
70          this.encoding = encoding;
71      }
72  
73      private void setSocketTcpNoDelay(boolean tcpNoDelay) throws IOException
74      {
75          try
76          {
77              socket.setTcpNoDelay(tcpNoDelay);
78          }
79          catch (SocketException se)
80          {
81              if (SystemUtils.IS_OS_SOLARIS || SystemUtils.IS_OS_SUN_OS)
82              {
83                  // this is a known Solaris bug, see
84                  // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378870
85                  
86                  if (logger.isDebugEnabled())
87                  {
88                      logger.debug("Failed to set tcpNoDelay on socket", se);
89                  }
90              }
91              else
92              {
93                  throw se;
94              }
95          }
96      }
97  
98      public synchronized void close()
99      {
100         try
101         {
102             if (socket != null)
103             {
104                 if (logger.isDebugEnabled())
105                 {
106                     logger.debug("Closing: " + socket);
107                 }
108                 
109                 try
110                 {
111                     socket.shutdownOutput();
112                 }
113                 catch (UnsupportedOperationException e)
114                 {
115                     //Can't shutdown in/output on SSL sockets
116                 }
117                 
118                 if (in != null)
119                 {
120                     in.close();
121                 }
122                 if (out != null)
123                 {
124                     out.close();
125                 }
126                 socket.close();
127             }
128         }
129         catch (IOException e)
130         {
131             if (logger.isDebugEnabled())
132             {
133                 logger.debug("(Ignored) Error closing the socket: " + e.getMessage());
134             }
135         }
136         finally
137         {
138             socket = null;
139         }
140     }
141 
142     public synchronized boolean isOpen()
143     {
144         return this.socket != null;
145     }
146 
147     public void setKeepAlive(boolean b)
148     {
149         this.keepAlive = b;
150     }
151 
152     public boolean isKeepAlive()
153     {
154         return this.keepAlive;
155     }
156 
157     public InputStream getInputStream()
158     {
159         return this.in;
160     }
161 
162     public OutputStream getOutputStream()
163     {
164         return this.out;
165     }
166 
167     /**
168      * Returns the ResponseWriter used to write the output to the socket.
169      *
170      * @return This connection's ResponseWriter
171      */
172     public ResponseWriter getWriter() throws UnsupportedEncodingException
173     {
174         return new ResponseWriter(out);
175     }
176 
177     public HttpRequest readRequest() throws IOException
178     {
179         try
180         {
181             String line = readLine();
182             if (line == null)
183             {
184                 return null;
185             }
186             return new HttpRequest(RequestLine.parseLine(line), HttpParser.parseHeaders(this.in, encoding), this.in, encoding);
187         }
188         catch (IOException e)
189         {
190             close();
191             throw e;
192         }
193     }
194 
195     public HttpResponse readResponse() throws IOException
196     {
197         try
198         {
199             String line = readLine();
200             return new HttpResponse(new StatusLine(line), HttpParser.parseHeaders(this.in, encoding), this.in);
201         }
202         catch (IOException e)
203         {
204             close();
205             throw e;
206         }
207     }
208 
209     private String readLine() throws IOException
210     {
211         String line;
212 
213         do
214         {
215             line = HttpParser.readLine(in, encoding);
216         }
217         while (line != null && line.length() == 0);
218 
219         if (line == null)
220         {
221             setKeepAlive(false);
222             return null;
223         }
224 
225         return line;
226     }
227 
228     public void writeRequest(final HttpRequest request) throws IOException
229     {
230         if (request == null)
231         {
232             return;
233         }
234         ResponseWriter writer = new ResponseWriter(this.out, encoding);
235         writer.println(request.getRequestLine().toString());
236         Iterator item = request.getHeaderIterator();
237         while (item.hasNext())
238         {
239             Header header = (Header) item.next();
240             writer.print(header.toExternalForm());
241         }
242         writer.println();
243         writer.flush();
244 
245         OutputStream outstream = this.out;
246         InputStream content = request.getBody();
247         if (content != null)
248         {
249             Header transferenc = request.getFirstHeader(HttpConstants.HEADER_TRANSFER_ENCODING);
250             if (transferenc != null)
251             {
252                 request.removeHeaders(HttpConstants.HEADER_CONTENT_LENGTH);
253                 if (transferenc.getValue().indexOf(HttpConstants.TRANSFER_ENCODING_CHUNKED) != -1)
254                 {
255                     outstream = new ChunkedOutputStream(outstream);
256                 }
257             }
258 
259             IOUtils.copy(content, outstream);
260 
261             if (outstream instanceof ChunkedOutputStream)
262             {
263                 ((ChunkedOutputStream) outstream).finish();
264             }
265         }
266 
267         outstream.flush();
268     }
269 
270     public void writeResponse(final HttpResponse response) throws IOException, TransformerException
271     {
272         if (response == null)
273         {
274             return;
275         }
276         
277         if (!response.isKeepAlive()) 
278         {
279             Header header = new Header(HttpConstants.HEADER_CONNECTION, "close");
280             response.setHeader(header);
281         }
282         
283         setKeepAlive(response.isKeepAlive());
284         
285         ResponseWriter writer = new ResponseWriter(this.out, encoding);
286         OutputStream outstream = this.out;
287 
288         writer.println(response.getStatusLine());
289         Iterator item = response.getHeaderIterator();
290         while (item.hasNext())
291         {
292             Header header = (Header) item.next();
293             writer.print(header.toExternalForm());
294         }
295         writer.println();
296         writer.flush();
297 
298         OutputHandler content = response.getBody();
299         if (content != null)
300         {
301             Header transferenc = response.getFirstHeader(HttpConstants.HEADER_TRANSFER_ENCODING);
302             if (transferenc != null)
303             {
304                 response.removeHeaders(HttpConstants.HEADER_CONTENT_LENGTH);
305                 if (transferenc.getValue().indexOf(HttpConstants.TRANSFER_ENCODING_CHUNKED) != -1)
306                 {
307                     outstream = new ChunkedOutputStream(outstream);
308                 }
309             }
310 
311             content.write(RequestContext.getEvent(), outstream);
312 
313             if (outstream instanceof ChunkedOutputStream)
314             {
315                 ((ChunkedOutputStream) outstream).finish();
316             }
317         }
318 
319         outstream.flush();
320     }
321 
322     /**
323      * Returns the value of the SO_TIMEOUT for the underlying socket.
324      *
325      * @return The value of the SO_TIMEOUT for the underlying socket.
326      * @throws SocketException If there is an error in the underlying protocol.
327      */
328     public int getSocketTimeout() throws SocketException
329     {
330         return socket.getSoTimeout();
331     }
332 
333     public void setSocketTimeout(int timeout) throws SocketException
334     {
335         socket.setSoTimeout(timeout);
336     }
337 
338     /**
339      * Tests if SO_KEEPALIVE is enabled in the underlying socket.
340      *
341      * @return a <code>boolean</code> indicating whether or not SO_KEEPALIVE is enabled.
342      * @throws SocketException If there is an error in the underlying protocol.
343      */
344     public boolean isSocketKeepAlive() throws SocketException
345     {
346         return socket.getKeepAlive();
347     }
348 
349     /**
350      * Tests if TCP_NODELAY is enabled in the underlying socket.
351      *
352      * @return a <code>boolean</code> indicating whether or not TCP_NODELAY is enabled.
353      * @throws SocketException If there is an error in the underlying protocol.
354      */
355     public boolean isSocketTcpNoDelay() throws SocketException
356     {
357         return socket.getTcpNoDelay();
358     }
359 
360     /**
361      * Gets the value of the SO_RCVBUF for the underlying socket.
362      *
363      * @return The value of the SO_RCVBUF for the underlying socket.
364      * @throws SocketException If there is an error in the underlying protocol.
365      */
366     public int getSocketReceiveBufferSize() throws SocketException
367     {
368         return socket.getReceiveBufferSize();
369     }
370 }