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 String proxy = SystemProperties.getProperty("http.proxyHost");
103 if (proxy != null && proxy.length() > 0)
105 String port = SystemProperties.getProperty("http.proxyPort");
106 if (port != null && port.length() > 0)
110 proxyPort = Integer.parseInt(port);
111 proxyHostname = proxy;
113 catch (NumberFormatException _)
119 agent = SystemProperties.getProperty("http.agent");
120 String ka = SystemProperties.getProperty("http.keepAlive");
121 keepAlive = !(ka != null && "false".equals(ka));
124 public void connect()
131 String protocol = url.getProtocol();
132 boolean secure = "https".equals(protocol);
133 String host = url.getHost();
134 int port = url.getPort();
137 port = secure ? HTTPConnection.HTTPS_PORT :
138 HTTPConnection.HTTP_PORT;
140 String file = url.getFile();
141 String username = url.getUserInfo();
142 String password = null;
143 if (username != null)
145 int ci = username.indexOf(':');
148 password = username.substring(ci + 1);
149 username = username.substring(0, ci);
152 final Credentials creds = (username == null) ? null :
153 new Credentials (username, password);
155 if ("POST".equals(method))
157 String contentType = requestHeaders.getValue("Content-Type");
158 if (null == contentType)
159 requestHeaders.addValue("Content-Type",
160 "application/x-www-form-urlencoded");
167 if (connection == null)
169 connection = getConnection(host, port, secure);
172 SSLSocketFactory factory = getSSLSocketFactory();
173 HostnameVerifier verifier = getHostnameVerifier();
176 connection.setSSLSocketFactory(factory);
178 connection.addHandshakeCompletedListener(this);
182 if (proxyHostname != null)
186 proxyPort = secure ? HTTPConnection.HTTPS_PORT :
187 HTTPConnection.HTTP_PORT;
189 connection.setProxy(proxyHostname, proxyPort);
193 request = connection.newRequest(method, file);
196 request.setHeader("Connection", "close");
200 request.setHeader("User-Agent", agent);
202 request.getHeaders().putAll(requestHeaders);
203 if (requestSink != null)
205 byte[] content = requestSink.toByteArray();
206 RequestBodyWriter writer = new ByteArrayRequestBodyWriter(content);
207 request.setRequestBodyWriter(writer);
211 request.setAuthenticator(new Authenticator() {
212 public Credentials getCredentials(String realm, int attempts)
214 return (attempts < 2) ? creds : null;
218 response = request.dispatch();
220 catch (IOException ioe)
222 if (connection.useCount > 0)
224 // Connection re-use failed: Try a new connection.
229 catch (IOException _)
239 // First time the connection was used: Hard failure.
244 if (response.isRedirect() && getInstanceFollowRedirects())
246 // Read the response body, if there is one. If the
247 // redirect points us back at the same server, we will use
248 // the cached connection, so we must make sure there is no
249 // pending data in it.
250 InputStream body = response.getBody();
253 byte[] ignore = new byte[1024];
256 int n = body.read(ignore, 0, ignore.length);
263 String location = response.getHeader("Location");
264 if (location != null)
266 String connectionUri = connection.getURI();
267 int start = connectionUri.length();
268 if (location.startsWith(connectionUri) &&
269 location.charAt(start) == '/')
271 file = location.substring(start);
274 else if (location.startsWith("http:"))
280 int end = location.indexOf('/', start);
282 end = location.length();
283 host = location.substring(start, end);
284 int ci = host.lastIndexOf(':');
287 port = Integer.parseInt(host.substring (ci + 1));
288 host = host.substring(0, ci);
292 port = HTTPConnection.HTTP_PORT;
294 file = location.substring(end);
297 else if (location.startsWith("https:"))
303 int end = location.indexOf('/', start);
305 end = location.length();
306 host = location.substring(start, end);
307 int ci = host.lastIndexOf(':');
310 port = Integer.parseInt(host.substring (ci + 1));
311 host = host.substring(0, ci);
315 port = HTTPConnection.HTTPS_PORT;
317 file = location.substring(end);
320 else if (location.length() > 0)
322 // Malformed absolute URI, treat as file part of URI
323 if (location.charAt(0) == '/')
331 int lsi = file.lastIndexOf('/');
332 file = (lsi == -1) ? "/" : file.substring(0, lsi + 1);
341 responseSink = response.getBody();
343 if (response.isError())
344 errorSink = responseSink;
352 * Returns a connection, from the pool if necessary.
354 HTTPConnection getConnection(String host, int port, boolean secure)
357 HTTPConnection connection;
360 connection = HTTPConnection.Pool.instance.get(host, port, secure,
366 connection = new HTTPConnection(host, port, secure,
367 getConnectTimeout(), getReadTimeout());
372 public void disconnect()
374 if (connection != null)
380 catch (IOException e)
386 public boolean usingProxy()
388 return (proxyHostname != null);
392 * Overrides the corresponding method in HttpURLConnection to permit
393 * arbitrary methods, as long as they're valid ASCII alphabetic
394 * characters. This is to permit WebDAV and other HTTP extensions to
396 * @param method the method
398 public void setRequestMethod(String method)
399 throws ProtocolException
403 throw new ProtocolException("Already connected");
406 method = method.toUpperCase();
407 int len = method.length();
410 throw new ProtocolException("Empty method name");
412 for (int i = 0; i < len; i++)
414 char c = method.charAt(i);
415 if (c < 0x41 || c > 0x5a)
417 throw new ProtocolException("Illegal character '" + c +
422 this.method = method;
423 requestMethodSetExplicitly = true;
426 public String getRequestProperty(String key)
428 return requestHeaders.getValue(key);
431 public Map getRequestProperties()
434 throw new IllegalStateException("Already connected");
436 Map m = requestHeaders.getAsMap();
437 return Collections.unmodifiableMap(m);
440 public void setRequestProperty(String key, String value)
442 super.setRequestProperty(key, value);
444 requestHeaders.put(key, value);
447 public void addRequestProperty(String key, String value)
449 super.addRequestProperty(key, value);
450 requestHeaders.addValue(key, value);
453 public OutputStream getOutputStream()
458 throw new ProtocolException("Already connected");
462 throw new ProtocolException("doOutput is false");
464 else if (!requestMethodSetExplicitly)
467 * Silently change the method to POST if no method was set
468 * explicitly. This is due to broken applications depending on this
469 * behaviour (Apache XMLRPC for one).
473 if (requestSink == null)
475 requestSink = new ByteArrayOutputStream();
482 public InputStream getInputStream()
491 throw new ProtocolException("doInput is false");
494 if (response.isError())
496 int code = response.getCode();
497 if (code == 404 || code == 410)
498 throw new FileNotFoundException(url.toString());
500 throw new IOException("Server returned HTTP response code " + code
501 + " for URL " + url.toString());
507 public InputStream getErrorStream()
512 public Map getHeaderFields()
520 catch (IOException e)
525 Map m = response.getHeaders().getAsMap();
526 m.put(null, Collections.singletonList(getStatusLine(response)));
527 return Collections.unmodifiableMap(m);
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 return response.getHeaders().getHeaderValue(index - 1);
558 public String getHeaderFieldKey(int index)
566 catch (IOException e)
571 // index of zero is the status line.
572 return response.getHeaders().getHeaderName(index - 1);
575 public String getHeaderField(String name)
583 catch (IOException e)
588 return response.getHeader(name);
591 public long getHeaderFieldDate(String name, long def)
599 catch (IOException e)
604 Date date = response.getDateHeader(name);
605 return (date == null) ? def : date.getTime();
608 public String getContentType()
610 return getHeaderField("Content-Type");
613 public int getResponseCode()
620 return response.getCode();
623 public String getResponseMessage()
630 return response.getMessage();
633 // -- HTTPS specific --
635 public String getCipherSuite()
639 throw new IllegalStateException("not connected");
641 return handshakeEvent.getCipherSuite();
644 public Certificate[] getLocalCertificates()
648 throw new IllegalStateException("not connected");
650 return handshakeEvent.getLocalCertificates();
653 public Certificate[] getServerCertificates()
654 throws SSLPeerUnverifiedException
658 throw new IllegalStateException("not connected");
660 return handshakeEvent.getPeerCertificates();
663 // HandshakeCompletedListener
665 public void handshakeCompleted(HandshakeCompletedEvent event)
667 handshakeEvent = event;
671 * Set the read timeout, in milliseconds, or zero if the timeout
672 * is to be considered infinite.
677 public void setReadTimeout(int timeout)
678 throws IllegalArgumentException
680 super.setReadTimeout(timeout);
681 if (connection == null)
685 connection.getSocket().setSoTimeout(timeout);
687 catch (IOException se)
689 // Ignore socket exceptions.