io.milton.httpclient.Host Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 io.milton.httpclient;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import io.milton.common.Path;
import io.milton.http.Range;
import io.milton.http.Response;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.ConflictException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.exceptions.NotFoundException;
import io.milton.httpclient.Utils.CancelledException;
import io.milton.httpclient.zsyncclient.FileSyncer;
import io.milton.common.LogUtils;
import io.milton.http.DateUtils;
import io.milton.http.DateUtils.DateParseException;
import java.io.*;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.*;
import org.apache.http.auth.*;
import org.apache.http.client.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.entity.StringEntity;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.*;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author mcevoyb
*/
public class Host extends Folder {
public static final List defaultFields = Arrays.asList(
RespUtils.davName("resourcetype"),
RespUtils.davName("etag"),
RespUtils.davName("displayname"),
RespUtils.davName("getcontentlength"),
RespUtils.davName("creationdate"),
RespUtils.davName("getlastmodified"),
RespUtils.davName("iscollection"),
RespUtils.davName("lockdiscovery"));
private static final String LOCK_XML = ""
+ ""
+ " "
+ " "
+ "${owner} "
+ " ";
private static final Set WEBDAV_REDIRECTABLE = new HashSet<>(Arrays.asList("PROPFIND", "LOCK", "UNLOCK", "DELETE"));
private static final Logger log = LoggerFactory.getLogger(Host.class);
public final String server;
public final Integer port;
public final String user;
public final String password;
public final String rootPath;
/**
* time in milliseconds to be used for all timeout parameters
*/
private int timeout;
private final DefaultHttpClient client;
private final TransferService transferService;
private final FileSyncer fileSyncer;
private final List connectionListeners = new ArrayList<>();
private boolean secure; // use HTTPS if true
private boolean usePreemptiveAuth = true;
private boolean useDigestForPreemptiveAuth = true; // if true we will do pre-emptive auth with Digest, otherwise will use Basic
private final Map cookies = new HashMap<>();
static {
// System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
// System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
// System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire.header", "debug");
// System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "debug");
}
public static org.jdom2.Document getJDomDocument(InputStream in) throws JDOMException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
IOUtils.copy(in, bout);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
// System.out.println("");
// System.out.println(bout.toString());
// System.out.println("");
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
try {
SAXBuilder builder = new SAXBuilder();
builder.setExpandEntities(false);
return builder.build(bin);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public Host(String server, Integer port, String user, String password, ProxyDetails proxyDetails) {
this(server, null, port, user, password, proxyDetails, 30000, null, null);
}
public Host(String server, Integer port, String user, String password, ProxyDetails proxyDetails, Map> cache) {
this(server, null, port, user, password, proxyDetails, 30000, cache, null); // defaul timeout of 30sec
}
public Host(String server, String rootPath, Integer port, String user, String password, ProxyDetails proxyDetails, Map> cache) {
this(server, rootPath, port, user, password, proxyDetails, 30000, cache, null); // defaul timeout of 30sec
}
public Host(String server, String rootPath, Integer port, String user, String password, ProxyDetails proxyDetails, int timeoutMillis, Map> cache, FileSyncer fileSyncer) {
//super((cache != null ? cache : new MemoryCache>("resource-cache-default", 50, 20)));
super((cache != null ? cache : new ConcurrentLinkedHashMap.Builder().maximumWeightedCapacity(1000).build()));
if (server == null) {
throw new IllegalArgumentException("host name cannot be null");
}
if (rootPath != null) {
String rp = rootPath;
if (rp.startsWith("/")) { // strip leading slash so can be concatenated
rp = rp.substring(1);
}
this.rootPath = rp;
} else {
this.rootPath = null;
}
this.timeout = timeoutMillis;
this.server = server;
this.port = port;
this.user = user;
this.password = password;
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, timeoutMillis);
HttpConnectionParams.setSoTimeout(params, timeoutMillis);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
// Create and initialize scheme registry
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
// Create an HttpClient with the ThreadSafeClientConnManager.
// This connection manager must be used if more than one thread will
// be using the HttpClient.
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
cm.setMaxTotal(200);
client = new MyDefaultHttpClient(cm, params);
HttpRequestRetryHandler handler = new NoRetryHttpRequestRetryHandler();
client.setHttpRequestRetryHandler(handler);
client.setRedirectStrategy(new DefaultRedirectStrategy() {
@Override
public boolean isRedirected(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws ProtocolException {
if (super.isRedirected(request, response, context)) {
return true;
}
int statusCode = response.getStatusLine().getStatusCode();
String method = request.getRequestLine().getMethod();
Header locationHeader = response.getFirstHeader("location");
switch (statusCode) {
case HttpStatus.SC_MOVED_TEMPORARILY:
return locationHeader != null && WEBDAV_REDIRECTABLE.contains(method);
case HttpStatus.SC_MOVED_PERMANENTLY:
case HttpStatus.SC_TEMPORARY_REDIRECT:
return WEBDAV_REDIRECTABLE.contains(method);
default:
return false;
}
}
});
if (user != null) {
client.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(user, password));
PreemptiveAuthInterceptor interceptor = new PreemptiveAuthInterceptor();
client.addRequestInterceptor(interceptor, 0);
}
if (proxyDetails != null) {
if (proxyDetails.isUseSystemProxy()) {
System.setProperty("java.net.useSystemProxies", "true");
} else {
System.setProperty("java.net.useSystemProxies", "false");
if (proxyDetails.getProxyHost() != null && proxyDetails.getProxyHost().length() > 0) {
HttpHost proxy = new HttpHost(proxyDetails.getProxyHost(), proxyDetails.getProxyPort(), "http");
client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
if (proxyDetails.hasAuth()) {
client.getCredentialsProvider().setCredentials(
new AuthScope(proxyDetails.getProxyHost(), proxyDetails.getProxyPort()),
new UsernamePasswordCredentials(proxyDetails.getUserName(), proxyDetails.getPassword()));
}
}
}
}
transferService = new TransferService(client, connectionListeners);
transferService.setTimeout(timeoutMillis);
this.fileSyncer = fileSyncer;
}
/**
* Finds the resource by iterating through the path parts resolving
* collections as it goes. If any path component is not founfd returns null
*
* @param path
* @return
* @throws IOException
* @throws io.milton.httpclient.HttpException
*/
public Resource find(String path) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
return find(path, false);
}
public Resource find(String path, boolean invalidateCache) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
if (path == null || path.length() == 0 || path.equals("/")) {
return this;
}
if (path.startsWith("/")) {
path = path.substring(1);
}
String[] arr = path.split("/");
return _find(this, arr, 0, invalidateCache);
}
public static Resource _find(Folder parent, String[] arr, int i, boolean invalidateCache) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
String childName = arr[i];
if (invalidateCache) {
parent.flush();
}
Resource child = parent.child(childName);
if (i == arr.length - 1) {
return child;
} else {
if (child instanceof Folder) {
return _find((Folder) child, arr, i + 1, invalidateCache);
} else {
return null;
}
}
}
/**
* Find a folder at the given path. Is much the same as find(path), except
* that it throws an exception if the resource is not a folder
*
* @param path
* @return
* @throws IOException
* @throws io.milton.httpclient.HttpException
* @throws NotAuthorizedException
* @throws BadRequestException
*/
public Folder getFolder(String path) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
Resource res = find(path);
if (res instanceof Folder) {
return (Folder) res;
} else {
throw new RuntimeException("Not a folder: " + res.href());
}
}
/**
* Create a collection at the given absolute path. This path is NOT relative
* to the host's base path
*
* @param newUri
* @return
* @throws io.milton.httpclient.HttpException
* @throws NotAuthorizedException
* @throws ConflictException
* @throws BadRequestException
* @throws NotFoundException
* @throws URISyntaxException
*/
public synchronized int doMkCol(Path newUri) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
String url = this.buildEncodedUrl(newUri);
return doMkCol(url);
}
/**
*
* @param newUri - must be fully qualified and correctly encoded
* @return
* @throws io.milton.httpclient.HttpException
*/
public synchronized int doMkCol(String newUri) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
notifyStartRequest();
MkColMethod p = new MkColMethod(newUri);
try {
int result = Utils.executeHttpWithStatus(client, p, null, newContext());
if (result == 409) {
// probably means the folder already exists
p.abort();
return result;
}
Utils.processResultCode(result, newUri);
return result;
} catch (IOException ex) {
p.abort();
throw new RuntimeException(ex);
} finally {
notifyFinishRequest();
}
}
/**
* Attempts to lock a resource with infinite timeout and returns the lock
* token, which must be retained to unlock the resource
*
* @param uri - must be encoded
* @return
*/
public synchronized String doLock(String uri) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
return doLock(uri, -1);
}
/**
* Attempts to lock a resource with the specified timeout and returns the
* lock token, which must be retained to unlock the resource
*
* @param uri - must be encoded
* @param timeout lock timeout in seconds, or -1 if infinite
* @return
* @throws io.milton.httpclient.HttpException
*/
public synchronized String doLock(String uri, int timeout) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
notifyStartRequest();
LockMethod p = new LockMethod(uri, timeout);
try {
String lockXml = LOCK_XML.replace("${owner}", user);
HttpEntity requestEntity = new StringEntity(lockXml, "UTF-8");
p.setEntity(requestEntity);
HttpResponse resp = host().client.execute(p);
int result = resp.getStatusLine().getStatusCode();
Utils.processResultCode(result, uri);
return p.getLockToken(resp);
} catch (IOException ex) {
throw new RuntimeException(ex);
} finally {
notifyFinishRequest();
}
}
/**
*
* @param uri - must be encoded
* @param lockToken
* @return
* @throws io.milton.httpclient.HttpException
*/
public synchronized int doUnLock(String uri, String lockToken) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
notifyStartRequest();
UnLockMethod p = new UnLockMethod(uri, lockToken);
try {
int result = Utils.executeHttpWithStatus(client, p, null, newContext());
Utils.processResultCode(result, uri);
return result;
} catch (IOException ex) {
throw new RuntimeException(ex);
} finally {
notifyFinishRequest();
}
}
/**
*
* @param path - an Un-encoded path. Eg /a/b c/ = /a/b%20c/
* @param content
* @param contentLength
* @param contentType
* @return
*/
public HttpResult doPut(Path path, InputStream content, Long contentLength, String contentType, IfMatchCheck matchCheck) {
String dest = buildEncodedUrl(path);
return doPut(dest, content, contentLength, contentType, matchCheck, null);
}
public HttpResult doPut(Path path, byte[] data, String contentType) {
String dest = buildEncodedUrl(path);
ByteArrayInputStream bin = new ByteArrayInputStream(data);
return transferService.put(dest, bin, (long) data.length, contentType, null, null, newContext());
}
/**
*
* @param remotePath
* @param file
* @param listener
* @return - the result code
* @throws FileNotFoundException
* @throws HttpException
*/
public HttpResult doPut(Path remotePath, java.io.File file, IfMatchCheck matchCheck, ProgressListener listener) throws FileNotFoundException, HttpException, CancelledException, NotAuthorizedException, ConflictException {
if (fileSyncer != null) {
try {
fileSyncer.upload(this, file, remotePath, listener);
LogUtils.trace(log, "doPut: uploaded");
return new HttpResult(Response.Status.SC_OK.code, null);
} catch (NotFoundException e) {
// ZSync file was not found
log.trace("Not found: " + remotePath);
} catch (IOException ex) {
throw new GenericHttpException(remotePath.toString(), ex);
}
}
InputStream in = null;
try {
in = new FileInputStream(file);
String dest = buildEncodedUrl(remotePath);
return doPut(dest, in, file.length(), null, matchCheck, listener);
} finally {
IOUtils.closeQuietly(in);
}
}
/**
* Uploads the data. Does not do any file syncronisation
*
* @param newUri - encoded full URL
* @param content
* @param contentLength
* @param contentType
* @return - the result code
*/
public synchronized HttpResult doPut(String newUri, InputStream content, Long contentLength, String contentType, IfMatchCheck matchCheck, ProgressListener listener) {
LogUtils.trace(log, "doPut", newUri);
return transferService.put(newUri, content, contentLength, contentType, matchCheck, listener, newContext());
}
/**
*
* @param from - encoded source url
* @param newUri - encoded destination
* @return
* @throws io.milton.httpclient.HttpException
*/
public synchronized int doCopy(String from, String newUri) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
notifyStartRequest();
CopyMethod m = new CopyMethod(from, newUri);
m.addHeader("Overwrite", "T");
try {
int res = Utils.executeHttpWithStatus(client, m, null, newContext());
Utils.processResultCode(res, from);
return res;
} catch (HttpException | IOException ex) {
throw new RuntimeException(ex);
} finally {
notifyFinishRequest();
}
}
/**
* Deletes the item at the given path, relative to the root path of this
* host
*
* @param path - unencoded and relative to Host's rootPath
* @return
* @throws IOException
* @throws io.milton.httpclient.HttpException
* @throws NotAuthorizedException
* @throws ConflictException
* @throws BadRequestException
* @throws NotFoundException
*/
public synchronized int doDelete(Path path) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
String dest = buildEncodedUrl(path);
return doDelete(dest);
}
/**
*
* @param url - encoded url
* @return
* @throws IOException
* @throws io.milton.httpclient.HttpException
*/
public synchronized int doDelete(String url) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
notifyStartRequest();
HttpDelete m = new HttpDelete(url);
try {
int res = Utils.executeHttpWithStatus(client, m, null, newContext());
Utils.processResultCode(res, url);
return res;
} catch (HttpException ex) {
throw new RuntimeException(ex);
} finally {
notifyFinishRequest();
}
}
/**
*
* @param sourceUrl - encoded source url
* @param newUri - encoded destination url
* @return
* @throws IOException
*/
public synchronized int doMove(String sourceUrl, String newUri) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException, URISyntaxException {
notifyStartRequest();
MoveMethod m = new MoveMethod(sourceUrl, newUri);
try {
int res = Utils.executeHttpWithStatus(client, m, null, newContext());
Utils.processResultCode(res, sourceUrl);
return res;
} finally {
notifyFinishRequest();
}
}
public synchronized List propFind(Path path, int depth, QName... fields) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
List list = new ArrayList<>(Arrays.asList(fields));
return propFind(path, depth, list);
}
public synchronized List propFind(String path, int depth, QName... fields) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
List list = new ArrayList<>(Arrays.asList(fields));
String href = baseHref() + rootPath + path;
log.info("propFind: href={}", href);
return _doPropFind(href, depth, list);
}
/**
*
* @param path - unencoded path, which will be evaluated relative to this
* Host's basePath
* @param depth - 1 is to find immediate children, 2 includes their
* children, etc
* @param fields - the list of fields to get, or null to use default fields
* @return
* @throws IOException
* @throws io.milton.httpclient.HttpException
* @throws NotAuthorizedException
* @throws BadRequestException
*/
public synchronized List propFind(Path path, int depth, List fields) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
String url = buildEncodedUrl(path);
return _doPropFind(url, depth, fields);
}
/**
*
* @param url - the encoded absolute URL to query. This method does not
* apply basePath
* @param depth - depth to generate responses for. Zero means only the
* specified url, 1 means it and its direct children, etc
* @return
* @throws IOException
* @throws io.milton.httpclient.HttpException
*/
public synchronized List _doPropFind(final String url, final int depth, List fields) throws IOException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException {
log.info("doPropFind: " + url);
notifyStartRequest();
final PropFindMethod m = new PropFindMethod(url);
m.addHeader("Depth", depth + "");
m.addHeader("Accept-Charset", "utf-8,*;q=0.1");
m.addHeader("Accept", "text/xml");
try {
String propFindXml = buildPropFindXml(fields);
HttpEntity requestEntity = new StringEntity(propFindXml, "text/xml", "UTF-8");
m.setEntity(requestEntity);
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final List responses = new ArrayList<>();
ResponseHandler respHandler = response -> {
Header serverDateHeader = response.getFirstHeader("Date");
if (response.getStatusLine().getStatusCode() == 207) {
HttpEntity entity = response.getEntity();
if (entity != null) {
entity.writeTo(bout);
//String s = new String(bout.toByteArray());
//log.info("_doPropFind: res{}", s);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
Document document = getResponseAsDocument(bin);
String sServerDate = null;
if (serverDateHeader != null) {
sServerDate = serverDateHeader.getValue();
}
Date serverDate = null;
if (sServerDate != null && sServerDate.length() > 0) {
try {
serverDate = DateUtils.parseDate(sServerDate);
} catch (DateParseException ex) {
log.warn("Couldnt parse date header: " + sServerDate, ex);
}
}
//System.out.println("propfind: " + url);
buildResponses(document, serverDate, responses, depth);
}
}
return response.getStatusLine().getStatusCode();
};
Integer res = client.execute(m, respHandler, newContext());
log.info("_doPropFind: result code {}", res);
Utils.processResultCode(res, url);
return responses;
} catch (ConflictException | HttpException ex) {
throw new RuntimeException(ex);
} catch (NotFoundException e) {
log.trace("not found: " + url);
return null;
} finally {
notifyFinishRequest();
}
}
/**
*
* @return - child responses only, not the requested url
*/
public void buildResponses(Document document, Date serverDate, List responses, int depth) {
Element root = document.getRootElement();
List responseEls = RespUtils.getElements(root, "response");
boolean isFirst = true;
for (Element el : responseEls) {
if (!isFirst || depth == 0) { // if depth=0 must return first and only result
PropFindResponse resp = new PropFindResponse(serverDate, el);
//String href = resp.getHref();
responses.add(resp);
} else {
isFirst = false;
}
}
}
public Document getResponseAsDocument(InputStream in) throws IOException {
// IOUtils.copy( in, out );
// String xml = out.toString();
try {
return getJDomDocument(in);
} catch (JDOMException ex) {
throw new RuntimeException(ex);
}
}
/**
*
* @param url - fully qualified and encoded URL
* @param receiver
* @param rangeList - if null does a normal GET request
* @throws io.milton.httpclient.HttpException
* @throws io.milton.httpclient.Utils.CancelledException
*/
public synchronized void doGet(String url, StreamReceiver receiver, List rangeList, ProgressListener listener) throws io.milton.httpclient.HttpException, Utils.CancelledException, NotAuthorizedException, BadRequestException, ConflictException, NotFoundException {
transferService.get(url, receiver, rangeList, listener, newContext());
}
/**
*
* @param path - the path to get, relative to the base path of the host
* @param file - the file to write content to
* @param listener
* @throws IOException
* @throws NotFoundException
* @throws io.milton.httpclient.HttpException
* @throws io.milton.httpclient.Utils.CancelledException
* @throws NotAuthorizedException
* @throws BadRequestException
* @throws ConflictException
*/
public synchronized void doGet(Path path, final java.io.File file, ProgressListener listener) throws IOException, NotFoundException, io.milton.httpclient.HttpException, CancelledException, NotAuthorizedException, BadRequestException, ConflictException {
LogUtils.trace(log, "doGet", path);
if (fileSyncer != null) {
fileSyncer.download(this, path, file, listener);
} else {
String url = this.buildEncodedUrl(path);
transferService.get(url, in -> {
OutputStream out = null;
BufferedOutputStream bout = null;
try {
out = FileUtils.openOutputStream(file);
bout = new BufferedOutputStream(out);
IOUtils.copy(in, bout);
bout.flush();
} finally {
IOUtils.closeQuietly(bout);
IOUtils.closeQuietly(out);
}
}, null, listener, newContext());
}
}
public synchronized byte[] doGet(Path path) throws IOException, NotFoundException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException, ConflictException {
return doGet(path, null);
}
public synchronized byte[] doGet(Path path, Map queryParams) throws IOException, NotFoundException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException, ConflictException {
LogUtils.trace(log, "doGet", path);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
doGet(path, bout, queryParams);
return bout.toByteArray();
}
public synchronized void doGet(Path path, final OutputStream out, Map queryParams) throws IOException, NotFoundException, io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException, ConflictException {
String url = this.buildEncodedUrl(path);
LogUtils.trace(log, "doGet", url);
if (queryParams != null && queryParams.size() > 0) {
String qs = Utils.format(queryParams, "UTF-8");
url += "?" + qs;
}
transferService.get(url, in -> IOUtils.copy(in, out), null, null, newContext());
}
/**
*
* @param path - encoded path, but not fully qualified. Must not be prefixed
* with a slash, as it will be appended to the host's URL
* @throws java.net.ConnectException
* @throws NotAuthorizedException
* @throws UnknownHostException
* @throws SocketTimeoutException
* @throws IOException
* @throws io.milton.httpclient.HttpException
*/
public synchronized void options(String path) throws java.net.ConnectException, NotAuthorizedException, UnknownHostException, SocketTimeoutException, IOException, io.milton.httpclient.HttpException, NotFoundException {
String url = this.encodedUrl() + path;
doOptions(url);
}
public void doOptions(Path path) throws NotFoundException, NotAuthorizedException, IOException, io.milton.httpclient.HttpException {
String dest = buildEncodedUrl(path);
doOptions(dest);
}
private synchronized void doOptions(String url) throws NotFoundException, NotAuthorizedException, IOException, io.milton.httpclient.HttpException {
notifyStartRequest();
log.trace("doOptions: {}", url);
HttpOptions m = new HttpOptions(url);
InputStream in = null;
try {
int res = Utils.executeHttpWithStatus(client, m, null, newContext());
log.trace("result code: " + res);
if (res == 301 || res == 302) {
return;
}
Utils.processResultCode(res, url);
} catch (ConflictException | BadRequestException ex) {
throw new RuntimeException(ex);
} finally {
Utils.close(in);
notifyFinishRequest();
}
}
/**
* GET the contents of the given path. The path is non-encoded, and it
* relative to the host's root.
*
* @param path
* @return
* @throws io.milton.httpclient.HttpException
* @throws NotAuthorizedException
* @throws BadRequestException
* @throws ConflictException
* @throws NotFoundException
*/
public synchronized byte[] get(Path path) throws io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException, ConflictException, NotFoundException {
String url = buildEncodedUrl(path);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
transferService.get(url, in -> {
try {
IOUtils.copy(in, out);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}, null, null, newContext());
} catch (CancelledException ex) {
throw new RuntimeException("Should never happen because no progress listener is set", ex);
}
return out.toByteArray();
}
/**
* Retrieve the bytes at the specified path.
*
* @param path - encoded and relative to host's rootPath. Must NOT be slash
* prefixed as it will be appended to the host's url
* @return
* @throws io.milton.httpclient.HttpException
* @throws io.milton.http.exceptions.NotAuthorizedException
* @throws io.milton.http.exceptions.BadRequestException
* @throws io.milton.http.exceptions.ConflictException
* @throws io.milton.http.exceptions.NotFoundException
*/
public synchronized byte[] get(String path) throws io.milton.httpclient.HttpException, NotAuthorizedException, BadRequestException, ConflictException, NotFoundException {
String url = this.encodedUrl() + path;
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
transferService.get(url, in -> {
try {
IOUtils.copy(in, out);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}, null, null, newContext());
} catch (CancelledException ex) {
throw new RuntimeException("Should never happen because no progress listener is set", ex);
}
return out.toByteArray();
}
/**
* POSTs the variables and returns the body
*
* @param url - fully qualified and encoded URL to post to
* @param params
* @return - the body of the response
*/
public String doPost(String url, Map params) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
log.info("POST: url={} timeout={}", url, timeout);
notifyStartRequest();
HttpPost m = new HttpPost(url);
List formparams = new ArrayList<>();
for (Entry entry : params.entrySet()) {
formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity entity;
try {
entity = new UrlEncodedFormEntity(formparams);
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
m.setEntity(entity);
long tm = System.currentTimeMillis();
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int res = Utils.executeHttpWithStatus(client, m, bout, newContext());
Utils.processResultCode(res, url);
return bout.toString();
} catch (HttpException ex) {
tm = System.currentTimeMillis() - tm;
throw new RuntimeException("RuntimeException URL=" + url + " duration=" + tm, ex);
} catch (IOException ex) {
throw new RuntimeException("IOException URL=" + url + " duration=" + tm, ex);
} finally {
notifyFinishRequest();
}
}
// /**
// *
// * @param url - fully qualified and encoded
// * @param params
// * @param parts
// * @return
// * @throws io.milton.httpclient.HttpException
// */
// public String doPost(String url, Map params, Part[] parts) throws io.milton.httpclient.HttpException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
// notifyStartRequest();
// PostMethod filePost = new PostMethod(url);
// if (params != null) {
// for (Entry entry : params.entrySet()) {
// filePost.addParameter(entry.getKey(), entry.getValue());
// }
// }
// filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
//
// InputStream in = null;
// try {
// int res = client.executeMethod(filePost);
// Utils.processResultCode(res, url);
// in = filePost.getResponseBodyAsStream();
// ByteArrayOutputStream bout = new ByteArrayOutputStream();
// IOUtils.copy(in, bout);
// return bout.toString();
// } catch (HttpException ex) {
// throw new RuntimeException(ex);
// } catch (IOException ex) {
// throw new RuntimeException(ex);
// } finally {
// Utils.close(in);
// filePost.releaseConnection();
// notifyFinishRequest();
// }
// }
@Override
public Host host() {
return this;
}
@Override
public String href() {
String s = baseHref();
if (rootPath != null) {
s += rootPath;
}
if (!s.endsWith("/")) {
s += "/";
}
return s;
}
public String baseHref() {
String s = "http";
int defaultPort = 80;
if (secure) {
s += "s";
defaultPort = 443;
}
s += "://" + server;
if (this.port != null && this.port != defaultPort && this.port > 0) {
s += ":" + this.port;
}
s += "/";
return s;
}
/**
* Returns the fully qualified URL for the given path
*
* @param path
* @return
*/
public String getHref(Path path) {
String s = href();
if (!path.isRelative()) {
s = s.substring(0, s.length() - 1);
}
//log.trace("host href: " + s);
return s + path; // path will be absolute
}
@Override
public String encodedUrl() {
String s = buildEncodedUrl(Path.root);
if (!s.endsWith("/")) {
s += "/";
}
return s;
}
public io.milton.httpclient.Folder getOrCreateFolder(Path remoteParentPath, boolean create) throws io.milton.httpclient.HttpException, IOException, NotAuthorizedException, ConflictException, BadRequestException, NotFoundException {
log.trace("getOrCreateFolder: {}", remoteParentPath);
io.milton.httpclient.Folder f = this;
if (remoteParentPath != null) {
for (String childName : remoteParentPath.getParts()) {
if (childName.equals("_code")) {
f = new Folder(f, childName, cache);
} else {
io.milton.httpclient.Resource child = f.child(childName);
if (child == null) {
if (create) {
f = f.createFolder(childName);
} else {
return null;
}
} else if (child instanceof io.milton.httpclient.Folder) {
f = (io.milton.httpclient.Folder) child;
} else {
log.warn("Can't upload. A resource exists with the same name as a folder, but is a file: " + remoteParentPath + " - " + child.getClass());
return null;
}
}
}
}
return f;
}
/**
* @return the timeout
*/
public int getTimeout() {
return timeout;
}
/**
* @param timeout the timeout to set
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
transferService.setTimeout(timeout);
}
public Map getCookies() {
return cookies;
}
public void addCookie(String name, String value) {
cookies.put(name, value);
}
private void notifyStartRequest() {
for (ConnectionListener l : connectionListeners) {
l.onStartRequest();
}
}
private void notifyFinishRequest() {
for (ConnectionListener l : connectionListeners) {
l.onFinishRequest();
}
}
public void addConnectionListener(ConnectionListener e) {
connectionListeners.add(e);
}
public String buildEncodedUrl(Path path) {
Path base = Path.path(rootPath);
Path p = base.add(path);
return baseHref() + Utils.buildEncodedUrl(p);
}
public boolean isSecure() {
return secure;
}
public void setSecure(boolean secure) {
this.secure = secure;
}
public HttpClient getClient() {
return client;
}
/**
* TODO: should optimise so it only generates once per set of fields
*
* @param fields
* @return
*/
private String buildPropFindXml(List fields) {
try {
if (fields == null) {
fields = defaultFields;
}
Element elPropfind = new Element("propfind", RespUtils.NS_DAV);
Document doc = new Document(elPropfind);
Element elProp = new Element("prop", RespUtils.NS_DAV);
elPropfind.addContent(elProp);
for (QName qn : fields) {
Element elName = new Element(qn.getLocalPart(), qn.getPrefix(), qn.getNamespaceURI());
elProp.addContent(elName);
}
XMLOutputter outputter = new XMLOutputter();
ByteArrayOutputStream out = new ByteArrayOutputStream();
outputter.output(doc, out);
return out.toString("UTF-8");
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public boolean isUseDigestForPreemptiveAuth() {
return useDigestForPreemptiveAuth;
}
public void setUseDigestForPreemptiveAuth(boolean useDigestForPreemptiveAuth) {
this.useDigestForPreemptiveAuth = useDigestForPreemptiveAuth;
}
public boolean isUsePreemptiveAuth() {
return usePreemptiveAuth;
}
public void setUsePreemptiveAuth(boolean usePreemptiveAuth) {
this.usePreemptiveAuth = usePreemptiveAuth;
}
protected HttpContext newContext() {
HttpContext context = new BasicHttpContext();
if (usePreemptiveAuth) {
AuthScheme authScheme;
if (useDigestForPreemptiveAuth) {
authScheme = new DigestScheme();
} else {
authScheme = new BasicScheme();
}
context.setAttribute("preemptive-auth", authScheme);
}
CookieStore cookieStore = new BasicCookieStore();
for (Entry entry : cookies.entrySet()) {
BasicClientCookie cookie = new BasicClientCookie(entry.getKey(), entry.getValue());
cookie.setDomain(this.server);
cookie.setPath("/");
cookieStore.addCookie(cookie);
}
//cookieStore.addCookie(new BasicClientCookie(name, name));
context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);
return context;
}
static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
private String nonce;
private String realm;
public PreemptiveAuthInterceptor() {
}
@Override
public void process(final HttpRequest request, final HttpContext context) {
AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
// If no auth scheme avaialble yet, try to initialize it
// preemptively
if (authState.getAuthScheme() == null) {
AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
//AuthScheme authScheme = cachedAuthScheme;
if (authScheme != null) {
boolean canDoAuth = false;
if (authScheme instanceof DigestScheme) {
DigestScheme d = (DigestScheme) authScheme;
if (nonce != null) {
d.overrideParamter("nonce", nonce);
}
if (realm != null) {
d.overrideParamter("realm", realm);
canDoAuth = true;
}
} else if (authScheme instanceof BasicScheme) {
canDoAuth = true;
}
if (canDoAuth) {
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
if (creds == null) {
throw new RuntimeException("No credentials for preemptive authentication");
}
authState.setAuthScheme(authScheme);
authState.setCredentials(creds);
}
}
} else {
if (authState.getAuthScheme() instanceof DigestScheme) {
DigestScheme scheme = (DigestScheme) authState.getAuthScheme();
nonce = scheme.getParameter("nonce");
realm = scheme.getParameter("realm");
// log.info("PreemptiveAuthInterceptor: record cached realm: " + realm + " and nonce: " + nonce);
}
}
}
}
static class NoRetryHttpRequestRetryHandler implements HttpRequestRetryHandler {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
return false;
}
}
static class MyDefaultHttpClient extends DefaultHttpClient {
public MyDefaultHttpClient(ClientConnectionManager cm, HttpParams params) {
super(cm, params);
}
@Override
protected HttpRequestRetryHandler createHttpRequestRetryHandler() {
return new NoRetryHttpRequestRetryHandler();
}
@Override
protected RequestDirector createClientRequestDirector(HttpRequestExecutor requestExec, ClientConnectionManager conman, ConnectionReuseStrategy reustrat, ConnectionKeepAliveStrategy kastrat, HttpRoutePlanner rouplan, HttpProcessor httpProcessor, HttpRequestRetryHandler retryHandler, RedirectStrategy redirectStrategy, AuthenticationHandler targetAuthHandler, AuthenticationHandler proxyAuthHandler, UserTokenHandler stateHandler, HttpParams params) {
return super.createClientRequestDirector(requestExec, conman, reustrat, kastrat, rouplan, httpProcessor, retryHandler, redirectStrategy, targetAuthHandler, proxyAuthHandler, stateHandler, params);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy