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