| /* |
| * 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: |
| * <ul> |
| * <li>It may be transformed by the HTTP client. For example, the client |
| * may copy headers like {@code Content-Length} from the request body. |
| * <li>It may be the request generated in response to an HTTP redirect or |
| * authentication challenge. In this case the request URL may be |
| * different than the initial request URL. |
| * </ul> |
| */ |
| 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<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 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<Challenge> 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); |
| } |
| } |
| } |