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;
53 import java.util.List;
56 import javax.net.ssl.HandshakeCompletedEvent;
57 import javax.net.ssl.HandshakeCompletedListener;
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 // FIXME: use the verifier
174 // HostnameVerifier verifier = getHostnameVerifier();
177 connection.setSSLSocketFactory(factory);
179 connection.addHandshakeCompletedListener(this);
183 if (proxyHostname != null)
187 proxyPort = secure ? HTTPConnection.HTTPS_PORT :
188 HTTPConnection.HTTP_PORT;
190 connection.setProxy(proxyHostname, proxyPort);
194 request = connection.newRequest(method, file);
197 request.setHeader("Connection", "close");
201 request.setHeader("User-Agent", agent);
203 request.getHeaders().putAll(requestHeaders);
204 if (requestSink != null)
206 byte[] content = requestSink.toByteArray();
207 RequestBodyWriter writer = new ByteArrayRequestBodyWriter(content);
208 request.setRequestBodyWriter(writer);
212 request.setAuthenticator(new Authenticator() {
213 public Credentials getCredentials(String realm, int attempts)
215 return (attempts < 2) ? creds : null;
219 response = request.dispatch();
221 catch (IOException ioe)
223 if (connection.useCount > 0)
225 // Connection re-use failed: Try a new connection.
230 catch (IOException _)
240 // First time the connection was used: Hard failure.
245 if (response.isRedirect() && getInstanceFollowRedirects())
247 // Read the response body, if there is one. If the
248 // redirect points us back at the same server, we will use
249 // the cached connection, so we must make sure there is no
250 // pending data in it.
251 InputStream body = response.getBody();
254 byte[] ignore = new byte[1024];
257 int n = body.read(ignore, 0, ignore.length);
264 String location = response.getHeader("Location");
265 if (location != null)
267 String connectionUri = connection.getURI();
268 int start = connectionUri.length();
269 if (location.startsWith(connectionUri) &&
270 location.charAt(start) == '/')
272 file = location.substring(start);
275 else if (location.startsWith("http:"))
281 int end = location.indexOf('/', start);
283 end = location.length();
284 host = location.substring(start, end);
285 int ci = host.lastIndexOf(':');
288 port = Integer.parseInt(host.substring (ci + 1));
289 host = host.substring(0, ci);
293 port = HTTPConnection.HTTP_PORT;
295 file = location.substring(end);
298 else if (location.startsWith("https:"))
304 int end = location.indexOf('/', start);
306 end = location.length();
307 host = location.substring(start, end);
308 int ci = host.lastIndexOf(':');
311 port = Integer.parseInt(host.substring (ci + 1));
312 host = host.substring(0, ci);
316 port = HTTPConnection.HTTPS_PORT;
318 file = location.substring(end);
321 else if (location.length() > 0)
323 // Malformed absolute URI, treat as file part of URI
324 if (location.charAt(0) == '/')
332 int lsi = file.lastIndexOf('/');
333 file = (lsi == -1) ? "/" : file.substring(0, lsi + 1);
342 responseSink = response.getBody();
344 if (response.isError())
345 errorSink = responseSink;
353 * Returns a connection, from the pool if necessary.
355 HTTPConnection getConnection(String host, int port, boolean secure)
358 HTTPConnection connection;
361 connection = HTTPConnection.Pool.instance.get(host, port, secure,
367 connection = new HTTPConnection(host, port, secure,
368 getConnectTimeout(), getReadTimeout());
373 public void disconnect()
375 if (connection != null)
381 catch (IOException e)
387 public boolean usingProxy()
389 return (proxyHostname != null);
393 * Overrides the corresponding method in HttpURLConnection to permit
394 * arbitrary methods, as long as they're valid ASCII alphabetic
395 * characters. This is to permit WebDAV and other HTTP extensions to
397 * @param method the method
399 public void setRequestMethod(String method)
400 throws ProtocolException
404 throw new ProtocolException("Already connected");
407 method = method.toUpperCase();
408 int len = method.length();
411 throw new ProtocolException("Empty method name");
413 for (int i = 0; i < len; i++)
415 char c = method.charAt(i);
416 if (c < 0x41 || c > 0x5a)
418 throw new ProtocolException("Illegal character '" + c +
423 this.method = method;
424 requestMethodSetExplicitly = true;
427 public String getRequestProperty(String key)
429 return requestHeaders.getValue(key);
432 public Map<String, List<String>> getRequestProperties()
435 throw new IllegalStateException("Already connected");
437 Map<String, List<String>> m = requestHeaders.getAsMap();
438 return Collections.unmodifiableMap(m);
441 public void setRequestProperty(String key, String value)
443 super.setRequestProperty(key, value);
445 requestHeaders.put(key, value);
448 public void addRequestProperty(String key, String value)
450 super.addRequestProperty(key, value);
451 requestHeaders.addValue(key, value);
454 public OutputStream getOutputStream()
459 throw new ProtocolException("Already connected");
463 throw new ProtocolException("doOutput is false");
465 else if (!requestMethodSetExplicitly)
468 * Silently change the method to POST if no method was set
469 * explicitly. This is due to broken applications depending on this
470 * behaviour (Apache XMLRPC for one).
474 if (requestSink == null)
476 requestSink = new ByteArrayOutputStream();
483 public InputStream getInputStream()
492 throw new ProtocolException("doInput is false");
495 if (response.isError())
497 int code = response.getCode();
498 if (code == 404 || code == 410)
499 throw new FileNotFoundException(url.toString());
501 throw new IOException("Server returned HTTP response code " + code
502 + " for URL " + url.toString());
508 public InputStream getErrorStream()
513 public Map<String,List<String>> getHeaderFields()
521 catch (IOException e)
526 Map<String,List<String>> m = response.getHeaders().getAsMap();
527 m.put(null, Collections.singletonList(getStatusLine(response)));
528 return Collections.unmodifiableMap(m);
531 String getStatusLine(Response response)
533 return "HTTP/" + response.getMajorVersion() +
534 "." + response.getMinorVersion() +
535 " " + response.getCode() +
536 " " + response.getMessage();
539 public String getHeaderField(int index)
547 catch (IOException e)
554 return getStatusLine(response);
556 return response.getHeaders().getHeaderValue(index - 1);
559 public String getHeaderFieldKey(int index)
567 catch (IOException e)
572 // index of zero is the status line.
573 return response.getHeaders().getHeaderName(index - 1);
576 public String getHeaderField(String name)
584 catch (IOException e)
589 return response.getHeader(name);
592 public long getHeaderFieldDate(String name, long def)
600 catch (IOException e)
605 Date date = response.getDateHeader(name);
606 return (date == null) ? def : date.getTime();
609 public String getContentType()
611 return getHeaderField("Content-Type");
614 public int getResponseCode()
621 return response.getCode();
624 public String getResponseMessage()
631 return response.getMessage();
634 // -- HTTPS specific --
636 public String getCipherSuite()
640 throw new IllegalStateException("not connected");
642 return handshakeEvent.getCipherSuite();
645 public Certificate[] getLocalCertificates()
649 throw new IllegalStateException("not connected");
651 return handshakeEvent.getLocalCertificates();
654 public Certificate[] getServerCertificates()
655 throws SSLPeerUnverifiedException
659 throw new IllegalStateException("not connected");
661 return handshakeEvent.getPeerCertificates();
664 // HandshakeCompletedListener
666 public void handshakeCompleted(HandshakeCompletedEvent event)
668 handshakeEvent = event;
672 * Set the read timeout, in milliseconds, or zero if the timeout
673 * is to be considered infinite.
678 public void setReadTimeout(int timeout)
679 throws IllegalArgumentException
681 super.setReadTimeout(timeout);
682 if (connection == null)
686 connection.getSocket().setSoTimeout(timeout);
688 catch (IOException se)
690 // Ignore socket exceptions.