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.nio.charset.UnsupportedCharsetException;
26  import java.nio.file.Path;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Objects;
32  import java.util.concurrent.ExecutionException;
33  import java.util.concurrent.TimeUnit;
34  import java.util.concurrent.TimeoutException;
35  import java.util.concurrent.atomic.AtomicLong;
36  
37  import org.eclipse.jetty.client.api.ContentProvider;
38  import org.eclipse.jetty.client.api.ContentResponse;
39  import org.eclipse.jetty.client.api.Request;
40  import org.eclipse.jetty.client.api.Response;
41  import org.eclipse.jetty.client.util.FutureResponseListener;
42  import org.eclipse.jetty.client.util.PathContentProvider;
43  import org.eclipse.jetty.http.HttpFields;
44  import org.eclipse.jetty.http.HttpHeader;
45  import org.eclipse.jetty.http.HttpMethod;
46  import org.eclipse.jetty.http.HttpVersion;
47  import org.eclipse.jetty.util.Fields;
48  
49  public class HttpRequest implements Request
50  {
51      private static final AtomicLong ids = new AtomicLong();
52  
53      private final HttpFields headers = new HttpFields();
54      private final Fields params = new Fields();
55      private final Map<String, Object> attributes = new HashMap<>();
56      private final List<RequestListener> requestListeners = new ArrayList<>();
57      private final List<Response.ResponseListener> responseListeners = new ArrayList<>();
58      private final HttpClient client;
59      private final long conversation;
60      private final String host;
61      private final int port;
62      private URI uri;
63      private String scheme;
64      private String path;
65      private HttpMethod method;
66      private HttpVersion version;
67      private long idleTimeout;
68      private long timeout;
69      private ContentProvider content;
70      private boolean followRedirects;
71      private volatile Throwable aborted;
72  
73      public HttpRequest(HttpClient client, URI uri)
74      {
75          this(client, ids.incrementAndGet(), uri);
76      }
77  
78      protected HttpRequest(HttpClient client, long conversation, URI uri)
79      {
80          this.client = client;
81          this.conversation = conversation;
82          scheme = uri.getScheme();
83          host = uri.getHost();
84          port = client.normalizePort(scheme, uri.getPort());
85          path = uri.getRawPath();
86          String query = uri.getRawQuery();
87          if (query != null)
88          {
89              for (String nameValue : query.split("&"))
90              {
91                  String[] parts = nameValue.split("=");
92                  param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
93              }
94          }
95          this.uri = buildURI();
96          followRedirects(client.isFollowRedirects());
97      }
98  
99      private String urlDecode(String value)
100     {
101         String charset = "UTF-8";
102         try
103         {
104             return URLDecoder.decode(value, charset);
105         }
106         catch (UnsupportedEncodingException x)
107         {
108             throw new UnsupportedCharsetException(charset);
109         }
110     }
111 
112     @Override
113     public long getConversationID()
114     {
115         return conversation;
116     }
117 
118     @Override
119     public String getScheme()
120     {
121         return scheme;
122     }
123 
124     @Override
125     public Request scheme(String scheme)
126     {
127         this.scheme = scheme;
128         this.uri = buildURI();
129         return this;
130     }
131 
132     @Override
133     public String getHost()
134     {
135         return host;
136     }
137 
138     @Override
139     public int getPort()
140     {
141         return port;
142     }
143 
144     @Override
145     public HttpMethod getMethod()
146     {
147         return method;
148     }
149 
150     @Override
151     public Request method(HttpMethod method)
152     {
153         this.method = method;
154         return this;
155     }
156 
157     @Override
158     public String getPath()
159     {
160         return path;
161     }
162 
163     @Override
164     public Request path(String path)
165     {
166         this.path = path;
167         this.uri = buildURI();
168         return this;
169     }
170 
171     @Override
172     public URI getURI()
173     {
174         return uri;
175     }
176 
177     @Override
178     public HttpVersion getVersion()
179     {
180         return version;
181     }
182 
183     @Override
184     public Request version(HttpVersion version)
185     {
186         this.version = version;
187         return this;
188     }
189 
190     @Override
191     public Request param(String name, String value)
192     {
193         params.add(name, value);
194         return this;
195     }
196 
197     @Override
198     public Fields getParams()
199     {
200         return params;
201     }
202 
203     @Override
204     public String getAgent()
205     {
206         return headers.get(HttpHeader.USER_AGENT);
207     }
208 
209     @Override
210     public Request agent(String agent)
211     {
212         headers.put(HttpHeader.USER_AGENT, agent);
213         return this;
214     }
215 
216     @Override
217     public Request header(String name, String value)
218     {
219         if (value == null)
220             headers.remove(name);
221         else
222             headers.add(name, value);
223         return this;
224     }
225 
226     @Override
227     public Request attribute(String name, Object value)
228     {
229         attributes.put(name, value);
230         return this;
231     }
232 
233     @Override
234     public Map<String, Object> getAttributes()
235     {
236         return attributes;
237     }
238 
239     @Override
240     public HttpFields getHeaders()
241     {
242         return headers;
243     }
244 
245     @Override
246     public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
247     {
248         // This method is invoked often in a request/response conversation,
249         // so we avoid allocation if there is no need to filter.
250         if (type == null)
251             return (List<T>)requestListeners;
252 
253         ArrayList<T> result = new ArrayList<>();
254         for (RequestListener listener : requestListeners)
255             if (type.isInstance(listener))
256                 result.add((T)listener);
257         return result;
258     }
259 
260     @Override
261     public Request listener(Request.Listener listener)
262     {
263         this.requestListeners.add(listener);
264         return this;
265     }
266 
267     @Override
268     public Request onRequestQueued(QueuedListener listener)
269     {
270         this.requestListeners.add(listener);
271         return this;
272     }
273 
274     @Override
275     public Request onRequestBegin(BeginListener listener)
276     {
277         this.requestListeners.add(listener);
278         return this;
279     }
280 
281     @Override
282     public Request onRequestHeaders(HeadersListener listener)
283     {
284         this.requestListeners.add(listener);
285         return this;
286     }
287 
288     @Override
289     public Request onRequestCommit(CommitListener listener)
290     {
291         this.requestListeners.add(listener);
292         return this;
293     }
294 
295     @Override
296     public Request onRequestContent(ContentListener listener)
297     {
298         this.requestListeners.add(listener);
299         return this;
300     }
301 
302     @Override
303     public Request onRequestSuccess(SuccessListener listener)
304     {
305         this.requestListeners.add(listener);
306         return this;
307     }
308 
309     @Override
310     public Request onRequestFailure(FailureListener listener)
311     {
312         this.requestListeners.add(listener);
313         return this;
314     }
315 
316     @Override
317     public Request onResponseBegin(Response.BeginListener listener)
318     {
319         this.responseListeners.add(listener);
320         return this;
321     }
322 
323     @Override
324     public Request onResponseHeader(Response.HeaderListener listener)
325     {
326         this.responseListeners.add(listener);
327         return this;
328     }
329 
330     @Override
331     public Request onResponseHeaders(Response.HeadersListener listener)
332     {
333         this.responseListeners.add(listener);
334         return this;
335     }
336 
337     @Override
338     public Request onResponseContent(Response.ContentListener listener)
339     {
340         this.responseListeners.add(listener);
341         return this;
342     }
343 
344     @Override
345     public Request onResponseSuccess(Response.SuccessListener listener)
346     {
347         this.responseListeners.add(listener);
348         return this;
349     }
350 
351     @Override
352     public Request onResponseFailure(Response.FailureListener listener)
353     {
354         this.responseListeners.add(listener);
355         return this;
356     }
357 
358     @Override
359     public ContentProvider getContent()
360     {
361         return content;
362     }
363 
364     @Override
365     public Request content(ContentProvider content)
366     {
367         return content(content, null);
368     }
369 
370     @Override
371     public Request content(ContentProvider content, String contentType)
372     {
373         if (contentType != null)
374             header(HttpHeader.CONTENT_TYPE.asString(), contentType);
375         this.content = content;
376         return this;
377     }
378 
379     @Override
380     public Request file(Path file) throws IOException
381     {
382         return file(file, "application/octet-stream");
383     }
384 
385     @Override
386     public Request file(Path file, String contentType) throws IOException
387     {
388         if (contentType != null)
389             header(HttpHeader.CONTENT_TYPE.asString(), contentType);
390         return content(new PathContentProvider(file));
391     }
392 
393     @Override
394     public boolean isFollowRedirects()
395     {
396         return followRedirects;
397     }
398 
399     @Override
400     public Request followRedirects(boolean follow)
401     {
402         this.followRedirects = follow;
403         return this;
404     }
405 
406     @Override
407     public long getIdleTimeout()
408     {
409         return idleTimeout;
410     }
411 
412     @Override
413     public Request idleTimeout(long timeout, TimeUnit unit)
414     {
415         this.idleTimeout = unit.toMillis(timeout);
416         return this;
417     }
418 
419     @Override
420     public long getTimeout()
421     {
422         return timeout;
423     }
424 
425     @Override
426     public Request timeout(long timeout, TimeUnit unit)
427     {
428         this.timeout = unit.toMillis(timeout);
429         return this;
430     }
431 
432     @Override
433     public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException
434     {
435         FutureResponseListener listener = new FutureResponseListener(this);
436         send(this, listener);
437 
438         long timeout = getTimeout();
439         if (timeout <= 0)
440             return listener.get();
441 
442         try
443         {
444             return listener.get(timeout, TimeUnit.MILLISECONDS);
445         }
446         catch (InterruptedException | TimeoutException x)
447         {
448             // Differently from the Future, the semantic of this method is that if
449             // the send() is interrupted or times out, we abort the request.
450             abort(x);
451             throw x;
452         }
453     }
454 
455     @Override
456     public void send(Response.CompleteListener listener)
457     {
458         if (getTimeout() > 0)
459         {
460             TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(this);
461             timeoutListener.schedule(client.getScheduler());
462             responseListeners.add(timeoutListener);
463         }
464         send(this, listener);
465     }
466 
467     private void send(Request request, Response.CompleteListener listener)
468     {
469         if (listener != null)
470             responseListeners.add(listener);
471         client.send(request, responseListeners);
472     }
473 
474     @Override
475     public boolean abort(Throwable cause)
476     {
477         aborted = Objects.requireNonNull(cause);
478         // The conversation may be null if it is already completed
479         HttpConversation conversation = client.getConversation(getConversationID(), false);
480         return conversation != null && conversation.abort(cause);
481     }
482 
483     @Override
484     public Throwable getAbortCause()
485     {
486         return aborted;
487     }
488 
489     private URI buildURI()
490     {
491         String path = getPath();
492         URI result = URI.create(path);
493         if (!result.isAbsolute())
494             result = URI.create(getScheme() + "://" + getHost() + ":" + getPort() + path);
495         return result;
496     }
497 
498     @Override
499     public String toString()
500     {
501         return String.format("%s[%s %s %s]@%x", HttpRequest.class.getSimpleName(), getMethod(), getPath(), getVersion(), hashCode());
502     }
503 }