View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v 1.18.2.3 2004/06/25 03:27:40 mbecke Exp $
3    * $Revision: 1.18.2.3 $
4    * $Date: 2004/06/25 03:27:40 $
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;
33  
34  import java.io.IOException;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  
39  /***
40   * <p>Wraps another method to tunnel through a proxy.</p>
41   *
42   * @author Ortwin Gl�ck
43   * @author dIon Gillard
44   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
45   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
46   * @since 2.0
47   * @version $Revision: 1.18.2.3 $ $Date: 2004/06/25 03:27:40 $
48   */
49  public class ConnectMethod extends HttpMethodBase {
50      /*** the name of this method */
51      public static final String NAME = "CONNECT";
52  
53      /***
54       * Create a connect method wrapping the existing method
55       *
56       * @param method the {@link HttpMethod method} to execute after connecting
57       *      to the server
58       */
59      public ConnectMethod(HttpMethod method) {
60          LOG.trace("enter ConnectMethod(HttpMethod)");
61          this.method = method;
62      }
63  
64      /***
65       * Provide the {@link #NAME name} of this method.
66       *
67       * @return the String "CONNECT"
68       */
69      public String getName() {
70          return NAME;
71      }
72  
73  
74      /***
75       * This method does nothing. <tt>CONNECT</tt> request is not supposed 
76       * to contain <tt>Authorization</tt> request header.
77       *
78       * @param state current state of http requests
79       * @param conn the connection to use for I/O
80       *
81       * @throws IOException when errors occur reading or writing to/from the
82       *         connection
83       * @throws HttpException when a recoverable error occurs
84       *  
85       * @see HttpMethodBase#addAuthorizationRequestHeader(HttpState, HttpConnection)
86       */
87      protected void addAuthorizationRequestHeader(HttpState state, HttpConnection conn)
88          throws IOException, HttpException {
89          // Do nothing. Not applicable to CONNECT method
90      }
91  
92      /***
93       * This method does nothing. <tt>CONNECT</tt> request is not supposed 
94       * to contain <tt>Content-Length</tt> request header.
95       *
96       * @param state current state of http requests
97       * @param conn the connection to use for I/O
98       *
99       * @throws IOException when errors occur reading or writing to/from the
100      *         connection
101      * @throws HttpException when a recoverable error occurs
102      *  
103      * @see HttpMethodBase#addContentLengthRequestHeader(HttpState, HttpConnection)
104      */
105     protected void addContentLengthRequestHeader(HttpState state, HttpConnection conn)
106         throws IOException, HttpException {
107         // Do nothing. Not applicable to CONNECT method
108     }
109 
110     /***
111      * This method does nothing. <tt>CONNECT</tt> request is not supposed 
112      * to contain <tt>Cookie</tt> request header.
113      *
114      * @param state current state of http requests
115      * @param conn the connection to use for I/O
116      *
117      * @throws IOException when errors occur reading or writing to/from the
118      *         connection
119      * @throws HttpException when a recoverable error occurs
120      *  
121      * @see HttpMethodBase#addCookieRequestHeader(HttpState, HttpConnection)
122      */
123     protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
124         throws IOException, HttpException {
125         // Do nothing. Not applicable to CONNECT method
126     }
127 
128 
129     /***
130      * Populates the request headers map to with additional {@link Header
131      * headers} to be submitted to the given {@link HttpConnection}.
132      *
133      * <p>
134      * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
135      * and <tt>Proxy-Authorization</tt> headers, when appropriate.
136      * </p>
137      *
138      * @param state the client state
139      * @param conn the {@link HttpConnection} the headers will eventually be
140      *        written to
141      * @throws IOException when an error occurs writing the request
142      * @throws HttpException when a HTTP protocol error occurs
143      *
144      * @see #writeRequestHeaders
145      */
146     protected void addRequestHeaders(HttpState state, HttpConnection conn)
147         throws IOException, HttpException {
148         LOG.trace("enter ConnectMethod.addRequestHeaders(HttpState, "
149             + "HttpConnection)");
150         addUserAgentRequestHeader(state, conn);
151         addHostRequestHeader(state, conn);
152         addProxyAuthorizationRequestHeader(state, conn);
153         addProxyConnectionHeader(state, conn);
154     }
155 
156     /***
157      * Execute this method by tunnelling and then executing the wrapped method.
158      *
159      * @param state the current http state
160      * @param conn the connection to write to
161      * @return the http status code from execution
162      * @throws HttpException when an error occurs writing the headers
163      * @throws IOException when an error occurs writing the headers
164      */
165     public int execute(HttpState state, HttpConnection conn) 
166     throws IOException, HttpException {
167 
168         LOG.trace("enter ConnectMethod.execute(HttpState, HttpConnection)");
169         int code = super.execute(state, conn);
170         LOG.debug("CONNECT status code " + code);
171         if ((code >= 200) && (code < 300)) {
172             conn.tunnelCreated();
173             code = method.execute(state, conn);
174         } else {
175             // What is to follow is an ugly hack.
176             // I REALLY hate having to resort to such
177             // an appalling trick
178             // TODO: Connect method must be redesigned.
179             // The only feasible solution is to split monolithic
180             // HttpMethod into HttpRequest/HttpResponse pair.
181             // That would allow to execute CONNECT method 
182             // behind the scene and return CONNECT HttpResponse 
183             // object in response to the original request that 
184             // contains the correct status line, headers & 
185             // response body.
186             
187             LOG.debug("CONNECT failed, fake the response for the original method");
188             if (method instanceof HttpMethodBase) {
189                 // Pass the status, headers and response stream to the wrapped
190                 // method.
191                 // To ensure that the connection is not released more than once
192                 // this method is still responsible for releasing the connection. 
193                 // This will happen when the response body is consumed, or when
194                 // the wrapped method closes the response connection in 
195                 // releaseConnection().
196                 ((HttpMethodBase) method).fakeResponse(
197                     getStatusLine(), 
198                     getResponseHeaderGroup(),
199                     getResponseStream()
200                 );
201             } else {
202                 releaseConnection();
203             }
204         }
205         return code;
206     }
207 
208     /***
209      * Special Connect request.
210      *
211      * @param state the current http state
212      * @param conn the connection to write to
213      * @throws IOException when an error occurs writing the request
214      * @throws HttpException when an error occurs writing the request
215      */
216     protected void writeRequestLine(HttpState state, HttpConnection conn)
217     throws IOException, HttpException {
218         int port = conn.getPort();
219         if (port == -1) {
220             port = conn.getProtocol().getDefaultPort();  
221         }
222         StringBuffer buffer = new StringBuffer();
223         buffer.append(getName()); 
224         buffer.append(' '); 
225         buffer.append(conn.getHost()); 
226         if (port > -1) {
227             buffer.append(':'); 
228             buffer.append(port); 
229         }
230         buffer.append(" HTTP/1.1"); 
231         String line = buffer.toString();
232         conn.printLine(line);
233         if (Wire.HEADER_WIRE.enabled()) {
234             Wire.HEADER_WIRE.output(line);
235         }
236     }
237 
238     /***
239      * Returns <code>true</code> if the status code is anything other than
240      * SC_OK, <code>false</code> otherwise.
241      * 
242      * @param conn the connection to test
243      * 
244      * @return <code>true</code> if the connection should be closed
245      * 
246      * @see HttpMethodBase#shouldCloseConnection(HttpConnection)
247      * @see HttpStatus#SC_OK
248      */
249     protected boolean shouldCloseConnection(HttpConnection conn) {
250         if (getStatusCode() == HttpStatus.SC_OK) {
251             Header connectionHeader = null;
252             if (!conn.isTransparent()) {
253                 connectionHeader = getResponseHeader("proxy-connection");
254             }
255             if (connectionHeader == null) {
256                 connectionHeader = getResponseHeader("connection");
257             }
258             if (connectionHeader != null) {
259                 if (connectionHeader.getValue().equalsIgnoreCase("close")) {
260                     if (LOG.isWarnEnabled()) {
261                         LOG.warn("Invalid header encountered '" + connectionHeader.toExternalForm() 
262                             + "' in response " + getStatusLine().toString());
263                     }
264                 }
265             }
266             return false;
267         } else {
268             return super.shouldCloseConnection(conn);
269         }
270     }
271     
272     /*** Log object for this class. */
273     private static final Log LOG = LogFactory.getLog(ConnectMethod.class);
274 
275     /*** The wrapped method */
276     private HttpMethod method;
277 
278 }