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.Util; 19 import com.squareup.okhttp.internal.http.RawHeaders; 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.InputStreamReader; 24 import java.io.Reader; 25 import java.util.List; 26 import java.util.Set; 27 28 /** 29 * An HTTP response. Instances of this class are not immutable: the response 30 * body is a one-shot value that may be consumed only once. All other properties 31 * are immutable. 32 * 33 * <h3>Warning: Experimental OkHttp 2.0 API</h3> 34 * This class is in beta. APIs are subject to change! 35 */ 36 public final class Response { 37 private final Request request; 38 private final int code; 39 private final RawHeaders headers; 40 private final Body body; 41 private final Response redirectedBy; 42 43 private Response(Builder builder) { 44 this.request = builder.request; 45 this.code = builder.code; 46 this.headers = new RawHeaders(builder.headers); 47 this.body = builder.body; 48 this.redirectedBy = builder.redirectedBy; 49 } 50 51 /** 52 * The wire-level request that initiated this HTTP response. This is usually 53 * <strong>not</strong> the same request instance provided to the HTTP client: 54 * <ul> 55 * <li>It may be transformed by the HTTP client. For example, the client 56 * may have added its own {@code Content-Encoding} header to enable 57 * response compression. 58 * <li>It may be the request generated in response to an HTTP redirect. 59 * In this case the request URL may be different than the initial 60 * request URL. 61 * </ul> 62 */ 63 public Request request() { 64 return request; 65 } 66 67 public int code() { 68 return code; 69 } 70 71 public String header(String name) { 72 return header(name, null); 73 } 74 75 public String header(String name, String defaultValue) { 76 String result = headers.get(name); 77 return result != null ? result : defaultValue; 78 } 79 80 public List<String> headers(String name) { 81 return headers.values(name); 82 } 83 84 public Set<String> headerNames() { 85 return headers.names(); 86 } 87 88 public int headerCount() { 89 return headers.length(); 90 } 91 92 public String headerName(int index) { 93 return headers.getFieldName(index); 94 } 95 96 public String headerValue(int index) { 97 return headers.getValue(index); 98 } 99 100 public Body body() { 101 return body; 102 } 103 104 /** 105 * Returns the response for the HTTP redirect that triggered this response, or 106 * null if this response wasn't triggered by an automatic redirect. The body 107 * of the returned response should not be read because it has already been 108 * consumed by the redirecting client. 109 */ 110 public Response redirectedBy() { 111 return redirectedBy; 112 } 113 114 public abstract static class Body { 115 public String contentType() { 116 return null; 117 } 118 119 public long contentLength() { 120 return -1; 121 } 122 123 public abstract InputStream byteStream() throws IOException; 124 125 public byte[] bytes() throws IOException { 126 long contentLength = contentLength(); 127 if (contentLength > Integer.MAX_VALUE) { 128 throw new IOException("Cannot buffer entire body for content length: " + contentLength); 129 } 130 131 if (contentLength != -1) { 132 byte[] content = new byte[(int) contentLength]; 133 InputStream in = byteStream(); 134 Util.readFully(in, content); 135 if (in.read() != -1) throw new IOException("Content-Length and stream length disagree"); 136 return content; 137 138 } else { 139 ByteArrayOutputStream out = new ByteArrayOutputStream(); 140 Util.copy(byteStream(), out); 141 return out.toByteArray(); 142 } 143 } 144 145 /** 146 * Returns the response bytes as a UTF-8 character stream. Do not call this 147 * method if the response content is not a UTF-8 character stream. 148 */ 149 public Reader charStream() throws IOException { 150 // TODO: parse content-type. 151 return new InputStreamReader(byteStream(), "UTF-8"); 152 } 153 154 /** 155 * Returns the response bytes as a UTF-8 string. Do not call this method if 156 * the response content is not a UTF-8 character stream. 157 */ 158 public String string() throws IOException { 159 // TODO: parse content-type. 160 return new String(bytes(), "UTF-8"); 161 } 162 } 163 164 public interface Receiver { 165 void onFailure(Failure failure); 166 void onResponse(Response response) throws IOException; 167 } 168 169 public static class Builder { 170 private final Request request; 171 private final int code; 172 private final RawHeaders headers = new RawHeaders(); 173 private Body body; 174 private Response redirectedBy; 175 176 public Builder(Request request, int code) { 177 if (request == null) throw new IllegalArgumentException("request == null"); 178 if (code <= 0) throw new IllegalArgumentException("code <= 0"); 179 this.request = request; 180 this.code = code; 181 } 182 183 /** 184 * Sets the header named {@code name} to {@code value}. If this request 185 * already has any headers with that name, they are all replaced. 186 */ 187 public Builder header(String name, String value) { 188 headers.set(name, value); 189 return this; 190 } 191 192 /** 193 * Adds a header with {@code name} and {@code value}. Prefer this method for 194 * multiply-valued headers like "Set-Cookie". 195 */ 196 public Builder addHeader(String name, String value) { 197 headers.add(name, value); 198 return this; 199 } 200 201 public Builder body(Body body) { 202 this.body = body; 203 return this; 204 } 205 206 public Builder redirectedBy(Response redirectedBy) { 207 this.redirectedBy = redirectedBy; 208 return this; 209 } 210 211 public Response build() { 212 if (request == null) throw new IllegalStateException("Response has no request."); 213 if (code == -1) throw new IllegalStateException("Response has no code."); 214 return new Response(this); 215 } 216 } 217 } 218