src/core/net/sf/basedb/util/uri/http/HttpConnectionManager.java

Code
Comments
Other
Rev Date Author Line
5582 15 Mar 11 nicklas 1 /**
5582 15 Mar 11 nicklas 2   $Id$
5582 15 Mar 11 nicklas 3
5582 15 Mar 11 nicklas 4   Copyright (C) 2011 Nicklas Nordborg
5582 15 Mar 11 nicklas 5
5582 15 Mar 11 nicklas 6   This file is part of BASE - BioArray Software Environment.
5582 15 Mar 11 nicklas 7   Available at http://base.thep.lu.se/
5582 15 Mar 11 nicklas 8
5582 15 Mar 11 nicklas 9   BASE is free software; you can redistribute it and/or
5582 15 Mar 11 nicklas 10   modify it under the terms of the GNU General Public License
5582 15 Mar 11 nicklas 11   as published by the Free Software Foundation; either version 3
5582 15 Mar 11 nicklas 12   of the License, or (at your option) any later version.
5582 15 Mar 11 nicklas 13
5582 15 Mar 11 nicklas 14   BASE is distributed in the hope that it will be useful,
5582 15 Mar 11 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
5582 15 Mar 11 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
5582 15 Mar 11 nicklas 17   GNU General Public License for more details.
5582 15 Mar 11 nicklas 18
5582 15 Mar 11 nicklas 19   You should have received a copy of the GNU General Public License
5582 15 Mar 11 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
5582 15 Mar 11 nicklas 21 */
5582 15 Mar 11 nicklas 22 package net.sf.basedb.util.uri.http;
5582 15 Mar 11 nicklas 23
5582 15 Mar 11 nicklas 24 import java.io.IOException;
5582 15 Mar 11 nicklas 25 import java.io.InputStream;
5582 15 Mar 11 nicklas 26 import java.net.URI;
7415 13 Oct 17 nicklas 27 import java.util.TimerTask;
5582 15 Mar 11 nicklas 28 import java.util.regex.Matcher;
5582 15 Mar 11 nicklas 29 import java.util.regex.Pattern;
5582 15 Mar 11 nicklas 30
5582 15 Mar 11 nicklas 31 import org.apache.http.HttpEntity;
5582 15 Mar 11 nicklas 32 import org.apache.http.HttpResponse;
5582 15 Mar 11 nicklas 33 import org.apache.http.auth.AuthScope;
5582 15 Mar 11 nicklas 34 import org.apache.http.auth.Credentials;
5582 15 Mar 11 nicklas 35 import org.apache.http.auth.UsernamePasswordCredentials;
6477 12 Jun 14 nicklas 36 import org.apache.http.client.CredentialsProvider;
6477 12 Jun 14 nicklas 37 import org.apache.http.impl.client.CloseableHttpClient;
5582 15 Mar 11 nicklas 38 import org.apache.http.client.methods.HttpGet;
5582 15 Mar 11 nicklas 39 import org.apache.http.client.methods.HttpHead;
6477 12 Jun 14 nicklas 40 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
6477 12 Jun 14 nicklas 41 import org.apache.http.impl.client.BasicCredentialsProvider;
6477 12 Jun 14 nicklas 42 import org.apache.http.impl.client.HttpClientBuilder;
6477 12 Jun 14 nicklas 43 import org.apache.http.impl.conn.DefaultSchemePortResolver;
5582 15 Mar 11 nicklas 44
7415 13 Oct 17 nicklas 45 import net.sf.basedb.core.Application;
5582 15 Mar 11 nicklas 46 import net.sf.basedb.core.InvalidDataException;
5582 15 Mar 11 nicklas 47 import net.sf.basedb.util.HttpUtil;
7415 13 Oct 17 nicklas 48 import net.sf.basedb.util.filter.Filter;
6477 12 Jun 14 nicklas 49 import net.sf.basedb.util.ssl.SSLUtil2;
5582 15 Mar 11 nicklas 50 import net.sf.basedb.util.uri.CloseResourceInputStream;
5582 15 Mar 11 nicklas 51 import net.sf.basedb.util.uri.ConnectionParameters;
6497 26 Jun 14 nicklas 52 import net.sf.basedb.util.uri.ResumableConnectionManager;
5582 15 Mar 11 nicklas 53 import net.sf.basedb.util.uri.UriMetadata;
5582 15 Mar 11 nicklas 54
5582 15 Mar 11 nicklas 55 /**
5582 15 Mar 11 nicklas 56   Connection manager implementation that support HTTP and HTTPS URI:s. 
5582 15 Mar 11 nicklas 57   It supports Basic and Digest authentication schemes. When HTTPS
5582 15 Mar 11 nicklas 58   is used it provides support for validation of both server- and 
5582 15 Mar 11 nicklas 59   client-side certificates.
5582 15 Mar 11 nicklas 60
5582 15 Mar 11 nicklas 61   @author Nicklas
5582 15 Mar 11 nicklas 62   @since 3.0
5582 15 Mar 11 nicklas 63   @base.modified $Date$
5582 15 Mar 11 nicklas 64 */
5582 15 Mar 11 nicklas 65 public class HttpConnectionManager
6497 26 Jun 14 nicklas 66   implements ResumableConnectionManager
5582 15 Mar 11 nicklas 67 {
5582 15 Mar 11 nicklas 68
5582 15 Mar 11 nicklas 69   private final URI uri;
5582 15 Mar 11 nicklas 70   private final ConnectionParameters parameters;
5582 15 Mar 11 nicklas 71   private UriMetadata metadata;
7415 13 Oct 17 nicklas 72   private Filter<HttpResponse> responseFilter;
7415 13 Oct 17 nicklas 73   private int timeout;
5582 15 Mar 11 nicklas 74   
5582 15 Mar 11 nicklas 75   public HttpConnectionManager(URI uri, ConnectionParameters parameters)
5582 15 Mar 11 nicklas 76   {
5582 15 Mar 11 nicklas 77     if (uri == null) throw new NullPointerException("uri");
5582 15 Mar 11 nicklas 78     this.uri = uri;
5582 15 Mar 11 nicklas 79     this.parameters = parameters;
5582 15 Mar 11 nicklas 80   }
5582 15 Mar 11 nicklas 81   
5582 15 Mar 11 nicklas 82   /*
5582 15 Mar 11 nicklas 83     From the UriHandler interface
5582 15 Mar 11 nicklas 84     -----------------------------
5582 15 Mar 11 nicklas 85   */
5582 15 Mar 11 nicklas 86   @Override
5582 15 Mar 11 nicklas 87   public URI getURI()
5582 15 Mar 11 nicklas 88   {
5582 15 Mar 11 nicklas 89     return uri;
5582 15 Mar 11 nicklas 90   }
6497 26 Jun 14 nicklas 91   
5582 15 Mar 11 nicklas 92   @Override
5582 15 Mar 11 nicklas 93   public InputStream getInputStream() 
5582 15 Mar 11 nicklas 94     throws IOException
5582 15 Mar 11 nicklas 95   {
6497 26 Jun 14 nicklas 96     return getInputStream(0);
6497 26 Jun 14 nicklas 97   }
6497 26 Jun 14 nicklas 98
6497 26 Jun 14 nicklas 99   @Override
6497 26 Jun 14 nicklas 100   public InputStream getInputStream(long offset) 
6497 26 Jun 14 nicklas 101     throws IOException
6497 26 Jun 14 nicklas 102   {
5582 15 Mar 11 nicklas 103     URI uri = getURI();
6477 12 Jun 14 nicklas 104     CloseableHttpClient client = null;
5582 15 Mar 11 nicklas 105     InputStream stream = null;
7415 13 Oct 17 nicklas 106     TimeoutTimer timer = null;
5582 15 Mar 11 nicklas 107     try
5582 15 Mar 11 nicklas 108     {
5582 15 Mar 11 nicklas 109       HttpGet get = new HttpGet(uri);
6497 26 Jun 14 nicklas 110       if (offset > 0)
6497 26 Jun 14 nicklas 111       {
6497 26 Jun 14 nicklas 112         get.addHeader("Range", "bytes="+offset + "-");
6497 26 Jun 14 nicklas 113       }
6477 12 Jun 14 nicklas 114       client = createCloseableHttpClient(uri, parameters);
7415 13 Oct 17 nicklas 115       if (timeout > 0) 
7415 13 Oct 17 nicklas 116       {
7415 13 Oct 17 nicklas 117         timer = new TimeoutTimer(get);
7415 13 Oct 17 nicklas 118         Application.getScheduler().schedule(timer, timeout*1000, false);
7415 13 Oct 17 nicklas 119       }
5582 15 Mar 11 nicklas 120       HttpResponse response = client.execute(get);
7415 13 Oct 17 nicklas 121       if (timer != null) timer.setCompleted();
7415 13 Oct 17 nicklas 122
7415 13 Oct 17 nicklas 123       if (responseFilter != null) 
7415 13 Oct 17 nicklas 124       {
7415 13 Oct 17 nicklas 125         if (!responseFilter.evaluate(response))
7415 13 Oct 17 nicklas 126         {
7415 13 Oct 17 nicklas 127           throw new IOException("Aborted by filter: "+ responseFilter);
7415 13 Oct 17 nicklas 128         }
7415 13 Oct 17 nicklas 129       }
5582 15 Mar 11 nicklas 130       if (metadata == null) metadata = createMetadata(uri, response);
5582 15 Mar 11 nicklas 131       HttpEntity entity = response.getEntity();
5582 15 Mar 11 nicklas 132       if (entity != null)
5582 15 Mar 11 nicklas 133       {
5582 15 Mar 11 nicklas 134         // Wrap the stream so that stream.close() will also shutdown the client
6477 12 Jun 14 nicklas 135         stream = new CloseResourceInputStream(entity.getContent(), client);
5582 15 Mar 11 nicklas 136       }
5582 15 Mar 11 nicklas 137     }
7415 13 Oct 17 nicklas 138     catch (IOException ex)
7415 13 Oct 17 nicklas 139     {
7415 13 Oct 17 nicklas 140       if (timer != null && timer.hasTimedOut())
7415 13 Oct 17 nicklas 141       {
7415 13 Oct 17 nicklas 142         throw new IOException("Timeout exceeded: " + timeout + "s", ex);
7415 13 Oct 17 nicklas 143       }
7415 13 Oct 17 nicklas 144       throw ex;
7415 13 Oct 17 nicklas 145     }
5582 15 Mar 11 nicklas 146     finally
5582 15 Mar 11 nicklas 147     {
5582 15 Mar 11 nicklas 148       // If no stream has been created we shutdown the client immediately
5582 15 Mar 11 nicklas 149       if (stream == null && client != null) HttpUtil.shutdown(client);
5582 15 Mar 11 nicklas 150     }
5582 15 Mar 11 nicklas 151     return stream;
5582 15 Mar 11 nicklas 152   }
5582 15 Mar 11 nicklas 153
5582 15 Mar 11 nicklas 154   @Override
5582 15 Mar 11 nicklas 155   public UriMetadata getMetadata() 
5582 15 Mar 11 nicklas 156     throws IOException
5582 15 Mar 11 nicklas 157   {
5582 15 Mar 11 nicklas 158     if (metadata == null) 
5582 15 Mar 11 nicklas 159     {
5582 15 Mar 11 nicklas 160       URI uri = getURI();
6477 12 Jun 14 nicklas 161       CloseableHttpClient client = null;
5582 15 Mar 11 nicklas 162       try
5582 15 Mar 11 nicklas 163       {
5582 15 Mar 11 nicklas 164         HttpHead head = new HttpHead(uri);
6477 12 Jun 14 nicklas 165         client = createCloseableHttpClient(uri, parameters);
5582 15 Mar 11 nicklas 166         HttpResponse response = client.execute(head);
5582 15 Mar 11 nicklas 167
5582 15 Mar 11 nicklas 168         int responseCode = response.getStatusLine().getStatusCode();
5582 15 Mar 11 nicklas 169         if (responseCode >= 200 && responseCode < 300)
5582 15 Mar 11 nicklas 170         {
5582 15 Mar 11 nicklas 171           metadata = createMetadata(uri, response);
5582 15 Mar 11 nicklas 172         }
5582 15 Mar 11 nicklas 173         else if (responseCode == 404)
5582 15 Mar 11 nicklas 174         {
5582 15 Mar 11 nicklas 175           throw new InvalidDataException("404 URL not found: " + uri);
5582 15 Mar 11 nicklas 176         }
5582 15 Mar 11 nicklas 177         else if (responseCode == 401)
5582 15 Mar 11 nicklas 178         {
5582 15 Mar 11 nicklas 179           throw new InvalidDataException("401 Authorization required: " + uri);
5582 15 Mar 11 nicklas 180         }
5582 15 Mar 11 nicklas 181         else
5582 15 Mar 11 nicklas 182         {
5582 15 Mar 11 nicklas 183           throw new IOException(responseCode + " " + response.getStatusLine().getReasonPhrase());
5582 15 Mar 11 nicklas 184         }
5582 15 Mar 11 nicklas 185       }
5582 15 Mar 11 nicklas 186       finally
5582 15 Mar 11 nicklas 187       {
5582 15 Mar 11 nicklas 188         if (client != null) HttpUtil.shutdown(client);
5582 15 Mar 11 nicklas 189       }
5582 15 Mar 11 nicklas 190     }
5582 15 Mar 11 nicklas 191     return metadata;
5582 15 Mar 11 nicklas 192   }
5582 15 Mar 11 nicklas 193   // --------------------------------------
5582 15 Mar 11 nicklas 194   
5582 15 Mar 11 nicklas 195   /**
7415 13 Oct 17 nicklas 196     Set a filter that will inspect the response before an input stream
7415 13 Oct 17 nicklas 197     is returned to the calling code. The filter must return true to allow
7415 13 Oct 17 nicklas 198     the processing to proceed. A false value aborts the processing.
7415 13 Oct 17 nicklas 199     The filter may also throw an exception.
7415 13 Oct 17 nicklas 200     @since 3.12
7415 13 Oct 17 nicklas 201   */
7415 13 Oct 17 nicklas 202   public void setResponseFilter(Filter<HttpResponse> filter)
7415 13 Oct 17 nicklas 203   {
7415 13 Oct 17 nicklas 204     this.responseFilter = filter;
7415 13 Oct 17 nicklas 205   }
7415 13 Oct 17 nicklas 206   
7415 13 Oct 17 nicklas 207   /**
7415 13 Oct 17 nicklas 208     Set a timeout in seconds to allocate for the request to return
7415 13 Oct 17 nicklas 209     (not including downloading of data).
7415 13 Oct 17 nicklas 210     @param timeoutInSeconds Timeout in seconds
7415 13 Oct 17 nicklas 211     @since 3.12
7415 13 Oct 17 nicklas 212   */
7415 13 Oct 17 nicklas 213   public void setTimeout(int timeoutInSeconds)
7415 13 Oct 17 nicklas 214   {
7415 13 Oct 17 nicklas 215     this.timeout = timeoutInSeconds;
7415 13 Oct 17 nicklas 216   }
7415 13 Oct 17 nicklas 217   
7415 13 Oct 17 nicklas 218   /**
5582 15 Mar 11 nicklas 219     Get a HttpClient object that has been configured to access the 
5582 15 Mar 11 nicklas 220     given URI.
5582 15 Mar 11 nicklas 221   */
6477 12 Jun 14 nicklas 222   public CloseableHttpClient createCloseableHttpClient(URI uri, ConnectionParameters parameters)
5582 15 Mar 11 nicklas 223     throws IOException
5582 15 Mar 11 nicklas 224   {
6477 12 Jun 14 nicklas 225     HttpClientBuilder builder = HttpClientBuilder.create();
6477 12 Jun 14 nicklas 226     //HttpClient httpClient = builder.build();
6477 12 Jun 14 nicklas 227     //DefaultHttpClient httpClient = new DefaultHttpClient();
5582 15 Mar 11 nicklas 228   
5582 15 Mar 11 nicklas 229     // Do we need to provide username/password?
5582 15 Mar 11 nicklas 230     if (parameters != null && parameters.getUsername() != null)
5582 15 Mar 11 nicklas 231     {
5582 15 Mar 11 nicklas 232       Credentials credentials = new UsernamePasswordCredentials(parameters.getUsername(), parameters.getPassword());
6477 12 Jun 14 nicklas 233       CredentialsProvider provider = new BasicCredentialsProvider();
6477 12 Jun 14 nicklas 234       provider.setCredentials(AuthScope.ANY, credentials);
6477 12 Jun 14 nicklas 235       builder.setDefaultCredentialsProvider(provider);
6477 12 Jun 14 nicklas 236 //      httpClient.getCredentialsProvider().setCredentials(AuthScope.ANY, credentials);
5582 15 Mar 11 nicklas 237     }
5582 15 Mar 11 nicklas 238   
5582 15 Mar 11 nicklas 239     if (uri.getScheme().equals("https"))
5582 15 Mar 11 nicklas 240     {
5582 15 Mar 11 nicklas 241       // Adds support for https urls
6477 12 Jun 14 nicklas 242       SSLConnectionSocketFactory sslFactory = null;
5582 15 Mar 11 nicklas 243       if (parameters == null)
5582 15 Mar 11 nicklas 244       {
6477 12 Jun 14 nicklas 245         sslFactory = SSLUtil2.getSSLSocketFactory();
5582 15 Mar 11 nicklas 246       }
5582 15 Mar 11 nicklas 247       else
5582 15 Mar 11 nicklas 248       {
6477 12 Jun 14 nicklas 249         sslFactory = SSLUtil2.getSSLSocketFactory(parameters.getServerCertificate(), 
5582 15 Mar 11 nicklas 250             parameters.getClientCertificate(), parameters.getClientCertificatePassword());
5582 15 Mar 11 nicklas 251       }
6477 12 Jun 14 nicklas 252       //Scheme https = new Scheme("https", 443, sslFactory);
6477 12 Jun 14 nicklas 253       builder.setSchemePortResolver(DefaultSchemePortResolver.INSTANCE);
6477 12 Jun 14 nicklas 254       builder.setSSLSocketFactory(sslFactory);
6477 12 Jun 14 nicklas 255       //httpClient.getConnectionManager().getSchemeRegistry().register(https);
5582 15 Mar 11 nicklas 256     }
6477 12 Jun 14 nicklas 257     return builder.build();
5582 15 Mar 11 nicklas 258   }
5582 15 Mar 11 nicklas 259   
5582 15 Mar 11 nicklas 260   /**
5582 15 Mar 11 nicklas 261     Read metadata from the given http response and put the information
5582 15 Mar 11 nicklas 262     in a UriMetadata object. This method uses whatever information that
5582 15 Mar 11 nicklas 263     is in the response headers:
5582 15 Mar 11 nicklas 264     <ul>
5582 15 Mar 11 nicklas 265     <li>Content-Length: The length of the resource
5582 15 Mar 11 nicklas 266     <li>Last-Modified: The last modified date
5582 15 Mar 11 nicklas 267     <li>Content-Type: The MIME type and character set
5582 15 Mar 11 nicklas 268     </ul>
5582 15 Mar 11 nicklas 269   */
5582 15 Mar 11 nicklas 270   public UriMetadata createMetadata(URI uri, HttpResponse response)
5582 15 Mar 11 nicklas 271   {
5582 15 Mar 11 nicklas 272     UriMetadata meta = new UriMetadata(uri);
5582 15 Mar 11 nicklas 273     meta.setLength(HttpUtil.getLong(response, "Content-Length", null));
5582 15 Mar 11 nicklas 274     meta.setLastModified(HttpUtil.getLastModified(response));
5582 15 Mar 11 nicklas 275     String mimeType = HttpUtil.getContentType(response);
5582 15 Mar 11 nicklas 276     String charset = null;
5582 15 Mar 11 nicklas 277     if (mimeType != null)
5582 15 Mar 11 nicklas 278     {
5582 15 Mar 11 nicklas 279       Pattern p = Pattern.compile("(\\w+/\\w+)\\s*;\\s*charset=(.*)");
5582 15 Mar 11 nicklas 280       Matcher m = p.matcher(mimeType);
5582 15 Mar 11 nicklas 281       if (m.matches())
5582 15 Mar 11 nicklas 282       {
5582 15 Mar 11 nicklas 283         mimeType = m.group(1);
5582 15 Mar 11 nicklas 284         charset = m.group(2);
5582 15 Mar 11 nicklas 285       }
5582 15 Mar 11 nicklas 286     }
5582 15 Mar 11 nicklas 287     meta.setMimeType(mimeType);
5582 15 Mar 11 nicklas 288     meta.setCharacterSet(charset);
5582 15 Mar 11 nicklas 289     return meta;
5582 15 Mar 11 nicklas 290   }
5582 15 Mar 11 nicklas 291
7415 13 Oct 17 nicklas 292   static class TimeoutTimer
7415 13 Oct 17 nicklas 293     extends TimerTask
7415 13 Oct 17 nicklas 294   {
7415 13 Oct 17 nicklas 295
7415 13 Oct 17 nicklas 296     private final HttpGet get;
7415 13 Oct 17 nicklas 297     private volatile boolean hasTimedOut;
7415 13 Oct 17 nicklas 298     private volatile boolean hasCompleted;
7415 13 Oct 17 nicklas 299     
7415 13 Oct 17 nicklas 300     TimeoutTimer(HttpGet get)
7415 13 Oct 17 nicklas 301     {
7415 13 Oct 17 nicklas 302       this.get = get;
7415 13 Oct 17 nicklas 303     }
7415 13 Oct 17 nicklas 304     
7415 13 Oct 17 nicklas 305     void setCompleted()
7415 13 Oct 17 nicklas 306     {
7415 13 Oct 17 nicklas 307       this.hasCompleted = true;
7415 13 Oct 17 nicklas 308     }
7415 13 Oct 17 nicklas 309     
7415 13 Oct 17 nicklas 310     boolean hasTimedOut()
7415 13 Oct 17 nicklas 311     {
7415 13 Oct 17 nicklas 312       return hasTimedOut;
7415 13 Oct 17 nicklas 313     }
7415 13 Oct 17 nicklas 314     
7415 13 Oct 17 nicklas 315     @Override
7415 13 Oct 17 nicklas 316     public void run() 
7415 13 Oct 17 nicklas 317     {
7415 13 Oct 17 nicklas 318       if (!hasCompleted)
7415 13 Oct 17 nicklas 319       {
7415 13 Oct 17 nicklas 320         hasTimedOut = true;
7415 13 Oct 17 nicklas 321         get.abort();
7415 13 Oct 17 nicklas 322       }
7415 13 Oct 17 nicklas 323     }
7415 13 Oct 17 nicklas 324     
7415 13 Oct 17 nicklas 325   }
5582 15 Mar 11 nicklas 326 }