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