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 */ 16package com.squareup.okhttp; 17 18import com.squareup.okhttp.internal.Platform; 19import com.squareup.okhttp.internal.Util; 20import java.io.File; 21import java.io.FileInputStream; 22import java.io.IOException; 23import java.io.InputStream; 24import java.io.UnsupportedEncodingException; 25import java.net.MalformedURLException; 26import java.net.URI; 27import java.net.URISyntaxException; 28import java.net.URL; 29import java.util.List; 30import okio.BufferedSink; 31 32/** 33 * An HTTP request. Instances of this class are immutable if their {@link #body} 34 * is null or itself immutable. 35 */ 36public final class Request { 37 private final URL url; 38 private final String method; 39 private final Headers headers; 40 private final Body body; 41 private final Object tag; 42 43 private volatile ParsedHeaders parsedHeaders; // Lazily initialized. 44 private volatile URI uri; // Lazily initialized. 45 private volatile CacheControl cacheControl; // Lazily initialized. 46 47 private Request(Builder builder) { 48 this.url = builder.url; 49 this.method = builder.method; 50 this.headers = builder.headers.build(); 51 this.body = builder.body; 52 this.tag = builder.tag != null ? builder.tag : this; 53 } 54 55 public URL url() { 56 return url; 57 } 58 59 public URI uri() throws IOException { 60 try { 61 URI result = uri; 62 return result != null ? result : (uri = Platform.get().toUriLenient(url)); 63 } catch (URISyntaxException e) { 64 throw new IOException(e.getMessage()); 65 } 66 } 67 68 public String urlString() { 69 return url.toString(); 70 } 71 72 public String method() { 73 return method; 74 } 75 76 public Headers headers() { 77 return headers; 78 } 79 80 public String header(String name) { 81 return headers.get(name); 82 } 83 84 public List<String> headers(String name) { 85 return headers.values(name); 86 } 87 88 public Body body() { 89 return body; 90 } 91 92 public Object tag() { 93 return tag; 94 } 95 96 public Builder newBuilder() { 97 return new Builder(this); 98 } 99 100 public Headers getHeaders() { 101 return headers; 102 } 103 104 public String getUserAgent() { 105 return parsedHeaders().userAgent; 106 } 107 108 public String getProxyAuthorization() { 109 return parsedHeaders().proxyAuthorization; 110 } 111 112 private ParsedHeaders parsedHeaders() { 113 ParsedHeaders result = parsedHeaders; 114 return result != null ? result : (parsedHeaders = new ParsedHeaders(headers)); 115 } 116 117 /** 118 * Returns the cache control directives for this response. This is never null, 119 * even if this response contains no {@code Cache-Control} header. 120 */ 121 public CacheControl cacheControl() { 122 CacheControl result = cacheControl; 123 return result != null ? result : (cacheControl = CacheControl.parse(headers)); 124 } 125 126 public boolean isHttps() { 127 return url().getProtocol().equals("https"); 128 } 129 130 /** Parsed request headers, computed on-demand and cached. */ 131 private static class ParsedHeaders { 132 private String userAgent; 133 private String proxyAuthorization; 134 135 public ParsedHeaders(Headers headers) { 136 for (int i = 0; i < headers.size(); i++) { 137 String fieldName = headers.name(i); 138 String value = headers.value(i); 139 if ("User-Agent".equalsIgnoreCase(fieldName)) { 140 userAgent = value; 141 } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) { 142 proxyAuthorization = value; 143 } 144 } 145 } 146 } 147 148 public abstract static class Body { 149 /** Returns the Content-Type header for this body. */ 150 public abstract MediaType contentType(); 151 152 /** 153 * Returns the number of bytes that will be written to {@code out} in a call 154 * to {@link #writeTo}, or -1 if that count is unknown. 155 */ 156 public long contentLength() { 157 return -1; 158 } 159 160 /** Writes the content of this request to {@code out}. */ 161 public abstract void writeTo(BufferedSink sink) throws IOException; 162 163 /** 164 * Returns a new request body that transmits {@code content}. If {@code 165 * contentType} lacks a charset, this will use UTF-8. 166 */ 167 public static Body create(MediaType contentType, String content) { 168 contentType = contentType.charset() != null 169 ? contentType 170 : MediaType.parse(contentType + "; charset=utf-8"); 171 try { 172 byte[] bytes = content.getBytes(contentType.charset().name()); 173 return create(contentType, bytes); 174 } catch (UnsupportedEncodingException e) { 175 throw new AssertionError(); 176 } 177 } 178 179 /** Returns a new request body that transmits {@code content}. */ 180 public static Body create(final MediaType contentType, final byte[] content) { 181 if (contentType == null) throw new NullPointerException("contentType == null"); 182 if (content == null) throw new NullPointerException("content == null"); 183 184 return new Body() { 185 @Override public MediaType contentType() { 186 return contentType; 187 } 188 189 @Override public long contentLength() { 190 return content.length; 191 } 192 193 @Override public void writeTo(BufferedSink sink) throws IOException { 194 sink.write(content); 195 } 196 }; 197 } 198 199 /** Returns a new request body that transmits the content of {@code file}. */ 200 public static Body create(final MediaType contentType, final File file) { 201 if (contentType == null) throw new NullPointerException("contentType == null"); 202 if (file == null) throw new NullPointerException("content == null"); 203 204 return new Body() { 205 @Override public MediaType contentType() { 206 return contentType; 207 } 208 209 @Override public long contentLength() { 210 return file.length(); 211 } 212 213 @Override public void writeTo(BufferedSink sink) throws IOException { 214 long length = contentLength(); 215 if (length == 0) return; 216 217 InputStream in = null; 218 try { 219 in = new FileInputStream(file); 220 byte[] buffer = new byte[(int) Math.min(8192, length)]; 221 for (int c; (c = in.read(buffer)) != -1; ) { 222 sink.write(buffer, 0, c); 223 } 224 } finally { 225 Util.closeQuietly(in); 226 } 227 } 228 }; 229 } 230 } 231 232 public static class Builder { 233 private URL url; 234 private String method; 235 private Headers.Builder headers; 236 private Body body; 237 private Object tag; 238 239 public Builder() { 240 this.method = "GET"; 241 this.headers = new Headers.Builder(); 242 } 243 244 private Builder(Request request) { 245 this.url = request.url; 246 this.method = request.method; 247 this.body = request.body; 248 this.tag = request.tag; 249 this.headers = request.headers.newBuilder(); 250 } 251 252 public Builder url(String url) { 253 try { 254 return url(new URL(url)); 255 } catch (MalformedURLException e) { 256 throw new IllegalArgumentException("Malformed URL: " + url); 257 } 258 } 259 260 public Builder url(URL url) { 261 if (url == null) throw new IllegalArgumentException("url == null"); 262 this.url = url; 263 return this; 264 } 265 266 /** 267 * Sets the header named {@code name} to {@code value}. If this request 268 * already has any headers with that name, they are all replaced. 269 */ 270 public Builder header(String name, String value) { 271 headers.set(name, value); 272 return this; 273 } 274 275 /** 276 * Adds a header with {@code name} and {@code value}. Prefer this method for 277 * multiply-valued headers like "Cookie". 278 */ 279 public Builder addHeader(String name, String value) { 280 headers.add(name, value); 281 return this; 282 } 283 284 public Builder removeHeader(String name) { 285 headers.removeAll(name); 286 return this; 287 } 288 289 /** Removes all headers on this builder and adds {@code headers}. */ 290 public Builder headers(Headers headers) { 291 this.headers = headers.newBuilder(); 292 return this; 293 } 294 295 public Builder setUserAgent(String userAgent) { 296 return header("User-Agent", userAgent); 297 } 298 299 public Builder get() { 300 return method("GET", null); 301 } 302 303 public Builder head() { 304 return method("HEAD", null); 305 } 306 307 public Builder post(Body body) { 308 return method("POST", body); 309 } 310 311 public Builder put(Body body) { 312 return method("PUT", body); 313 } 314 315 public Builder method(String method, Body body) { 316 if (method == null || method.length() == 0) { 317 throw new IllegalArgumentException("method == null || method.length() == 0"); 318 } 319 this.method = method; 320 this.body = body; 321 return this; 322 } 323 324 /** 325 * Attaches {@code tag} to the request. It can be used later to cancel the 326 * request. If the tag is unspecified or null, the request is canceled by 327 * using the request itself as the tag. 328 */ 329 public Builder tag(Object tag) { 330 this.tag = tag; 331 return this; 332 } 333 334 public Request build() { 335 if (url == null) throw new IllegalStateException("url == null"); 336 return new Request(this); 337 } 338 } 339} 340