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