1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.client;
20
21 import java.io.IOException;
22 import java.io.UnsupportedEncodingException;
23 import java.net.URI;
24 import java.net.URLDecoder;
25 import java.nio.charset.UnsupportedCharsetException;
26 import java.nio.file.Path;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.TimeUnit;
34 import java.util.concurrent.TimeoutException;
35 import java.util.concurrent.atomic.AtomicLong;
36
37 import org.eclipse.jetty.client.api.ContentProvider;
38 import org.eclipse.jetty.client.api.ContentResponse;
39 import org.eclipse.jetty.client.api.Request;
40 import org.eclipse.jetty.client.api.Response;
41 import org.eclipse.jetty.client.util.FutureResponseListener;
42 import org.eclipse.jetty.client.util.PathContentProvider;
43 import org.eclipse.jetty.http.HttpFields;
44 import org.eclipse.jetty.http.HttpHeader;
45 import org.eclipse.jetty.http.HttpMethod;
46 import org.eclipse.jetty.http.HttpVersion;
47 import org.eclipse.jetty.util.Fields;
48
49 public class HttpRequest implements Request
50 {
51 private static final AtomicLong ids = new AtomicLong();
52
53 private final HttpFields headers = new HttpFields();
54 private final Fields params = new Fields();
55 private final Map<String, Object> attributes = new HashMap<>();
56 private final List<RequestListener> requestListeners = new ArrayList<>();
57 private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
58 private final HttpClient client;
59 private final long conversation;
60 private final String host;
61 private final int port;
62 private URI uri;
63 private String scheme;
64 private String path;
65 private HttpMethod method;
66 private HttpVersion version;
67 private long idleTimeout;
68 private long timeout;
69 private ContentProvider content;
70 private boolean followRedirects;
71 private volatile Throwable aborted;
72
73 public HttpRequest(HttpClient client, URI uri)
74 {
75 this(client, ids.incrementAndGet(), uri);
76 }
77
78 protected HttpRequest(HttpClient client, long conversation, URI uri)
79 {
80 this.client = client;
81 this.conversation = conversation;
82 scheme = uri.getScheme();
83 host = uri.getHost();
84 port = client.normalizePort(scheme, uri.getPort());
85 path = uri.getRawPath();
86 String query = uri.getRawQuery();
87 if (query != null)
88 {
89 for (String nameValue : query.split("&"))
90 {
91 String[] parts = nameValue.split("=");
92 param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
93 }
94 }
95 this.uri = buildURI();
96 followRedirects(client.isFollowRedirects());
97 }
98
99 private String urlDecode(String value)
100 {
101 String charset = "UTF-8";
102 try
103 {
104 return URLDecoder.decode(value, charset);
105 }
106 catch (UnsupportedEncodingException x)
107 {
108 throw new UnsupportedCharsetException(charset);
109 }
110 }
111
112 @Override
113 public long getConversationID()
114 {
115 return conversation;
116 }
117
118 @Override
119 public String getScheme()
120 {
121 return scheme;
122 }
123
124 @Override
125 public Request scheme(String scheme)
126 {
127 this.scheme = scheme;
128 this.uri = buildURI();
129 return this;
130 }
131
132 @Override
133 public String getHost()
134 {
135 return host;
136 }
137
138 @Override
139 public int getPort()
140 {
141 return port;
142 }
143
144 @Override
145 public HttpMethod getMethod()
146 {
147 return method;
148 }
149
150 @Override
151 public Request method(HttpMethod method)
152 {
153 this.method = method;
154 return this;
155 }
156
157 @Override
158 public String getPath()
159 {
160 return path;
161 }
162
163 @Override
164 public Request path(String path)
165 {
166 this.path = path;
167 this.uri = buildURI();
168 return this;
169 }
170
171 @Override
172 public URI getURI()
173 {
174 return uri;
175 }
176
177 @Override
178 public HttpVersion getVersion()
179 {
180 return version;
181 }
182
183 @Override
184 public Request version(HttpVersion version)
185 {
186 this.version = version;
187 return this;
188 }
189
190 @Override
191 public Request param(String name, String value)
192 {
193 params.add(name, value);
194 return this;
195 }
196
197 @Override
198 public Fields getParams()
199 {
200 return params;
201 }
202
203 @Override
204 public String getAgent()
205 {
206 return headers.get(HttpHeader.USER_AGENT);
207 }
208
209 @Override
210 public Request agent(String agent)
211 {
212 headers.put(HttpHeader.USER_AGENT, agent);
213 return this;
214 }
215
216 @Override
217 public Request header(String name, String value)
218 {
219 if (value == null)
220 headers.remove(name);
221 else
222 headers.add(name, value);
223 return this;
224 }
225
226 @Override
227 public Request attribute(String name, Object value)
228 {
229 attributes.put(name, value);
230 return this;
231 }
232
233 @Override
234 public Map<String, Object> getAttributes()
235 {
236 return attributes;
237 }
238
239 @Override
240 public HttpFields getHeaders()
241 {
242 return headers;
243 }
244
245 @Override
246 public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
247 {
248
249
250 if (type == null)
251 return (List<T>)requestListeners;
252
253 ArrayList<T> result = new ArrayList<>();
254 for (RequestListener listener : requestListeners)
255 if (type.isInstance(listener))
256 result.add((T)listener);
257 return result;
258 }
259
260 @Override
261 public Request listener(Request.Listener listener)
262 {
263 this.requestListeners.add(listener);
264 return this;
265 }
266
267 @Override
268 public Request onRequestQueued(QueuedListener listener)
269 {
270 this.requestListeners.add(listener);
271 return this;
272 }
273
274 @Override
275 public Request onRequestBegin(BeginListener listener)
276 {
277 this.requestListeners.add(listener);
278 return this;
279 }
280
281 @Override
282 public Request onRequestHeaders(HeadersListener listener)
283 {
284 this.requestListeners.add(listener);
285 return this;
286 }
287
288 @Override
289 public Request onRequestCommit(CommitListener listener)
290 {
291 this.requestListeners.add(listener);
292 return this;
293 }
294
295 @Override
296 public Request onRequestContent(ContentListener listener)
297 {
298 this.requestListeners.add(listener);
299 return this;
300 }
301
302 @Override
303 public Request onRequestSuccess(SuccessListener listener)
304 {
305 this.requestListeners.add(listener);
306 return this;
307 }
308
309 @Override
310 public Request onRequestFailure(FailureListener listener)
311 {
312 this.requestListeners.add(listener);
313 return this;
314 }
315
316 @Override
317 public Request onResponseBegin(Response.BeginListener listener)
318 {
319 this.responseListeners.add(listener);
320 return this;
321 }
322
323 @Override
324 public Request onResponseHeader(Response.HeaderListener listener)
325 {
326 this.responseListeners.add(listener);
327 return this;
328 }
329
330 @Override
331 public Request onResponseHeaders(Response.HeadersListener listener)
332 {
333 this.responseListeners.add(listener);
334 return this;
335 }
336
337 @Override
338 public Request onResponseContent(Response.ContentListener listener)
339 {
340 this.responseListeners.add(listener);
341 return this;
342 }
343
344 @Override
345 public Request onResponseSuccess(Response.SuccessListener listener)
346 {
347 this.responseListeners.add(listener);
348 return this;
349 }
350
351 @Override
352 public Request onResponseFailure(Response.FailureListener listener)
353 {
354 this.responseListeners.add(listener);
355 return this;
356 }
357
358 @Override
359 public ContentProvider getContent()
360 {
361 return content;
362 }
363
364 @Override
365 public Request content(ContentProvider content)
366 {
367 return content(content, null);
368 }
369
370 @Override
371 public Request content(ContentProvider content, String contentType)
372 {
373 if (contentType != null)
374 header(HttpHeader.CONTENT_TYPE.asString(), contentType);
375 this.content = content;
376 return this;
377 }
378
379 @Override
380 public Request file(Path file) throws IOException
381 {
382 return file(file, "application/octet-stream");
383 }
384
385 @Override
386 public Request file(Path file, String contentType) throws IOException
387 {
388 if (contentType != null)
389 header(HttpHeader.CONTENT_TYPE.asString(), contentType);
390 return content(new PathContentProvider(file));
391 }
392
393 @Override
394 public boolean isFollowRedirects()
395 {
396 return followRedirects;
397 }
398
399 @Override
400 public Request followRedirects(boolean follow)
401 {
402 this.followRedirects = follow;
403 return this;
404 }
405
406 @Override
407 public long getIdleTimeout()
408 {
409 return idleTimeout;
410 }
411
412 @Override
413 public Request idleTimeout(long timeout, TimeUnit unit)
414 {
415 this.idleTimeout = unit.toMillis(timeout);
416 return this;
417 }
418
419 @Override
420 public long getTimeout()
421 {
422 return timeout;
423 }
424
425 @Override
426 public Request timeout(long timeout, TimeUnit unit)
427 {
428 this.timeout = unit.toMillis(timeout);
429 return this;
430 }
431
432 @Override
433 public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
434 {
435 FutureResponseListener listener = new FutureResponseListener(this);
436 send(this, listener);
437
438 long timeout = getTimeout();
439 if (timeout <= 0)
440 return listener.get();
441
442 try
443 {
444 return listener.get(timeout, TimeUnit.MILLISECONDS);
445 }
446 catch (InterruptedException | TimeoutException x)
447 {
448
449
450 abort(x);
451 throw x;
452 }
453 }
454
455 @Override
456 public void send(Response.CompleteListener listener)
457 {
458 if (getTimeout() > 0)
459 {
460 TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(this);
461 timeoutListener.schedule(client.getScheduler());
462 responseListeners.add(timeoutListener);
463 }
464 send(this, listener);
465 }
466
467 private void send(Request request, Response.CompleteListener listener)
468 {
469 if (listener != null)
470 responseListeners.add(listener);
471 client.send(request, responseListeners);
472 }
473
474 @Override
475 public boolean abort(Throwable cause)
476 {
477 aborted = Objects.requireNonNull(cause);
478
479 HttpConversation conversation = client.getConversation(getConversationID(), false);
480 return conversation != null && conversation.abort(cause);
481 }
482
483 @Override
484 public Throwable getAbortCause()
485 {
486 return aborted;
487 }
488
489 private URI buildURI()
490 {
491 String path = getPath();
492 URI result = URI.create(path);
493 if (!result.isAbsolute())
494 result = URI.create(getScheme() + "://" + getHost() + ":" + getPort() + path);
495 return result;
496 }
497
498 @Override
499 public String toString()
500 {
501 return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
502 }
503 }