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.charset.UnsupportedCharsetException;
27 import java.nio.file.Path;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.TimeoutException;
38 import java.util.concurrent.atomic.AtomicLong;
39
40 import org.eclipse.jetty.client.api.ContentProvider;
41 import org.eclipse.jetty.client.api.ContentResponse;
42 import org.eclipse.jetty.client.api.Request;
43 import org.eclipse.jetty.client.api.Response;
44 import org.eclipse.jetty.client.util.FutureResponseListener;
45 import org.eclipse.jetty.client.util.PathContentProvider;
46 import org.eclipse.jetty.http.HttpField;
47 import org.eclipse.jetty.http.HttpFields;
48 import org.eclipse.jetty.http.HttpHeader;
49 import org.eclipse.jetty.http.HttpMethod;
50 import org.eclipse.jetty.http.HttpVersion;
51 import org.eclipse.jetty.util.Fields;
52
53 public class HttpRequest implements Request
54 {
55 private static final AtomicLong ids = new AtomicLong();
56
57 private final HttpFields headers = new HttpFields();
58 private final Fields params = new Fields();
59 private final Map<String, Object> attributes = new HashMap<>();
60 private final List<RequestListener> requestListeners = new ArrayList<>();
61 private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
62 private final HttpClient client;
63 private final long conversation;
64 private final String host;
65 private final int port;
66 private URI uri;
67 private String scheme;
68 private String path;
69 private String query;
70 private String method = HttpMethod.GET.asString();
71 private HttpVersion version = HttpVersion.HTTP_1_1;
72 private long idleTimeout;
73 private long timeout;
74 private ContentProvider content;
75 private boolean followRedirects;
76 private volatile Throwable aborted;
77
78 public HttpRequest(HttpClient client, URI uri)
79 {
80 this(client, ids.incrementAndGet(), uri);
81 }
82
83 protected HttpRequest(HttpClient client, long conversation, URI uri)
84 {
85 this.client = client;
86 this.conversation = conversation;
87 scheme = uri.getScheme();
88 host = client.normalizeHost(uri.getHost());
89 port = client.normalizePort(scheme, uri.getPort());
90 path = uri.getRawPath();
91 query = uri.getRawQuery();
92 extractParams(query);
93 followRedirects(client.isFollowRedirects());
94 idleTimeout = client.getIdleTimeout();
95 HttpField acceptEncodingField = client.getAcceptEncodingField();
96 if (acceptEncodingField != null)
97 headers.put(acceptEncodingField);
98 headers.put(client.getUserAgentField());
99 }
100
101 @Override
102 public long getConversationID()
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 params.add(name, value);
212 this.query = buildQuery();
213 this.uri = null;
214 return this;
215 }
216
217 @Override
218 public Fields getParams()
219 {
220 return new Fields(params, true);
221 }
222
223 @Override
224 public String getAgent()
225 {
226 return headers.get(HttpHeader.USER_AGENT);
227 }
228
229 @Override
230 public Request agent(String agent)
231 {
232 headers.put(HttpHeader.USER_AGENT, agent);
233 return this;
234 }
235
236 @Override
237 public Request header(String name, String value)
238 {
239 if (value == null)
240 headers.remove(name);
241 else
242 headers.add(name, value);
243 return this;
244 }
245
246 @Override
247 public Request header(HttpHeader header, String value)
248 {
249 if (value == null)
250 headers.remove(header);
251 else
252 headers.add(header, value);
253 return this;
254 }
255
256 @Override
257 public Request attribute(String name, Object value)
258 {
259 attributes.put(name, value);
260 return this;
261 }
262
263 @Override
264 public Map<String, Object> getAttributes()
265 {
266 return attributes;
267 }
268
269 @Override
270 public HttpFields getHeaders()
271 {
272 return headers;
273 }
274
275 @Override
276 @SuppressWarnings("unchecked")
277 public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
278 {
279
280
281 if (type == null)
282 return (List<T>)requestListeners;
283
284 ArrayList<T> result = new ArrayList<>();
285 for (RequestListener listener : requestListeners)
286 if (type.isInstance(listener))
287 result.add((T)listener);
288 return result;
289 }
290
291 @Override
292 public Request listener(Request.Listener listener)
293 {
294 this.requestListeners.add(listener);
295 return this;
296 }
297
298 @Override
299 public Request onRequestQueued(QueuedListener listener)
300 {
301 this.requestListeners.add(listener);
302 return this;
303 }
304
305 @Override
306 public Request onRequestBegin(BeginListener listener)
307 {
308 this.requestListeners.add(listener);
309 return this;
310 }
311
312 @Override
313 public Request onRequestHeaders(HeadersListener listener)
314 {
315 this.requestListeners.add(listener);
316 return this;
317 }
318
319 @Override
320 public Request onRequestCommit(CommitListener listener)
321 {
322 this.requestListeners.add(listener);
323 return this;
324 }
325
326 @Override
327 public Request onRequestContent(ContentListener listener)
328 {
329 this.requestListeners.add(listener);
330 return this;
331 }
332
333 @Override
334 public Request onRequestSuccess(SuccessListener listener)
335 {
336 this.requestListeners.add(listener);
337 return this;
338 }
339
340 @Override
341 public Request onRequestFailure(FailureListener listener)
342 {
343 this.requestListeners.add(listener);
344 return this;
345 }
346
347 @Override
348 public Request onResponseBegin(Response.BeginListener listener)
349 {
350 this.responseListeners.add(listener);
351 return this;
352 }
353
354 @Override
355 public Request onResponseHeader(Response.HeaderListener listener)
356 {
357 this.responseListeners.add(listener);
358 return this;
359 }
360
361 @Override
362 public Request onResponseHeaders(Response.HeadersListener listener)
363 {
364 this.responseListeners.add(listener);
365 return this;
366 }
367
368 @Override
369 public Request onResponseContent(Response.ContentListener listener)
370 {
371 this.responseListeners.add(listener);
372 return this;
373 }
374
375 @Override
376 public Request onResponseSuccess(Response.SuccessListener listener)
377 {
378 this.responseListeners.add(listener);
379 return this;
380 }
381
382 @Override
383 public Request onResponseFailure(Response.FailureListener listener)
384 {
385 this.responseListeners.add(listener);
386 return this;
387 }
388
389 @Override
390 public ContentProvider getContent()
391 {
392 return content;
393 }
394
395 @Override
396 public Request content(ContentProvider content)
397 {
398 return content(content, null);
399 }
400
401 @Override
402 public Request content(ContentProvider content, String contentType)
403 {
404 if (contentType != null)
405 header(HttpHeader.CONTENT_TYPE, contentType);
406 this.content = content;
407 return this;
408 }
409
410 @Override
411 public Request file(Path file) throws IOException
412 {
413 return file(file, "application/octet-stream");
414 }
415
416 @Override
417 public Request file(Path file, String contentType) throws IOException
418 {
419 if (contentType != null)
420 header(HttpHeader.CONTENT_TYPE, contentType);
421 return content(new PathContentProvider(file));
422 }
423
424 @Override
425 public boolean isFollowRedirects()
426 {
427 return followRedirects;
428 }
429
430 @Override
431 public Request followRedirects(boolean follow)
432 {
433 this.followRedirects = follow;
434 return this;
435 }
436
437 @Override
438 public long getIdleTimeout()
439 {
440 return idleTimeout;
441 }
442
443 @Override
444 public Request idleTimeout(long timeout, TimeUnit unit)
445 {
446 this.idleTimeout = unit.toMillis(timeout);
447 return this;
448 }
449
450 @Override
451 public long getTimeout()
452 {
453 return timeout;
454 }
455
456 @Override
457 public Request timeout(long timeout, TimeUnit unit)
458 {
459 this.timeout = unit.toMillis(timeout);
460 return this;
461 }
462
463 @Override
464 public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
465 {
466 FutureResponseListener listener = new FutureResponseListener(this);
467 send(this, listener);
468
469 try
470 {
471 long timeout = getTimeout();
472 if (timeout <= 0)
473 return listener.get();
474
475 return listener.get(timeout, TimeUnit.MILLISECONDS);
476 }
477 catch (InterruptedException | TimeoutException x)
478 {
479
480
481 abort(x);
482 throw x;
483 }
484 }
485
486 @Override
487 public void send(Response.CompleteListener listener)
488 {
489 if (getTimeout() > 0)
490 {
491 TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(this);
492 timeoutListener.schedule(client.getScheduler());
493 responseListeners.add(timeoutListener);
494 }
495 send(this, listener);
496 }
497
498 private void send(Request request, Response.CompleteListener listener)
499 {
500 if (listener != null)
501 responseListeners.add(listener);
502 client.send(request, responseListeners);
503 }
504
505 @Override
506 public boolean abort(Throwable cause)
507 {
508 aborted = Objects.requireNonNull(cause);
509
510 HttpConversation conversation = client.getConversation(getConversationID(), false);
511 return conversation != null && conversation.abort(cause);
512 }
513
514 @Override
515 public Throwable getAbortCause()
516 {
517 return aborted;
518 }
519
520 private String buildQuery()
521 {
522 StringBuilder result = new StringBuilder();
523 for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
524 {
525 Fields.Field field = iterator.next();
526 String[] values = field.values();
527 for (int i = 0; i < values.length; ++i)
528 {
529 if (i > 0)
530 result.append("&");
531 result.append(field.name()).append("=");
532 result.append(urlEncode(values[i]));
533 }
534 if (iterator.hasNext())
535 result.append("&");
536 }
537 return result.toString();
538 }
539
540 private String urlEncode(String value)
541 {
542 String encoding = "UTF-8";
543 try
544 {
545 return URLEncoder.encode(value, encoding);
546 }
547 catch (UnsupportedEncodingException e)
548 {
549 throw new UnsupportedCharsetException(encoding);
550 }
551 }
552
553 private void extractParams(String query)
554 {
555 if (query != null)
556 {
557 for (String nameValue : query.split("&"))
558 {
559 String[] parts = nameValue.split("=");
560 if (parts.length > 0)
561 {
562 String name = parts[0];
563 if (name.trim().length() == 0)
564 continue;
565 param(name, parts.length < 2 ? "" : urlDecode(parts[1]));
566 }
567 }
568 }
569 }
570
571 private String urlDecode(String value)
572 {
573 String charset = "UTF-8";
574 try
575 {
576 return URLDecoder.decode(value, charset);
577 }
578 catch (UnsupportedEncodingException x)
579 {
580 throw new UnsupportedCharsetException(charset);
581 }
582 }
583
584 private URI buildURI(boolean withQuery)
585 {
586 String path = getPath();
587 String query = getQuery();
588 if (query != null && withQuery)
589 path += "?" + query;
590 URI result = URI.create(path);
591 if (!result.isAbsolute() && !result.isOpaque())
592 result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
593 return result;
594 }
595
596 @Override
597 public String toString()
598 {
599 return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
600 }
601 }