View Javadoc

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