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