/* * 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.http.OkHeaders; import java.util.Collections; import java.util.List; import static com.squareup.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT; import static com.squareup.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT; import static java.net.HttpURLConnection.HTTP_MOVED_PERM; import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; import static java.net.HttpURLConnection.HTTP_MULT_CHOICE; import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; import static java.net.HttpURLConnection.HTTP_SEE_OTHER; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; /** * 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 Protocol protocol; private final int code; private final String message; private final Handshake handshake; private final Headers headers; private final ResponseBody body; private Response networkResponse; private Response cacheResponse; private final Response priorResponse; private volatile CacheControl cacheControl; // Lazily initialized. private Response(Builder builder) { this.request = builder.request; this.protocol = builder.protocol; this.code = builder.code; this.message = builder.message; 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 not * necessarily the same request issued by the application: * */ public Request request() { return request; } /** * Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link * Protocol#HTTP_1_0}. */ public Protocol protocol() { return protocol; } /** Returns the HTTP status code. */ public int code() { return code; } /** * Returns true if the code is in [200..300), which means the request was * successfully received, understood, and accepted. */ public boolean isSuccessful() { return code >= 200 && code < 300; } /** Returns the HTTP status message or null if it is unknown. */ public String message() { return message; } /** * 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 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 ResponseBody body() { return body; } public Builder newBuilder() { return new Builder(this); } /** Returns true if this response redirects to another resource. */ public boolean isRedirect() { switch (code) { case HTTP_PERM_REDIRECT: case HTTP_TEMP_REDIRECT: case HTTP_MULT_CHOICE: case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: return true; default: return false; } } /** * 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; } /** * Returns the response for the HTTP redirect or authorization challenge that * triggered this response, or null if this response wasn't triggered by an * automatic retry. 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 authorization challenges appropriate for this response's code. * If the response code is 401 unauthorized, this returns the * "WWW-Authenticate" challenges. If the response code is 407 proxy * unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise * this returns an empty list of challenges. */ public List challenges() { String responseField; if (code == HTTP_UNAUTHORIZED) { responseField = "WWW-Authenticate"; } else if (code == HTTP_PROXY_AUTH) { responseField = "Proxy-Authenticate"; } else { return Collections.emptyList(); } return OkHeaders.parseChallenges(headers(), responseField); } /** * 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)); } @Override public String toString() { return "Response{protocol=" + protocol + ", code=" + code + ", message=" + message + ", url=" + request.urlString() + '}'; } public static class Builder { private Request request; private Protocol protocol; private int code = -1; private String message; private Handshake handshake; private Headers.Builder headers; private ResponseBody 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.protocol = response.protocol; this.code = response.code; this.message = response.message; 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 protocol(Protocol protocol) { this.protocol = protocol; return this; } public Builder code(int code) { this.code = code; return this; } public Builder message(String message) { this.message = message; return this; } 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(ResponseBody body) { this.body = body; return this; } 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) { if (priorResponse != null) checkPriorResponse(priorResponse); this.priorResponse = priorResponse; return this; } private void checkPriorResponse(Response response) { if (response.body != null) { throw new IllegalArgumentException("priorResponse.body != null"); } } public Response build() { if (request == null) throw new IllegalStateException("request == null"); if (protocol == null) throw new IllegalStateException("protocol == null"); if (code < 0) throw new IllegalStateException("code < 0: " + code); return new Response(this); } } }