/** * Copyright 2010 JBoss Inc * * 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.eclipse.webdav.http.client; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import org.eclipse.webdav.IContext; import org.eclipse.webdav.client.WebDAVFactory; import org.eclipse.webdav.internal.authentication.AuthorizationAuthority; import org.eclipse.webdav.internal.kernel.Context; import org.eclipse.webdav.internal.kernel.utils.Assert; /** * <p>An HTTP 1.0 or 1.1 client. Single instances of this client enable * users to talk with multiple origin servers. Moreover, connections * to origin servers are maintained for reuse. * * <p>Conveniences are provided for managing proxy servers, handling * authentication, and setting up request headers. * * <p>Here is some sample code: * * <code> * HttpClient client = new HttpClient(); * try { * Request request = null; * Response response = null; * try { * URL resourceUrl = new URL("http://hostname/index.html"); * request = new Request("GET", resourceUrl, (Context) null); * response = client.invoke(request); * System.out.print(response); * InputStream is = response.getInputStream(); * int c; * while ((c = is.read()) != -1) { * System.out.print((char) c); * } * } catch (IOException e) { * e.printStackTrace(); * } finally { * if (request != null) { * try { * request.close(); * } catch (IOException e) { * e.printStackTrace(); * } * } * if (response != null) { * try { * response.close(); * } catch (IOException e) { * e.printStackTrace(); * } * } * } * } finally { * client.close(); * } * </code> * <p> * <b>Note:</b> This class/interface is part of an interim API that is still under * development and expected to change significantly before reaching stability. * It is being made available at this early stage to solicit feedback from pioneering * adopters on the understanding that any code that uses this API will almost * certainly be broken (repeatedly) as the API evolves. * </p> */ public class HttpClient implements IStatusCodes { /** * The HTTP version of this client. */ private double httpVersion = 1.1; /** * Indicates whether this client is closed. */ private boolean closed = true; /** * The connections recycler is responsible for closing and discarding * unused connections that have become stale. */ private ConnectionsRecycler connectionsRecycler = new ConnectionsRecycler("Connections Recycler"); //$NON-NLS-1$ /** * The authorization authority authorizes requests. */ private AuthorizationAuthority authority = null; /** * A <code>Hashtable</code> mapping origin server <code>URL</code>s * to <code>Context</code>s. The contexts in this table are preferred * over the default context. */ private Hashtable contexts = new Hashtable(5); /** * The default context is used when one hasn't been set for a * particular origin server. */ private IContext defaultContext = null; /** * The <code>URL</code> of the default proxy server. The default is * not used if the particular origin server matches a proxy server * exception, or if there is already a proxy server associated with * the origin server. */ private URL defaultProxyServerUrl = null; /** * A <code>Hashtable</code> mapping origin server <code>URL</code>s * to proxy server <code>URL</code>s. If an origin server * <code>URL</code> has an entry in this table, the proxy server * with the mapped <code>URL</code> is used. */ private Hashtable proxyServerUrls = new Hashtable(5); /** * A set of proxy server exception patterns used to bypass the * default proxy server. Any origin server whos host and port * match the pattern do not use the default proxy server. */ private Hashtable proxyServerExceptions = new Hashtable(5); /** * The maximum number of times a request is retried after an * <code>IOException</code> occurs. */ private int maxRetries = 1; /** * The maximum number of <code>URL</code> location redirects. */ private int maxRedirects = 4; /** * The socket timeout (in milliseconds). */ private int socketTimeout = 0; /** * The factory used to create <code>Socket</code>s. */ private ISocketFactory socketFactory = null; /** * The factory used to create <code>Context</code>s. */ private WebDAVFactory webDAVFactory = new WebDAVFactory(); /** * The <code>ConnectionsRecycler</code> manages a collection of * persistent <code>HttpConnection</code>s. Connections that remain * unused for a given period of time are closed and discarded. */ public class ConnectionsRecycler extends Thread { /** * The time (in milliseconds) that a connection remains unused * before it is closed and discared. */ private long connectionTimeout = 10000; /** * A <code>Hashtable</code> who's keys are origin server * <code>URL</code>s that map to vectors of connections that are * currently unused. */ private Hashtable unusedConnections = new Hashtable(5); /** * A <code>Hashtable</code> who's keys are origin server * <code>URL</code>s that map to vectors of connections that are * currently in use. */ private Hashtable usedConnections = new Hashtable(5); /** * Creates a new <code>ConnectionsRecycler</code> with the given * name. * * @param name the name of the new connections recycler */ public ConnectionsRecycler(String name) { super(name); this.setDaemon(true); } /** * Closes this connections recycler. All of its connections are * closed and discarded. */ public synchronized void close() { interrupt(); closeConnections(unusedConnections); closeConnections(usedConnections); } private synchronized void closeConnections(Hashtable connections) { Enumeration originServerUrls = connections.keys(); while (originServerUrls.hasMoreElements()) { URL originServerUrl = (URL) originServerUrls.nextElement(); Vector connectionsVector = (Vector) connections.get(originServerUrl); Enumeration connectionsEnum = connectionsVector.elements(); while (connectionsEnum.hasMoreElements()) { HttpConnection connection = (HttpConnection) connectionsEnum.nextElement(); try { connection.close(); } catch (IOException e) { // ignore } } } connections.clear(); } /** * Returns an unused connection that is connected to the origin * server at the given <code>URL</code>. The connection is marked * as in use before it is returned. * * @param originServerUrl the <code>URL</code> of an origin * server * @return an unused connection that is connected to the origin * server at the given <code>URL</code> * @see #putConnection(HttpConnection) */ public synchronized HttpConnection getConnection(URL originServerUrl) { HttpConnection connection = null; Vector unusedConnections = (Vector) this.unusedConnections.get(originServerUrl); if (unusedConnections == null || unusedConnections.isEmpty()) { connection = new HttpConnection(originServerUrl); } else { connection = (HttpConnection) unusedConnections.lastElement(); unusedConnections.removeElementAt(unusedConnections.size() - 1); } Vector usedConnections = (Vector) this.usedConnections.get(originServerUrl); if (usedConnections == null) { usedConnections = new Vector(5); this.usedConnections.put(originServerUrl, usedConnections); } usedConnections.addElement(connection); connection.setTimestamp(new Date()); return connection; } public long getConnectionTimeout() { return connectionTimeout; } /** * Marks the given connection that is in use as unused. * * @param connection a connection that is in use * @see #getConnection(URL) */ public synchronized void putConnection(HttpConnection connection) { URL resourceUrl = connection.getResourceUrl(); URL originServerUrl = null; try { originServerUrl = new URL(resourceUrl.getProtocol(), resourceUrl.getHost(), resourceUrl.getPort(), "/"); //$NON-NLS-1$ } catch (MalformedURLException e) { // ignore? } Vector usedConnections = (Vector) this.usedConnections.get(originServerUrl); usedConnections.remove(connection); Vector unusedConnections = (Vector) this.unusedConnections.get(originServerUrl); if (unusedConnections == null) { unusedConnections = new Vector(5); this.unusedConnections.put(originServerUrl, unusedConnections); } unusedConnections.addElement(connection); connection.setTimestamp(new Date()); } private synchronized void recycle() { Vector staleOriginServerUrls = new Vector(3); Enumeration originServerUrls = unusedConnections.keys(); while (originServerUrls.hasMoreElements()) { URL originServerUrl = (URL) originServerUrls.nextElement(); Vector connectionsVector = (Vector) unusedConnections.get(originServerUrl); if (connectionsVector.isEmpty()) { staleOriginServerUrls.add(originServerUrl); break; } Vector staleConnections = new Vector(5); Enumeration connectionsEnum = connectionsVector.elements(); while (connectionsEnum.hasMoreElements()) { HttpConnection connection = (HttpConnection) connectionsEnum.nextElement(); long currentTime = System.currentTimeMillis(); long connectionTime = connection.getTimestamp().getTime(); if (currentTime - connectionTime >= connectionTimeout) { staleConnections.addElement(connection); } } // end-while connectionsEnum = staleConnections.elements(); while (connectionsEnum.hasMoreElements()) { HttpConnection connection = (HttpConnection) connectionsEnum.nextElement(); connectionsVector.remove(connection); try { connection.close(); } catch (IOException e) { // ignore? } } // end-while } // end-while Enumeration staleOriginServerUrlsEnum = staleOriginServerUrls.elements(); while (staleOriginServerUrlsEnum.hasMoreElements()) { unusedConnections.remove(staleOriginServerUrlsEnum.nextElement()); } } public void run() { while (!interrupted()) { try { Thread.sleep(connectionTimeout); recycle(); } catch (InterruptedException e) { interrupt(); } } } public void setConnectionTimeout(long connectionTimeout) { this.connectionTimeout = connectionTimeout; } } /** * This type of input stream is passed back to the user in message * responses. It serves to keep the stream's connection alive by * returning it to this client's unused connections when the stream * is closed. * * @see Response */ class PersistentInputStream extends FilterInputStream { /** * This input stream's connection. */ private HttpConnection connection; /** * Creates a new persistent input stream on the given connection. * * @param connection this input stream's connection */ PersistentInputStream(HttpConnection connection) throws IOException { super(null); try { in = connection.getInputStream(); } catch (IOException e) { closeConnection(); throw e; } this.connection = connection; } /** * @see InputStream#available() */ public int available() throws IOException { try { return super.available(); } catch (IOException e) { closeConnection(); throw e; } } /** * @see InputStream#close() */ public void close() throws IOException { try { super.close(); } catch (IOException e) { closeConnection(); throw e; } finally { connectionsRecycler.putConnection(connection); } } private void closeConnection() { try { connection.close(); } catch (IOException e) { // ignore? } } /** * @see InputStream#read() */ public int read() throws IOException { try { return super.read(); } catch (IOException e) { closeConnection(); throw e; } } /** * @see InputStream#read(byte[]) */ public int read(byte b[]) throws IOException { try { return super.read(b); } catch (IOException e) { closeConnection(); throw e; } } /** * @see InputStream#read(byte[], int, int) */ public int read(byte b[], int off, int len) throws IOException { try { return super.read(b, off, len); } catch (IOException e) { closeConnection(); throw e; } } /** * @see InputStream#reset() */ public synchronized void reset() throws IOException { try { super.reset(); } catch (IOException e) { closeConnection(); throw e; } } /** * @see InputStream#skip(long) */ public long skip(long n) throws IOException { try { return super.skip(n); } catch (IOException e) { closeConnection(); throw e; } } } /** * Creates a new <code>HttpClient</code>. */ public HttpClient() { open(); } /** * Adds the given proxy server exception pattern to this client. * Origin servers whose hostname match the pattern do not communicate * through the defualt proxy server. The pattern must contain zero or * one stars (*). A star must appear at either the beginning or the * end of the pattern. A star matches zero or more characters. The * following are valid patterns: * <ul> * <li>www.company.com:80</li> * <li>*.company.com</li> * <li>www.company.*</li> * </ul> * * @param pattern a proxy server exception pattern, for example: * "*.company.com". * @see #getDefaultProxyServerUrl() * @see #getProxyServerExceptions() * @see #getProxyServerUrl(URL) * @see #removeProxyServerException(String) * @see #setDefaultProxyServerUrl(URL) * @see #setProxyServerUrl(URL, URL) */ public void addProxyServerException(String pattern) { Assert.isNotNull(pattern); proxyServerExceptions.put(pattern, pattern); } /** * Closes this client. */ public void close() { if (!closed) { connectionsRecycler.close(); closed = true; } } public long getConnectionTimeout() { return connectionsRecycler.getConnectionTimeout(); } /** * Returns the context for the origin server at the given * <code>URL</code>. * * @param originServerUrl the <code>URL</code> of an origin server * @return the context for the origin server at the given * <code>URL</code> * @see #getDefaultContext() * @see #setDefaultContext(Context) * @see #setContext(URL, Context) */ public IContext getContext(URL originServerUrl) { Assert.isNotNull(originServerUrl); return (IContext) contexts.get(originServerUrl); } /** * Returns the default context. The default context is used for servers * that do not have a context set. Initially the default context is * <code>null</code>. * * @return the default context, or <code>null</code> * @see #getContext(URL) * @see #setContext(URL, Context) * @see #setDefaultContext(Context) */ public IContext getDefaultContext() { return defaultContext; } /** * Returns the <code>URL</code> of the default proxy server which is * used for all servers that do not have their proxy server set and * do not match a proxy server exception. If the default proxy server * <code>URL</code> is <code>null</code>, no default proxy server is * used. Initially the default proxy server <code>URL</code> is * <code>null</code>. * * @return the <code>URL</code> of the default proxy server, or * <code>null</code> * @see #addProxyServerException(String) * @see #getProxyServerExceptions() * @see #getProxyServerUrl(URL) * @see #removeProxyServerException(String) * @see #setDefaultProxyServerUrl(URL) * @see #setProxyServerUrl(URL, URL) */ public URL getDefaultProxyServerUrl() { return defaultProxyServerUrl; } /** * Returns the version of HTTP this client uses for communication with * servers. HTTP/1.1 is used by default. * * @return the version of HTTP this client uses for communication with * servers * @see #setHttpVersion(double) */ public double getHttpVersion() { return httpVersion; } /** * Returns the maximum number of <code>URL</code> location redirects. The * maximum is 4 by default. * * @return the maximum number of <code>URL</code> location redirects */ public int getMaxRedirects() { return maxRedirects; } /** * Returns the maximum number of times a request is retried after an * <code>IOException</code> occurs. The maximum is 1 by default. * * @return the maximum number of retries * @see #setMaxRetries(int) */ public int getMaxRetries() { return maxRetries; } /** * Returns an <code>Enumeration</code> over the origin server * <code>URL</code>s known to this client. The known origin server * <code>URL</code>s are gleaned from this client's mapped contexts and * mapped proxy server <code>URL</code>s. * * @return an <code>Enumeration</code> over the origin server * <code>URL</code>s known to this client * @see #getContext(URL) * @see #setContext(URL, Context) * @see #getProxyServerUrl(URL) * @see #setProxyServerUrl(URL, URL) */ public Enumeration getOriginServerUrls() { final Enumeration enum1 = contexts.keys(); final Enumeration enum2 = proxyServerUrls.keys(); Enumeration e = new Enumeration() { public boolean hasMoreElements() { return enum1.hasMoreElements() || enum2.hasMoreElements(); } public Object nextElement() { if (enum1.hasMoreElements()) return enum1.nextElement(); return enum2.nextElement(); } }; return e; } /** * Returns an <code>Enumeration</code> over this client's proxy server * exception patterns. * * @return an <code>Enumeration</code> over this client's proxy server * exception patterns * @see #addProxyServerException(String) * @see #getDefaultProxyServerUrl() * @see #getProxyServerUrl(URL) * @see #removeProxyServerException(String) * @see #setDefaultProxyServerUrl(URL) * @see #setProxyServerUrl(URL, URL) */ public Enumeration getProxyServerExceptions() { return proxyServerExceptions.keys(); } /** * Returns the <code>URL</code> of the proxy server that the origin * server at the given <code>URL</code> uses, or <code>null</code> if * no proxy server is used. * * @param originServerUrl the <code>URL<code> of an origin server * @return the <code>URL</code> of a proxy server, or <code>null</code> * @see #addProxyServerException(String) * @see #getDefaultProxyServerUrl() * @see #getProxyServerExceptions() * @see #removeProxyServerException(String) * @see #setDefaultProxyServerUrl(URL) * @see #setProxyServerUrl(URL, URL) */ public URL getProxyServerUrl(URL originServerUrl) { Assert.isNotNull(originServerUrl); return (URL) proxyServerUrls.get(originServerUrl); } /** * Returns the socket read timeout (in milliseconds) for this client. A * value of zero indicates that a socket read operation will block * indefinitely waiting for data. The value is zero by default. * * @return the socket read timeout (in milliseconds) for this client * @see #setSoTimeout(int) */ public int getSoTimeout() { return socketTimeout; } /** * Sends the given request to the server and returns the server's * response. * * @param request the request to send to the server * @return the server's response * @throws IOException if an I/O error occurs. Reasons include: * <ul> * <li>The client is closed. * <li>The client could not connect to the server * <li>An I/O error occurs while communicating with the server * <ul> */ public Response invoke(Request request) throws IOException { Assert.isNotNull(request); try { open(); URL resourceUrl = request.getResourceUrl(); URL originServerUrl = new URL(resourceUrl.getProtocol(), resourceUrl.getHost(), resourceUrl.getPort(), "/"); //$NON-NLS-1$ URL proxyServerUrl = getProxyServerUrl(originServerUrl); if (proxyServerUrl == null && !matchesProxyServerException(originServerUrl)) { proxyServerUrl = getDefaultProxyServerUrl(); } IContext context = webDAVFactory.newContext(request.getContext()); IContext defaultContext = getContext(originServerUrl); if (defaultContext == null) { defaultContext = getDefaultContext(); } if (defaultContext != null) { Enumeration e = defaultContext.keys(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); context.put(key, defaultContext.get(key)); } } if (authority != null) { authority.authorize(request, null, context, proxyServerUrl, true); authority.authorize(request, null, context, proxyServerUrl, false); } return invoke1(request, context, proxyServerUrl, originServerUrl, 0, 0); } finally { request.close(); } } private Response invoke1(Request request, IContext context, URL proxyServerUrl, URL originServerUrl, int retries, int redirects) throws IOException { Response response = null; try { response = invoke2(request, context, proxyServerUrl, originServerUrl, false); int sc = response.getStatusCode(); if (sc == HTTP_UNAUTHORIZED || sc == HTTP_PROXY_AUTHENTICATION_REQUIRED) { if (authority == null || !authority.authorize(request, response, context, proxyServerUrl, sc == IStatusCodes.HTTP_PROXY_AUTHENTICATION_REQUIRED)) { return response; } response.close(); return invoke1(request, context, proxyServerUrl, originServerUrl, 0, 0); } else if (sc >= 300 && sc < 400) { if (redirects >= maxRedirects) { return response; } String location = response.getContext().getLocation(); if (location == null) { return response; } URL url = new URL(location); if (sc == HTTP_USE_PROXY) { proxyServerUrl = url; } else { originServerUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), "/"); //$NON-NLS-1$ request.setResourceUrl(url); } response.close(); return invoke1(request, context, proxyServerUrl, originServerUrl, 0, redirects + 1); } if (authority != null) { authority.confirm(request, response, proxyServerUrl); } return response; } catch (IOException e) { if (response != null) { response.close(); } if (retries < maxRetries) { return invoke1(request, context, proxyServerUrl, originServerUrl, retries + 1, 0); } throw e; } } private Response invoke2(Request request, IContext context, URL proxyServerUrl, URL originServerUrl, boolean expect100Continue) throws IOException { HttpConnection connection = null; try { // get the connection connection = connectionsRecycler.getConnection(originServerUrl); // set the http version connection.setHttpVersion(httpVersion); // set the method, resource url, and proxy server url connection.setRequestMethod(request.getMethod()); connection.setResourceUrl(request.getResourceUrl()); connection.setProxyServerUrl(proxyServerUrl); // set the request header connection.clearRequestHeader(); Enumeration e = context.keys(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); connection.setRequestHeaderField(key, context.get(key)); } // set the content length long contentLength = request.getContentLength(); if (contentLength >= 0 && context.getContentLength() == -1) { connection.setRequestHeaderField("Content-Length", "" + contentLength); //$NON-NLS-1$ //$NON-NLS-2$ } // set the chunked encoding String method = request.getMethod(); boolean sendChunked = contentLength == -1 && httpVersion > 0 && !(method.equals("PROPPATCH") || method.equals("PROPFIND")); //$NON-NLS-1$ //$NON-NLS-2$ connection.setSendChunked(sendChunked); // make the connection persistent connection.setPersistent(httpVersion > 0); // set the socket timeout connection.setSoTimeout(socketTimeout); // set the socket factory connection.setSocketFactory(socketFactory); // send the request header IContext responseHeader = null; if (expect100Continue && (contentLength > 0 && httpVersion > 0 || sendChunked)) { if (!"100-continue".equalsIgnoreCase(context.get("Expect"))) { //$NON-NLS-1$ //$NON-NLS-2$ connection.setRequestHeaderField("Expect", "100-continue"); //$NON-NLS-1$ //$NON-NLS-2$ } responseHeader = readResponseHeader(connection); if (connection.getStatusCode() != IStatusCodes.HTTP_CONTINUE) { return new Response(connection.getStatusCode(), connection.getStatusMessage(), responseHeader, new PersistentInputStream(connection)); } } // send the request body if (contentLength != 0) { OutputStream os = connection.getOutputStream(); request.write(os); os.close(); } // get the response header responseHeader = readResponseHeader(connection); // return the response return new Response(connection.getStatusCode(), connection.getStatusMessage(), responseHeader, new PersistentInputStream(connection)); } catch (IOException e1) { try { connection.close(); } catch (IOException e2) { // ignore? } connectionsRecycler.putConnection(connection); throw e1; } // end-catch } private boolean matchesProxyServerException(URL originServerUrl) { String host = originServerUrl.getHost(); int port = originServerUrl.getPort(); String originServerUrlString = host + (port == -1 ? "" : ":" + port); //$NON-NLS-1$ //$NON-NLS-2$ boolean found = false; Enumeration keys = proxyServerExceptions.keys(); while (!found && keys.hasMoreElements()) { String httpProxyException = (String) keys.nextElement(); int len = httpProxyException.length(); found = originServerUrlString.equals(httpProxyException); if (!found) { if (httpProxyException.startsWith("*")) { //$NON-NLS-1$ found = originServerUrlString.endsWith(httpProxyException.substring(1, len)); } else if (httpProxyException.endsWith("*")) { //$NON-NLS-1$ found = originServerUrlString.startsWith(httpProxyException.substring(0, len - 1)); } } } return found; } private void open() { if (closed) { connectionsRecycler.start(); closed = false; } } private IContext readResponseHeader(HttpConnection httpConnection) throws IOException { IContext responseHeader = webDAVFactory.newContext(); int position = 0; String fieldName = null; while ((fieldName = httpConnection.getResponseHeaderFieldName(position)) != null) { // TBD : Should combine multiple headers if (responseHeader.get(fieldName.toLowerCase()) == null) responseHeader.put(fieldName.toLowerCase(), httpConnection.getResponseHeaderFieldValue(position)); ++position; } return responseHeader; } /** * Removes the given proxy server exception pattern from this client. * * @param pattern a proxy server exception pattern * @see #addProxyServerException(String) * @see #getDefaultProxyServerUrl() * @see #getProxyServerExceptions() * @see #getProxyServerUrl(URL) * @see #setDefaultProxyServerUrl(URL) * @see #setProxyServerUrl(URL, URL) */ public void removeProxyServerException(String pattern) { Assert.isNotNull(pattern); proxyServerExceptions.remove(pattern); } /** * Sets the authenticator. Authenticators store authentication * information and are required to access protected resources. If the * authenticator is <code>null</code> protected resources cannot be * accessed. The authenticator is <code>null</code> by default. * * @param authenticator the authenticator, or <code>null</code> */ public void setAuthenticator(IAuthenticator authenticator) { authority = authenticator == null ? null : new AuthorizationAuthority(authenticator); } public void setConnectionTimeout(long connectionTimeout) { connectionsRecycler.setConnectionTimeout(connectionTimeout); } /** * Set the context for the origin server at the given <code>URL</code>. * If the given context is <code>null</code>, the context for the * specified origin server is removed. * * @param originServerUrl the <code>URL</code> of an origin server * @param context the context for the specified origin server * @see #getContext(URL) * @see #getDefaultContext() * @see #setDefaultContext(Context) */ public void setContext(URL originServerUrl, IContext context) { Assert.isNotNull(originServerUrl); if (context == null) contexts.remove(originServerUrl); else contexts.put(originServerUrl, context); } /** * Sets the default context which is used by all servers that do not * already have a context set. * * @param context the default context * @see #getContext(URL) * @see #getDefaultContext() * @see #setContext(URL, Context) */ public void setDefaultContext(IContext context) { defaultContext = context; } /** * Sets the <code>URL</code> of the default proxy server that is used by * all servers that do not have their proxy server set and do not match * a proxy server exception pattern. If the given proxy server * <code>URL</code> is <code>null</code>, no default proxy server is * used. * * @param proxyServerUrl the <code>URL</code> of the default proxy server * @see #addProxyServerException(String) * @see #getDefaultProxyServerUrl() * @see #getProxyServerExceptions() * @see #getProxyServerUrl(URL) * @see #removeProxyServerException(String) * @see #setProxyServerUrl(URL, URL) */ public void setDefaultProxyServerUrl(URL proxyServerUrl) { defaultProxyServerUrl = proxyServerUrl; } /** * Sets the version of HTTP this client uses for communication with * servers. HTTP/1.1 is used by default. * * @param httpVersion the version of HTTP this client uses for * communication with servers * @see #getHttpVersion() */ public void setHttpVersion(double httpVersion) { Assert.isTrue(httpVersion == 1.0 || httpVersion == 1.1); this.httpVersion = httpVersion; } /** * Sets the maximum number of <code>URL</code> location redirects. The * maximum is 4 by default. * * @param maxRedirects the maximum number of <code>URL</code> redirects */ public void setMaxRedirects(int maxRedirects) { Assert.isTrue(maxRedirects >= 0); this.maxRedirects = maxRedirects; } /** * Sets the maximum number of times a request is retried after an * <code>IOException</code> occurs. The maximum is 1 by default. * * @param maxRetries the maximum number of times a request is retried * after an <code>IOException</code> occurs * @see #getMaxRetries() */ public void setMaxRetries(int maxRetries) { Assert.isTrue(maxRetries >= 0); this.maxRetries = maxRetries; } /** * Sets the <code>URL</code> of the proxy server that this client uses * to communicate with the origin server at the given <code>URL</code>. * If the proxy server <code>URL</code> is <code>null</code>, the * default proxy server is used if the specified origin server does not * match a proxy server exception pattern. * * @param originServerUrl the <code>URL</code> of on origin server * @param proxyServerUrl the <code>URL</code> of a proxy server, or * <code>null</code> * @see #addProxyServerException(String) * @see #getDefaultProxyServerUrl() * @see #getProxyServerExceptions() * @see #getProxyServerUrl(URL) * @see #removeProxyServerException(String) * @see #setDefaultProxyServerUrl(URL) */ public void setProxyServerUrl(URL originServerUrl, URL proxyServerUrl) { Assert.isNotNull(originServerUrl); if (proxyServerUrl == null) proxyServerUrls.remove(originServerUrl); else proxyServerUrls.put(originServerUrl, proxyServerUrl); } /** * Sets the factory that this client uses to create sockets. * * @param socketFactory the factory that this client uses to create * sockets */ public void setSocketFactory(ISocketFactory socketFactory) { this.socketFactory = socketFactory; } /** * Sets the socket read timeout (in milliseconds) for this client. A * value of zero indicates that a socket read operation will block * indefinitely waiting for data. The value is zero by default. * * @param timeout the socket read timeout * @see #getSoTimeout() */ public void setSoTimeout(int timeout) { socketTimeout = timeout; } }