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.net.URLEncoder;
26 import java.nio.ByteBuffer;
27 import java.nio.charset.UnsupportedCharsetException;
28 import java.nio.file.Path;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.concurrent.ExecutionException;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.TimeoutException;
39 import java.util.concurrent.atomic.AtomicLong;
40 import java.util.concurrent.atomic.AtomicReference;
41
42 import org.eclipse.jetty.client.api.ContentProvider;
43 import org.eclipse.jetty.client.api.ContentResponse;
44 import org.eclipse.jetty.client.api.Request;
45 import org.eclipse.jetty.client.api.Response;
46 import org.eclipse.jetty.client.api.Result;
47 import org.eclipse.jetty.client.util.FutureResponseListener;
48 import org.eclipse.jetty.client.util.PathContentProvider;
49 import org.eclipse.jetty.http.HttpField;
50 import org.eclipse.jetty.http.HttpFields;
51 import org.eclipse.jetty.http.HttpHeader;
52 import org.eclipse.jetty.http.HttpMethod;
53 import org.eclipse.jetty.http.HttpVersion;
54 import org.eclipse.jetty.util.Fields;
55
56 public class HttpRequest implements Request
57 {
58 private static final AtomicLong ids = new AtomicLong();
59
60 private final HttpFields headers = new HttpFields();
61 private final Fields params = new Fields(true);
62 private final Map<String, Object> attributes = new HashMap<>();
63 private final List<RequestListener> requestListeners = new ArrayList<>();
64 private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
65 private final AtomicReference<Throwable> aborted = new AtomicReference<>();
66 private final HttpClient client;
67 private final long conversation;
68 private final String host;
69 private final int port;
70 private URI uri;
71 private String scheme;
72 private String path;
73 private String query;
74 private String method = HttpMethod.GET.asString();
75 private HttpVersion version = HttpVersion.HTTP_1_1;
76 private long idleTimeout;
77 private long timeout;
78 private ContentProvider content;
79 private boolean followRedirects;
80
81 public HttpRequest(HttpClient client, URI uri)
82 {
83 this(client, ids.incrementAndGet(), uri);
84 }
85
86 protected HttpRequest(HttpClient client, long conversation, URI uri)
87 {
88 this.client = client;
89 this.conversation = conversation;
90 scheme = uri.getScheme();
91 host = client.normalizeHost(uri.getHost());
92 port = client.normalizePort(scheme, uri.getPort());
93 path = uri.getRawPath();
94 query = uri.getRawQuery();
95 extractParams(query);
96 followRedirects(client.isFollowRedirects());
97 idleTimeout = client.getIdleTimeout();
98 HttpField acceptEncodingField = client.getAcceptEncodingField();
99 if (acceptEncodingField != null)
100 headers.put(acceptEncodingField);
101 }
102
103 @Override
104 public long getConversationID()
105 {
106 return conversation;
107 }
108
109 @Override
110 public String getScheme()
111 {
112 return scheme;
113 }
114
115 @Override
116 public Request scheme(String scheme)
117 {
118 this.scheme = scheme;
119 this.uri = null;
120 return this;
121 }
122
123 @Override
124 public String getHost()
125 {
126 return host;
127 }
128
129 @Override
130 public int getPort()
131 {
132 return port;
133 }
134
135 @Override
136 public String getMethod()
137 {
138 return method;
139 }
140
141 @Override
142 public Request method(HttpMethod method)
143 {
144 return method(method.asString());
145 }
146
147 @Override
148 public Request method(String method)
149 {
150 this.method = Objects.requireNonNull(method).toUpperCase(Locale.ENGLISH);
151 return this;
152 }
153
154 @Override
155 public String getPath()
156 {
157 return path;
158 }
159
160 @Override
161 public Request path(String path)
162 {
163 URI uri = URI.create(path);
164 String rawPath = uri.getRawPath();
165 if (uri.isOpaque())
166 rawPath = path;
167 if (rawPath == null)
168 rawPath = "";
169 this.path = rawPath;
170 String query = uri.getRawQuery();
171 if (query != null)
172 {
173 this.query = query;
174 params.clear();
175 extractParams(query);
176 }
177 if (uri.isAbsolute())
178 this.path = buildURI(false).toString();
179 this.uri = null;
180 return this;
181 }
182
183 @Override
184 public String getQuery()
185 {
186 return query;
187 }
188
189 @Override
190 public URI getURI()
191 {
192 if (uri != null)
193 return uri;
194 return uri = buildURI(true);
195 }
196
197 @Override
198 public HttpVersion getVersion()
199 {
200 return version;
201 }
202
203 @Override
204 public Request version(HttpVersion version)
205 {
206 this.version = Objects.requireNonNull(version);
207 return this;
208 }
209
210 @Override
211 public Request param(String name, String value)
212 {
213 params.add(name, value);
214 this.query = buildQuery();
215 this.uri = null;
216 return this;
217 }
218
219 @Override
220 public Fields getParams()
221 {
222 return new Fields(params, true);
223 }
224
225 @Override
226 public String getAgent()
227 {
228 return headers.get(HttpHeader.USER_AGENT);
229 }
230
231 @Override
232 public Request agent(String agent)
233 {
234 headers.put(HttpHeader.USER_AGENT, agent);
235 return this;
236 }
237
238 @Override
239 public Request header(String name, String value)
240 {
241 if (value == null)
242 headers.remove(name);
243 else
244 headers.add(name, value);
245 return this;
246 }
247
248 @Override
249 public Request header(HttpHeader header, String value)
250 {
251 if (value == null)
252 headers.remove(header);
253 else
254 headers.add(header, value);
255 return this;
256 }
257
258 @Override
259 public Request attribute(String name, Object value)
260 {
261 attributes.put(name, value);
262 return this;
263 }
264
265 @Override
266 public Map<String, Object> getAttributes()
267 {
268 return attributes;
269 }
270
271 @Override
272 public HttpFields getHeaders()
273 {
274 return headers;
275 }
276
277 @Override
278 @SuppressWarnings("unchecked")
279 public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
280 {
281
282
283 if (type == null)
284 return (List<T>)requestListeners;
285
286 ArrayList<T> result = new ArrayList<>();
287 for (RequestListener listener : requestListeners)
288 if (type.isInstance(listener))
289 result.add((T)listener);
290 return result;
291 }
292
293 @Override
294 public Request listener(Request.Listener listener)
295 {
296 this.requestListeners.add(listener);
297 return this;
298 }
299
300 @Override
301 public Request onRequestQueued(final QueuedListener listener)
302 {
303 this.requestListeners.add(new QueuedListener()
304 {
305 @Override
306 public void onQueued(Request request)
307 {
308 listener.onQueued(request);
309 }
310 });
311 return this;
312 }
313
314 @Override
315 public Request onRequestBegin(final BeginListener listener)
316 {
317 this.requestListeners.add(new BeginListener()
318 {
319 @Override
320 public void onBegin(Request request)
321 {
322 listener.onBegin(request);
323 }
324 });
325 return this;
326 }
327
328 @Override
329 public Request onRequestHeaders(final HeadersListener listener)
330 {
331 this.requestListeners.add(new HeadersListener()
332 {
333 @Override
334 public void onHeaders(Request request)
335 {
336 listener.onHeaders(request);
337 }
338 });
339 return this;
340 }
341
342 @Override
343 public Request onRequestCommit(final CommitListener listener)
344 {
345 this.requestListeners.add(new CommitListener()
346 {
347 @Override
348 public void onCommit(Request request)
349 {
350 listener.onCommit(request);
351 }
352 });
353 return this;
354 }
355
356 @Override
357 public Request onRequestContent(final ContentListener listener)
358 {
359 this.requestListeners.add(new ContentListener()
360 {
361 @Override
362 public void onContent(Request request, ByteBuffer content)
363 {
364 listener.onContent(request, content);
365 }
366 });
367 return this;
368 }
369
370 @Override
371 public Request onRequestSuccess(final SuccessListener listener)
372 {
373 this.requestListeners.add(new SuccessListener()
374 {
375 @Override
376 public void onSuccess(Request request)
377 {
378 listener.onSuccess(request);
379 }
380 });
381 return this;
382 }
383
384 @Override
385 public Request onRequestFailure(final FailureListener listener)
386 {
387 this.requestListeners.add(new FailureListener()
388 {
389 @Override
390 public void onFailure(Request request, Throwable failure)
391 {
392 listener.onFailure(request, failure);
393 }
394 });
395 return this;
396 }
397
398 @Override
399 public Request onResponseBegin(final Response.BeginListener listener)
400 {
401 this.responseListeners.add(new Response.BeginListener()
402 {
403 @Override
404 public void onBegin(Response response)
405 {
406 listener.onBegin(response);
407 }
408 });
409 return this;
410 }
411
412 @Override
413 public Request onResponseHeader(final Response.HeaderListener listener)
414 {
415 this.responseListeners.add(new Response.HeaderListener()
416 {
417 @Override
418 public boolean onHeader(Response response, HttpField field)
419 {
420 return listener.onHeader(response, field);
421 }
422 });
423 return this;
424 }
425
426 @Override
427 public Request onResponseHeaders(final Response.HeadersListener listener)
428 {
429 this.responseListeners.add(new Response.HeadersListener()
430 {
431 @Override
432 public void onHeaders(Response response)
433 {
434 listener.onHeaders(response);
435 }
436 });
437 return this;
438 }
439
440 @Override
441 public Request onResponseContent(final Response.ContentListener listener)
442 {
443 this.responseListeners.add(new Response.ContentListener()
444 {
445 @Override
446 public void onContent(Response response, ByteBuffer content)
447 {
448 listener.onContent(response, content);
449 }
450 });
451 return this;
452 }
453
454 @Override
455 public Request onResponseSuccess(final Response.SuccessListener listener)
456 {
457 this.responseListeners.add(new Response.SuccessListener()
458 {
459 @Override
460 public void onSuccess(Response response)
461 {
462 listener.onSuccess(response);
463 }
464 });
465 return this;
466 }
467
468 @Override
469 public Request onResponseFailure(final Response.FailureListener listener)
470 {
471 this.responseListeners.add(new Response.FailureListener()
472 {
473 @Override
474 public void onFailure(Response response, Throwable failure)
475 {
476 listener.onFailure(response, failure);
477 }
478 });
479 return this;
480 }
481
482 @Override
483 public Request onComplete(final Response.CompleteListener listener)
484 {
485 this.responseListeners.add(new Response.CompleteListener()
486 {
487 @Override
488 public void onComplete(Result result)
489 {
490 listener.onComplete(result);
491 }
492 });
493 return this;
494 }
495
496 @Override
497 public ContentProvider getContent()
498 {
499 return content;
500 }
501
502 @Override
503 public Request content(ContentProvider content)
504 {
505 return content(content, null);
506 }
507
508 @Override
509 public Request content(ContentProvider content, String contentType)
510 {
511 if (contentType != null)
512 header(HttpHeader.CONTENT_TYPE, contentType);
513 this.content = content;
514 return this;
515 }
516
517 @Override
518 public Request file(Path file) throws IOException
519 {
520 return file(file, "application/octet-stream");
521 }
522
523 @Override
524 public Request file(Path file, String contentType) throws IOException
525 {
526 if (contentType != null)
527 header(HttpHeader.CONTENT_TYPE, contentType);
528 return content(new PathContentProvider(file));
529 }
530
531 @Override
532 public boolean isFollowRedirects()
533 {
534 return followRedirects;
535 }
536
537 @Override
538 public Request followRedirects(boolean follow)
539 {
540 this.followRedirects = follow;
541 return this;
542 }
543
544 @Override
545 public long getIdleTimeout()
546 {
547 return idleTimeout;
548 }
549
550 @Override
551 public Request idleTimeout(long timeout, TimeUnit unit)
552 {
553 this.idleTimeout = unit.toMillis(timeout);
554 return this;
555 }
556
557 @Override
558 public long getTimeout()
559 {
560 return timeout;
561 }
562
563 @Override
564 public Request timeout(long timeout, TimeUnit unit)
565 {
566 this.timeout = unit.toMillis(timeout);
567 return this;
568 }
569
570 @Override
571 public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
572 {
573 FutureResponseListener listener = new FutureResponseListener(this);
574 send(this, listener);
575
576 try
577 {
578 long timeout = getTimeout();
579 if (timeout <= 0)
580 return listener.get();
581
582 return listener.get(timeout, TimeUnit.MILLISECONDS);
583 }
584 catch (InterruptedException | TimeoutException x)
585 {
586
587
588 abort(x);
589 throw x;
590 }
591 }
592
593 @Override
594 public void send(Response.CompleteListener listener)
595 {
596 if (getTimeout() > 0)
597 {
598 TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(this);
599 timeoutListener.schedule(client.getScheduler());
600 responseListeners.add(timeoutListener);
601 }
602 send(this, listener);
603 }
604
605 private void send(Request request, Response.CompleteListener listener)
606 {
607 if (listener != null)
608 responseListeners.add(listener);
609 client.send(request, responseListeners);
610 }
611
612 @Override
613 public boolean abort(Throwable cause)
614 {
615 if (aborted.compareAndSet(null, Objects.requireNonNull(cause)))
616 {
617
618 HttpConversation conversation = client.getConversation(getConversationID(), false);
619 return conversation != null && conversation.abort(cause);
620 }
621 return false;
622 }
623
624 @Override
625 public Throwable getAbortCause()
626 {
627 return aborted.get();
628 }
629
630 private String buildQuery()
631 {
632 StringBuilder result = new StringBuilder();
633 for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
634 {
635 Fields.Field field = iterator.next();
636 List<String> values = field.getValues();
637 for (int i = 0; i < values.size(); ++i)
638 {
639 if (i > 0)
640 result.append("&");
641 result.append(field.getName()).append("=");
642 result.append(urlEncode(values.get(i)));
643 }
644 if (iterator.hasNext())
645 result.append("&");
646 }
647 return result.toString();
648 }
649
650 private String urlEncode(String value)
651 {
652 String encoding = "UTF-8";
653 try
654 {
655 return URLEncoder.encode(value, encoding);
656 }
657 catch (UnsupportedEncodingException e)
658 {
659 throw new UnsupportedCharsetException(encoding);
660 }
661 }
662
663 private void extractParams(String query)
664 {
665 if (query != null)
666 {
667 for (String nameValue : query.split("&"))
668 {
669 String[] parts = nameValue.split("=");
670 if (parts.length > 0)
671 {
672 String name = parts[0];
673 if (name.trim().length() == 0)
674 continue;
675 param(name, parts.length < 2 ? "" : urlDecode(parts[1]));
676 }
677 }
678 }
679 }
680
681 private String urlDecode(String value)
682 {
683 String charset = "UTF-8";
684 try
685 {
686 return URLDecoder.decode(value, charset);
687 }
688 catch (UnsupportedEncodingException x)
689 {
690 throw new UnsupportedCharsetException(charset);
691 }
692 }
693
694 private URI buildURI(boolean withQuery)
695 {
696 String path = getPath();
697 String query = getQuery();
698 if (query != null && withQuery)
699 path += "?" + query;
700 URI result = URI.create(path);
701 if (!result.isAbsolute() && !result.isOpaque())
702 result = URI.create(new Origin(getScheme(), getHost(), getPort()).asString() + path);
703 return result;
704 }
705
706 @Override
707 public String toString()
708 {
709 return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
710 }
711 }