This project has retired. For details please refer to its Attic page.
HttpUtils xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.bindings.spi.http;
20  
21  import java.io.BufferedOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStream;
26  import java.math.BigInteger;
27  import java.net.HttpURLConnection;
28  import java.net.URL;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.zip.GZIPInputStream;
34  import java.util.zip.GZIPOutputStream;
35  import java.util.zip.Inflater;
36  import java.util.zip.InflaterInputStream;
37  
38  import org.apache.chemistry.opencmis.client.bindings.impl.ClientVersion;
39  import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
40  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
41  import org.apache.chemistry.opencmis.commons.SessionParameter;
42  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
43  import org.apache.chemistry.opencmis.commons.impl.Base64;
44  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
45  import org.apache.chemistry.opencmis.commons.spi.AuthenticationProvider;
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  
49  /**
50   * HTTP helper methods.
51   */
52  public class HttpUtils {
53  
54      private static final Log log = LogFactory.getLog(HttpUtils.class);
55  
56      private static final int BUFFER_SIZE = 2 * 1024 * 1024;
57  
58      private HttpUtils() {
59      }
60  
61      public static Response invokeGET(UrlBuilder url, BindingSession session) {
62          return invoke(url, "GET", null, null, null, session, null, null);
63      }
64  
65      public static Response invokeGET(UrlBuilder url, BindingSession session, BigInteger offset, BigInteger length) {
66          return invoke(url, "GET", null, null, null, session, offset, length);
67      }
68  
69      public static Response invokePOST(UrlBuilder url, String contentType, Output writer, BindingSession session) {
70          return invoke(url, "POST", contentType, null, writer, session, null, null);
71      }
72  
73      public static Response invokePUT(UrlBuilder url, String contentType, Map<String, String> headers, Output writer,
74              BindingSession session) {
75          return invoke(url, "PUT", contentType, headers, writer, session, null, null);
76      }
77  
78      public static Response invokeDELETE(UrlBuilder url, BindingSession session) {
79          return invoke(url, "DELETE", null, null, null, session, null, null);
80      }
81  
82      private static Response invoke(UrlBuilder url, String method, String contentType, Map<String, String> headers,
83              Output writer, BindingSession session, BigInteger offset, BigInteger length) {
84          try {
85              // log before connect
86              if (log.isDebugEnabled()) {
87                  log.debug(method + " " + url);
88              }
89  
90              // connect
91              HttpURLConnection conn = (HttpURLConnection) (new URL(url.toString())).openConnection();
92              conn.setRequestMethod(method);
93              conn.setDoInput(true);
94              conn.setDoOutput(writer != null);
95              conn.setAllowUserInteraction(false);
96              conn.setUseCaches(false);
97              conn.setRequestProperty("User-Agent", ClientVersion.OPENCMIS_CLIENT);
98  
99              // timeouts
100             int connectTimeout = session.get(SessionParameter.CONNECT_TIMEOUT, -1);
101             if (connectTimeout >= 0) {
102                 conn.setConnectTimeout(connectTimeout);
103             }
104 
105             int readTimeout = session.get(SessionParameter.READ_TIMEOUT, -1);
106             if (readTimeout >= 0) {
107                 conn.setReadTimeout(readTimeout);
108             }
109 
110             // set content type
111             if (contentType != null) {
112                 conn.setRequestProperty("Content-Type", contentType);
113             }
114             // set other headers
115             if (headers != null) {
116                 for (Map.Entry<String, String> header : headers.entrySet()) {
117                     conn.addRequestProperty(header.getKey(), header.getValue());
118                 }
119             }
120 
121             // authenticate
122             AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
123             if (authProvider != null) {
124                 Map<String, List<String>> httpHeaders = authProvider.getHTTPHeaders(url.toString());
125                 if (httpHeaders != null) {
126                     for (Map.Entry<String, List<String>> header : httpHeaders.entrySet()) {
127                         if (header.getValue() != null) {
128                             for (String value : header.getValue()) {
129                                 conn.addRequestProperty(header.getKey(), value);
130                             }
131                         }
132                     }
133                 }
134             }
135 
136             // range
137             if ((offset != null) || (length != null)) {
138                 StringBuilder sb = new StringBuilder("bytes=");
139 
140                 if ((offset == null) || (offset.signum() == -1)) {
141                     offset = BigInteger.ZERO;
142                 }
143 
144                 sb.append(offset.toString());
145                 sb.append("-");
146 
147                 if ((length != null) && (length.signum() == 1)) {
148                     sb.append(offset.add(length.subtract(BigInteger.ONE)).toString());
149                 }
150 
151                 conn.setRequestProperty("Range", sb.toString());
152             }
153 
154             // compression
155             Object compression = session.get(SessionParameter.COMPRESSION);
156             if ((compression != null) && Boolean.parseBoolean(compression.toString())) {
157                 conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
158             }
159 
160             // locale
161             if (session.get(CmisBindingsHelper.ACCEPT_LANGUAGE) instanceof String) {
162                 conn.setRequestProperty("Accept-Language", session.get(CmisBindingsHelper.ACCEPT_LANGUAGE).toString());
163             }
164 
165             // send data
166             if (writer != null) {
167                 conn.setChunkedStreamingMode((64 * 1024) - 1);
168 
169                 OutputStream connOut = null;
170 
171                 Object clientCompression = session.get(SessionParameter.CLIENT_COMPRESSION);
172                 if ((clientCompression != null) && Boolean.parseBoolean(clientCompression.toString())) {
173                     conn.setRequestProperty("Content-Encoding", "gzip");
174                     connOut = new GZIPOutputStream(conn.getOutputStream(), 4096);
175                 } else {
176                     connOut = conn.getOutputStream();
177                 }
178 
179                 OutputStream out = new BufferedOutputStream(connOut, BUFFER_SIZE);
180                 writer.write(out);
181                 out.flush();
182             }
183 
184             // connect
185             conn.connect();
186 
187             // get stream, if present
188             int respCode = conn.getResponseCode();
189             InputStream inputStream = null;
190             if ((respCode == 200) || (respCode == 201) || (respCode == 203) || (respCode == 206)) {
191                 inputStream = conn.getInputStream();
192             }
193 
194             // log after connect
195             if (log.isTraceEnabled()) {
196                 log.trace(method + " " + url + " > Headers: " + conn.getHeaderFields());
197             }
198 
199             // forward response HTTP headers
200             if (authProvider != null) {
201                 authProvider.putResponseHeaders(url.toString(), respCode, conn.getHeaderFields());
202             }
203 
204             // get the response
205             return new Response(respCode, conn.getResponseMessage(), conn.getHeaderFields(), inputStream,
206                     conn.getErrorStream());
207         } catch (Exception e) {
208             throw new CmisConnectionException("Cannot access " + url + ": " + e.getMessage(), e);
209         }
210     }
211 
212     /**
213      * HTTP Response.
214      */
215     public static class Response {
216         private final int responseCode;
217         private final String responseMessage;
218         private final Map<String, List<String>> headers;
219         private InputStream stream;
220         private String errorContent;
221         private BigInteger length;
222         private String charset;
223 
224         public Response(int responseCode, String responseMessage, Map<String, List<String>> headers,
225                 InputStream responseStream, InputStream errorStream) {
226             this.responseCode = responseCode;
227             this.responseMessage = responseMessage;
228             stream = responseStream;
229 
230             this.headers = new HashMap<String, List<String>>();
231             if (headers != null) {
232                 for (Map.Entry<String, List<String>> e : headers.entrySet()) {
233                     this.headers.put(e.getKey() == null ? null : e.getKey().toLowerCase(), e.getValue());
234                 }
235             }
236 
237             // determine charset
238             charset = "UTF-8";
239             String contentType = getContentTypeHeader();
240             if (contentType != null) {
241                 String[] parts = contentType.split(";");
242                 for (int i = 1; i < parts.length; i++) {
243                     String part = parts[i].trim().toLowerCase();
244                     if (part.startsWith("charset")) {
245                         int x = part.indexOf('=');
246                         charset = part.substring(x + 1).trim();
247                         break;
248                     }
249                 }
250             }
251 
252             // if there is an error page, get it
253             if (errorStream != null) {
254                 if (contentType != null) {
255                     String contentTypeLower = contentType.toLowerCase().split(";")[0];
256                     if (contentTypeLower.startsWith("text/") || contentTypeLower.endsWith("+xml")
257                             || contentTypeLower.startsWith("application/json")) {
258                         StringBuilder sb = new StringBuilder();
259 
260                         try {
261                             String encoding = getContentEncoding();
262                             if (encoding != null) {
263                                 if (encoding.toLowerCase().trim().equals("gzip")) {
264                                     try {
265                                         errorStream = new GZIPInputStream(errorStream, 4096);
266                                     } catch (IOException e) {
267                                     }
268                                 } else if (encoding.toLowerCase().trim().equals("deflate")) {
269                                     errorStream = new InflaterInputStream(errorStream, new Inflater(true), 4096);
270                                 }
271                             }
272 
273                             InputStreamReader reader = new InputStreamReader(errorStream, charset);
274                             char[] buffer = new char[4096];
275                             int b;
276                             while ((b = reader.read(buffer)) > -1) {
277                                 sb.append(buffer, 0, b);
278                             }
279                             reader.close();
280 
281                             errorContent = sb.toString();
282                         } catch (IOException e) {
283                             errorContent = "Unable to retrieve content: " + e.getMessage();
284                         }
285                     }
286                 } else {
287                     try {
288                         errorStream.close();
289                     } catch (IOException e) {
290                     }
291                 }
292 
293                 if (responseStream != null) {
294                     try {
295                         responseStream.close();
296                     } catch (IOException e) {
297                     }
298                 }
299 
300                 return;
301             }
302 
303             // get the stream length
304             String lengthStr = getHeader("Content-Length");
305             if (lengthStr != null) {
306                 try {
307                     length = new BigInteger(lengthStr);
308                 } catch (NumberFormatException e) {
309                 }
310             }
311 
312             if (stream != null) {
313                 String encoding = getContentEncoding();
314                 if (encoding != null) {
315                     if (encoding.toLowerCase().trim().equals("gzip")) {
316                         // if the stream is gzip encoded, decode it
317                         length = null;
318                         try {
319                             stream = new GZIPInputStream(stream, 4096);
320                         } catch (IOException e) {
321                             errorContent = e.getMessage();
322                             stream = null;
323                             try {
324                                 responseStream.close();
325                             } catch (IOException ec) {
326                             }
327                         }
328                     } else if (encoding.toLowerCase().trim().equals("deflate")) {
329                         // if the stream is deflate encoded, decode it
330                         length = null;
331                         stream = new InflaterInputStream(stream, new Inflater(true), 4096);
332                     }
333                 }
334 
335                 String transferEncoding = getContentTransferEncoding();
336                 if ((stream != null) && (transferEncoding != null)
337                         && (transferEncoding.toLowerCase().trim().equals("base64"))) {
338                     // if the stream is base64 encoded, decode it
339                     length = null;
340                     stream = new Base64.InputStream(stream);
341                 }
342             }
343         }
344 
345         public int getResponseCode() {
346             return responseCode;
347         }
348 
349         public String getResponseMessage() {
350             return responseMessage;
351         }
352 
353         public Map<String, List<String>> getHeaders() {
354             return headers;
355         }
356 
357         public String getHeader(String name) {
358             List<String> list = headers.get(name.toLowerCase(Locale.US));
359             if ((list == null) || (list.isEmpty())) {
360                 return null;
361             }
362 
363             return list.get(0);
364         }
365 
366         public String getContentTypeHeader() {
367             return getHeader("Content-Type");
368         }
369 
370         public BigInteger getContentLengthHeader() {
371             String lengthStr = getHeader("Content-Length");
372             if (lengthStr == null) {
373                 return null;
374             }
375 
376             try {
377                 return new BigInteger(lengthStr);
378             } catch (NumberFormatException e) {
379                 return null;
380             }
381         }
382 
383         public String getLocactionHeader() {
384             return getHeader("Location");
385         }
386 
387         public String getContentLocactionHeader() {
388             return getHeader("Content-Location");
389         }
390 
391         public String getContentTransferEncoding() {
392             return getHeader("Content-Transfer-Encoding");
393         }
394 
395         public String getContentEncoding() {
396             return getHeader("Content-Encoding");
397         }
398 
399         public String getCharset() {
400             return charset;
401         }
402 
403         public BigInteger getContentLength() {
404             return length;
405         }
406 
407         public InputStream getStream() {
408             return stream;
409         }
410 
411         public String getErrorContent() {
412             return errorContent;
413         }
414     }
415 
416     /**
417      * Output interface.
418      */
419     public interface Output {
420         void write(OutputStream out) throws Exception;
421     }
422 }