| /* |
| * Copyright (C) 2013 Square, Inc. |
| * |
| * Licensed 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 com.squareup.okhttp; |
| |
| import com.squareup.okhttp.internal.Util; |
| import com.squareup.okhttp.internal.http.HttpDate; |
| import com.squareup.okhttp.internal.http.OkHeaders; |
| import com.squareup.okhttp.internal.http.StatusLine; |
| import java.io.ByteArrayOutputStream; |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.net.HttpURLConnection; |
| import java.nio.charset.Charset; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import okio.Okio; |
| import okio.Source; |
| |
| import static com.squareup.okhttp.internal.Util.UTF_8; |
| import static com.squareup.okhttp.internal.Util.equal; |
| |
| /** |
| * An HTTP response. Instances of this class are not immutable: the response |
| * body is a one-shot value that may be consumed only once. All other properties |
| * are immutable. |
| */ |
| public final class Response { |
| private final Request request; |
| private final StatusLine statusLine; |
| private final Handshake handshake; |
| private final Headers headers; |
| private final Body body; |
| private Response networkResponse; |
| private Response cacheResponse; |
| private final Response priorResponse; |
| |
| private volatile ParsedHeaders parsedHeaders; // Lazily initialized. |
| private volatile CacheControl cacheControl; // Lazily initialized. |
| |
| private Response(Builder builder) { |
| this.request = builder.request; |
| this.statusLine = builder.statusLine; |
| this.handshake = builder.handshake; |
| this.headers = builder.headers.build(); |
| this.body = builder.body; |
| this.networkResponse = builder.networkResponse; |
| this.cacheResponse = builder.cacheResponse; |
| this.priorResponse = builder.priorResponse; |
| } |
| |
| /** |
| * The wire-level request that initiated this HTTP response. This is usually |
| * <strong>not</strong> the same request instance provided to the HTTP client: |
| * <ul> |
| * <li>It may be transformed by the HTTP client. For example, the client |
| * may have added its own {@code Content-Encoding} header to enable |
| * response compression. |
| * <li>It may be the request generated in response to an HTTP redirect. |
| * In this case the request URL may be different than the initial |
| * request URL. |
| * </ul> |
| */ |
| public Request request() { |
| return request; |
| } |
| |
| public String statusLine() { |
| return statusLine.getStatusLine(); |
| } |
| |
| public int code() { |
| return statusLine.code(); |
| } |
| |
| public String statusMessage() { |
| return statusLine.message(); |
| } |
| |
| public int httpMinorVersion() { |
| return statusLine.httpMinorVersion(); |
| } |
| |
| /** |
| * Returns the TLS handshake of the connection that carried this response, or |
| * null if the response was received without TLS. |
| */ |
| public Handshake handshake() { |
| return handshake; |
| } |
| |
| public List<String> headers(String name) { |
| return headers.values(name); |
| } |
| |
| public String header(String name) { |
| return header(name, null); |
| } |
| |
| public String header(String name, String defaultValue) { |
| String result = headers.get(name); |
| return result != null ? result : defaultValue; |
| } |
| |
| public Headers headers() { |
| return headers; |
| } |
| |
| public Body body() { |
| return body; |
| } |
| |
| public Builder newBuilder() { |
| return new Builder(this); |
| } |
| |
| /** |
| * Returns the response for the HTTP redirect that triggered this response, or |
| * null if this response wasn't triggered by an automatic redirect. The body |
| * of the returned response should not be read because it has already been |
| * consumed by the redirecting client. |
| */ |
| public Response priorResponse() { |
| return priorResponse; |
| } |
| |
| /** |
| * Returns the raw response received from the network. Will be null if this |
| * response didn't use the network, such as when the response is fully cached. |
| * The body of the returned response should not be read. |
| */ |
| public Response networkResponse() { |
| return networkResponse; |
| } |
| |
| /** |
| * Returns the raw response received from the cache. Will be null if this |
| * response didn't use the cache. For conditional get requests the cache |
| * response and network response may both be non-null. The body of the |
| * returned response should not be read. |
| */ |
| public Response cacheResponse() { |
| return cacheResponse; |
| } |
| |
| // TODO: move out of public API |
| public Set<String> getVaryFields() { |
| return parsedHeaders().varyFields; |
| } |
| |
| /** |
| * Returns true if a Vary header contains an asterisk. Such responses cannot |
| * be cached. |
| */ |
| // TODO: move out of public API |
| public boolean hasVaryAll() { |
| return parsedHeaders().varyFields.contains("*"); |
| } |
| |
| /** |
| * Returns true if none of the Vary headers on this response have changed |
| * between {@code cachedRequest} and {@code newRequest}. |
| */ |
| // TODO: move out of public API |
| public boolean varyMatches(Headers varyHeaders, Request newRequest) { |
| for (String field : parsedHeaders().varyFields) { |
| if (!equal(varyHeaders.values(field), newRequest.headers(field))) return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Returns true if this cached response should be used; false if the |
| * network response should be used. |
| */ |
| // TODO: move out of public API |
| public boolean validate(Response network) { |
| if (network.code() == HttpURLConnection.HTTP_NOT_MODIFIED) { |
| return true; |
| } |
| |
| // The HTTP spec says that if the network's response is older than our |
| // cached response, we may return the cache's response. Like Chrome (but |
| // unlike Firefox), this client prefers to return the newer response. |
| ParsedHeaders networkHeaders = network.parsedHeaders(); |
| if (parsedHeaders().lastModified != null |
| && networkHeaders.lastModified != null |
| && networkHeaders.lastModified.getTime() < parsedHeaders().lastModified.getTime()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| public abstract static class Body implements Closeable { |
| /** Multiple calls to {@link #charStream()} must return the same instance. */ |
| private Reader reader; |
| |
| /** Multiple calls to {@link #source()} must return the same instance. */ |
| private Source source; |
| |
| /** |
| * Returns true if further data from this response body should be read at |
| * this time. For asynchronous protocols like SPDY and HTTP/2, this will |
| * return false once all locally-available body bytes have been read. |
| * |
| * <p>Clients with many concurrent downloads can use this method to reduce |
| * the number of idle threads blocking on reads. See {@link |
| * Receiver#onResponse} for details. |
| */ |
| // <h3>Body.ready() vs. InputStream.available()</h3> |
| // TODO: Can we fix response bodies to implement InputStream.available well? |
| // The deflater implementation is broken by default but we could do better. |
| public abstract boolean ready() throws IOException; |
| |
| public abstract MediaType contentType(); |
| |
| /** |
| * Returns the number of bytes in that will returned by {@link #bytes}, or |
| * {@link #byteStream}, or -1 if unknown. |
| */ |
| public abstract long contentLength(); |
| |
| public abstract InputStream byteStream(); |
| |
| // TODO: Source needs to be an API type for this to be public |
| public Source source() { |
| Source s = source; |
| return s != null ? s : (source = Okio.source(byteStream())); |
| } |
| |
| public final byte[] bytes() throws IOException { |
| long contentLength = contentLength(); |
| if (contentLength > Integer.MAX_VALUE) { |
| throw new IOException("Cannot buffer entire body for content length: " + contentLength); |
| } |
| |
| if (contentLength != -1) { |
| byte[] content = new byte[(int) contentLength]; |
| InputStream in = byteStream(); |
| Util.readFully(in, content); |
| if (in.read() != -1) throw new IOException("Content-Length and stream length disagree"); |
| return content; |
| |
| } else { |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| Util.copy(byteStream(), out); |
| return out.toByteArray(); |
| } |
| } |
| |
| /** |
| * Returns the response as a character stream decoded with the charset |
| * of the Content-Type header. If that header is either absent or lacks a |
| * charset, this will attempt to decode the response body as UTF-8. |
| */ |
| public final Reader charStream() { |
| Reader r = reader; |
| return r != null ? r : (reader = new InputStreamReader(byteStream(), charset())); |
| } |
| |
| /** |
| * Returns the response as a string decoded with the charset of the |
| * Content-Type header. If that header is either absent or lacks a charset, |
| * this will attempt to decode the response body as UTF-8. |
| */ |
| public final String string() throws IOException { |
| return new String(bytes(), charset().name()); |
| } |
| |
| private Charset charset() { |
| MediaType contentType = contentType(); |
| return contentType != null ? contentType.charset(UTF_8) : UTF_8; |
| } |
| |
| @Override public void close() throws IOException { |
| byteStream().close(); |
| } |
| } |
| |
| private ParsedHeaders parsedHeaders() { |
| ParsedHeaders result = parsedHeaders; |
| return result != null ? result : (parsedHeaders = new ParsedHeaders(headers)); |
| } |
| |
| /** |
| * Returns the cache control directives for this response. This is never null, |
| * even if this response contains no {@code Cache-Control} header. |
| */ |
| public CacheControl cacheControl() { |
| CacheControl result = cacheControl; |
| return result != null ? result : (cacheControl = CacheControl.parse(headers)); |
| } |
| |
| /** Parsed response headers, computed on-demand and cached. */ |
| private static class ParsedHeaders { |
| /** The last modified date of the response, if known. */ |
| Date lastModified; |
| |
| /** Case-insensitive set of field names. */ |
| private Set<String> varyFields = Collections.emptySet(); |
| |
| private ParsedHeaders(Headers headers) { |
| for (int i = 0; i < headers.size(); i++) { |
| String fieldName = headers.name(i); |
| String value = headers.value(i); |
| if ("Last-Modified".equalsIgnoreCase(fieldName)) { |
| lastModified = HttpDate.parse(value); |
| } else if ("Vary".equalsIgnoreCase(fieldName)) { |
| // Replace the immutable empty set with something we can mutate. |
| if (varyFields.isEmpty()) { |
| varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); |
| } |
| for (String varyField : value.split(",")) { |
| varyFields.add(varyField.trim()); |
| } |
| } |
| } |
| } |
| } |
| |
| public interface Receiver { |
| /** |
| * Called when the request could not be executed due to a connectivity |
| * problem or timeout. Because networks can fail during an exchange, it is |
| * possible that the remote server accepted the request before the failure. |
| */ |
| void onFailure(Failure failure); |
| |
| /** |
| * Called when the HTTP response was successfully returned by the remote |
| * server. The receiver may proceed to read the response body with the |
| * response's {@link #body} method. |
| * |
| * <p>Note that transport-layer success (receiving a HTTP response code, |
| * headers and body) does not necessarily indicate application-layer |
| * success: {@code response} may still indicate an unhappy HTTP response |
| * code like 404 or 500. |
| * |
| * <h3>Non-blocking responses</h3> |
| * |
| * <p>Receivers do not need to block while waiting for the response body to |
| * download. Instead, they can get called back as data arrives. Use {@link |
| * Body#ready} to check if bytes should be read immediately. While there is |
| * data ready, read it. If there isn't, return false: receivers will be |
| * called back with {@code onResponse()} as additional data is downloaded. |
| * |
| * <p>Return true to indicate that the receiver has finished handling the |
| * response body. If the response body has unread data, it will be |
| * discarded. |
| * |
| * <p>When the response body has been fully consumed the returned value is |
| * undefined. |
| * |
| * <p>The current implementation of {@link Body#ready} always returns true |
| * when the underlying transport is HTTP/1. This results in blocking on that |
| * transport. For effective non-blocking your server must support SPDY or |
| * HTTP/2. |
| */ |
| boolean onResponse(Response response) throws IOException; |
| } |
| |
| public static class Builder { |
| private Request request; |
| private StatusLine statusLine; |
| private Handshake handshake; |
| private Headers.Builder headers; |
| private Body body; |
| private Response networkResponse; |
| private Response cacheResponse; |
| private Response priorResponse; |
| |
| public Builder() { |
| headers = new Headers.Builder(); |
| } |
| |
| private Builder(Response response) { |
| this.request = response.request; |
| this.statusLine = response.statusLine; |
| this.handshake = response.handshake; |
| this.headers = response.headers.newBuilder(); |
| this.body = response.body; |
| this.networkResponse = response.networkResponse; |
| this.cacheResponse = response.cacheResponse; |
| this.priorResponse = response.priorResponse; |
| } |
| |
| public Builder request(Request request) { |
| this.request = request; |
| return this; |
| } |
| |
| public Builder statusLine(StatusLine statusLine) { |
| if (statusLine == null) throw new IllegalArgumentException("statusLine == null"); |
| this.statusLine = statusLine; |
| return this; |
| } |
| |
| public Builder statusLine(String statusLine) { |
| try { |
| return statusLine(new StatusLine(statusLine)); |
| } catch (IOException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| |
| public Builder handshake(Handshake handshake) { |
| this.handshake = handshake; |
| return this; |
| } |
| |
| /** |
| * Sets the header named {@code name} to {@code value}. If this request |
| * already has any headers with that name, they are all replaced. |
| */ |
| public Builder header(String name, String value) { |
| headers.set(name, value); |
| return this; |
| } |
| |
| /** |
| * Adds a header with {@code name} and {@code value}. Prefer this method for |
| * multiply-valued headers like "Set-Cookie". |
| */ |
| public Builder addHeader(String name, String value) { |
| headers.add(name, value); |
| return this; |
| } |
| |
| public Builder removeHeader(String name) { |
| headers.removeAll(name); |
| return this; |
| } |
| |
| /** Removes all headers on this builder and adds {@code headers}. */ |
| public Builder headers(Headers headers) { |
| this.headers = headers.newBuilder(); |
| return this; |
| } |
| |
| public Builder body(Body body) { |
| this.body = body; |
| return this; |
| } |
| |
| // TODO: move out of public API |
| public Builder setResponseSource(ResponseSource responseSource) { |
| return header(OkHeaders.RESPONSE_SOURCE, responseSource + " " + statusLine.code()); |
| } |
| |
| public Builder networkResponse(Response networkResponse) { |
| if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse); |
| this.networkResponse = networkResponse; |
| return this; |
| } |
| |
| public Builder cacheResponse(Response cacheResponse) { |
| if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse); |
| this.cacheResponse = cacheResponse; |
| return this; |
| } |
| |
| private void checkSupportResponse(String name, Response response) { |
| if (response.body != null) { |
| throw new IllegalArgumentException(name + ".body != null"); |
| } else if (response.networkResponse != null) { |
| throw new IllegalArgumentException(name + ".networkResponse != null"); |
| } else if (response.cacheResponse != null) { |
| throw new IllegalArgumentException(name + ".cacheResponse != null"); |
| } else if (response.priorResponse != null) { |
| throw new IllegalArgumentException(name + ".priorResponse != null"); |
| } |
| } |
| |
| public Builder priorResponse(Response priorResponse) { |
| this.priorResponse = priorResponse; |
| return this; |
| } |
| |
| public Response build() { |
| if (request == null) throw new IllegalStateException("request == null"); |
| if (statusLine == null) throw new IllegalStateException("statusLine == null"); |
| return new Response(this); |
| } |
| } |
| } |