1 /* 2 * Copyright (C) 2013 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp; 17 18 import com.squareup.okhttp.internal.http.OkHeaders; 19 import java.util.Collections; 20 import java.util.List; 21 22 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_PERM_REDIRECT; 23 import static com.squareup.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT; 24 import static java.net.HttpURLConnection.HTTP_MOVED_PERM; 25 import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; 26 import static java.net.HttpURLConnection.HTTP_MULT_CHOICE; 27 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; 28 import static java.net.HttpURLConnection.HTTP_SEE_OTHER; 29 import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; 30 31 /** 32 * An HTTP response. Instances of this class are not immutable: the response 33 * body is a one-shot value that may be consumed only once. All other properties 34 * are immutable. 35 */ 36 public final class Response { 37 private final Request request; 38 private final Protocol protocol; 39 private final int code; 40 private final String message; 41 private final Handshake handshake; 42 private final Headers headers; 43 private final ResponseBody body; 44 private Response networkResponse; 45 private Response cacheResponse; 46 private final Response priorResponse; 47 48 private volatile CacheControl cacheControl; // Lazily initialized. 49 50 private Response(Builder builder) { 51 this.request = builder.request; 52 this.protocol = builder.protocol; 53 this.code = builder.code; 54 this.message = builder.message; 55 this.handshake = builder.handshake; 56 this.headers = builder.headers.build(); 57 this.body = builder.body; 58 this.networkResponse = builder.networkResponse; 59 this.cacheResponse = builder.cacheResponse; 60 this.priorResponse = builder.priorResponse; 61 } 62 63 /** 64 * The wire-level request that initiated this HTTP response. This is not 65 * necessarily the same request issued by the application: 66 * <ul> 67 * <li>It may be transformed by the HTTP client. For example, the client 68 * may copy headers like {@code Content-Length} from the request body. 69 * <li>It may be the request generated in response to an HTTP redirect or 70 * authentication challenge. In this case the request URL may be 71 * different than the initial request URL. 72 * </ul> 73 */ 74 public Request request() { 75 return request; 76 } 77 78 /** 79 * Returns the HTTP protocol, such as {@link Protocol#HTTP_1_1} or {@link 80 * Protocol#HTTP_1_0}. 81 */ 82 public Protocol protocol() { 83 return protocol; 84 } 85 86 /** Returns the HTTP status code. */ 87 public int code() { 88 return code; 89 } 90 91 /** 92 * Returns true if the code is in [200..300), which means the request was 93 * successfully received, understood, and accepted. 94 */ 95 public boolean isSuccessful() { 96 return code >= 200 && code < 300; 97 } 98 99 /** Returns the HTTP status message or null if it is unknown. */ 100 public String message() { 101 return message; 102 } 103 104 /** 105 * Returns the TLS handshake of the connection that carried this response, or 106 * null if the response was received without TLS. 107 */ 108 public Handshake handshake() { 109 return handshake; 110 } 111 112 public List<String> headers(String name) { 113 return headers.values(name); 114 } 115 116 public String header(String name) { 117 return header(name, null); 118 } 119 120 public String header(String name, String defaultValue) { 121 String result = headers.get(name); 122 return result != null ? result : defaultValue; 123 } 124 125 public Headers headers() { 126 return headers; 127 } 128 129 public ResponseBody body() { 130 return body; 131 } 132 133 public Builder newBuilder() { 134 return new Builder(this); 135 } 136 137 /** Returns true if this response redirects to another resource. */ 138 public boolean isRedirect() { 139 switch (code) { 140 case HTTP_PERM_REDIRECT: 141 case HTTP_TEMP_REDIRECT: 142 case HTTP_MULT_CHOICE: 143 case HTTP_MOVED_PERM: 144 case HTTP_MOVED_TEMP: 145 case HTTP_SEE_OTHER: 146 return true; 147 default: 148 return false; 149 } 150 } 151 152 /** 153 * Returns the raw response received from the network. Will be null if this 154 * response didn't use the network, such as when the response is fully cached. 155 * The body of the returned response should not be read. 156 */ 157 public Response networkResponse() { 158 return networkResponse; 159 } 160 161 /** 162 * Returns the raw response received from the cache. Will be null if this 163 * response didn't use the cache. For conditional get requests the cache 164 * response and network response may both be non-null. The body of the 165 * returned response should not be read. 166 */ 167 public Response cacheResponse() { 168 return cacheResponse; 169 } 170 171 /** 172 * Returns the response for the HTTP redirect or authorization challenge that 173 * triggered this response, or null if this response wasn't triggered by an 174 * automatic retry. The body of the returned response should not be read 175 * because it has already been consumed by the redirecting client. 176 */ 177 public Response priorResponse() { 178 return priorResponse; 179 } 180 181 /** 182 * Returns the authorization challenges appropriate for this response's code. 183 * If the response code is 401 unauthorized, this returns the 184 * "WWW-Authenticate" challenges. If the response code is 407 proxy 185 * unauthorized, this returns the "Proxy-Authenticate" challenges. Otherwise 186 * this returns an empty list of challenges. 187 */ 188 public List<Challenge> challenges() { 189 String responseField; 190 if (code == HTTP_UNAUTHORIZED) { 191 responseField = "WWW-Authenticate"; 192 } else if (code == HTTP_PROXY_AUTH) { 193 responseField = "Proxy-Authenticate"; 194 } else { 195 return Collections.emptyList(); 196 } 197 return OkHeaders.parseChallenges(headers(), responseField); 198 } 199 200 /** 201 * Returns the cache control directives for this response. This is never null, 202 * even if this response contains no {@code Cache-Control} header. 203 */ 204 public CacheControl cacheControl() { 205 CacheControl result = cacheControl; 206 return result != null ? result : (cacheControl = CacheControl.parse(headers)); 207 } 208 209 @Override public String toString() { 210 return "Response{protocol=" 211 + protocol 212 + ", code=" 213 + code 214 + ", message=" 215 + message 216 + ", url=" 217 + request.urlString() 218 + '}'; 219 } 220 221 public static class Builder { 222 private Request request; 223 private Protocol protocol; 224 private int code = -1; 225 private String message; 226 private Handshake handshake; 227 private Headers.Builder headers; 228 private ResponseBody body; 229 private Response networkResponse; 230 private Response cacheResponse; 231 private Response priorResponse; 232 233 public Builder() { 234 headers = new Headers.Builder(); 235 } 236 237 private Builder(Response response) { 238 this.request = response.request; 239 this.protocol = response.protocol; 240 this.code = response.code; 241 this.message = response.message; 242 this.handshake = response.handshake; 243 this.headers = response.headers.newBuilder(); 244 this.body = response.body; 245 this.networkResponse = response.networkResponse; 246 this.cacheResponse = response.cacheResponse; 247 this.priorResponse = response.priorResponse; 248 } 249 250 public Builder request(Request request) { 251 this.request = request; 252 return this; 253 } 254 255 public Builder protocol(Protocol protocol) { 256 this.protocol = protocol; 257 return this; 258 } 259 260 public Builder code(int code) { 261 this.code = code; 262 return this; 263 } 264 265 public Builder message(String message) { 266 this.message = message; 267 return this; 268 } 269 270 public Builder handshake(Handshake handshake) { 271 this.handshake = handshake; 272 return this; 273 } 274 275 /** 276 * Sets the header named {@code name} to {@code value}. If this request 277 * already has any headers with that name, they are all replaced. 278 */ 279 public Builder header(String name, String value) { 280 headers.set(name, value); 281 return this; 282 } 283 284 /** 285 * Adds a header with {@code name} and {@code value}. Prefer this method for 286 * multiply-valued headers like "Set-Cookie". 287 */ 288 public Builder addHeader(String name, String value) { 289 headers.add(name, value); 290 return this; 291 } 292 293 public Builder removeHeader(String name) { 294 headers.removeAll(name); 295 return this; 296 } 297 298 /** Removes all headers on this builder and adds {@code headers}. */ 299 public Builder headers(Headers headers) { 300 this.headers = headers.newBuilder(); 301 return this; 302 } 303 304 public Builder body(ResponseBody body) { 305 this.body = body; 306 return this; 307 } 308 309 public Builder networkResponse(Response networkResponse) { 310 if (networkResponse != null) checkSupportResponse("networkResponse", networkResponse); 311 this.networkResponse = networkResponse; 312 return this; 313 } 314 315 public Builder cacheResponse(Response cacheResponse) { 316 if (cacheResponse != null) checkSupportResponse("cacheResponse", cacheResponse); 317 this.cacheResponse = cacheResponse; 318 return this; 319 } 320 321 private void checkSupportResponse(String name, Response response) { 322 if (response.body != null) { 323 throw new IllegalArgumentException(name + ".body != null"); 324 } else if (response.networkResponse != null) { 325 throw new IllegalArgumentException(name + ".networkResponse != null"); 326 } else if (response.cacheResponse != null) { 327 throw new IllegalArgumentException(name + ".cacheResponse != null"); 328 } else if (response.priorResponse != null) { 329 throw new IllegalArgumentException(name + ".priorResponse != null"); 330 } 331 } 332 333 public Builder priorResponse(Response priorResponse) { 334 if (priorResponse != null) checkPriorResponse(priorResponse); 335 this.priorResponse = priorResponse; 336 return this; 337 } 338 339 private void checkPriorResponse(Response response) { 340 if (response.body != null) { 341 throw new IllegalArgumentException("priorResponse.body != null"); 342 } 343 } 344 345 public Response build() { 346 if (request == null) throw new IllegalStateException("request == null"); 347 if (protocol == null) throw new IllegalStateException("protocol == null"); 348 if (code < 0) throw new IllegalStateException("code < 0: " + code); 349 return new Response(this); 350 } 351 } 352 } 353