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.HttpMethod; 19 import java.io.IOException; 20 import java.net.URI; 21 import java.net.URL; 22 import java.util.List; 23 24 /** 25 * An HTTP request. Instances of this class are immutable if their {@link #body} 26 * is null or itself immutable. 27 */ 28 public final class Request { 29 private final HttpUrl url; 30 private final String method; 31 private final Headers headers; 32 private final RequestBody body; 33 private final Object tag; 34 35 private volatile URL javaNetUrl; // Lazily initialized. 36 private volatile URI javaNetUri; // Lazily initialized. 37 private volatile CacheControl cacheControl; // Lazily initialized. 38 39 private Request(Builder builder) { 40 this.url = builder.url; 41 this.method = builder.method; 42 this.headers = builder.headers.build(); 43 this.body = builder.body; 44 this.tag = builder.tag != null ? builder.tag : this; 45 } 46 47 public HttpUrl httpUrl() { 48 return url; 49 } 50 51 public URL url() { 52 URL result = javaNetUrl; 53 return result != null ? result : (javaNetUrl = url.url()); 54 } 55 56 public URI uri() throws IOException { 57 try { 58 URI result = javaNetUri; 59 return result != null ? result : (javaNetUri = url.uri()); 60 } catch (IllegalStateException e) { 61 throw new IOException(e.getMessage()); 62 } 63 } 64 65 public String urlString() { 66 return url.toString(); 67 } 68 69 public String method() { 70 return method; 71 } 72 73 public Headers headers() { 74 return headers; 75 } 76 77 public String header(String name) { 78 return headers.get(name); 79 } 80 81 public List<String> headers(String name) { 82 return headers.values(name); 83 } 84 85 public RequestBody body() { 86 return body; 87 } 88 89 public Object tag() { 90 return tag; 91 } 92 93 public Builder newBuilder() { 94 return new Builder(this); 95 } 96 97 /** 98 * Returns the cache control directives for this response. This is never null, 99 * even if this response contains no {@code Cache-Control} header. 100 */ 101 public CacheControl cacheControl() { 102 CacheControl result = cacheControl; 103 return result != null ? result : (cacheControl = CacheControl.parse(headers)); 104 } 105 106 public boolean isHttps() { 107 return url.isHttps(); 108 } 109 110 @Override public String toString() { 111 return "Request{method=" 112 + method 113 + ", url=" 114 + url 115 + ", tag=" 116 + (tag != this ? tag : null) 117 + '}'; 118 } 119 120 public static class Builder { 121 private HttpUrl url; 122 private String method; 123 private Headers.Builder headers; 124 private RequestBody body; 125 private Object tag; 126 127 public Builder() { 128 this.method = "GET"; 129 this.headers = new Headers.Builder(); 130 } 131 132 private Builder(Request request) { 133 this.url = request.url; 134 this.method = request.method; 135 this.body = request.body; 136 this.tag = request.tag; 137 this.headers = request.headers.newBuilder(); 138 } 139 140 public Builder url(HttpUrl url) { 141 if (url == null) throw new IllegalArgumentException("url == null"); 142 this.url = url; 143 return this; 144 } 145 146 /** 147 * Sets the URL target of this request. 148 * 149 * @throws IllegalArgumentException if {@code url} is not a valid HTTP or HTTPS URL. Avoid this 150 * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs. 151 */ 152 public Builder url(String url) { 153 if (url == null) throw new IllegalArgumentException("url == null"); 154 155 // Silently replace websocket URLs with HTTP URLs. 156 if (url.regionMatches(true, 0, "ws:", 0, 3)) { 157 url = "http:" + url.substring(3); 158 } else if (url.regionMatches(true, 0, "wss:", 0, 4)) { 159 url = "https:" + url.substring(4); 160 } 161 162 HttpUrl parsed = HttpUrl.parse(url); 163 if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url); 164 return url(parsed); 165 } 166 167 /** 168 * Sets the URL target of this request. 169 * 170 * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code 171 * https}. 172 */ 173 public Builder url(URL url) { 174 if (url == null) throw new IllegalArgumentException("url == null"); 175 HttpUrl parsed = HttpUrl.get(url); 176 if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url); 177 return url(parsed); 178 } 179 180 /** 181 * Sets the header named {@code name} to {@code value}. If this request 182 * already has any headers with that name, they are all replaced. 183 */ 184 public Builder header(String name, String value) { 185 headers.set(name, value); 186 return this; 187 } 188 189 /** 190 * Adds a header with {@code name} and {@code value}. Prefer this method for 191 * multiply-valued headers like "Cookie". 192 * 193 * <p>Note that for some headers including {@code Content-Length} and {@code Content-Encoding}, 194 * OkHttp may replace {@code value} with a header derived from the request body. 195 */ 196 public Builder addHeader(String name, String value) { 197 headers.add(name, value); 198 return this; 199 } 200 201 public Builder removeHeader(String name) { 202 headers.removeAll(name); 203 return this; 204 } 205 206 /** Removes all headers on this builder and adds {@code headers}. */ 207 public Builder headers(Headers headers) { 208 this.headers = headers.newBuilder(); 209 return this; 210 } 211 212 /** 213 * Sets this request's {@code Cache-Control} header, replacing any cache 214 * control headers already present. If {@code cacheControl} doesn't define 215 * any directives, this clears this request's cache-control headers. 216 */ 217 public Builder cacheControl(CacheControl cacheControl) { 218 String value = cacheControl.toString(); 219 if (value.isEmpty()) return removeHeader("Cache-Control"); 220 return header("Cache-Control", value); 221 } 222 223 public Builder get() { 224 return method("GET", null); 225 } 226 227 public Builder head() { 228 return method("HEAD", null); 229 } 230 231 public Builder post(RequestBody body) { 232 return method("POST", body); 233 } 234 235 public Builder delete(RequestBody body) { 236 return method("DELETE", body); 237 } 238 239 public Builder delete() { 240 return delete(RequestBody.create(null, new byte[0])); 241 } 242 243 public Builder put(RequestBody body) { 244 return method("PUT", body); 245 } 246 247 public Builder patch(RequestBody body) { 248 return method("PATCH", body); 249 } 250 251 public Builder method(String method, RequestBody body) { 252 if (method == null || method.length() == 0) { 253 throw new IllegalArgumentException("method == null || method.length() == 0"); 254 } 255 if (body != null && !HttpMethod.permitsRequestBody(method)) { 256 throw new IllegalArgumentException("method " + method + " must not have a request body."); 257 } 258 if (body == null && HttpMethod.requiresRequestBody(method)) { 259 throw new IllegalArgumentException("method " + method + " must have a request body."); 260 } 261 this.method = method; 262 this.body = body; 263 return this; 264 } 265 266 /** 267 * Attaches {@code tag} to the request. It can be used later to cancel the 268 * request. If the tag is unspecified or null, the request is canceled by 269 * using the request itself as the tag. 270 */ 271 public Builder tag(Object tag) { 272 this.tag = tag; 273 return this; 274 } 275 276 public Request build() { 277 if (url == null) throw new IllegalStateException("url == null"); 278 return new Request(this); 279 } 280 } 281 } 282