1 /* HTTPURLConnection.java --
2 Copyright (C) 2004, 2005 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 java
.io
.ByteArrayOutputStream
;
42 import java
.io
.FileNotFoundException
;
43 import java
.io
.IOException
;
44 import java
.io
.InputStream
;
45 import java
.io
.OutputStream
;
46 import java
.net
.ProtocolException
;
48 import java
.security
.AccessController
;
49 import java
.security
.PrivilegedAction
;
50 import java
.security
.cert
.Certificate
;
51 import java
.util
.Collections
;
52 import java
.util
.Date
;
53 import java
.util
.Iterator
;
54 import java
.util
.LinkedHashMap
;
57 import javax
.net
.ssl
.HandshakeCompletedEvent
;
58 import javax
.net
.ssl
.HandshakeCompletedListener
;
59 import javax
.net
.ssl
.HostnameVerifier
;
60 import javax
.net
.ssl
.HttpsURLConnection
;
61 import javax
.net
.ssl
.SSLPeerUnverifiedException
;
62 import javax
.net
.ssl
.SSLSocketFactory
;
65 * A URLConnection that uses the HTTPConnection class.
67 * @author Chris Burdess (dog@gnu.org)
69 public class HTTPURLConnection
70 extends HttpsURLConnection
71 implements HandshakeCompletedListener
75 * Pool of reusable connections, used if keepAlive is true.
77 private static final LinkedHashMap connectionPool
= new LinkedHashMap();
78 static int maxConnections
;
81 * The underlying connection.
83 private HTTPConnection connection
;
85 // These are package private for use in anonymous inner classes.
91 private Request request
;
92 private Headers requestHeaders
;
93 private ByteArrayOutputStream requestSink
;
94 private boolean requestMethodSetExplicitly
;
96 private Response response
;
97 private InputStream responseSink
;
98 private InputStream errorSink
;
100 private HandshakeCompletedEvent handshakeEvent
;
106 public HTTPURLConnection(URL url
)
110 requestHeaders
= new Headers();
111 AccessController
.doPrivileged(this.new GetHTTPPropertiesAction());
114 class GetHTTPPropertiesAction
115 implements PrivilegedAction
120 proxyHostname
= System
.getProperty("http.proxyHost");
121 if (proxyHostname
!= null && proxyHostname
.length() > 0)
123 String port
= System
.getProperty("http.proxyPort");
124 if (port
!= null && port
.length() > 0)
126 proxyPort
= Integer
.parseInt(port
);
130 proxyHostname
= null;
134 agent
= System
.getProperty("http.agent");
135 String ka
= System
.getProperty("http.keepAlive");
136 keepAlive
= !(ka
!= null && "false".equals(ka
));
137 String mc
= System
.getProperty("http.maxConnections");
138 maxConnections
= (mc
!= null && mc
.length() > 0) ?
139 Math
.max(Integer
.parseInt(mc
), 1) : 5;
145 public void connect()
152 String protocol
= url
.getProtocol();
153 boolean secure
= "https".equals(protocol
);
154 String host
= url
.getHost();
155 int port
= url
.getPort();
158 port
= secure ? HTTPConnection
.HTTPS_PORT
:
159 HTTPConnection
.HTTP_PORT
;
161 String file
= url
.getFile();
162 String username
= url
.getUserInfo();
163 String password
= null;
164 if (username
!= null)
166 int ci
= username
.indexOf(':');
169 password
= username
.substring(ci
+ 1);
170 username
= username
.substring(0, ci
);
173 final Credentials creds
= (username
== null) ?
null :
174 new Credentials (username
, password
);
180 if (connection
== null)
182 connection
= getConnection(host
, port
, secure
);
185 SSLSocketFactory factory
= getSSLSocketFactory();
186 HostnameVerifier verifier
= getHostnameVerifier();
189 connection
.setSSLSocketFactory(factory
);
191 connection
.addHandshakeCompletedListener(this);
195 if (proxyHostname
!= null)
199 proxyPort
= secure ? HTTPConnection
.HTTPS_PORT
:
200 HTTPConnection
.HTTP_PORT
;
202 connection
.setProxy(proxyHostname
, proxyPort
);
206 request
= connection
.newRequest(method
, file
);
209 request
.setHeader("Connection", "close");
213 request
.setHeader("User-Agent", agent
);
215 request
.getHeaders().putAll(requestHeaders
);
216 if (requestSink
!= null)
218 byte[] content
= requestSink
.toByteArray();
219 RequestBodyWriter writer
= new ByteArrayRequestBodyWriter(content
);
220 request
.setRequestBodyWriter(writer
);
224 request
.setAuthenticator(new Authenticator() {
225 public Credentials
getCredentials(String realm
, int attempts
)
227 return (attempts
< 2) ? creds
: null;
231 response
= request
.dispatch();
233 catch (IOException ioe
)
235 if (connection
.useCount
> 0)
237 // Connection re-use failed: Try a new connection.
242 catch (IOException _
)
252 // First time the connection was used: Hard failure.
257 if (response
.getCodeClass() == 3 && getInstanceFollowRedirects())
260 String location
= response
.getHeader("Location");
261 if (location
!= null)
263 String connectionUri
= connection
.getURI();
264 int start
= connectionUri
.length();
265 if (location
.startsWith(connectionUri
) &&
266 location
.charAt(start
) == '/')
268 file
= location
.substring(start
);
271 else if (location
.startsWith("http:"))
277 int end
= location
.indexOf('/', start
);
278 host
= location
.substring(start
, end
);
279 int ci
= host
.lastIndexOf(':');
282 port
= Integer
.parseInt(host
.substring (ci
+ 1));
283 host
= host
.substring(0, ci
);
287 port
= HTTPConnection
.HTTP_PORT
;
289 file
= location
.substring(end
);
292 else if (location
.startsWith("https:"))
298 int end
= location
.indexOf('/', start
);
299 host
= location
.substring(start
, end
);
300 int ci
= host
.lastIndexOf(':');
303 port
= Integer
.parseInt(host
.substring (ci
+ 1));
304 host
= host
.substring(0, ci
);
308 port
= HTTPConnection
.HTTPS_PORT
;
310 file
= location
.substring(end
);
313 else if (location
.length() > 0)
315 // Malformed absolute URI, treat as file part of URI
316 if (location
.charAt(0) == '/')
324 int lsi
= file
.lastIndexOf('/');
325 file
= (lsi
== -1) ?
"/" : file
.substring(0, lsi
+ 1);
334 responseSink
= response
.getBody();
336 if (response
.getCode() == 404)
338 errorSink
= responseSink
;
339 throw new FileNotFoundException(url
.toString());
348 * Returns a connection, from the pool if necessary.
350 HTTPConnection
getConnection(String host
, int port
, boolean secure
)
353 HTTPConnection connection
;
356 Object key
= HTTPConnection
.getPoolKey(host
, port
, secure
);
357 synchronized (connectionPool
)
359 connection
= (HTTPConnection
) connectionPool
.remove(key
);
360 if (connection
== null)
362 connection
= new HTTPConnection(host
, port
, secure
);
363 connection
.setPool(connectionPool
);
369 connection
= new HTTPConnection(host
, port
, secure
);
374 public void disconnect()
376 if (connection
!= null)
382 catch (IOException e
)
388 public boolean usingProxy()
390 return (proxyHostname
!= null);
394 * Overrides the corresponding method in HttpURLConnection to permit
395 * arbitrary methods, as long as they're valid ASCII alphabetic
396 * characters. This is to permit WebDAV and other HTTP extensions to
398 * @param method the method
400 public void setRequestMethod(String method
)
401 throws ProtocolException
405 throw new ProtocolException("Already connected");
408 method
= method
.toUpperCase();
409 int len
= method
.length();
412 throw new ProtocolException("Empty method name");
414 for (int i
= 0; i
< len
; i
++)
416 char c
= method
.charAt(i
);
417 if (c
< 0x41 || c
> 0x5a)
419 throw new ProtocolException("Illegal character '" + c
+
424 this.method
= method
;
425 requestMethodSetExplicitly
= true;
428 public String
getRequestProperty(String key
)
430 return requestHeaders
.getValue(key
);
433 public Map
getRequestProperties()
435 return requestHeaders
;
438 public void setRequestProperty(String key
, String value
)
440 requestHeaders
.put(key
, value
);
443 public void addRequestProperty(String key
, String value
)
445 String old
= requestHeaders
.getValue(key
);
448 requestHeaders
.put(key
, value
);
452 requestHeaders
.put(key
, old
+ "," + value
);
456 public OutputStream
getOutputStream()
461 throw new ProtocolException("Already connected");
465 throw new ProtocolException("doOutput is false");
467 else if (!requestMethodSetExplicitly
)
470 * Silently change the method to POST if no method was set
471 * explicitly. This is due to broken applications depending on this
472 * behaviour (Apache XMLRPC for one).
476 if (requestSink
== null)
478 requestSink
= new ByteArrayOutputStream();
485 public InputStream
getInputStream()
494 throw new ProtocolException("doInput is false");
499 public InputStream
getErrorStream()
504 public Map
getHeaderFields()
512 catch (IOException e
)
517 Headers headers
= response
.getHeaders();
518 LinkedHashMap ret
= new LinkedHashMap();
519 ret
.put(null, Collections
.singletonList(getStatusLine(response
)));
520 for (Iterator i
= headers
.entrySet().iterator(); i
.hasNext(); )
522 Map
.Entry entry
= (Map
.Entry
) i
.next();
523 String key
= (String
) entry
.getKey();
524 String value
= (String
) entry
.getValue();
525 ret
.put(key
, Collections
.singletonList(value
));
527 return Collections
.unmodifiableMap(ret
);
530 String
getStatusLine(Response response
)
532 return "HTTP/" + response
.getMajorVersion() +
533 "." + response
.getMinorVersion() +
534 " " + response
.getCode() +
535 " " + response
.getMessage();
538 public String
getHeaderField(int index
)
546 catch (IOException e
)
553 return getStatusLine(response
);
555 Iterator i
= response
.getHeaders().entrySet().iterator();
564 entry
= (Map
.Entry
) i
.next();
567 while (count
<= index
);
568 return (String
) entry
.getValue();
571 public String
getHeaderFieldKey(int index
)
579 catch (IOException e
)
588 Iterator i
= response
.getHeaders().entrySet().iterator();
597 entry
= (Map
.Entry
) i
.next();
600 while (count
<= index
);
601 return (String
) entry
.getKey();
604 public String
getHeaderField(String name
)
612 catch (IOException e
)
617 return (String
) response
.getHeader(name
);
620 public long getHeaderFieldDate(String name
, long def
)
628 catch (IOException e
)
633 Date date
= response
.getDateHeader(name
);
634 return (date
== null) ? def
: date
.getTime();
637 public String
getContentType()
639 return getHeaderField("Content-Type");
642 public int getResponseCode()
649 return response
.getCode();
652 public String
getResponseMessage()
659 return response
.getMessage();
662 // -- HTTPS specific --
664 public String
getCipherSuite()
668 throw new IllegalStateException("not connected");
670 return handshakeEvent
.getCipherSuite();
673 public Certificate
[] getLocalCertificates()
677 throw new IllegalStateException("not connected");
679 return handshakeEvent
.getLocalCertificates();
682 public Certificate
[] getServerCertificates()
683 throws SSLPeerUnverifiedException
687 throw new IllegalStateException("not connected");
689 return handshakeEvent
.getPeerCertificates();
692 // HandshakeCompletedListener
694 public void handshakeCompleted(HandshakeCompletedEvent event
)
696 handshakeEvent
= event
;