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