1 /* HTTPURLConnection.java --
2 Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu
.java
.net
.protocol
.http
;
41 import gnu
.classpath
.SystemProperties
;
43 import java
.io
.ByteArrayOutputStream
;
44 import java
.io
.FileNotFoundException
;
45 import java
.io
.IOException
;
46 import java
.io
.InputStream
;
47 import java
.io
.OutputStream
;
48 import java
.net
.ProtocolException
;
50 import java
.security
.cert
.Certificate
;
51 import java
.util
.Collections
;
52 import java
.util
.Date
;
55 import javax
.net
.ssl
.HandshakeCompletedEvent
;
56 import javax
.net
.ssl
.HandshakeCompletedListener
;
57 import javax
.net
.ssl
.HostnameVerifier
;
58 import javax
.net
.ssl
.HttpsURLConnection
;
59 import javax
.net
.ssl
.SSLPeerUnverifiedException
;
60 import javax
.net
.ssl
.SSLSocketFactory
;
63 * A URLConnection that uses the HTTPConnection class.
65 * @author Chris Burdess (dog@gnu.org)
67 public class HTTPURLConnection
68 extends HttpsURLConnection
69 implements HandshakeCompletedListener
72 * The underlying connection.
74 private HTTPConnection connection
;
76 // These are package private for use in anonymous inner classes.
82 private Request request
;
83 private Headers requestHeaders
;
84 private ByteArrayOutputStream requestSink
;
85 private boolean requestMethodSetExplicitly
;
87 private Response response
;
88 private InputStream responseSink
;
89 private InputStream errorSink
;
91 private HandshakeCompletedEvent handshakeEvent
;
97 public HTTPURLConnection(URL url
)
101 requestHeaders
= new Headers();
102 proxyHostname
= SystemProperties
.getProperty("http.proxyHost");
103 if (proxyHostname
!= null && proxyHostname
.length() > 0)
105 String port
= SystemProperties
.getProperty("http.proxyPort");
106 if (port
!= null && port
.length() > 0)
108 proxyPort
= Integer
.parseInt(port
);
112 proxyHostname
= null;
116 agent
= SystemProperties
.getProperty("http.agent");
117 String ka
= SystemProperties
.getProperty("http.keepAlive");
118 keepAlive
= !(ka
!= null && "false".equals(ka
));
121 public void connect()
128 String protocol
= url
.getProtocol();
129 boolean secure
= "https".equals(protocol
);
130 String host
= url
.getHost();
131 int port
= url
.getPort();
134 port
= secure ? HTTPConnection
.HTTPS_PORT
:
135 HTTPConnection
.HTTP_PORT
;
137 String file
= url
.getFile();
138 String username
= url
.getUserInfo();
139 String password
= null;
140 if (username
!= null)
142 int ci
= username
.indexOf(':');
145 password
= username
.substring(ci
+ 1);
146 username
= username
.substring(0, ci
);
149 final Credentials creds
= (username
== null) ?
null :
150 new Credentials (username
, password
);
156 if (connection
== null)
158 connection
= getConnection(host
, port
, secure
);
161 SSLSocketFactory factory
= getSSLSocketFactory();
162 HostnameVerifier verifier
= getHostnameVerifier();
165 connection
.setSSLSocketFactory(factory
);
167 connection
.addHandshakeCompletedListener(this);
171 if (proxyHostname
!= null)
175 proxyPort
= secure ? HTTPConnection
.HTTPS_PORT
:
176 HTTPConnection
.HTTP_PORT
;
178 connection
.setProxy(proxyHostname
, proxyPort
);
182 request
= connection
.newRequest(method
, file
);
185 request
.setHeader("Connection", "close");
189 request
.setHeader("User-Agent", agent
);
191 request
.getHeaders().putAll(requestHeaders
);
192 if (requestSink
!= null)
194 byte[] content
= requestSink
.toByteArray();
195 RequestBodyWriter writer
= new ByteArrayRequestBodyWriter(content
);
196 request
.setRequestBodyWriter(writer
);
200 request
.setAuthenticator(new Authenticator() {
201 public Credentials
getCredentials(String realm
, int attempts
)
203 return (attempts
< 2) ? creds
: null;
207 response
= request
.dispatch();
209 catch (IOException ioe
)
211 if (connection
.useCount
> 0)
213 // Connection re-use failed: Try a new connection.
218 catch (IOException _
)
228 // First time the connection was used: Hard failure.
233 if (response
.isRedirect() && getInstanceFollowRedirects())
235 // Read the response body, if there is one. If the
236 // redirect points us back at the same server, we will use
237 // the cached connection, so we must make sure there is no
238 // pending data in it.
239 InputStream body
= response
.getBody();
242 byte[] ignore
= new byte[1024];
245 int n
= body
.read(ignore
, 0, ignore
.length
);
252 String location
= response
.getHeader("Location");
253 if (location
!= null)
255 String connectionUri
= connection
.getURI();
256 int start
= connectionUri
.length();
257 if (location
.startsWith(connectionUri
) &&
258 location
.charAt(start
) == '/')
260 file
= location
.substring(start
);
263 else if (location
.startsWith("http:"))
269 int end
= location
.indexOf('/', start
);
271 end
= location
.length();
272 host
= location
.substring(start
, end
);
273 int ci
= host
.lastIndexOf(':');
276 port
= Integer
.parseInt(host
.substring (ci
+ 1));
277 host
= host
.substring(0, ci
);
281 port
= HTTPConnection
.HTTP_PORT
;
283 file
= location
.substring(end
);
286 else if (location
.startsWith("https:"))
292 int end
= location
.indexOf('/', start
);
294 end
= location
.length();
295 host
= location
.substring(start
, end
);
296 int ci
= host
.lastIndexOf(':');
299 port
= Integer
.parseInt(host
.substring (ci
+ 1));
300 host
= host
.substring(0, ci
);
304 port
= HTTPConnection
.HTTPS_PORT
;
306 file
= location
.substring(end
);
309 else if (location
.length() > 0)
311 // Malformed absolute URI, treat as file part of URI
312 if (location
.charAt(0) == '/')
320 int lsi
= file
.lastIndexOf('/');
321 file
= (lsi
== -1) ?
"/" : file
.substring(0, lsi
+ 1);
330 responseSink
= response
.getBody();
332 if (response
.isError())
333 errorSink
= responseSink
;
341 * Returns a connection, from the pool if necessary.
343 HTTPConnection
getConnection(String host
, int port
, boolean secure
)
346 HTTPConnection connection
;
349 connection
= HTTPConnection
.Pool
.instance
.get(host
, port
, secure
, getConnectTimeout(), 0);
353 connection
= new HTTPConnection(host
, port
, secure
, 0, getConnectTimeout());
358 public void disconnect()
360 if (connection
!= null)
366 catch (IOException e
)
372 public boolean usingProxy()
374 return (proxyHostname
!= null);
378 * Overrides the corresponding method in HttpURLConnection to permit
379 * arbitrary methods, as long as they're valid ASCII alphabetic
380 * characters. This is to permit WebDAV and other HTTP extensions to
382 * @param method the method
384 public void setRequestMethod(String method
)
385 throws ProtocolException
389 throw new ProtocolException("Already connected");
392 method
= method
.toUpperCase();
393 int len
= method
.length();
396 throw new ProtocolException("Empty method name");
398 for (int i
= 0; i
< len
; i
++)
400 char c
= method
.charAt(i
);
401 if (c
< 0x41 || c
> 0x5a)
403 throw new ProtocolException("Illegal character '" + c
+
408 this.method
= method
;
409 requestMethodSetExplicitly
= true;
412 public String
getRequestProperty(String key
)
414 return requestHeaders
.getValue(key
);
417 public Map
getRequestProperties()
420 throw new IllegalStateException("Already connected");
422 Map m
= requestHeaders
.getAsMap();
423 return Collections
.unmodifiableMap(m
);
426 public void setRequestProperty(String key
, String value
)
428 super.setRequestProperty(key
, value
);
430 requestHeaders
.put(key
, value
);
433 public void addRequestProperty(String key
, String value
)
435 super.addRequestProperty(key
, value
);
436 requestHeaders
.addValue(key
, value
);
439 public OutputStream
getOutputStream()
444 throw new ProtocolException("Already connected");
448 throw new ProtocolException("doOutput is false");
450 else if (!requestMethodSetExplicitly
)
453 * Silently change the method to POST if no method was set
454 * explicitly. This is due to broken applications depending on this
455 * behaviour (Apache XMLRPC for one).
459 if (requestSink
== null)
461 requestSink
= new ByteArrayOutputStream();
468 public InputStream
getInputStream()
477 throw new ProtocolException("doInput is false");
480 if (response
.isError())
482 int code
= response
.getCode();
483 if (code
== 404 || code
== 410)
484 throw new FileNotFoundException(url
.toString());
486 throw new IOException("Server returned HTTP response code " + code
487 + " for URL " + url
.toString());
493 public InputStream
getErrorStream()
498 public Map
getHeaderFields()
506 catch (IOException e
)
511 Map m
= response
.getHeaders().getAsMap();
512 m
.put(null, Collections
.singletonList(getStatusLine(response
)));
513 return Collections
.unmodifiableMap(m
);
516 String
getStatusLine(Response response
)
518 return "HTTP/" + response
.getMajorVersion() +
519 "." + response
.getMinorVersion() +
520 " " + response
.getCode() +
521 " " + response
.getMessage();
524 public String
getHeaderField(int index
)
532 catch (IOException e
)
539 return getStatusLine(response
);
541 return response
.getHeaders().getHeaderValue(index
- 1);
544 public String
getHeaderFieldKey(int index
)
552 catch (IOException e
)
557 // index of zero is the status line.
558 return response
.getHeaders().getHeaderName(index
- 1);
561 public String
getHeaderField(String name
)
569 catch (IOException e
)
574 return response
.getHeader(name
);
577 public long getHeaderFieldDate(String name
, long def
)
585 catch (IOException e
)
590 Date date
= response
.getDateHeader(name
);
591 return (date
== null) ? def
: date
.getTime();
594 public String
getContentType()
596 return getHeaderField("Content-Type");
599 public int getResponseCode()
606 return response
.getCode();
609 public String
getResponseMessage()
616 return response
.getMessage();
619 // -- HTTPS specific --
621 public String
getCipherSuite()
625 throw new IllegalStateException("not connected");
627 return handshakeEvent
.getCipherSuite();
630 public Certificate
[] getLocalCertificates()
634 throw new IllegalStateException("not connected");
636 return handshakeEvent
.getLocalCertificates();
639 public Certificate
[] getServerCertificates()
640 throws SSLPeerUnverifiedException
644 throw new IllegalStateException("not connected");
646 return handshakeEvent
.getPeerCertificates();
649 // HandshakeCompletedListener
651 public void handshakeCompleted(HandshakeCompletedEvent event
)
653 handshakeEvent
= event
;
657 * Set the connection timeout speed, in milliseconds, or zero if the timeout
658 * is to be considered infinite.
663 public void setConnectTimeout(int timeout
)
664 throws IllegalArgumentException
666 super.setConnectTimeout( timeout
);
667 if( connection
== null )
671 connection
.getSocket().setSoTimeout( timeout
);
673 catch(IOException se
)
675 // Ignore socket exceptions.