| /* |
| * 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.RawHeaders; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.nio.charset.Charset; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static com.squareup.okhttp.internal.Util.UTF_8; |
| |
| /** |
| * 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. |
| * |
| * <h3>Warning: Experimental OkHttp 2.0 API</h3> |
| * This class is in beta. APIs are subject to change! |
| */ |
| /* OkHttp 2.0: public */ final class Response { |
| private final Request request; |
| private final int code; |
| private final RawHeaders headers; |
| private final Body body; |
| private final Response redirectedBy; |
| |
| private Response(Builder builder) { |
| this.request = builder.request; |
| this.code = builder.code; |
| this.headers = new RawHeaders(builder.headers); |
| this.body = builder.body; |
| this.redirectedBy = builder.redirectedBy; |
| } |
| |
| /** |
| * 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 int code() { |
| return code; |
| } |
| |
| 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 List<String> headers(String name) { |
| return headers.values(name); |
| } |
| |
| public Set<String> headerNames() { |
| return headers.names(); |
| } |
| |
| public int headerCount() { |
| return headers.length(); |
| } |
| |
| public String headerName(int index) { |
| return headers.getFieldName(index); |
| } |
| |
| RawHeaders rawHeaders() { |
| return new RawHeaders(headers); |
| } |
| |
| public String headerValue(int index) { |
| return headers.getValue(index); |
| } |
| |
| public Body body() { |
| return body; |
| } |
| |
| /** |
| * 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 redirectedBy() { |
| return redirectedBy; |
| } |
| |
| public abstract static class Body { |
| /** Multiple calls to {@link #charStream()} must return the same instance. */ |
| private Reader reader; |
| |
| /** |
| * Returns true if further data from this response body should be read at |
| * this time. For asynchronous transports like SPDY and HTTP/2.0, 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() throws IOException; |
| |
| 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() throws IOException { |
| if (reader == null) { |
| reader = new InputStreamReader(byteStream(), charset()); |
| } |
| return reader; |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| 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 final Request request; |
| private final int code; |
| private RawHeaders headers = new RawHeaders(); |
| private Body body; |
| private Response redirectedBy; |
| |
| public Builder(Request request, int code) { |
| if (request == null) throw new IllegalArgumentException("request == null"); |
| if (code <= 0) throw new IllegalArgumentException("code <= 0"); |
| this.request = request; |
| this.code = code; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| Builder rawHeaders(RawHeaders rawHeaders) { |
| headers = new RawHeaders(rawHeaders); |
| return this; |
| } |
| |
| public Builder body(Body body) { |
| this.body = body; |
| return this; |
| } |
| |
| public Builder redirectedBy(Response redirectedBy) { |
| this.redirectedBy = redirectedBy; |
| return this; |
| } |
| |
| public Response build() { |
| if (request == null) throw new IllegalStateException("Response has no request."); |
| if (code == -1) throw new IllegalStateException("Response has no code."); |
| return new Response(this); |
| } |
| } |
| } |