View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.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             // If we have an existing query string, preserve it and append the new parameter.
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         // This method is invoked often in a request/response conversation,
324         // so we avoid allocation if there is no need to filter.
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             // Differently from the Future, the semantic of this method is that if
649             // the send() is interrupted or times out, we abort the request.
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 }