/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.net; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import libcore.net.http.HttpEngine; /** * An {@link URLConnection} for HTTP (RFC 2616) used to send and * receive data over the web. Data may be of any type and length. This class may * be used to send and receive streaming data whose length is not known in * advance. * *

Uses of this class follow a pattern: *

    *
  1. Obtain a new {@code HttpURLConnection} by calling {@link * URL#openConnection() URL.openConnection()} and casting the result to * {@code HttpURLConnection}. *
  2. Prepare the request. The primary property of a request is its URI. * Request headers may also include metadata such as credentials, preferred * content types, and session cookies. *
  3. Optionally upload a request body. Instances must be configured with * {@link #setDoOutput(boolean) setDoOutput(true)} if they include a * request body. Transmit data by writing to the stream returned by {@link * #getOutputStream()}. *
  4. Read the response. Response headers typically include metadata such as * the response body's content type and length, modified dates and session * cookies. The response body may be read from the stream returned by {@link * #getInputStream()}. If the response has no body, that method returns an * empty stream. *
  5. Disconnect. Once the response body has been read, the {@code * HttpURLConnection} should be closed by calling {@link #disconnect()}. * Disconnecting releases the resources held by a connection so they may * be closed or reused. *
* *

For example, to retrieve the webpage at {@code http://www.android.com/}: *

   {@code
 *   URL url = new URL("http://www.android.com/");
 *   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
 *   try {
 *     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
 *     readStream(in);
 *   } finally {
 *     urlConnection.disconnect();
 *   }
 * }
* *

Secure Communication with HTTPS

* Calling {@link URL#openConnection()} on a URL with the "https" * scheme will return an {@code HttpsURLConnection}, which allows for * overriding the default {@link javax.net.ssl.HostnameVerifier * HostnameVerifier} and {@link javax.net.ssl.SSLSocketFactory * SSLSocketFactory}. An application-supplied {@code SSLSocketFactory} * created from an {@link javax.net.ssl.SSLContext SSLContext} can * provide a custom {@link javax.net.ssl.X509TrustManager * X509TrustManager} for verifying certificate chains and a custom * {@link javax.net.ssl.X509KeyManager X509KeyManager} for supplying * client certificates. See {@link javax.net.ssl.HttpsURLConnection * HttpsURLConnection} for more details. * *

Response Handling

* {@code HttpURLConnection} will follow up to five HTTP redirects. It will * follow redirects from one origin server to another. This implementation * doesn't follow redirects from HTTPS to HTTP or vice versa. * *

If the HTTP response indicates that an error occurred, {@link * #getInputStream()} will throw an {@link IOException}. Use {@link * #getErrorStream()} to read the error response. The headers can be read in * the normal way using {@link #getHeaderFields()}, * *

Posting Content

* To upload data to a web server, configure the connection for output using * {@link #setDoOutput(boolean) setDoOutput(true)}. * *

For best performance, you should call either {@link * #setFixedLengthStreamingMode(int)} when the body length is known in advance, * or {@link #setChunkedStreamingMode(int)} when it is not. Otherwise {@code * HttpURLConnection} will be forced to buffer the complete request body in * memory before it is transmitted, wasting (and possibly exhausting) heap and * increasing latency. * *

For example, to perform an upload:

   {@code
 *   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
 *   try {
 *     urlConnection.setDoOutput(true);
 *     urlConnection.setChunkedStreamingMode(0);
 *
 *     OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
 *     writeStream(out);
 *
 *     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
 *     readStream(in);
 *   } finally {
 *     urlConnection.disconnect();
 *   }
 * }
* *

Performance

* The input and output streams returned by this class are not * buffered. Most callers should wrap the returned streams with {@link * java.io.BufferedInputStream BufferedInputStream} or {@link * java.io.BufferedOutputStream BufferedOutputStream}. Callers that do only bulk * reads or writes may omit buffering. * *

When transferring large amounts of data to or from a server, use streams * to limit how much data is in memory at once. Unless you need the entire * body to be in memory at once, process it as a stream (rather than storing * the complete body as a single byte array or string). * *

To reduce latency, this class may reuse the same underlying {@code Socket} * for multiple request/response pairs. As a result, HTTP connections may be * held open longer than necessary. Calls to {@link #disconnect()} may return * the socket to a pool of connected sockets. This behavior can be disabled by * setting the "http.keepAlive" system property to "false" before issuing any * HTTP requests. The "http.maxConnections" property may be used to control how * many idle connections to each server will be held. * *

By default, this implementation of {@code HttpURLConnection} requests that * servers use gzip compression. Since {@link #getContentLength()} returns the * number of bytes transmitted, you cannot use that method to predict how many * bytes can be read from {@link #getInputStream()}. Instead, read that stream * until it is exhausted: when {@link InputStream#read} returns -1. Gzip * compression can be disabled by setting the acceptable encodings in the * request header:

   {@code
 *   urlConnection.setRequestProperty("Accept-Encoding", "identity");
 * }
* *

Handling Network Sign-On

* Some Wi-Fi networks block Internet access until the user clicks through a * sign-on page. Such sign-on pages are typically presented by using HTTP * redirects. You can use {@link #getURL()} to test if your connection has been * unexpectedly redirected. This check is not valid until after * the response headers have been received, which you can trigger by calling * {@link #getHeaderFields()} or {@link #getInputStream()}. For example, to * check that a response was not redirected to an unexpected host: *
   {@code
 *   HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
 *   try {
 *     InputStream in = new BufferedInputStream(urlConnection.getInputStream());
 *     if (!url.getHost().equals(urlConnection.getURL().getHost())) {
 *       // we were redirected! Kick the user out to the browser to sign on?
 *     }
 *     ...
 *   } finally {
 *     urlConnection.disconnect();
 *   }
 * }
* *

HTTP Authentication

* {@code HttpURLConnection} supports HTTP basic authentication. Use * {@link Authenticator} to set the VM-wide authentication handler: *
   {@code
 *   Authenticator.setDefault(new Authenticator() {
 *     protected PasswordAuthentication getPasswordAuthentication() {
 *       return new PasswordAuthentication(username, password.toCharArray());
 *     }
 *   });
 * }
* Unless paired with HTTPS, this is not a secure mechanism for * user authentication. In particular, the username, password, request and * response are all transmitted over the network without encryption. * *

Sessions with Cookies

* To establish and maintain a potentially long-lived session between client * and server, {@code HttpURLConnection} includes an extensible cookie manager. * Enable VM-wide cookie management using {@link CookieHandler} and {@link * CookieManager}:
   {@code
 *   CookieManager cookieManager = new CookieManager();
 *   CookieHandler.setDefault(cookieManager);
 * }
* By default, {@code CookieManager} accepts cookies from the origin * server only. Two other policies are included: {@link * CookiePolicy#ACCEPT_ALL} and {@link CookiePolicy#ACCEPT_NONE}. Implement * {@link CookiePolicy} to define a custom policy. * *

The default {@code CookieManager} keeps all accepted cookies in memory. It * will forget these cookies when the VM exits. Implement {@link CookieStore} to * define a custom cookie store. * *

In addition to the cookies set by HTTP responses, you may set cookies * programmatically. To be included in HTTP request headers, cookies must have * the domain and path properties set. * *

By default, new instances of {@code HttpCookie} work only with servers * that support RFC 2965 * cookies. Many web servers support only the older specification, RFC 2109. For compatibility * with the most web servers, set the cookie version to 0. * *

For example, to receive {@code www.twitter.com} in French:

   {@code
 *   HttpCookie cookie = new HttpCookie("lang", "fr");
 *   cookie.setDomain("twitter.com");
 *   cookie.setPath("/");
 *   cookie.setVersion(0);
 *   cookieManager.getCookieStore().add(new URI("http://twitter.com/"), cookie);
 * }
* *

HTTP Methods

*

{@code HttpURLConnection} uses the {@code GET} method by default. It will * use {@code POST} if {@link #setDoOutput setDoOutput(true)} has been called. * Other HTTP methods ({@code OPTIONS}, {@code HEAD}, {@code PUT}, {@code * DELETE} and {@code TRACE}) can be used with {@link #setRequestMethod}. * *

Proxies

* By default, this class will connect directly to the origin * server. It can also connect via an {@link Proxy.Type#HTTP HTTP} or {@link * Proxy.Type#SOCKS SOCKS} proxy. To use a proxy, use {@link * URL#openConnection(Proxy) URL.openConnection(Proxy)} when creating the * connection. * *

IPv6 Support

*

This class includes transparent support for IPv6. For hosts with both IPv4 * and IPv6 addresses, it will attempt to connect to each of a host's addresses * until a connection is established. * *

Response Caching

*

{@code HttpURLConnection} supports a VM-wide HTTP response cache. * Implement {@link ResponseCache} and use {@link ResponseCache#setDefault} to * install a custom cache. Implementing this API is onerous: correct * implementations should follow all caching rules defined by Section 13 of RFC * 2616. * *

Each instance of {@code HttpURLConnection} may be used for one * request/response pair. Instances of this class are not thread safe. */ public abstract class HttpURLConnection extends URLConnection { /** * The subset of HTTP methods that the user may select via {@link * #setRequestMethod(String)}. */ private static final String[] PERMITTED_USER_METHODS = { HttpEngine.OPTIONS, HttpEngine.GET, HttpEngine.HEAD, HttpEngine.POST, HttpEngine.PUT, HttpEngine.DELETE, HttpEngine.TRACE // Note: we don't allow users to specify "CONNECT" }; /** * The HTTP request method of this {@code HttpURLConnection}. The default * value is {@code "GET"}. */ protected String method = HttpEngine.GET; /** * The status code of the response obtained from the HTTP request. The * default value is {@code -1}. *

*

  • 1xx: Informational
  • *
  • 2xx: Success
  • *
  • 3xx: Relocation/Redirection
  • *
  • 4xx: Client Error
  • *
  • 5xx: Server Error
  • */ protected int responseCode = -1; /** * The HTTP response message which corresponds to the response code. */ protected String responseMessage; /** * Flag to define whether the protocol will automatically follow redirects * or not. The default value is {@code true}. */ protected boolean instanceFollowRedirects = followRedirects; private static boolean followRedirects = true; /** * If the HTTP chunked encoding is enabled this parameter defines the * chunk-length. Default value is {@code -1} that means the chunked encoding * mode is disabled. */ protected int chunkLength = -1; /** * If using HTTP fixed-length streaming mode this parameter defines the * fixed length of content. Default value is {@code -1} that means the * fixed-length streaming mode is disabled. */ protected int fixedContentLength = -1; // 2XX: generally "OK" // 3XX: relocation/redirect // 4XX: client error // 5XX: server error /** * Numeric status code, 202: Accepted */ public static final int HTTP_ACCEPTED = 202; /** * Numeric status code, 502: Bad Gateway */ public static final int HTTP_BAD_GATEWAY = 502; /** * Numeric status code, 405: Bad Method */ public static final int HTTP_BAD_METHOD = 405; /** * Numeric status code, 400: Bad Request */ public static final int HTTP_BAD_REQUEST = 400; /** * Numeric status code, 408: Client Timeout */ public static final int HTTP_CLIENT_TIMEOUT = 408; /** * Numeric status code, 409: Conflict */ public static final int HTTP_CONFLICT = 409; /** * Numeric status code, 201: Created */ public static final int HTTP_CREATED = 201; /** * Numeric status code, 413: Entity too large */ public static final int HTTP_ENTITY_TOO_LARGE = 413; /** * Numeric status code, 403: Forbidden */ public static final int HTTP_FORBIDDEN = 403; /** * Numeric status code, 504: Gateway timeout */ public static final int HTTP_GATEWAY_TIMEOUT = 504; /** * Numeric status code, 410: Gone */ public static final int HTTP_GONE = 410; /** * Numeric status code, 500: Internal error */ public static final int HTTP_INTERNAL_ERROR = 500; /** * Numeric status code, 411: Length required */ public static final int HTTP_LENGTH_REQUIRED = 411; /** * Numeric status code, 301 Moved permanently */ public static final int HTTP_MOVED_PERM = 301; /** * Numeric status code, 302: Moved temporarily */ public static final int HTTP_MOVED_TEMP = 302; /** * Numeric status code, 300: Multiple choices */ public static final int HTTP_MULT_CHOICE = 300; /** * Numeric status code, 204: No content */ public static final int HTTP_NO_CONTENT = 204; /** * Numeric status code, 406: Not acceptable */ public static final int HTTP_NOT_ACCEPTABLE = 406; /** * Numeric status code, 203: Not authoritative */ public static final int HTTP_NOT_AUTHORITATIVE = 203; /** * Numeric status code, 404: Not found */ public static final int HTTP_NOT_FOUND = 404; /** * Numeric status code, 501: Not implemented */ public static final int HTTP_NOT_IMPLEMENTED = 501; /** * Numeric status code, 304: Not modified */ public static final int HTTP_NOT_MODIFIED = 304; /** * Numeric status code, 200: OK */ public static final int HTTP_OK = 200; /** * Numeric status code, 206: Partial */ public static final int HTTP_PARTIAL = 206; /** * Numeric status code, 402: Payment required */ public static final int HTTP_PAYMENT_REQUIRED = 402; /** * Numeric status code, 412: Precondition failed */ public static final int HTTP_PRECON_FAILED = 412; /** * Numeric status code, 407: Proxy authentication required */ public static final int HTTP_PROXY_AUTH = 407; /** * Numeric status code, 414: Request too long */ public static final int HTTP_REQ_TOO_LONG = 414; /** * Numeric status code, 205: Reset */ public static final int HTTP_RESET = 205; /** * Numeric status code, 303: See other */ public static final int HTTP_SEE_OTHER = 303; /** * Numeric status code, 500: Internal error * * @deprecated Use {@link #HTTP_INTERNAL_ERROR} */ @Deprecated public static final int HTTP_SERVER_ERROR = 500; /** * Numeric status code, 305: Use proxy. * *

    Like Firefox and Chrome, this class doesn't honor this response code. * Other implementations respond to this status code by retrying the request * using the HTTP proxy named by the response's Location header field. */ public static final int HTTP_USE_PROXY = 305; /** * Numeric status code, 401: Unauthorized */ public static final int HTTP_UNAUTHORIZED = 401; /** * Numeric status code, 415: Unsupported type */ public static final int HTTP_UNSUPPORTED_TYPE = 415; /** * Numeric status code, 503: Unavailable */ public static final int HTTP_UNAVAILABLE = 503; /** * Numeric status code, 505: Version not supported */ public static final int HTTP_VERSION = 505; /** * Constructs a new {@code HttpURLConnection} instance pointing to the * resource specified by the {@code url}. * * @param url * the URL of this connection. * @see URL * @see URLConnection */ protected HttpURLConnection(URL url) { super(url); } /** * Closes the connection to the HTTP server. * * @see URLConnection#connect() * @see URLConnection#connected */ public abstract void disconnect(); /** * Returns an input stream from the server in the case of an error such as * the requested file has not been found on the remote server. This stream * can be used to read the data the server will send back. * * @return the error input stream returned by the server. */ public InputStream getErrorStream() { return null; } /** * Returns the value of {@code followRedirects} which indicates if this * connection follows a different URL redirected by the server. It is * enabled by default. * * @return the value of the flag. * @see #setFollowRedirects */ public static boolean getFollowRedirects() { return followRedirects; } /** * Returns the permission object (in this case {@code SocketPermission}) * with the host and the port number as the target name and {@code * "resolve, connect"} as the action list. If the port number of this URL * instance is lower than {@code 0} the port will be set to {@code 80}. * * @return the permission object required for this connection. * @throws IOException * if an IO exception occurs during the creation of the * permission object. */ @Override public java.security.Permission getPermission() throws IOException { int port = url.getPort(); if (port < 0) { port = 80; } return new SocketPermission(url.getHost() + ":" + port, "connect, resolve"); } /** * Returns the request method which will be used to make the request to the * remote HTTP server. All possible methods of this HTTP implementation is * listed in the class definition. * * @return the request method string. * @see #method * @see #setRequestMethod */ public String getRequestMethod() { return method; } /** * Returns the response code returned by the remote HTTP server. * * @return the response code, -1 if no valid response code. * @throws IOException * if there is an IO error during the retrieval. * @see #getResponseMessage */ public int getResponseCode() throws IOException { // Call getInputStream() first since getHeaderField() doesn't return // exceptions getInputStream(); String response = getHeaderField(0); if (response == null) { return -1; } response = response.trim(); int mark = response.indexOf(" ") + 1; if (mark == 0) { return -1; } int last = mark + 3; if (last > response.length()) { last = response.length(); } responseCode = Integer.parseInt(response.substring(mark, last)); if (last + 1 <= response.length()) { responseMessage = response.substring(last + 1); } return responseCode; } /** * Returns the response message returned by the remote HTTP server. * * @return the response message. {@code null} if no such response exists. * @throws IOException * if there is an error during the retrieval. * @see #getResponseCode() */ public String getResponseMessage() throws IOException { if (responseMessage != null) { return responseMessage; } getResponseCode(); return responseMessage; } /** * Sets the flag of whether this connection will follow redirects returned * by the remote server. * * @param auto * the value to enable or disable this option. */ public static void setFollowRedirects(boolean auto) { followRedirects = auto; } /** * Sets the request command which will be sent to the remote HTTP server. * This method can only be called before the connection is made. * * @param method * the string representing the method to be used. * @throws ProtocolException * if this is called after connected, or the method is not * supported by this HTTP implementation. * @see #getRequestMethod() * @see #method */ public void setRequestMethod(String method) throws ProtocolException { if (connected) { throw new ProtocolException("Connection already established"); } for (String permittedUserMethod : PERMITTED_USER_METHODS) { if (permittedUserMethod.equals(method)) { // if there is a supported method that matches the desired // method, then set the current method and return this.method = permittedUserMethod; return; } } // if none matches, then throw ProtocolException throw new ProtocolException("Unknown method '" + method + "'; must be one of " + Arrays.toString(PERMITTED_USER_METHODS)); } /** * Returns whether this connection uses a proxy server or not. * * @return {@code true} if this connection passes a proxy server, false * otherwise. */ public abstract boolean usingProxy(); /** * Returns the encoding used to transmit the response body over the network. * This is null or "identity" if the content was not encoded, or "gzip" if * the body was gzip compressed. Most callers will be more interested in the * {@link #getContentType() content type}, which may also include the * content's character encoding. */ @Override public String getContentEncoding() { return super.getContentEncoding(); // overridden for Javadoc only } /** * Returns whether this connection follows redirects. * * @return {@code true} if this connection follows redirects, false * otherwise. */ public boolean getInstanceFollowRedirects() { return instanceFollowRedirects; } /** * Sets whether this connection follows redirects. * * @param followRedirects * {@code true} if this connection will follows redirects, false * otherwise. */ public void setInstanceFollowRedirects(boolean followRedirects) { instanceFollowRedirects = followRedirects; } /** * Returns the date value in milliseconds since {@code 01.01.1970, 00:00h} * corresponding to the header field {@code field}. The {@code defaultValue} * will be returned if no such field can be found in the response header. * * @param field * the header field name. * @param defaultValue * the default value to use if the specified header field wont be * found. * @return the header field represented in milliseconds since January 1, * 1970 GMT. */ @Override public long getHeaderFieldDate(String field, long defaultValue) { return super.getHeaderFieldDate(field, defaultValue); } /** * If the length of a HTTP request body is known ahead, sets fixed length to * enable streaming without buffering. Sets after connection will cause an * exception. * * @see #setChunkedStreamingMode * @param contentLength * the fixed length of the HTTP request body. * @throws IllegalStateException * if already connected or another mode already set. * @throws IllegalArgumentException * if {@code contentLength} is less than zero. */ public void setFixedLengthStreamingMode(int contentLength) { if (super.connected) { throw new IllegalStateException("Already connected"); } if (chunkLength > 0) { throw new IllegalStateException("Already in chunked mode"); } if (contentLength < 0) { throw new IllegalArgumentException("contentLength < 0"); } this.fixedContentLength = contentLength; } /** * Stream a request body whose length is not known in advance. Old HTTP/1.0 * only servers may not support this mode. * *

    When HTTP chunked encoding is used, the stream is divided into * chunks, each prefixed with a header containing the chunk's size. Setting * a large chunk length requires a large internal buffer, potentially * wasting memory. Setting a small chunk length increases the number of * bytes that must be transmitted because of the header on every chunk. * Most caller should use {@code 0} to get the system default. * * @see #setFixedLengthStreamingMode * @param chunkLength the length to use, or {@code 0} for the default chunk * length. * @throws IllegalStateException if already connected or another mode * already set. */ public void setChunkedStreamingMode(int chunkLength) { if (super.connected) { throw new IllegalStateException("Already connected"); } if (fixedContentLength >= 0) { throw new IllegalStateException("Already in fixed-length mode"); } if (chunkLength <= 0) { this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH; } else { this.chunkLength = chunkLength; } } }