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