org.labkey.remoteapi.Connection Maven / Gradle / Ivy
Show all versions of labkey-client-api Show documentation
/*
* Copyright (c) 2008-2016 LabKey Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.labkey.remoteapi;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.labkey.remoteapi.security.EnsureLoginCommand;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Represents connection information for a particular LabKey Server.
*
* Create an instance of this class for each server you wish to interact with.
* If the commands you execute require a login, you must also configure
* authentication via one of the supported methods: retrieving email address
* and password from a .netrc/_netrc file, providing an api key, or providing
* email address and password directly (which could be obtained from the
* program's environment, such as via command-line parameters, environment
* variables, a properties file, etc. See the individual constructors and
* implementations of CredentialsProvider
for more details.
*
* After creating and initializing the Connection instance, pass it to the
* Command.execute()
method.
* Example:
*
*
* Connection cn = new Connection("https://labkey.org");
* SelectRowsCommand cmd = new SelectRowsCommand("study", "Physical Exam");
* SelectRowsResponse response = cmd.execute(cn, "Home/Study/demo");
* for(Map<String, Object> row : response.getRows())
* {
* System.out.println(row.get("ParticipantId") + " weighs " + row.get("Weight"));
* }
*
*
* Example using Authentication
*
*
*
* //get the user email and password from command-line arguments,
* //environment variables, a file, or some other mechanism.
* String user = getUser();
* String password = getPassword();
*
* //create a new connection passing the user credentials
* Connection cn = new Connection("https://localhost:8080/labkey", user, password);
* SelectRowsCommand cmd = new SelectRowsCommand("lists", "People");
* SelectRowsResponse response = cmd.execute(cn, "Api Test");
*
*
*
* Note that this class is not thread-safe. Do not share instances of Connection
* between threads.
*
*
* @author Dave Stearns, LabKey Corporation
* @version 1.0
*/
public class Connection
{
private static final int DEFAULT_TIMEOUT = 60000; // 60 seconds
private static final HttpClientConnectionManager _connectionManager = new PoolingHttpClientConnectionManager();
private final String _baseUrl;
private final CredentialsProvider _credentialsProvider;
private final HttpClientContext _httpClientContext;
private final Map _clientMap = new HashMap<>(3); // Typically very small
private boolean _acceptSelfSignedCerts;
private int _timeout = DEFAULT_TIMEOUT;
/**
* Constructs a new Connection object given a base URL and a credentials provider.
*
* The baseUrl parameter should include the protocol, domain name, port,
* and LabKey web application context path (if configured). For example
* in a typical localhost configuration, the base URL would be:
*
* http://localhost:8080/labkey
*
* Note that https may also be used for the protocol. By default the
* Connection is configured to deny self-signed SSL certificates.
* If you want to accept self-signed certificates, use
* setAcceptSelfSignedCerts(false)
to enable this behavior.
*
* The email name and password should correspond to a valid user email
* and password on the target server.
* @param baseUrl The base URL
* @param credentialsProvider A credentials provider
*/
public Connection(String baseUrl, CredentialsProvider credentialsProvider)
{
_baseUrl = baseUrl;
_credentialsProvider = credentialsProvider;
_httpClientContext = HttpClientContext.create();
_httpClientContext.setCookieStore(new BasicCookieStore());
setAcceptSelfSignedCerts(false);
}
/**
* Constructs a new Connection object with a base URL that attempts authentication via .netrc/_netrc entry, if present.
* If not present, connects as guest.
* @param baseUrl The base URL
* @throws URISyntaxException if the given url is not a valid URI
* @throws IOException if there are problems reading the credentials
* @see #Connection(String, CredentialsProvider)
*/
public Connection(String baseUrl) throws URISyntaxException, IOException
{
this(baseUrl, new NetrcCredentialsProvider(new URI(baseUrl)));
}
/**
* Constructs a new Connection object for a base URL that attempts basic authentication.
*
* This is equivalent to calling Connection(baseUrl, new BasicAuthCredentialsProvider(email, password))
.
* @param baseUrl The base URL
* @param email The user email address to pass for authentication
* @param password The user password to send for authentication
* @see #Connection(String, CredentialsProvider)
*/
public Connection(String baseUrl, String email, String password)
{
this(baseUrl, new BasicAuthCredentialsProvider(email, password));
}
/**
* Returns the base URL for this connection.
* @return The base URL.
*/
public String getBaseUrl()
{
return _baseUrl;
}
/**
* Returns true if the connection should accept a self-signed
* SSL certificate when using HTTPS, false otherwise. Defaults
* to true.
* @return true or false
*/
public boolean isAcceptSelfSignedCerts()
{
return _acceptSelfSignedCerts;
}
/**
* Sets the accept self-signed certificates option. Set to false
* to disable automatic acceptance of self-signed SSL certificates
* when using HTTPS.
* @param acceptSelfSignedCerts set to false to not accept self-signed certificates
*/
public void setAcceptSelfSignedCerts(boolean acceptSelfSignedCerts)
{
// Handled in getHttpClient using 4.3.x approach documented here http://stackoverflow.com/questions/19517538/ignoring-ssl-certificate-in-apache-httpclient-4-3
_acceptSelfSignedCerts = acceptSelfSignedCerts;
_clientMap.clear(); // clear client cache
}
/**
* Returns the CloseableHttpClient object to use for this connection.
* @param timeout The socket timeout for this request
* @return The CloseableHttpClient object to use.
*/
public CloseableHttpClient getHttpClient(int timeout)
{
// We try to hand out the same HttpClient instance to share it across multiple requests, as the docs recommend.
// However, HttpClient 4.x moved setting of the timeout to HttpClient construction time (it used to be on the
// request-specific Method instance) but our API allows setting a timeout on a per-Command basis (which seems
// perfectly reasonable). So, we stash and retrieve HttpClients using a Map.
CloseableHttpClient client = _clientMap.get(timeout);
if (null == client)
{
HttpClientBuilder builder = HttpClientBuilder.create()
.setConnectionManager(_connectionManager)
.setDefaultRequestConfig(RequestConfig.custom().setSocketTimeout(timeout).build())
.setDefaultCookieStore(_httpClientContext.getCookieStore());
if (_acceptSelfSignedCerts)
{
try
{
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build());
builder.setSSLSocketFactory(sslConnectionSocketFactory);
}
catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e)
{
throw new RuntimeException(e);
}
}
client = builder.build();
_clientMap.put(timeout, client);
}
return client;
}
private String csrf = null;
protected void beforeExecute(HttpRequest request)
{
if (null == csrf && request instanceof HttpPost)
{
// need to preemptively login
// we're not really using the login form, just getting a JSESSIONID
try
{
new Command("login", "login").execute(this, "/");
}
catch (Exception ignored)
{
}
}
if (null != csrf)
request.setHeader("X-LABKEY-CSRF", csrf);
}
protected void afterExecute()
{
if (null == csrf)
{
for (Cookie c : _httpClientContext.getCookieStore().getCookies())
{
if ("JSESSIONID".equals(c.getName()))
csrf = c.getValue();
}
}
}
/**
* Ensures that the credentials have been used to authenticate the users and returns a client that can be used for other requests
* @return an HTTP client
* @throws IOException if there is an IO problem executing the command to ensure loginf
* @throws CommandException if the server returned a non-success status code.
*/
public CloseableHttpClient ensureAuthenticated() throws IOException, CommandException
{
EnsureLoginCommand command = new EnsureLoginCommand();
CommandResponse response = command.execute(this, "/home");
return getHttpClient(getTimeout());
}
CloseableHttpResponse executeRequest(HttpUriRequest request, Integer timeout) throws IOException, URISyntaxException, AuthenticationException
{
// Delegate authentication setup to CredentialsProvider
_credentialsProvider.configureRequest(getBaseUrl(), request, _httpClientContext);
int clientTimeout = null == timeout ? getTimeout() : timeout;
CloseableHttpClient client = getHttpClient(clientTimeout);
beforeExecute(request);
CloseableHttpResponse response = client.execute(request, _httpClientContext);
afterExecute();
return response;
}
/**
* Set a default timeout for Commands that have not established their own timeouts. Null resets the Connection to the
* default timeout (60 seconds). 0 means the request should never timeout.
* @param timeout the length of the timeout waiting for the server response, in milliseconds
*/
public void setTimeout(Integer timeout)
{
_timeout = timeout;
}
/**
* The timeout used for Commands that have not established their own timeouts. 0 means the request should never timeout.
* @return the length of the timeout waiting for the server response, in milliseconds
*/
public int getTimeout()
{
return _timeout;
}
/**
*
* @param name The cookie name
* @param value The cookie value
* @param domain The domain to which the cookie is visible
* @param path The path to which the cookie is visible
* @param expiry The cookie's expiration date
* @param isSecure Whether the cookie requires a secure connection
*/
public void addCookie(String name, String value, String domain, String path, Date expiry, boolean isSecure)
{
BasicClientCookie cookie = new BasicClientCookie(name, value);
cookie.setDomain(domain);
cookie.setPath(path);
cookie.setExpiryDate(expiry);
cookie.setSecure(isSecure);
_httpClientContext.getCookieStore().addCookie(cookie);
}
}