View Javadoc

1   /*
2    * Copyright (C) 2017-2019 Centre National d'Etudes Spatiales (CNES).
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 3.0 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17   * MA 02110-1301  USA
18   */
19  package org.restlet.ext.httpclient4;
20  
21  import java.io.FilterInputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.nio.channels.ReadableByteChannel;
28  import java.nio.channels.WritableByteChannel;
29  import java.util.logging.Level;
30  import org.apache.http.Header;
31  import org.apache.http.HttpResponse;
32  import org.apache.http.client.methods.HttpDelete;
33  import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
34  import org.apache.http.client.methods.HttpGet;
35  import org.apache.http.client.methods.HttpHead;
36  import org.apache.http.client.methods.HttpOptions;
37  import org.apache.http.client.methods.HttpPost;
38  import org.apache.http.client.methods.HttpPut;
39  import org.apache.http.client.methods.HttpTrace;
40  import org.apache.http.client.methods.HttpUriRequest;
41  import org.apache.http.entity.AbstractHttpEntity;
42  import org.apache.http.message.BasicHeader;
43  import org.apache.http.util.EntityUtils;
44  import org.restlet.Request;
45  import org.restlet.Response;
46  import org.restlet.Uniform;
47  import org.restlet.data.Method;
48  import org.restlet.data.Protocol;
49  import org.restlet.data.Status;
50  import org.restlet.engine.adapter.ClientCall;
51  import org.restlet.engine.header.HeaderConstants;
52  import org.restlet.representation.Representation;
53  import org.restlet.util.Series;
54  
55  /**
56   * Facade between HTTP call and the HTTP plugin.
57   *
58   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
59   */
60  public class HttpMethodCall extends ClientCall {
61  
62      /**
63       * The associated HTTP client.
64       */
65      private volatile HttpDOIClientHelper clientHelper;
66  
67      /**
68       * The wrapped HTTP request.
69       */
70      private volatile HttpUriRequest httpRequest;
71  
72      /**
73       * The wrapped HTTP response.
74       */
75      private volatile HttpResponse httpResponse;
76  
77      /**
78       * Indicates if the response headers were added.
79       */
80      private volatile boolean responseHeadersAdded;
81  
82      /**
83       * Constructor.
84       *
85       * @param helper The parent HTTP client helper.
86       * @param method The method name.
87       * @param requestUri The request URI.
88       * @param hasEntity Indicates if the call will have an entity to send to the
89       * server.
90       * @throws IOException when an error happens
91       */
92      public HttpMethodCall(final HttpDOIClientHelper helper, final String method,
93              final String requestUri, boolean hasEntity) throws IOException {
94          super(helper, method, requestUri);
95          this.clientHelper = helper;
96  
97          if (requestUri.startsWith("http")) {
98              if (method.equalsIgnoreCase(Method.GET.getName())) {
99                  this.httpRequest = new HttpGet(requestUri);
100             } else if (method.equalsIgnoreCase(Method.POST.getName())) {
101                 this.httpRequest = new HttpPost(requestUri);
102             } else if (method.equalsIgnoreCase(Method.PUT.getName())) {
103                 this.httpRequest = new HttpPut(requestUri);
104             } else if (method.equalsIgnoreCase(Method.HEAD.getName())) {
105                 this.httpRequest = new HttpHead(requestUri);
106             } else if (method.equalsIgnoreCase(Method.DELETE.getName())) {
107                 this.httpRequest = new HttpDelete(requestUri);
108                 if (hasEntity) {
109                     getLogger()
110                             .warning(
111                                     "The current DELETE request provides an entity that may be not "
112                                     + "supported by the Apache HTTP Client library. If you "
113                                     + "face such issues, you can still move to another HTTP"
114                                     + " client connector.");
115                 }
116             } else if (method.equalsIgnoreCase(Method.OPTIONS.getName())) {
117                 this.httpRequest = new HttpOptions(requestUri);
118             } else if (method.equalsIgnoreCase(Method.TRACE.getName())) {
119                 this.httpRequest = new HttpTrace(requestUri);
120             } else {
121                 this.httpRequest = new HttpEntityEnclosingRequestBase() {
122 
123                     /**
124                      * {@inheritDoc }
125                      */
126                     @Override
127                     public String getMethod() {
128                         return method;
129                     }
130 
131                     /**
132                      * {@inheritDoc }
133                      */
134                     @Override
135                     public URI getURI() {
136                         URI uri;
137                         try {
138                             uri = new URI(requestUri);
139                         } catch (URISyntaxException e) {
140                             getLogger().log(Level.WARNING,
141                                     "Invalid URI syntax", e);
142                             uri = null;
143                         }
144                         return uri;
145                     }
146                 };
147             }
148 
149             this.responseHeadersAdded = false;
150             setConfidential(this.httpRequest.getURI().getScheme()
151                     .equalsIgnoreCase(Protocol.HTTPS.getSchemeName()));
152         } else {
153             throw new IllegalArgumentException(
154                     "Only HTTP or HTTPS resource URIs are allowed here");
155         }
156     }
157 
158     /**
159      * Returns the HTTP request.
160      *
161      * @return The HTTP request.
162      */
163     public HttpUriRequest getHttpRequest() {
164         return this.httpRequest;
165     }
166 
167     /**
168      * Returns the HTTP response.
169      *
170      * @return The HTTP response.
171      */
172     public HttpResponse getHttpResponse() {
173         return this.httpResponse;
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     @Override
180     public String getReasonPhrase() {
181         final String reasonPhrase;
182         if ((getHttpResponse() != null)
183                 && (getHttpResponse().getStatusLine() != null)) {
184             reasonPhrase = getHttpResponse().getStatusLine().getReasonPhrase();
185         } else {
186             reasonPhrase = null;
187         }
188         return reasonPhrase;
189     }
190 
191     /**
192      * Not used.
193      *
194      * @return null
195      */
196     @Override
197     public WritableByteChannel getRequestEntityChannel() {
198         return null;
199     }
200 
201     /**
202      * Not used.
203      *
204      * @return null
205      */
206     @Override
207     public OutputStream getRequestEntityStream() {
208         return null;
209     }
210 
211     /**
212      * Not used.
213      *
214      * @return null
215      */
216     @Override
217     public OutputStream getRequestHeadStream() {
218         return null;
219     }
220 
221     /**
222      * Not used.
223      *
224      * @return null
225      */
226     @Override
227     public ReadableByteChannel getResponseEntityChannel(final long size) {
228         return null;
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     @Override
235     public InputStream getResponseEntityStream(final long size) {
236         InputStream result = null;
237 
238         try {
239             //EntityUtils.toString(getHttpResponse().getEntity());
240             // Return a wrapper filter that will release the connection when
241             // needed
242             final InputStream responseStream = (getHttpResponse() == null) ? null
243                     : (getHttpResponse().getEntity() == null) ? null
244                             : getHttpResponse().getEntity().getContent();
245             if (responseStream != null) {
246                 result = new FilterInputStream(responseStream) {
247                     @Override
248                     public void close() throws IOException {
249                         super.close();
250                         EntityUtils.consume(getHttpResponse().getEntity());
251                     }
252                 };
253             }
254         } catch (IOException ioe) {
255             this.clientHelper
256                     .getLogger()
257                     .log(Level.WARNING,
258                             "An error occurred during the communication with the remote HTTP server.",
259                             ioe);
260         }
261 
262         return result;
263     }
264 
265     /**
266      * {@inheritDoc}
267      */
268     @Override
269     public Series<org.restlet.data.Header> getResponseHeaders() {
270         final Series<org.restlet.data.Header> result = super.getResponseHeaders();
271 
272         if (!this.responseHeadersAdded) {
273             if ((getHttpResponse() != null)
274                     && (getHttpResponse().getAllHeaders() != null)) {
275                 for (Header header : getHttpResponse().getAllHeaders()) {
276                     result.add(header.getName(), header.getValue());
277                 }
278             }
279 
280             this.responseHeadersAdded = true;
281         }
282 
283         return result;
284     }
285 
286     /**
287      * {@inheritDoc}
288      */
289     @Override
290     public String getServerAddress() {
291         return getHttpRequest().getURI().getHost();
292     }
293 
294     /**
295      * {@inheritDoc}
296      */
297     @Override
298     public int getStatusCode() {
299         final int statusCode;
300         if (getHttpResponse() != null
301                 && getHttpResponse().getStatusLine() != null) {
302             statusCode = getHttpResponse().getStatusLine().getStatusCode();
303         } else {
304             statusCode = Status.CONNECTOR_ERROR_COMMUNICATION.getCode();
305         }
306         return statusCode;
307     }
308 
309     /**
310      * {@inheritDoc}
311      */
312     @Override
313     public Status sendRequest(final Request request) {
314         Status result;
315 
316         try {
317             final Representation entity = request.getEntity();
318 
319             // Set the request headers
320             for (final org.restlet.data.Header header : getRequestHeaders()) {
321                 if (!header.getName().equals(
322                         HeaderConstants.HEADER_CONTENT_LENGTH)) {
323                     getHttpRequest().addHeader(header.getName(),
324                             header.getValue());
325                 }
326             }
327 
328             // For those method that accept enclosing entities, provide it
329             if ((entity != null)
330                     && (getHttpRequest() instanceof HttpEntityEnclosingRequestBase)) {
331                 final HttpEntityEnclosingRequestBase eem = (HttpEntityEnclosingRequestBase) getHttpRequest();
332                 eem.setEntity(new AbstractHttpEntity() {
333                     /**
334                      * {@inheritDoc}
335                      */
336                     @Override
337                     public InputStream getContent() throws IOException,
338                             IllegalStateException {
339                         return entity.getStream();
340                     }
341 
342                     /**
343                      * {@inheritDoc}
344                      */
345                     @Override
346                     public long getContentLength() {
347                         return entity.getSize();
348                     }
349 
350                     /**
351                      * {@inheritDoc}
352                      */
353                     @Override
354                     public Header getContentType() {
355                         return new BasicHeader(
356                                 HeaderConstants.HEADER_CONTENT_TYPE, (entity
357                                 .getMediaType() != null) ? entity
358                                         .getMediaType().toString() : null);
359                     }
360 
361                     /**
362                      * {@inheritDoc}
363                      */
364                     @Override
365                     public boolean isRepeatable() {
366                         return !entity.isTransient();
367                     }
368 
369                     /**
370                      * {@inheritDoc}
371                      */
372                     @Override
373                     public boolean isStreaming() {
374                         return (entity.getSize() == Representation.UNKNOWN_SIZE);
375                     }
376 
377                     /**
378                      * {@inheritDoc}
379                      */
380                     @Override
381                     public void writeTo(final OutputStream os) throws IOException {
382                         entity.write(os);
383                         os.flush();
384                     }
385                 });
386             }
387 
388             // Ensure that the connection is active
389             this.httpResponse = this.clientHelper.getHttpClient().execute(
390                     getHttpRequest());
391             //EntityUtils.toString(this.httpResponse.getEntity());
392             // Now we can access the status code, this MUST happen after closing
393             // any open request stream.
394             result = new Status(getStatusCode(), getReasonPhrase());
395         } catch (IOException ioe) {
396             this.clientHelper
397                     .getLogger()
398                     .log(Level.WARNING,
399                             "An error occurred during the communication with the remote HTTP server.",
400                             ioe);
401             result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, ioe);
402 
403             // Release the connection
404             getHttpRequest().abort();
405         }
406 
407         return result;
408     }
409 
410     /**
411      * {@inheritDoc}
412      */
413     @Override
414     public void sendRequest(final Request request, final Response response, final Uniform callback)
415             throws Exception {
416         sendRequest(request);
417 
418         if (request.getOnSent() != null) {
419             request.getOnSent().handle(request, response);
420         }
421 
422         if (callback != null) {
423             // Transmit to the callback, if any.
424             callback.handle(request, response);
425         }
426     }
427 
428 }