diff options
Diffstat (limited to 'src/org/apache/http/impl')
128 files changed, 19755 insertions, 0 deletions
diff --git a/src/org/apache/http/impl/AbstractHttpClientConnection.java b/src/org/apache/http/impl/AbstractHttpClientConnection.java new file mode 100644 index 0000000..ebfaabb --- /dev/null +++ b/src/org/apache/http/impl/AbstractHttpClientConnection.java @@ -0,0 +1,212 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/AbstractHttpClientConnection.java $ + * $Revision: 627457 $ + * $Date: 2008-02-13 07:14:19 -0800 (Wed, 13 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; + +import org.apache.http.HttpClientConnection; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.impl.entity.EntityDeserializer; +import org.apache.http.impl.entity.EntitySerializer; +import org.apache.http.impl.entity.LaxContentLengthStrategy; +import org.apache.http.impl.entity.StrictContentLengthStrategy; +import org.apache.http.impl.io.HttpRequestWriter; +import org.apache.http.impl.io.HttpResponseParser; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpParams; + +/** + * Abstract client-side HTTP connection capable of transmitting and receiving data + * using arbitrary {@link SessionInputBuffer} and {@link SessionOutputBuffer} + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 627457 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpClientConnection implements HttpClientConnection { + + private final EntitySerializer entityserializer; + private final EntityDeserializer entitydeserializer; + + private SessionInputBuffer inbuffer = null; + private SessionOutputBuffer outbuffer = null; + private HttpMessageParser responseParser = null; + private HttpMessageWriter requestWriter = null; + private HttpConnectionMetricsImpl metrics = null; + + + + public AbstractHttpClientConnection() { + super(); + this.entityserializer = createEntitySerializer(); + this.entitydeserializer = createEntityDeserializer(); + } + + protected abstract void assertOpen() throws IllegalStateException; + + protected EntityDeserializer createEntityDeserializer() { + return new EntityDeserializer(new LaxContentLengthStrategy()); + } + + protected EntitySerializer createEntitySerializer() { + return new EntitySerializer(new StrictContentLengthStrategy()); + } + + protected HttpResponseFactory createHttpResponseFactory() { + return new DefaultHttpResponseFactory(); + } + + protected HttpMessageParser createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new HttpResponseParser(buffer, null, responseFactory, params); + } + + protected HttpMessageWriter createRequestWriter( + final SessionOutputBuffer buffer, + final HttpParams params) { + // override in derived class to specify a line formatter + return new HttpRequestWriter(buffer, null, params); + } + + protected void init( + final SessionInputBuffer inbuffer, + final SessionOutputBuffer outbuffer, + final HttpParams params) { + if (inbuffer == null) { + throw new IllegalArgumentException("Input session buffer may not be null"); + } + if (outbuffer == null) { + throw new IllegalArgumentException("Output session buffer may not be null"); + } + this.inbuffer = inbuffer; + this.outbuffer = outbuffer; + this.responseParser = createResponseParser( + inbuffer, + createHttpResponseFactory(), + params); + this.requestWriter = createRequestWriter( + outbuffer, params); + this.metrics = new HttpConnectionMetricsImpl( + inbuffer.getMetrics(), + outbuffer.getMetrics()); + } + + public boolean isResponseAvailable(int timeout) throws IOException { + assertOpen(); + return this.inbuffer.isDataAvailable(timeout); + } + + public void sendRequestHeader(final HttpRequest request) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + assertOpen(); + this.requestWriter.write(request); + this.metrics.incrementRequestCount(); + } + + public void sendRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + assertOpen(); + if (request.getEntity() == null) { + return; + } + this.entityserializer.serialize( + this.outbuffer, + request, + request.getEntity()); + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public void flush() throws IOException { + assertOpen(); + doFlush(); + } + + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + assertOpen(); + HttpResponse response = (HttpResponse) this.responseParser.parse(); + if (response.getStatusLine().getStatusCode() >= 200) { + this.metrics.incrementResponseCount(); + } + return response; + } + + public void receiveResponseEntity(final HttpResponse response) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + assertOpen(); + HttpEntity entity = this.entitydeserializer.deserialize(this.inbuffer, response); + response.setEntity(entity); + } + + public boolean isStale() { + if (!isOpen()) { + return true; + } + try { + this.inbuffer.isDataAvailable(1); + return false; + } catch (IOException ex) { + return true; + } + } + + public HttpConnectionMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/AbstractHttpServerConnection.java b/src/org/apache/http/impl/AbstractHttpServerConnection.java new file mode 100644 index 0000000..ef68ed3 --- /dev/null +++ b/src/org/apache/http/impl/AbstractHttpServerConnection.java @@ -0,0 +1,202 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/AbstractHttpServerConnection.java $ + * $Revision: 618017 $ + * $Date: 2008-02-03 08:42:22 -0800 (Sun, 03 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; + +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestFactory; +import org.apache.http.HttpResponse; +import org.apache.http.HttpServerConnection; +import org.apache.http.impl.entity.EntityDeserializer; +import org.apache.http.impl.entity.EntitySerializer; +import org.apache.http.impl.entity.LaxContentLengthStrategy; +import org.apache.http.impl.entity.StrictContentLengthStrategy; +import org.apache.http.impl.io.HttpRequestParser; +import org.apache.http.impl.io.HttpResponseWriter; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpParams; + +/** + * Abstract server-side HTTP connection capable of transmitting and receiving data + * using arbitrary {@link SessionInputBuffer} and {@link SessionOutputBuffer} + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618017 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpServerConnection implements HttpServerConnection { + + private final EntitySerializer entityserializer; + private final EntityDeserializer entitydeserializer; + + private SessionInputBuffer inbuffer = null; + private SessionOutputBuffer outbuffer = null; + private HttpMessageParser requestParser = null; + private HttpMessageWriter responseWriter = null; + private HttpConnectionMetricsImpl metrics = null; + + + + public AbstractHttpServerConnection() { + super(); + this.entityserializer = createEntitySerializer(); + this.entitydeserializer = createEntityDeserializer(); + } + + protected abstract void assertOpen() throws IllegalStateException; + + protected EntityDeserializer createEntityDeserializer() { + return new EntityDeserializer(new LaxContentLengthStrategy()); + } + + protected EntitySerializer createEntitySerializer() { + return new EntitySerializer(new StrictContentLengthStrategy()); + } + + protected HttpRequestFactory createHttpRequestFactory() { + return new DefaultHttpRequestFactory(); + } + + protected HttpMessageParser createRequestParser( + final SessionInputBuffer buffer, + final HttpRequestFactory requestFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new HttpRequestParser(buffer, null, requestFactory, params); + } + + protected HttpMessageWriter createResponseWriter( + final SessionOutputBuffer buffer, + final HttpParams params) { + // override in derived class to specify a line formatter + return new HttpResponseWriter(buffer, null, params); + } + + + protected void init( + final SessionInputBuffer inbuffer, + final SessionOutputBuffer outbuffer, + final HttpParams params) { + if (inbuffer == null) { + throw new IllegalArgumentException("Input session buffer may not be null"); + } + if (outbuffer == null) { + throw new IllegalArgumentException("Output session buffer may not be null"); + } + this.inbuffer = inbuffer; + this.outbuffer = outbuffer; + this.requestParser = createRequestParser( + inbuffer, + createHttpRequestFactory(), + params); + this.responseWriter = createResponseWriter( + outbuffer, params); + this.metrics = new HttpConnectionMetricsImpl( + inbuffer.getMetrics(), + outbuffer.getMetrics()); + } + + public HttpRequest receiveRequestHeader() + throws HttpException, IOException { + assertOpen(); + HttpRequest request = (HttpRequest) this.requestParser.parse(); + this.metrics.incrementRequestCount(); + return request; + } + + public void receiveRequestEntity(final HttpEntityEnclosingRequest request) + throws HttpException, IOException { + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + assertOpen(); + HttpEntity entity = this.entitydeserializer.deserialize(this.inbuffer, request); + request.setEntity(entity); + } + + protected void doFlush() throws IOException { + this.outbuffer.flush(); + } + + public void flush() throws IOException { + assertOpen(); + doFlush(); + } + + public void sendResponseHeader(final HttpResponse response) + throws HttpException, IOException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + assertOpen(); + this.responseWriter.write(response); + if (response.getStatusLine().getStatusCode() >= 200) { + this.metrics.incrementResponseCount(); + } + } + + public void sendResponseEntity(final HttpResponse response) + throws HttpException, IOException { + if (response.getEntity() == null) { + return; + } + this.entityserializer.serialize( + this.outbuffer, + response, + response.getEntity()); + } + + public boolean isStale() { + assertOpen(); + try { + this.inbuffer.isDataAvailable(1); + return false; + } catch (IOException ex) { + return true; + } + } + + public HttpConnectionMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/DefaultConnectionReuseStrategy.java b/src/org/apache/http/impl/DefaultConnectionReuseStrategy.java new file mode 100644 index 0000000..da1d5fd --- /dev/null +++ b/src/org/apache/http/impl/DefaultConnectionReuseStrategy.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultConnectionReuseStrategy.java $ + * $Revision: 602537 $ + * $Date: 2007-12-08 11:42:06 -0800 (Sat, 08 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpConnection; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; +import org.apache.http.ParseException; +import org.apache.http.ProtocolVersion; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.TokenIterator; +import org.apache.http.message.BasicTokenIterator; + +/** + * Default implementation of a strategy deciding about connection re-use. + * The default implementation first checks some basics, for example + * whether the connection is still open or whether the end of the + * request entity can be determined without closing the connection. + * If these checks pass, the tokens in the "Connection" header will + * be examined. In the absence of a "Connection" header, the + * non-standard but commonly used "Proxy-Connection" header takes + * it's role. A token "close" indicates that the connection cannot + * be reused. If there is no such token, a token "keep-alive" indicates + * that the connection should be re-used. If neither token is found, + * or if there are no "Connection" headers, the default policy for + * the HTTP version is applied. Since HTTP/1.1, connections are re-used + * by default. Up until HTTP/1.0, connections are not re-used by default. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 602537 $ + * + * @since 4.0 + */ +public class DefaultConnectionReuseStrategy + implements ConnectionReuseStrategy { + + public DefaultConnectionReuseStrategy() { + super(); + } + + // see interface ConnectionReuseStrategy + public boolean keepAlive(final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException + ("HTTP response may not be null."); + } + if (context == null) { + throw new IllegalArgumentException + ("HTTP context may not be null."); + } + + HttpConnection conn = (HttpConnection) + context.getAttribute(ExecutionContext.HTTP_CONNECTION); + + if (conn != null && !conn.isOpen()) + return false; + // do NOT check for stale connection, that is an expensive operation + + // Check for a self-terminating entity. If the end of the entity will + // be indicated by closing the connection, there is no keep-alive. + HttpEntity entity = response.getEntity(); + ProtocolVersion ver = response.getStatusLine().getProtocolVersion(); + if (entity != null) { + if (entity.getContentLength() < 0) { + if (!entity.isChunked() || + ver.lessEquals(HttpVersion.HTTP_1_0)) { + // if the content length is not known and is not chunk + // encoded, the connection cannot be reused + return false; + } + } + } + + // Check for the "Connection" header. If that is absent, check for + // the "Proxy-Connection" header. The latter is an unspecified and + // broken but unfortunately common extension of HTTP. + HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE); + if (!hit.hasNext()) + hit = response.headerIterator("Proxy-Connection"); + + // Experimental usage of the "Connection" header in HTTP/1.0 is + // documented in RFC 2068, section 19.7.1. A token "keep-alive" is + // used to indicate that the connection should be persistent. + // Note that the final specification of HTTP/1.1 in RFC 2616 does not + // include this information. Neither is the "Connection" header + // mentioned in RFC 1945, which informally describes HTTP/1.0. + // + // RFC 2616 specifies "close" as the only connection token with a + // specific meaning: it disables persistent connections. + // + // The "Proxy-Connection" header is not formally specified anywhere, + // but is commonly used to carry one token, "close" or "keep-alive". + // The "Connection" header, on the other hand, is defined as a + // sequence of tokens, where each token is a header name, and the + // token "close" has the above-mentioned additional meaning. + // + // To get through this mess, we treat the "Proxy-Connection" header + // in exactly the same way as the "Connection" header, but only if + // the latter is missing. We scan the sequence of tokens for both + // "close" and "keep-alive". As "close" is specified by RFC 2068, + // it takes precedence and indicates a non-persistent connection. + // If there is no "close" but a "keep-alive", we take the hint. + + if (hit.hasNext()) { + try { + TokenIterator ti = createTokenIterator(hit); + boolean keepalive = false; + while (ti.hasNext()) { + final String token = ti.nextToken(); + if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) { + return false; + } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) { + // continue the loop, there may be a "close" afterwards + keepalive = true; + } + } + if (keepalive) + return true; + // neither "close" nor "keep-alive", use default policy + + } catch (ParseException px) { + // invalid connection header means no persistent connection + // we don't have logging in HttpCore, so the exception is lost + return false; + } + } + + // default since HTTP/1.1 is persistent, before it was non-persistent + return !ver.lessEquals(HttpVersion.HTTP_1_0); + } + + + /** + * Creates a token iterator from a header iterator. + * This method can be overridden to replace the implementation of + * the token iterator. + * + * @param hit the header iterator + * + * @return the token iterator + */ + protected TokenIterator createTokenIterator(HeaderIterator hit) { + return new BasicTokenIterator(hit); + } +} diff --git a/src/org/apache/http/impl/DefaultHttpClientConnection.java b/src/org/apache/http/impl/DefaultHttpClientConnection.java new file mode 100644 index 0000000..c0a96f5 --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpClientConnection.java @@ -0,0 +1,87 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpClientConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Default implementation of a client-side HTTP connection. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class DefaultHttpClientConnection extends SocketHttpClientConnection { + + public DefaultHttpClientConnection() { + super(); + } + + public void bind( + final Socket socket, + final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + assertNotOpen(); + socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + super.bind(socket, params); + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("["); + if (isOpen()) { + buffer.append(getRemotePort()); + } else { + buffer.append("closed"); + } + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/impl/DefaultHttpRequestFactory.java b/src/org/apache/http/impl/DefaultHttpRequestFactory.java new file mode 100644 index 0000000..dee36c9 --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpRequestFactory.java @@ -0,0 +1,113 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpRequestFactory.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestFactory; +import org.apache.http.MethodNotSupportedException; +import org.apache.http.RequestLine; +import org.apache.http.message.BasicHttpEntityEnclosingRequest; +import org.apache.http.message.BasicHttpRequest; + +/** + * Default implementation of a factory for creating request objects. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public class DefaultHttpRequestFactory implements HttpRequestFactory { + + private static final String[] RFC2616_COMMON_METHODS = { + "GET" + }; + + private static final String[] RFC2616_ENTITY_ENC_METHODS = { + "POST", + "PUT" + }; + + private static final String[] RFC2616_SPECIAL_METHODS = { + "HEAD", + "OPTIONS", + "DELETE", + "TRACE" + }; + + + public DefaultHttpRequestFactory() { + super(); + } + + private static boolean isOneOf(final String[] methods, final String method) { + for (int i = 0; i < methods.length; i++) { + if (methods[i].equalsIgnoreCase(method)) { + return true; + } + } + return false; + } + + public HttpRequest newHttpRequest(final RequestLine requestline) + throws MethodNotSupportedException { + if (requestline == null) { + throw new IllegalArgumentException("Request line may not be null"); + } + String method = requestline.getMethod(); + if (isOneOf(RFC2616_COMMON_METHODS, method)) { + return new BasicHttpRequest(requestline); + } else if (isOneOf(RFC2616_ENTITY_ENC_METHODS, method)) { + return new BasicHttpEntityEnclosingRequest(requestline); + } else if (isOneOf(RFC2616_SPECIAL_METHODS, method)) { + return new BasicHttpRequest(requestline); + } else { + throw new MethodNotSupportedException(method + " method not supported"); + } + } + + public HttpRequest newHttpRequest(final String method, final String uri) + throws MethodNotSupportedException { + if (isOneOf(RFC2616_COMMON_METHODS, method)) { + return new BasicHttpRequest(method, uri); + } else if (isOneOf(RFC2616_ENTITY_ENC_METHODS, method)) { + return new BasicHttpEntityEnclosingRequest(method, uri); + } else if (isOneOf(RFC2616_SPECIAL_METHODS, method)) { + return new BasicHttpRequest(method, uri); + } else { + throw new MethodNotSupportedException(method + + " method not supported"); + } + } + +} diff --git a/src/org/apache/http/impl/DefaultHttpResponseFactory.java b/src/org/apache/http/impl/DefaultHttpResponseFactory.java new file mode 100644 index 0000000..40a2c9a --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpResponseFactory.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpResponseFactory.java $ + * $Revision: 618367 $ + * $Date: 2008-02-04 10:26:06 -0800 (Mon, 04 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.util.Locale; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.BasicStatusLine; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ReasonPhraseCatalog; +import org.apache.http.impl.EnglishReasonPhraseCatalog; + +/** + * Default implementation of a factory for creating response objects. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 618367 $ + * + * @since 4.0 + */ +public class DefaultHttpResponseFactory implements HttpResponseFactory { + + /** The catalog for looking up reason phrases. */ + protected final ReasonPhraseCatalog reasonCatalog; + + + /** + * Creates a new response factory with the given catalog. + * + * @param catalog the catalog of reason phrases + */ + public DefaultHttpResponseFactory(ReasonPhraseCatalog catalog) { + if (catalog == null) { + throw new IllegalArgumentException + ("Reason phrase catalog must not be null."); + } + this.reasonCatalog = catalog; + } + + /** + * Creates a new response factory with the default catalog. + * The default catalog is + * {@link EnglishReasonPhraseCatalog EnglishReasonPhraseCatalog}. + */ + public DefaultHttpResponseFactory() { + this(EnglishReasonPhraseCatalog.INSTANCE); + } + + + // non-javadoc, see interface HttpResponseFactory + public HttpResponse newHttpResponse(final ProtocolVersion ver, + final int status, + HttpContext context) { + if (ver == null) { + throw new IllegalArgumentException("HTTP version may not be null"); + } + final Locale loc = determineLocale(context); + final String reason = reasonCatalog.getReason(status, loc); + StatusLine statusline = new BasicStatusLine(ver, status, reason); + return new BasicHttpResponse(statusline, reasonCatalog, loc); + } + + + // non-javadoc, see interface HttpResponseFactory + public HttpResponse newHttpResponse(final StatusLine statusline, + HttpContext context) { + if (statusline == null) { + throw new IllegalArgumentException("Status line may not be null"); + } + final Locale loc = determineLocale(context); + return new BasicHttpResponse(statusline, reasonCatalog, loc); + } + + + /** + * Determines the locale of the response. + * The implementation in this class always returns the default locale. + * + * @param context the context from which to determine the locale, or + * <code>null</code> to use the default locale + * + * @return the locale for the response, never <code>null</code> + */ + protected Locale determineLocale(HttpContext context) { + return Locale.getDefault(); + } +} diff --git a/src/org/apache/http/impl/DefaultHttpServerConnection.java b/src/org/apache/http/impl/DefaultHttpServerConnection.java new file mode 100644 index 0000000..d296fc8 --- /dev/null +++ b/src/org/apache/http/impl/DefaultHttpServerConnection.java @@ -0,0 +1,85 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/DefaultHttpServerConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Default implementation of a server-side HTTP connection. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class DefaultHttpServerConnection extends SocketHttpServerConnection { + + public DefaultHttpServerConnection() { + super(); + } + + public void bind(final Socket socket, final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + assertNotOpen(); + socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + socket.setSoLinger(linger > 0, linger); + } + super.bind(socket, params); + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("["); + if (isOpen()) { + buffer.append(getRemotePort()); + } else { + buffer.append("closed"); + } + buffer.append("]"); + return buffer.toString(); + } + +} diff --git a/src/org/apache/http/impl/EnglishReasonPhraseCatalog.java b/src/org/apache/http/impl/EnglishReasonPhraseCatalog.java new file mode 100644 index 0000000..f1aeee1 --- /dev/null +++ b/src/org/apache/http/impl/EnglishReasonPhraseCatalog.java @@ -0,0 +1,234 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/EnglishReasonPhraseCatalog.java $ + * $Revision: 505744 $ + * $Date: 2007-02-10 10:58:45 -0800 (Sat, 10 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.util.Locale; + +import org.apache.http.HttpStatus; +import org.apache.http.ReasonPhraseCatalog; + + +/** + * English reason phrases for HTTP status codes. + * All status codes defined in RFC1945 (HTTP/1.0), RFC2616 (HTTP/1.1), and + * RFC2518 (WebDAV) are supported. + * + * @author Unascribed + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * + * @version $Revision: 505744 $ + */ +public class EnglishReasonPhraseCatalog + implements ReasonPhraseCatalog { + + // static array with english reason phrases defined below + + /** + * The default instance of this catalog. + * This catalog is thread safe, so there typically + * is no need to create other instances. + */ + public final static EnglishReasonPhraseCatalog INSTANCE = + new EnglishReasonPhraseCatalog(); + + + /** + * Restricted default constructor, for derived classes. + * If you need an instance of this class, use {@link #INSTANCE INSTANCE}. + */ + protected EnglishReasonPhraseCatalog() { + // no body + } + + + /** + * Obtains the reason phrase for a status code. + * + * @param status the status code, in the range 100-599 + * @param loc ignored + * + * @return the reason phrase, or <code>null</code> + */ + public String getReason(int status, Locale loc) { + if ((status < 100) || (status >= 600)) { + throw new IllegalArgumentException + ("Unknown category for status code " + status + "."); + } + + final int category = status / 100; + final int subcode = status - 100*category; + + String reason = null; + if (REASON_PHRASES[category].length > subcode) + reason = REASON_PHRASES[category][subcode]; + + return reason; + } + + + /** Reason phrases lookup table. */ + private static final String[][] REASON_PHRASES = new String[][]{ + null, + new String[3], // 1xx + new String[8], // 2xx + new String[8], // 3xx + new String[25], // 4xx + new String[8] // 5xx + }; + + + + /** + * Stores the given reason phrase, by status code. + * Helper method to initialize the static lookup table. + * + * @param status the status code for which to define the phrase + * @param reason the reason phrase for this status code + */ + private static void setReason(int status, String reason) { + final int category = status / 100; + final int subcode = status - 100*category; + REASON_PHRASES[category][subcode] = reason; + } + + + // ----------------------------------------------------- Static Initializer + + /** Set up status code to "reason phrase" map. */ + static { + // HTTP 1.0 Server status codes -- see RFC 1945 + setReason(HttpStatus.SC_OK, + "OK"); + setReason(HttpStatus.SC_CREATED, + "Created"); + setReason(HttpStatus.SC_ACCEPTED, + "Accepted"); + setReason(HttpStatus.SC_NO_CONTENT, + "No Content"); + setReason(HttpStatus.SC_MOVED_PERMANENTLY, + "Moved Permanently"); + setReason(HttpStatus.SC_MOVED_TEMPORARILY, + "Moved Temporarily"); + setReason(HttpStatus.SC_NOT_MODIFIED, + "Not Modified"); + setReason(HttpStatus.SC_BAD_REQUEST, + "Bad Request"); + setReason(HttpStatus.SC_UNAUTHORIZED, + "Unauthorized"); + setReason(HttpStatus.SC_FORBIDDEN, + "Forbidden"); + setReason(HttpStatus.SC_NOT_FOUND, + "Not Found"); + setReason(HttpStatus.SC_INTERNAL_SERVER_ERROR, + "Internal Server Error"); + setReason(HttpStatus.SC_NOT_IMPLEMENTED, + "Not Implemented"); + setReason(HttpStatus.SC_BAD_GATEWAY, + "Bad Gateway"); + setReason(HttpStatus.SC_SERVICE_UNAVAILABLE, + "Service Unavailable"); + + // HTTP 1.1 Server status codes -- see RFC 2048 + setReason(HttpStatus.SC_CONTINUE, + "Continue"); + setReason(HttpStatus.SC_TEMPORARY_REDIRECT, + "Temporary Redirect"); + setReason(HttpStatus.SC_METHOD_NOT_ALLOWED, + "Method Not Allowed"); + setReason(HttpStatus.SC_CONFLICT, + "Conflict"); + setReason(HttpStatus.SC_PRECONDITION_FAILED, + "Precondition Failed"); + setReason(HttpStatus.SC_REQUEST_TOO_LONG, + "Request Too Long"); + setReason(HttpStatus.SC_REQUEST_URI_TOO_LONG, + "Request-URI Too Long"); + setReason(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, + "Unsupported Media Type"); + setReason(HttpStatus.SC_MULTIPLE_CHOICES, + "Multiple Choices"); + setReason(HttpStatus.SC_SEE_OTHER, + "See Other"); + setReason(HttpStatus.SC_USE_PROXY, + "Use Proxy"); + setReason(HttpStatus.SC_PAYMENT_REQUIRED, + "Payment Required"); + setReason(HttpStatus.SC_NOT_ACCEPTABLE, + "Not Acceptable"); + setReason(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, + "Proxy Authentication Required"); + setReason(HttpStatus.SC_REQUEST_TIMEOUT, + "Request Timeout"); + + setReason(HttpStatus.SC_SWITCHING_PROTOCOLS, + "Switching Protocols"); + setReason(HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, + "Non Authoritative Information"); + setReason(HttpStatus.SC_RESET_CONTENT, + "Reset Content"); + setReason(HttpStatus.SC_PARTIAL_CONTENT, + "Partial Content"); + setReason(HttpStatus.SC_GATEWAY_TIMEOUT, + "Gateway Timeout"); + setReason(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED, + "Http Version Not Supported"); + setReason(HttpStatus.SC_GONE, + "Gone"); + setReason(HttpStatus.SC_LENGTH_REQUIRED, + "Length Required"); + setReason(HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE, + "Requested Range Not Satisfiable"); + setReason(HttpStatus.SC_EXPECTATION_FAILED, + "Expectation Failed"); + + // WebDAV Server-specific status codes + setReason(HttpStatus.SC_PROCESSING, + "Processing"); + setReason(HttpStatus.SC_MULTI_STATUS, + "Multi-Status"); + setReason(HttpStatus.SC_UNPROCESSABLE_ENTITY, + "Unprocessable Entity"); + setReason(HttpStatus.SC_INSUFFICIENT_SPACE_ON_RESOURCE, + "Insufficient Space On Resource"); + setReason(HttpStatus.SC_METHOD_FAILURE, + "Method Failure"); + setReason(HttpStatus.SC_LOCKED, + "Locked"); + setReason(HttpStatus.SC_INSUFFICIENT_STORAGE, + "Insufficient Storage"); + setReason(HttpStatus.SC_FAILED_DEPENDENCY, + "Failed Dependency"); + } + + +} diff --git a/src/org/apache/http/impl/HttpConnectionMetricsImpl.java b/src/org/apache/http/impl/HttpConnectionMetricsImpl.java new file mode 100644 index 0000000..4f4eacf --- /dev/null +++ b/src/org/apache/http/impl/HttpConnectionMetricsImpl.java @@ -0,0 +1,146 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/HttpConnectionMetricsImpl.java $ + * $Revision: 548031 $ + * $Date: 2007-06-17 04:28:38 -0700 (Sun, 17 Jun 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.util.HashMap; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.io.HttpTransportMetrics; + +/** + * Implementation of the metrics interface. + */ +public class HttpConnectionMetricsImpl implements HttpConnectionMetrics { + + public static final String REQUEST_COUNT = "http.request-count"; + public static final String RESPONSE_COUNT = "http.response-count"; + public static final String SENT_BYTES_COUNT = "http.sent-bytes-count"; + public static final String RECEIVED_BYTES_COUNT = "http.received-bytes-count"; + + private final HttpTransportMetrics inTransportMetric; + private final HttpTransportMetrics outTransportMetric; + private long requestCount = 0; + private long responseCount = 0; + + /** + * The cache map for all metrics values. + */ + private HashMap metricsCache; + + public HttpConnectionMetricsImpl( + final HttpTransportMetrics inTransportMetric, + final HttpTransportMetrics outTransportMetric) { + super(); + this.inTransportMetric = inTransportMetric; + this.outTransportMetric = outTransportMetric; + } + + /* ------------------ Public interface method -------------------------- */ + + public long getReceivedBytesCount() { + if (this.inTransportMetric != null) { + return this.inTransportMetric.getBytesTransferred(); + } else { + return -1; + } + } + + public long getSentBytesCount() { + if (this.outTransportMetric != null) { + return this.outTransportMetric.getBytesTransferred(); + } else { + return -1; + } + } + + public long getRequestCount() { + return this.requestCount; + } + + public void incrementRequestCount() { + this.requestCount++; + } + + public long getResponseCount() { + return this.responseCount; + } + + public void incrementResponseCount() { + this.responseCount++; + } + + public Object getMetric(final String metricName) { + Object value = null; + if (this.metricsCache != null) { + value = this.metricsCache.get(metricName); + } + if (value == null) { + if (REQUEST_COUNT.equals(metricName)) { + value = new Long(requestCount); + } else if (RESPONSE_COUNT.equals(metricName)) { + value = new Long(responseCount); + } else if (RECEIVED_BYTES_COUNT.equals(metricName)) { + if (this.inTransportMetric != null) { + return new Long(this.inTransportMetric.getBytesTransferred()); + } else { + return null; + } + } else if (SENT_BYTES_COUNT.equals(metricName)) { + if (this.outTransportMetric != null) { + return new Long(this.outTransportMetric.getBytesTransferred()); + } else { + return null; + } + } + } + return value; + } + + public void setMetric(final String metricName, Object obj) { + if (this.metricsCache == null) { + this.metricsCache = new HashMap(); + } + this.metricsCache.put(metricName, obj); + } + + public void reset() { + if (this.outTransportMetric != null) { + this.outTransportMetric.reset(); + } + if (this.inTransportMetric != null) { + this.inTransportMetric.reset(); + } + this.requestCount = 0; + this.responseCount = 0; + this.metricsCache = null; + } + +} diff --git a/src/org/apache/http/impl/NoConnectionReuseStrategy.java b/src/org/apache/http/impl/NoConnectionReuseStrategy.java new file mode 100644 index 0000000..c7a5f73 --- /dev/null +++ b/src/org/apache/http/impl/NoConnectionReuseStrategy.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/NoConnectionReuseStrategy.java $ + * $Revision: 502684 $ + * $Date: 2007-02-02 10:25:38 -0800 (Fri, 02 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpResponse; +import org.apache.http.protocol.HttpContext; + + +/** + * A strategy that never re-uses a connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * @version $Revision: 502684 $ + * + * @since 4.0 + */ +public class NoConnectionReuseStrategy implements ConnectionReuseStrategy { + + // default constructor + + + // non-JavaDoc, see interface ConnectionReuseStrategy + public boolean keepAlive(final HttpResponse response, final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + + return false; + } + +} diff --git a/src/org/apache/http/impl/SocketHttpClientConnection.java b/src/org/apache/http/impl/SocketHttpClientConnection.java new file mode 100644 index 0000000..1e551e0 --- /dev/null +++ b/src/org/apache/http/impl/SocketHttpClientConnection.java @@ -0,0 +1,208 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/SocketHttpClientConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; + +import org.apache.http.HttpInetConnection; +import org.apache.http.impl.io.SocketInputBuffer; +import org.apache.http.impl.io.SocketOutputBuffer; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Implementation of a client-side HTTP connection that can be bound to a + * network Socket in order to receive and transmit data. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class SocketHttpClientConnection + extends AbstractHttpClientConnection implements HttpInetConnection { + + private volatile boolean open; + private Socket socket = null; + + public SocketHttpClientConnection() { + super(); + } + + protected void assertNotOpen() { + if (this.open) { + throw new IllegalStateException("Connection is already open"); + } + } + + protected void assertOpen() { + if (!this.open) { + throw new IllegalStateException("Connection is not open"); + } + } + + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketInputBuffer(socket, buffersize, params); + } + + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketOutputBuffer(socket, buffersize, params); + } + + protected void bind( + final Socket socket, + final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.socket = socket; + + int buffersize = HttpConnectionParams.getSocketBufferSize(params); + + init( + createSessionInputBuffer(socket, buffersize, params), + createSessionOutputBuffer(socket, buffersize, params), + params); + + this.open = true; + } + + public boolean isOpen() { + return this.open; + } + + protected Socket getSocket() { + return this.socket; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(int timeout) { + assertOpen(); + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + doFlush(); + try { + try { + this.socket.shutdownOutput(); + } catch (IOException ignore) { + } + try { + this.socket.shutdownInput(); + } catch (IOException ignore) { + } + } catch (UnsupportedOperationException ignore) { + // if one isn't supported, the other one isn't either + } + this.socket.close(); + } + +} diff --git a/src/org/apache/http/impl/SocketHttpServerConnection.java b/src/org/apache/http/impl/SocketHttpServerConnection.java new file mode 100644 index 0000000..cfa2bf9 --- /dev/null +++ b/src/org/apache/http/impl/SocketHttpServerConnection.java @@ -0,0 +1,202 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/SocketHttpServerConnection.java $ + * $Revision: 561083 $ + * $Date: 2007-07-30 11:31:17 -0700 (Mon, 30 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; + +import org.apache.http.HttpInetConnection; +import org.apache.http.impl.io.SocketInputBuffer; +import org.apache.http.impl.io.SocketOutputBuffer; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +/** + * Implementation of a server-side HTTP connection that can be bound to a + * network Socket in order to receive and transmit data. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 561083 $ + * + * @since 4.0 + */ +public class SocketHttpServerConnection extends + AbstractHttpServerConnection implements HttpInetConnection { + + private volatile boolean open; + private Socket socket = null; + + public SocketHttpServerConnection() { + super(); + } + + protected void assertNotOpen() { + if (this.open) { + throw new IllegalStateException("Connection is already open"); + } + } + + protected void assertOpen() { + if (!this.open) { + throw new IllegalStateException("Connection is not open"); + } + } + + protected SessionInputBuffer createHttpDataReceiver( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketInputBuffer(socket, buffersize, params); + } + + protected SessionOutputBuffer createHttpDataTransmitter( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + return new SocketOutputBuffer(socket, buffersize, params); + } + + protected void bind(final Socket socket, final HttpParams params) throws IOException { + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.socket = socket; + + int buffersize = HttpConnectionParams.getSocketBufferSize(params); + + init( + createHttpDataReceiver(socket, buffersize, params), + createHttpDataTransmitter(socket, buffersize, params), + params); + + this.open = true; + } + + protected Socket getSocket() { + return this.socket; + } + + public boolean isOpen() { + return this.open; + } + + public InetAddress getLocalAddress() { + if (this.socket != null) { + return this.socket.getLocalAddress(); + } else { + return null; + } + } + + public int getLocalPort() { + if (this.socket != null) { + return this.socket.getLocalPort(); + } else { + return -1; + } + } + + public InetAddress getRemoteAddress() { + if (this.socket != null) { + return this.socket.getInetAddress(); + } else { + return null; + } + } + + public int getRemotePort() { + if (this.socket != null) { + return this.socket.getPort(); + } else { + return -1; + } + } + + public void setSocketTimeout(int timeout) { + assertOpen(); + if (this.socket != null) { + try { + this.socket.setSoTimeout(timeout); + } catch (SocketException ignore) { + // It is not quite clear from the Sun's documentation if there are any + // other legitimate cases for a socket exception to be thrown when setting + // SO_TIMEOUT besides the socket being already closed + } + } + } + + public int getSocketTimeout() { + if (this.socket != null) { + try { + return this.socket.getSoTimeout(); + } catch (SocketException ignore) { + return -1; + } + } else { + return -1; + } + } + + public void shutdown() throws IOException { + this.open = false; + Socket tmpsocket = this.socket; + if (tmpsocket != null) { + tmpsocket.close(); + } + } + + public void close() throws IOException { + if (!this.open) { + return; + } + this.open = false; + doFlush(); + try { + this.socket.shutdownOutput(); + } catch (IOException ignore) { + } + try { + this.socket.shutdownInput(); + } catch (IOException ignore) { + } + this.socket.close(); + } + +} diff --git a/src/org/apache/http/impl/auth/AuthSchemeBase.java b/src/org/apache/http/impl/auth/AuthSchemeBase.java new file mode 100644 index 0000000..689ce5d --- /dev/null +++ b/src/org/apache/http/impl/auth/AuthSchemeBase.java @@ -0,0 +1,128 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/AuthSchemeBase.java $ + * $Revision: 653867 $ + * $Date: 2008-05-06 11:17:29 -0700 (Tue, 06 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract authentication scheme class that serves as a basis + * for all authentication schemes supported by HttpClient. This class + * defines the generic way of parsing an authentication challenge. It + * does not make any assumptions regarding the format of the challenge + * nor does it impose any specific way of responding to that challenge. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> +*/ +public abstract class AuthSchemeBase implements AuthScheme { + + /** + * Flag whether authenticating against a proxy. + */ + private boolean proxy; + + public AuthSchemeBase() { + super(); + } + + /** + * Processes the given challenge token. Some authentication schemes + * may involve multiple challenge-response exchanges. Such schemes must be able + * to maintain the state information when dealing with sequential challenges + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(final Header header) throws MalformedChallengeException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + String authheader = header.getName(); + if (authheader.equalsIgnoreCase(AUTH.WWW_AUTH)) { + this.proxy = false; + } else if (authheader.equalsIgnoreCase(AUTH.PROXY_AUTH)) { + this.proxy = true; + } else { + throw new MalformedChallengeException("Unexpected header name: " + authheader); + } + + CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int endIndex = pos; + String s = buffer.substring(beginIndex, endIndex); + if (!s.equalsIgnoreCase(getSchemeName())) { + throw new MalformedChallengeException("Invalid scheme identifier: " + s); + } + + parseChallenge(buffer, pos, buffer.length()); + } + + protected abstract void parseChallenge( + CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException; + + /** + * Returns <code>true</code> if authenticating against a proxy, <code>false</code> + * otherwise. + * + * @return <code>true</code> if authenticating against a proxy, <code>false</code> + * otherwise + */ + public boolean isProxy() { + return this.proxy; + } + +} diff --git a/src/org/apache/http/impl/auth/BasicScheme.java b/src/org/apache/http/impl/auth/BasicScheme.java new file mode 100644 index 0000000..88ea110 --- /dev/null +++ b/src/org/apache/http/impl/auth/BasicScheme.java @@ -0,0 +1,185 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/BasicScheme.java $ + * $Revision: 658430 $ + * $Date: 2008-05-20 14:04:27 -0700 (Tue, 20 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.commons.codec.binary.Base64; +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.params.AuthParams; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.EncodingUtils; + +/** + * <p> + * Basic authentication scheme as defined in RFC 2617. + * </p> + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ + +public class BasicScheme extends RFC2617Scheme { + + /** Whether the basic authentication process is complete */ + private boolean complete; + + /** + * Default constructor for the basic authetication scheme. + */ + public BasicScheme() { + super(); + this.complete = false; + } + + /** + * Returns textual designation of the basic authentication scheme. + * + * @return <code>basic</code> + */ + public String getSchemeName() { + return "basic"; + } + + /** + * Processes the Basic challenge. + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + @Override + public void processChallenge( + final Header header) throws MalformedChallengeException { + super.processChallenge(header); + this.complete = true; + } + + /** + * Tests if the Basic authentication process has been completed. + * + * @return <tt>true</tt> if Basic authorization has been processed, + * <tt>false</tt> otherwise. + */ + public boolean isComplete() { + return this.complete; + } + + /** + * Returns <tt>false</tt>. Basic authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; + } + + /** + * Produces basic authorization header for the given set of {@link Credentials}. + * + * @param credentials The set of credentials to be used for athentication + * @param request The request being authenticated + * @throws org.apache.http.auth.InvalidCredentialsException if authentication credentials + * are not valid or not applicable for this authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a basic authorization string + */ + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + + String charset = AuthParams.getCredentialCharset(request.getParams()); + return authenticate(credentials, charset, isProxy()); + } + + /** + * Returns a basic <tt>Authorization</tt> header value for the given + * {@link Credentials} and charset. + * + * @param credentials The credentials to encode. + * @param charset The charset to use for encoding the credentials + * + * @return a basic authorization header + */ + public static Header authenticate( + final Credentials credentials, + final String charset, + boolean proxy) { + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (charset == null) { + throw new IllegalArgumentException("charset may not be null"); + } + + StringBuilder tmp = new StringBuilder(); + tmp.append(credentials.getUserPrincipal().getName()); + tmp.append(":"); + tmp.append((credentials.getPassword() == null) ? "null" : credentials.getPassword()); + + byte[] base64password = Base64.encodeBase64( + EncodingUtils.getBytes(tmp.toString(), charset)); + + CharArrayBuffer buffer = new CharArrayBuffer(32); + if (proxy) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Basic "); + buffer.append(base64password, 0, base64password.length); + + return new BufferedHeader(buffer); + } + +} diff --git a/src/org/apache/http/impl/auth/BasicSchemeFactory.java b/src/org/apache/http/impl/auth/BasicSchemeFactory.java new file mode 100644 index 0000000..c5d28b0 --- /dev/null +++ b/src/org/apache/http/impl/auth/BasicSchemeFactory.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/BasicSchemeFactory.java $ + * $Revision: 534839 $ + * $Date: 2007-05-03 06:03:41 -0700 (Thu, 03 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeFactory; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BasicSchemeFactory implements AuthSchemeFactory { + + public AuthScheme newInstance(final HttpParams params) { + return new BasicScheme(); + } + +} diff --git a/src/org/apache/http/impl/auth/DigestScheme.java b/src/org/apache/http/impl/auth/DigestScheme.java new file mode 100644 index 0000000..803807b --- /dev/null +++ b/src/org/apache/http/impl/auth/DigestScheme.java @@ -0,0 +1,484 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/DigestScheme.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.params.AuthParams; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.message.BasicHeaderValueFormatter; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.EncodingUtils; + +/** + * <p> + * Digest authentication scheme as defined in RFC 2617. + * Both MD5 (default) and MD5-sess are supported. + * Currently only qop=auth or no qop is supported. qop=auth-int + * is unsupported. If auth and auth-int are provided, auth is + * used. + * </p> + * <p> + * Credential charset is configured via the + * {@link org.apache.http.auth.params.AuthPNames#CREDENTIAL_CHARSET + * credential charset} parameter. + * Since the digest username is included as clear text in the generated + * Authentication header, the charset of the username must be compatible + * with the + * {@link org.apache.http.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET + * http element charset}. + * </p> + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ + +public class DigestScheme extends RFC2617Scheme { + + /** + * Hexa values used when creating 32 character long digest in HTTP DigestScheme + * in case of authentication. + * + * @see #encode(byte[]) + */ + private static final char[] HEXADECIMAL = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' + }; + + /** Whether the digest authentication process is complete */ + private boolean complete; + + //TODO: supply a real nonce-count, currently a server will interprete a repeated request as a replay + private static final String NC = "00000001"; //nonce-count is always 1 + private static final int QOP_MISSING = 0; + private static final int QOP_AUTH_INT = 1; + private static final int QOP_AUTH = 2; + + private int qopVariant = QOP_MISSING; + private String cnonce; + + /** + * Default constructor for the digest authetication scheme. + */ + public DigestScheme() { + super(); + this.complete = false; + } + + /** + * Processes the Digest challenge. + * + * @param header the challenge header + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + @Override + public void processChallenge( + final Header header) throws MalformedChallengeException { + super.processChallenge(header); + + if (getParameter("realm") == null) { + throw new MalformedChallengeException("missing realm in challange"); + } + if (getParameter("nonce") == null) { + throw new MalformedChallengeException("missing nonce in challange"); + } + + boolean unsupportedQop = false; + // qop parsing + String qop = getParameter("qop"); + if (qop != null) { + StringTokenizer tok = new StringTokenizer(qop,","); + while (tok.hasMoreTokens()) { + String variant = tok.nextToken().trim(); + if (variant.equals("auth")) { + qopVariant = QOP_AUTH; + break; //that's our favourite, because auth-int is unsupported + } else if (variant.equals("auth-int")) { + qopVariant = QOP_AUTH_INT; + } else { + unsupportedQop = true; + } + } + } + + if (unsupportedQop && (qopVariant == QOP_MISSING)) { + throw new MalformedChallengeException("None of the qop methods is supported"); + } + // Reset cnonce + this.cnonce = null; + this.complete = true; + } + + /** + * Tests if the Digest authentication process has been completed. + * + * @return <tt>true</tt> if Digest authorization has been processed, + * <tt>false</tt> otherwise. + */ + public boolean isComplete() { + String s = getParameter("stale"); + if ("true".equalsIgnoreCase(s)) { + return false; + } else { + return this.complete; + } + } + + /** + * Returns textual designation of the digest authentication scheme. + * + * @return <code>digest</code> + */ + public String getSchemeName() { + return "digest"; + } + + /** + * Returns <tt>false</tt>. Digest authentication scheme is request based. + * + * @return <tt>false</tt>. + */ + public boolean isConnectionBased() { + return false; + } + + public void overrideParamter(final String name, final String value) { + getParameters().put(name, value); + } + + private String getCnonce() { + if (this.cnonce == null) { + this.cnonce = createCnonce(); + } + return this.cnonce; + } + + /** + * Produces a digest authorization string for the given set of + * {@link Credentials}, method name and URI. + * + * @param credentials A set of credentials to be used for athentication + * @param request The request being authenticated + * + * @throws org.apache.http.auth.InvalidCredentialsException if authentication credentials + * are not valid or not applicable for this authentication scheme + * @throws AuthenticationException if authorization string cannot + * be generated due to an authentication failure + * + * @return a digest authorization string + */ + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + + if (credentials == null) { + throw new IllegalArgumentException("Credentials may not be null"); + } + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + + // Add method name and request-URI to the parameter map + getParameters().put("methodname", request.getRequestLine().getMethod()); + getParameters().put("uri", request.getRequestLine().getUri()); + String charset = getParameter("charset"); + if (charset == null) { + charset = AuthParams.getCredentialCharset(request.getParams()); + getParameters().put("charset", charset); + } + String digest = createDigest(credentials); + return createDigestHeader(credentials, digest); + } + + private static MessageDigest createMessageDigest( + final String digAlg) throws UnsupportedDigestAlgorithmException { + try { + return MessageDigest.getInstance(digAlg); + } catch (Exception e) { + throw new UnsupportedDigestAlgorithmException( + "Unsupported algorithm in HTTP Digest authentication: " + + digAlg); + } + } + + /** + * Creates an MD5 response digest. + * + * @return The created digest as string. This will be the response tag's + * value in the Authentication HTTP header. + * @throws AuthenticationException when MD5 is an unsupported algorithm + */ + private String createDigest(final Credentials credentials) throws AuthenticationException { + // Collecting required tokens + String uri = getParameter("uri"); + String realm = getParameter("realm"); + String nonce = getParameter("nonce"); + String method = getParameter("methodname"); + String algorithm = getParameter("algorithm"); + if (uri == null) { + throw new IllegalStateException("URI may not be null"); + } + if (realm == null) { + throw new IllegalStateException("Realm may not be null"); + } + if (nonce == null) { + throw new IllegalStateException("Nonce may not be null"); + } + // If an algorithm is not specified, default to MD5. + if (algorithm == null) { + algorithm = "MD5"; + } + // If an charset is not specified, default to ISO-8859-1. + String charset = getParameter("charset"); + if (charset == null) { + charset = "ISO-8859-1"; + } + + if (qopVariant == QOP_AUTH_INT) { + throw new AuthenticationException( + "Unsupported qop in HTTP Digest authentication"); + } + + MessageDigest md5Helper = createMessageDigest("MD5"); + + String uname = credentials.getUserPrincipal().getName(); + String pwd = credentials.getPassword(); + + // 3.2.2.2: Calculating digest + StringBuilder tmp = new StringBuilder(uname.length() + realm.length() + pwd.length() + 2); + tmp.append(uname); + tmp.append(':'); + tmp.append(realm); + tmp.append(':'); + tmp.append(pwd); + // unq(username-value) ":" unq(realm-value) ":" passwd + String a1 = tmp.toString(); + + //a1 is suitable for MD5 algorithm + if(algorithm.equals("MD5-sess")) { + // H( unq(username-value) ":" unq(realm-value) ":" passwd ) + // ":" unq(nonce-value) + // ":" unq(cnonce-value) + + String cnonce = getCnonce(); + + String tmp2=encode(md5Helper.digest(EncodingUtils.getBytes(a1, charset))); + StringBuilder tmp3 = new StringBuilder(tmp2.length() + nonce.length() + cnonce.length() + 2); + tmp3.append(tmp2); + tmp3.append(':'); + tmp3.append(nonce); + tmp3.append(':'); + tmp3.append(cnonce); + a1 = tmp3.toString(); + } else if (!algorithm.equals("MD5")) { + throw new AuthenticationException("Unhandled algorithm " + algorithm + " requested"); + } + String md5a1 = encode(md5Helper.digest(EncodingUtils.getBytes(a1, charset))); + + String a2 = null; + if (qopVariant == QOP_AUTH_INT) { + // Unhandled qop auth-int + //we do not have access to the entity-body or its hash + //TODO: add Method ":" digest-uri-value ":" H(entity-body) + } else { + a2 = method + ':' + uri; + } + String md5a2 = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(a2))); + + // 3.2.2.1 + String serverDigestValue; + if (qopVariant == QOP_MISSING) { + StringBuilder tmp2 = new StringBuilder(md5a1.length() + nonce.length() + md5a2.length()); + tmp2.append(md5a1); + tmp2.append(':'); + tmp2.append(nonce); + tmp2.append(':'); + tmp2.append(md5a2); + serverDigestValue = tmp2.toString(); + } else { + String qopOption = getQopVariantString(); + String cnonce = getCnonce(); + + StringBuilder tmp2 = new StringBuilder(md5a1.length() + nonce.length() + + NC.length() + cnonce.length() + qopOption.length() + md5a2.length() + 5); + tmp2.append(md5a1); + tmp2.append(':'); + tmp2.append(nonce); + tmp2.append(':'); + tmp2.append(NC); + tmp2.append(':'); + tmp2.append(cnonce); + tmp2.append(':'); + tmp2.append(qopOption); + tmp2.append(':'); + tmp2.append(md5a2); + serverDigestValue = tmp2.toString(); + } + + String serverDigest = + encode(md5Helper.digest(EncodingUtils.getAsciiBytes(serverDigestValue))); + + return serverDigest; + } + + /** + * Creates digest-response header as defined in RFC2617. + * + * @param credentials User credentials + * @param digest The response tag's value as String. + * + * @return The digest-response as String. + */ + private Header createDigestHeader( + final Credentials credentials, + final String digest) throws AuthenticationException { + + CharArrayBuffer buffer = new CharArrayBuffer(128); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": Digest "); + + String uri = getParameter("uri"); + String realm = getParameter("realm"); + String nonce = getParameter("nonce"); + String opaque = getParameter("opaque"); + String response = digest; + String algorithm = getParameter("algorithm"); + + String uname = credentials.getUserPrincipal().getName(); + + List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(20); + params.add(new BasicNameValuePair("username", uname)); + params.add(new BasicNameValuePair("realm", realm)); + params.add(new BasicNameValuePair("nonce", nonce)); + params.add(new BasicNameValuePair("uri", uri)); + params.add(new BasicNameValuePair("response", response)); + + if (qopVariant != QOP_MISSING) { + params.add(new BasicNameValuePair("qop", getQopVariantString())); + params.add(new BasicNameValuePair("nc", NC)); + params.add(new BasicNameValuePair("cnonce", getCnonce())); + } + if (algorithm != null) { + params.add(new BasicNameValuePair("algorithm", algorithm)); + } + if (opaque != null) { + params.add(new BasicNameValuePair("opaque", opaque)); + } + + for (int i = 0; i < params.size(); i++) { + BasicNameValuePair param = params.get(i); + if (i > 0) { + buffer.append(", "); + } + boolean noQuotes = "nc".equals(param.getName()) || + "qop".equals(param.getName()); + BasicHeaderValueFormatter.DEFAULT + .formatNameValuePair(buffer, param, !noQuotes); + } + return new BufferedHeader(buffer); + } + + private String getQopVariantString() { + String qopOption; + if (qopVariant == QOP_AUTH_INT) { + qopOption = "auth-int"; + } else { + qopOption = "auth"; + } + return qopOption; + } + + /** + * Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long + * <CODE>String</CODE> according to RFC 2617. + * + * @param binaryData array containing the digest + * @return encoded MD5, or <CODE>null</CODE> if encoding failed + */ + private static String encode(byte[] binaryData) { + if (binaryData.length != 16) { + return null; + } + + char[] buffer = new char[32]; + for (int i = 0; i < 16; i++) { + int low = (binaryData[i] & 0x0f); + int high = ((binaryData[i] & 0xf0) >> 4); + buffer[i * 2] = HEXADECIMAL[high]; + buffer[(i * 2) + 1] = HEXADECIMAL[low]; + } + + return new String(buffer); + } + + + /** + * Creates a random cnonce value based on the current time. + * + * @return The cnonce value as String. + * @throws UnsupportedDigestAlgorithmException if MD5 algorithm is not supported. + */ + public static String createCnonce() { + String cnonce; + + MessageDigest md5Helper = createMessageDigest("MD5"); + + cnonce = Long.toString(System.currentTimeMillis()); + cnonce = encode(md5Helper.digest(EncodingUtils.getAsciiBytes(cnonce))); + + return cnonce; + } +} diff --git a/src/org/apache/http/impl/auth/DigestSchemeFactory.java b/src/org/apache/http/impl/auth/DigestSchemeFactory.java new file mode 100644 index 0000000..38f2e12 --- /dev/null +++ b/src/org/apache/http/impl/auth/DigestSchemeFactory.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/DigestSchemeFactory.java $ + * $Revision: 534839 $ + * $Date: 2007-05-03 06:03:41 -0700 (Thu, 03 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeFactory; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class DigestSchemeFactory implements AuthSchemeFactory { + + public AuthScheme newInstance(final HttpParams params) { + return new DigestScheme(); + } + +} diff --git a/src/org/apache/http/impl/auth/NTLMEngine.java b/src/org/apache/http/impl/auth/NTLMEngine.java new file mode 100644 index 0000000..7b6bf42 --- /dev/null +++ b/src/org/apache/http/impl/auth/NTLMEngine.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/NTLMEngine.java $ + * $Revision: 659788 $ + * $Date: 2008-05-24 03:42:23 -0700 (Sat, 24 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +/** + * Abstract NTLM authentication engine. The engine can be used to + * generate Type1 messages and Type3 messages in response to a + * Type2 challenge. + * <p/> + * For details see <a href="http://davenport.sourceforge.net/ntlm.html">this resource</a> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> +*/ +public interface NTLMEngine { + + /** + * Generates a Type1 message given the domain and workstation. + * + * @param domain Optional Windows domain name. Can be <code>null</code>. + * @param workstation Optional Windows workstation name. Can be + * <code>null</code>. + * @return Type1 message + * @throws NTLMEngineException + */ + String generateType1Msg( + String domain, + String workstation) throws NTLMEngineException; + + /** + * Generates a Type3 message given the user credentials and the + * authentication challenge. + * + * @param username Windows user name + * @param password Password + * @param domain Windows domain name + * @param workstation Windows workstation name + * @param challenge Type2 challenge. + * @return Type3 response. + * @throws NTLMEngineException + */ + String generateType3Msg( + String username, + String password, + String domain, + String workstation, + String challenge) throws NTLMEngineException; + +} diff --git a/src/org/apache/http/impl/auth/NTLMEngineException.java b/src/org/apache/http/impl/auth/NTLMEngineException.java new file mode 100644 index 0000000..73baabc --- /dev/null +++ b/src/org/apache/http/impl/auth/NTLMEngineException.java @@ -0,0 +1,70 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/NTLMEngineException.java $ + * $Revision: 655048 $ + * $Date: 2008-05-10 04:22:12 -0700 (Sat, 10 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.auth.AuthenticationException; + +/** + * Signals NTLM protocol failure. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class NTLMEngineException extends AuthenticationException { + + private static final long serialVersionUID = 6027981323731768824L; + + public NTLMEngineException() { + super(); + } + + /** + * Creates a new NTLMEngineException with the specified message. + * + * @param message the exception detail message + */ + public NTLMEngineException(String message) { + super(message); + } + + /** + * Creates a new NTLMEngineException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public NTLMEngineException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/org/apache/http/impl/auth/NTLMScheme.java b/src/org/apache/http/impl/auth/NTLMScheme.java new file mode 100644 index 0000000..8dfdbba --- /dev/null +++ b/src/org/apache/http/impl/auth/NTLMScheme.java @@ -0,0 +1,149 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/NTLMScheme.java $ + * $Revision: 655048 $ + * $Date: 2008-05-10 04:22:12 -0700 (Sat, 10 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.InvalidCredentialsException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.NTCredentials; +import org.apache.http.impl.auth.AuthSchemeBase; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; + +public class NTLMScheme extends AuthSchemeBase { + + enum State { + UNINITIATED, + CHALLENGE_RECEIVED, + MSG_TYPE1_GENERATED, + MSG_TYPE2_RECEVIED, + MSG_TYPE3_GENERATED, + FAILED, + } + + private final NTLMEngine engine; + + private State state; + private String challenge; + + public NTLMScheme(final NTLMEngine engine) { + super(); + if (engine == null) { + throw new IllegalArgumentException("NTLM engine may not be null"); + } + this.engine = engine; + this.state = State.UNINITIATED; + this.challenge = null; + } + + public String getSchemeName() { + return "ntlm"; + } + + public String getParameter(String name) { + // String parameters not supported + return null; + } + + public String getRealm() { + // NTLM does not support the concept of an authentication realm + return null; + } + + public boolean isConnectionBased() { + return true; + } + + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException { + String challenge = buffer.substringTrimmed(pos, len); + if (challenge.length() == 0) { + if (this.state == State.UNINITIATED) { + this.state = State.CHALLENGE_RECEIVED; + } else { + this.state = State.FAILED; + } + this.challenge = null; + } else { + this.state = State.MSG_TYPE2_RECEVIED; + this.challenge = challenge; + } + } + + public Header authenticate( + final Credentials credentials, + final HttpRequest request) throws AuthenticationException { + NTCredentials ntcredentials = null; + try { + ntcredentials = (NTCredentials) credentials; + } catch (ClassCastException e) { + throw new InvalidCredentialsException( + "Credentials cannot be used for NTLM authentication: " + + credentials.getClass().getName()); + } + String response = null; + if (this.state == State.CHALLENGE_RECEIVED || this.state == State.FAILED) { + response = this.engine.generateType1Msg( + ntcredentials.getDomain(), + ntcredentials.getWorkstation()); + this.state = State.MSG_TYPE1_GENERATED; + } else if (this.state == State.MSG_TYPE2_RECEVIED) { + response = this.engine.generateType3Msg( + ntcredentials.getUserName(), + ntcredentials.getPassword(), + ntcredentials.getDomain(), + ntcredentials.getWorkstation(), + this.challenge); + this.state = State.MSG_TYPE3_GENERATED; + } else { + throw new AuthenticationException("Unexpected state: " + this.state); + } + CharArrayBuffer buffer = new CharArrayBuffer(32); + if (isProxy()) { + buffer.append(AUTH.PROXY_AUTH_RESP); + } else { + buffer.append(AUTH.WWW_AUTH_RESP); + } + buffer.append(": NTLM "); + buffer.append(response); + return new BufferedHeader(buffer); + } + + public boolean isComplete() { + return this.state == State.MSG_TYPE3_GENERATED || this.state == State.FAILED; + } + +} diff --git a/src/org/apache/http/impl/auth/RFC2617Scheme.java b/src/org/apache/http/impl/auth/RFC2617Scheme.java new file mode 100644 index 0000000..0ed0a28 --- /dev/null +++ b/src/org/apache/http/impl/auth/RFC2617Scheme.java @@ -0,0 +1,119 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java $ + * $Revision: 659595 $ + * $Date: 2008-05-23 09:47:14 -0700 (Fri, 23 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.HeaderElement; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.message.BasicHeaderValueParser; +import org.apache.http.message.HeaderValueParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract authentication scheme class that lays foundation for all + * RFC 2617 compliant authetication schemes and provides capabilities common + * to all authentication schemes defined in RFC 2617. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> +*/ +public abstract class RFC2617Scheme extends AuthSchemeBase { + + /** + * Authentication parameter map. + */ + private Map<String, String> params; + + /** + * Default constructor for RFC2617 compliant authetication schemes. + */ + public RFC2617Scheme() { + super(); + } + + @Override + protected void parseChallenge( + final CharArrayBuffer buffer, int pos, int len) throws MalformedChallengeException { + HeaderValueParser parser = BasicHeaderValueParser.DEFAULT; + ParserCursor cursor = new ParserCursor(pos, buffer.length()); + HeaderElement[] elements = parser.parseElements(buffer, cursor); + if (elements.length == 0) { + throw new MalformedChallengeException("Authentication challenge is empty"); + } + + this.params = new HashMap<String, String>(elements.length); + for (HeaderElement element : elements) { + this.params.put(element.getName(), element.getValue()); + } + } + + /** + * Returns authentication parameters map. Keys in the map are lower-cased. + * + * @return the map of authentication parameters + */ + protected Map<String, String> getParameters() { + if (this.params == null) { + this.params = new HashMap<String, String>(); + } + return this.params; + } + + /** + * Returns authentication parameter with the given name, if available. + * + * @param name The name of the parameter to be returned + * + * @return the parameter with the given name + */ + public String getParameter(final String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter name may not be null"); + } + if (this.params == null) { + return null; + } + return this.params.get(name.toLowerCase(Locale.ENGLISH)); + } + + /** + * Returns authentication realm. The realm may not be null. + * + * @return the authentication realm + */ + public String getRealm() { + return getParameter("realm"); + } + +} diff --git a/src/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java b/src/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java new file mode 100644 index 0000000..abd0a66 --- /dev/null +++ b/src/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/auth/UnsupportedDigestAlgorithmException.java $ + * $Revision: 527479 $ + * $Date: 2007-04-11 05:55:12 -0700 (Wed, 11 Apr 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.auth; + +/** + * Authentication credentials required to respond to a authentication + * challenge are invalid + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class UnsupportedDigestAlgorithmException extends RuntimeException { + + private static final long serialVersionUID = 319558534317118022L; + + /** + * Creates a new UnsupportedAuthAlgoritmException with a <tt>null</tt> detail message. + */ + public UnsupportedDigestAlgorithmException() { + super(); + } + + /** + * Creates a new UnsupportedAuthAlgoritmException with the specified message. + * + * @param message the exception detail message + */ + public UnsupportedDigestAlgorithmException(String message) { + super(message); + } + + /** + * Creates a new UnsupportedAuthAlgoritmException with the specified detail message and cause. + * + * @param message the exception detail message + * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt> + * if the cause is unavailable, unknown, or not a <tt>Throwable</tt> + */ + public UnsupportedDigestAlgorithmException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/org/apache/http/impl/auth/package.html b/src/org/apache/http/impl/auth/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/auth/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + diff --git a/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java b/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java new file mode 100644 index 0000000..57699d5 --- /dev/null +++ b/src/org/apache/http/impl/client/AbstractAuthenticationHandler.java @@ -0,0 +1,165 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractAuthenticationHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.CharArrayBuffer; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler { + + private final Log log = LogFactory.getLog(getClass()); + + private static final List<String> DEFAULT_SCHEME_PRIORITY = Arrays.asList(new String[] { + "ntlm", + "digest", + "basic" + }); + + public AbstractAuthenticationHandler() { + super(); + } + + protected Map<String, Header> parseChallenges( + final Header[] headers) throws MalformedChallengeException { + + Map<String, Header> map = new HashMap<String, Header>(headers.length); + for (Header header : headers) { + CharArrayBuffer buffer; + int pos; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + pos = ((FormattedHeader) header).getValuePos(); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedChallengeException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + pos = 0; + } + while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int beginIndex = pos; + while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) { + pos++; + } + int endIndex = pos; + String s = buffer.substring(beginIndex, endIndex); + map.put(s.toLowerCase(Locale.ENGLISH), header); + } + return map; + } + + protected List<String> getAuthPreferences() { + return DEFAULT_SCHEME_PRIORITY; + } + + public AuthScheme selectScheme( + final Map<String, Header> challenges, + final HttpResponse response, + final HttpContext context) throws AuthenticationException { + + AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute( + ClientContext.AUTHSCHEME_REGISTRY); + if (registry == null) { + throw new IllegalStateException("AuthScheme registry not set in HTTP context"); + } + + List<?> authPrefs = (List<?>) context.getAttribute( + ClientContext.AUTH_SCHEME_PREF); + if (authPrefs == null) { + authPrefs = getAuthPreferences(); + } + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication schemes in the order of preference: " + + authPrefs); + } + + AuthScheme authScheme = null; + for (int i = 0; i < authPrefs.size(); i++) { + String id = (String) authPrefs.get(i); + Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + + if (challenge != null) { + if (this.log.isDebugEnabled()) { + this.log.debug(id + " authentication scheme selected"); + } + try { + authScheme = registry.getAuthScheme(id, response.getParams()); + break; + } catch (IllegalStateException e) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication scheme " + id + " not supported"); + // Try again + } + } + } else { + if (this.log.isDebugEnabled()) { + this.log.debug("Challenge for " + id + " authentication scheme not available"); + // Try again + } + } + } + if (authScheme == null) { + // If none selected, something is wrong + throw new AuthenticationException( + "Unable to respond to any of these challenges: " + + challenges); + } + return authScheme; + } + +} diff --git a/src/org/apache/http/impl/client/AbstractHttpClient.java b/src/org/apache/http/impl/client/AbstractHttpClient.java new file mode 100644 index 0000000..3a1b838 --- /dev/null +++ b/src/org/apache/http/impl/client/AbstractHttpClient.java @@ -0,0 +1,697 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/AbstractHttpClient.java $ + * $Revision: 677250 $ + * $Date: 2008-07-16 04:45:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; +import java.net.URI; +import java.lang.reflect.UndeclaredThrowableException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpEntity; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.RequestDirector; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.cookie.CookieSpecRegistry; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.DefaultedHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestExecutor; + +/** + * Convenience base class for HTTP client implementations. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 677250 $ + * + * @since 4.0 + */ +public abstract class AbstractHttpClient implements HttpClient { + + private final Log log = LogFactory.getLog(getClass()); + + /** The parameters. */ + private HttpParams defaultParams; + + /** The request executor. */ + private HttpRequestExecutor requestExec; + + /** The connection manager. */ + private ClientConnectionManager connManager; + + /** The connection re-use strategy. */ + private ConnectionReuseStrategy reuseStrategy; + + /** The connection keep-alive strategy. */ + private ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The cookie spec registry. */ + private CookieSpecRegistry supportedCookieSpecs; + + /** The authentication scheme registry. */ + private AuthSchemeRegistry supportedAuthSchemes; + + /** The HTTP processor. */ + private BasicHttpProcessor httpProcessor; + + /** The request retry handler. */ + private HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + private RedirectHandler redirectHandler; + + /** The target authentication handler. */ + private AuthenticationHandler targetAuthHandler; + + /** The proxy authentication handler. */ + private AuthenticationHandler proxyAuthHandler; + + /** The cookie store. */ + private CookieStore cookieStore; + + /** The credentials provider. */ + private CredentialsProvider credsProvider; + + /** The route planner. */ + private HttpRoutePlanner routePlanner; + + /** The user token handler. */ + private UserTokenHandler userTokenHandler; + + + /** + * Creates a new HTTP client. + * + * @param conman the connection manager + * @param params the parameters + */ + protected AbstractHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + defaultParams = params; + connManager = conman; + } // constructor + + protected abstract HttpParams createHttpParams(); + + + protected abstract HttpContext createHttpContext(); + + + protected abstract HttpRequestExecutor createRequestExecutor(); + + + protected abstract ClientConnectionManager createClientConnectionManager(); + + + protected abstract AuthSchemeRegistry createAuthSchemeRegistry(); + + + protected abstract CookieSpecRegistry createCookieSpecRegistry(); + + + protected abstract ConnectionReuseStrategy createConnectionReuseStrategy(); + + + protected abstract ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy(); + + + protected abstract BasicHttpProcessor createHttpProcessor(); + + + protected abstract HttpRequestRetryHandler createHttpRequestRetryHandler(); + + + protected abstract RedirectHandler createRedirectHandler(); + + + protected abstract AuthenticationHandler createTargetAuthenticationHandler(); + + + protected abstract AuthenticationHandler createProxyAuthenticationHandler(); + + + protected abstract CookieStore createCookieStore(); + + + protected abstract CredentialsProvider createCredentialsProvider(); + + + protected abstract HttpRoutePlanner createHttpRoutePlanner(); + + + protected abstract UserTokenHandler createUserTokenHandler(); + + + // non-javadoc, see interface HttpClient + public synchronized final HttpParams getParams() { + if (defaultParams == null) { + defaultParams = createHttpParams(); + } + return defaultParams; + } + + + /** + * Replaces the parameters. + * The implementation here does not update parameters of dependent objects. + * + * @param params the new default parameters + */ + public synchronized void setParams(HttpParams params) { + defaultParams = params; + } + + + public synchronized final ClientConnectionManager getConnectionManager() { + if (connManager == null) { + connManager = createClientConnectionManager(); + } + return connManager; + } + + + public synchronized final HttpRequestExecutor getRequestExecutor() { + if (requestExec == null) { + requestExec = createRequestExecutor(); + } + return requestExec; + } + + + public synchronized final AuthSchemeRegistry getAuthSchemes() { + if (supportedAuthSchemes == null) { + supportedAuthSchemes = createAuthSchemeRegistry(); + } + return supportedAuthSchemes; + } + + + public synchronized void setAuthSchemes(final AuthSchemeRegistry authSchemeRegistry) { + supportedAuthSchemes = authSchemeRegistry; + } + + + public synchronized final CookieSpecRegistry getCookieSpecs() { + if (supportedCookieSpecs == null) { + supportedCookieSpecs = createCookieSpecRegistry(); + } + return supportedCookieSpecs; + } + + + public synchronized void setCookieSpecs(final CookieSpecRegistry cookieSpecRegistry) { + supportedCookieSpecs = cookieSpecRegistry; + } + + + public synchronized final ConnectionReuseStrategy getConnectionReuseStrategy() { + if (reuseStrategy == null) { + reuseStrategy = createConnectionReuseStrategy(); + } + return reuseStrategy; + } + + + public synchronized void setReuseStrategy(final ConnectionReuseStrategy reuseStrategy) { + this.reuseStrategy = reuseStrategy; + } + + + public synchronized final ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() { + if (keepAliveStrategy == null) { + keepAliveStrategy = createConnectionKeepAliveStrategy(); + } + return keepAliveStrategy; + } + + + public synchronized void setKeepAliveStrategy(final ConnectionKeepAliveStrategy keepAliveStrategy) { + this.keepAliveStrategy = keepAliveStrategy; + } + + + public synchronized final HttpRequestRetryHandler getHttpRequestRetryHandler() { + if (retryHandler == null) { + retryHandler = createHttpRequestRetryHandler(); + } + return retryHandler; + } + + + public synchronized void setHttpRequestRetryHandler(final HttpRequestRetryHandler retryHandler) { + this.retryHandler = retryHandler; + } + + + public synchronized final RedirectHandler getRedirectHandler() { + if (redirectHandler == null) { + redirectHandler = createRedirectHandler(); + } + return redirectHandler; + } + + + public synchronized void setRedirectHandler(final RedirectHandler redirectHandler) { + this.redirectHandler = redirectHandler; + } + + + public synchronized final AuthenticationHandler getTargetAuthenticationHandler() { + if (targetAuthHandler == null) { + targetAuthHandler = createTargetAuthenticationHandler(); + } + return targetAuthHandler; + } + + + public synchronized void setTargetAuthenticationHandler( + final AuthenticationHandler targetAuthHandler) { + this.targetAuthHandler = targetAuthHandler; + } + + + public synchronized final AuthenticationHandler getProxyAuthenticationHandler() { + if (proxyAuthHandler == null) { + proxyAuthHandler = createProxyAuthenticationHandler(); + } + return proxyAuthHandler; + } + + + public synchronized void setProxyAuthenticationHandler( + final AuthenticationHandler proxyAuthHandler) { + this.proxyAuthHandler = proxyAuthHandler; + } + + + public synchronized final CookieStore getCookieStore() { + if (cookieStore == null) { + cookieStore = createCookieStore(); + } + return cookieStore; + } + + + public synchronized void setCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + + public synchronized final CredentialsProvider getCredentialsProvider() { + if (credsProvider == null) { + credsProvider = createCredentialsProvider(); + } + return credsProvider; + } + + + public synchronized void setCredentialsProvider(final CredentialsProvider credsProvider) { + this.credsProvider = credsProvider; + } + + + public synchronized final HttpRoutePlanner getRoutePlanner() { + if (this.routePlanner == null) { + this.routePlanner = createHttpRoutePlanner(); + } + return this.routePlanner; + } + + + public synchronized void setRoutePlanner(final HttpRoutePlanner routePlanner) { + this.routePlanner = routePlanner; + } + + + public synchronized final UserTokenHandler getUserTokenHandler() { + if (this.userTokenHandler == null) { + this.userTokenHandler = createUserTokenHandler(); + } + return this.userTokenHandler; + } + + + public synchronized void setUserTokenHandler(final UserTokenHandler userTokenHandler) { + this.userTokenHandler = userTokenHandler; + } + + + protected synchronized final BasicHttpProcessor getHttpProcessor() { + if (httpProcessor == null) { + httpProcessor = createHttpProcessor(); + } + return httpProcessor; + } + + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + } + + + public synchronized void addResponseInterceptor(final HttpResponseInterceptor itcp, int index) { + getHttpProcessor().addInterceptor(itcp, index); + } + + + public synchronized HttpResponseInterceptor getResponseInterceptor(int index) { + return getHttpProcessor().getResponseInterceptor(index); + } + + + public synchronized int getResponseInterceptorCount() { + return getHttpProcessor().getResponseInterceptorCount(); + } + + + public synchronized void clearResponseInterceptors() { + getHttpProcessor().clearResponseInterceptors(); + } + + + public void removeResponseInterceptorByClass(Class<? extends HttpResponseInterceptor> clazz) { + getHttpProcessor().removeResponseInterceptorByClass(clazz); + } + + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp) { + getHttpProcessor().addInterceptor(itcp); + } + + + public synchronized void addRequestInterceptor(final HttpRequestInterceptor itcp, int index) { + getHttpProcessor().addInterceptor(itcp, index); + } + + + public synchronized HttpRequestInterceptor getRequestInterceptor(int index) { + return getHttpProcessor().getRequestInterceptor(index); + } + + + public synchronized int getRequestInterceptorCount() { + return getHttpProcessor().getRequestInterceptorCount(); + } + + + public synchronized void clearRequestInterceptors() { + getHttpProcessor().clearRequestInterceptors(); + } + + + public void removeRequestInterceptorByClass(Class<? extends HttpRequestInterceptor> clazz) { + getHttpProcessor().removeRequestInterceptorByClass(clazz); + } + + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpUriRequest request) + throws IOException, ClientProtocolException { + + return execute(request, (HttpContext) null); + } + + + /** + * Maps to {@link HttpClient#execute(HttpHost,HttpRequest,HttpContext) + * execute(target, request, context)}. + * The target is determined from the URI of the request. + * + * @param request the request to execute + * @param context the request-specific execution context, + * or <code>null</code> to use a default context + */ + public final HttpResponse execute(HttpUriRequest request, + HttpContext context) + throws IOException, ClientProtocolException { + + if (request == null) { + throw new IllegalArgumentException + ("Request must not be null."); + } + + return execute(determineTarget(request), request, context); + } + + private HttpHost determineTarget(HttpUriRequest request) { + // A null target may be acceptable if there is a default target. + // Otherwise, the null target is detected in the director. + HttpHost target = null; + + URI requestURI = request.getURI(); + if (requestURI.isAbsolute()) { + target = new HttpHost( + requestURI.getHost(), + requestURI.getPort(), + requestURI.getScheme()); + } + return target; + } + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpHost target, HttpRequest request) + throws IOException, ClientProtocolException { + + return execute(target, request, (HttpContext) null); + } + + + // non-javadoc, see interface HttpClient + public final HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws IOException, ClientProtocolException { + + if (request == null) { + throw new IllegalArgumentException + ("Request must not be null."); + } + // a null target may be acceptable, this depends on the route planner + // a null context is acceptable, default context created below + + HttpContext execContext = null; + RequestDirector director = null; + + // Initialize the request execution context making copies of + // all shared objects that are potentially threading unsafe. + synchronized (this) { + + HttpContext defaultContext = createHttpContext(); + if (context == null) { + execContext = defaultContext; + } else { + execContext = new DefaultedHttpContext(context, defaultContext); + } + // Create a director for this request + director = createClientRequestDirector( + getRequestExecutor(), + getConnectionManager(), + getConnectionReuseStrategy(), + getConnectionKeepAliveStrategy(), + getRoutePlanner(), + getHttpProcessor().copy(), + getHttpRequestRetryHandler(), + getRedirectHandler(), + getTargetAuthenticationHandler(), + getProxyAuthenticationHandler(), + getUserTokenHandler(), + determineParams(request)); + } + + try { + return director.execute(target, request, execContext); + } catch(HttpException httpException) { + throw new ClientProtocolException(httpException); + } + } // execute + + + protected RequestDirector createClientRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler stateHandler, + final HttpParams params) { + return new DefaultRequestDirector( + requestExec, + conman, + reustrat, + kastrat, + rouplan, + httpProcessor, + retryHandler, + redirectHandler, + targetAuthHandler, + proxyAuthHandler, + stateHandler, + params); + } + + /** + * Obtains parameters for executing a request. + * The default implementation in this class creates a new + * {@link ClientParamsStack} from the request parameters + * and the client parameters. + * <br/> + * This method is called by the default implementation of + * {@link #execute(HttpHost,HttpRequest,HttpContext)} + * to obtain the parameters for the + * {@link DefaultRequestDirector}. + * + * @param req the request that will be executed + * + * @return the parameters to use + */ + protected HttpParams determineParams(HttpRequest req) { + return new ClientParamsStack + (null, getParams(), req.getParams(), null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return execute(request, responseHandler, null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpUriRequest request, + final ResponseHandler<? extends T> responseHandler, + final HttpContext context) + throws IOException, ClientProtocolException { + HttpHost target = determineTarget(request); + return execute(target, request, responseHandler, context); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpHost target, + final HttpRequest request, + final ResponseHandler<? extends T> responseHandler) + throws IOException, ClientProtocolException { + return execute(target, request, responseHandler, null); + } + + + // non-javadoc, see interface HttpClient + public <T> T execute( + final HttpHost target, + final HttpRequest request, + final ResponseHandler<? extends T> responseHandler, + final HttpContext context) + throws IOException, ClientProtocolException { + if (responseHandler == null) { + throw new IllegalArgumentException + ("Response handler must not be null."); + } + + HttpResponse response = execute(target, request, context); + + T result; + try { + result = responseHandler.handleResponse(response); + } catch (Throwable t) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + try { + entity.consumeContent(); + } catch (Throwable t2) { + // Log this exception. The original exception is more + // important and will be thrown to the caller. + this.log.warn("Error consuming content after an exception.", t2); + } + } + + if (t instanceof Error) { + throw (Error) t; + } + + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + + if (t instanceof IOException) { + throw (IOException) t; + } + + throw new UndeclaredThrowableException(t); + } + + // Handling the response was successful. Ensure that the content has + // been fully consumed. + HttpEntity entity = response.getEntity(); + if (entity != null) { + // Let this exception go to the caller. + entity.consumeContent(); + } + + return result; + } + + +} // class AbstractHttpClient diff --git a/src/org/apache/http/impl/client/BasicCookieStore.java b/src/org/apache/http/impl/client/BasicCookieStore.java new file mode 100644 index 0000000..9970961 --- /dev/null +++ b/src/org/apache/http/impl/client/BasicCookieStore.java @@ -0,0 +1,162 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicCookieStore.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.apache.http.client.CookieStore; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieIdentityComparator; + +/** + * Default implementation of {@link CookieStore} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Sean C. Sullivan + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class BasicCookieStore implements CookieStore { + + private final ArrayList<Cookie> cookies; + + private final Comparator<Cookie> cookieComparator; + + // -------------------------------------------------------- Class Variables + + /** + * Default constructor. + */ + public BasicCookieStore() { + super(); + this.cookies = new ArrayList<Cookie>(); + this.cookieComparator = new CookieIdentityComparator(); + } + + /** + * Adds an {@link Cookie HTTP cookie}, replacing any existing equivalent cookies. + * If the given cookie has already expired it will not be added, but existing + * values will still be removed. + * + * @param cookie the {@link Cookie cookie} to be added + * + * @see #addCookies(Cookie[]) + * + */ + public synchronized void addCookie(Cookie cookie) { + if (cookie != null) { + // first remove any old cookie that is equivalent + for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (cookieComparator.compare(cookie, it.next()) == 0) { + it.remove(); + break; + } + } + if (!cookie.isExpired(new Date())) { + cookies.add(cookie); + } + } + } + + /** + * Adds an array of {@link Cookie HTTP cookies}. Cookies are added individually and + * in the given array order. If any of the given cookies has already expired it will + * not be added, but existing values will still be removed. + * + * @param cookies the {@link Cookie cookies} to be added + * + * @see #addCookie(Cookie) + * + */ + public synchronized void addCookies(Cookie[] cookies) { + if (cookies != null) { + for (Cookie cooky : cookies) { + this.addCookie(cooky); + } + } + } + + /** + * Returns an immutable array of {@link Cookie cookies} that this HTTP + * state currently contains. + * + * @return an array of {@link Cookie cookies}. + */ + public synchronized List<Cookie> getCookies() { + return Collections.unmodifiableList(this.cookies); + } + + /** + * Removes all of {@link Cookie cookies} in this HTTP state + * that have expired by the specified {@link java.util.Date date}. + * + * @return true if any cookies were purged. + * + * @see Cookie#isExpired(Date) + */ + public synchronized boolean clearExpired(final Date date) { + if (date == null) { + return false; + } + boolean removed = false; + for (Iterator<Cookie> it = cookies.iterator(); it.hasNext();) { + if (it.next().isExpired(date)) { + it.remove(); + removed = true; + } + } + return removed; + } + + @Override + public String toString() { + return cookies.toString(); + } + + /** + * Clears all cookies. + */ + public synchronized void clear() { + cookies.clear(); + } + +} diff --git a/src/org/apache/http/impl/client/BasicCredentialsProvider.java b/src/org/apache/http/impl/client/BasicCredentialsProvider.java new file mode 100644 index 0000000..02427ea --- /dev/null +++ b/src/org/apache/http/impl/client/BasicCredentialsProvider.java @@ -0,0 +1,143 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicCredentialsProvider.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.HashMap; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; + +/** + * Default implementation of {@link CredentialsProvider} + * + * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> + * @author Rodney Waldhoff + * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> + * @author Sean C. Sullivan + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:adrian@intencha.com">Adrian Sutton</a> + * + * @since 4.0 + */ +public class BasicCredentialsProvider implements CredentialsProvider { + + private final HashMap<AuthScope, Credentials> credMap; + + /** + * Default constructor. + */ + public BasicCredentialsProvider() { + super(); + this.credMap = new HashMap<AuthScope, Credentials>(); + } + + /** + * Sets the {@link Credentials credentials} for the given authentication + * scope. Any previous credentials for the given scope will be overwritten. + * + * @param authscope the {@link AuthScope authentication scope} + * @param credentials the authentication {@link Credentials credentials} + * for the given scope. + * + * @see #getCredentials(AuthScope) + */ + public synchronized void setCredentials( + final AuthScope authscope, + final Credentials credentials) { + if (authscope == null) { + throw new IllegalArgumentException("Authentication scope may not be null"); + } + credMap.put(authscope, credentials); + } + + /** + * Find matching {@link Credentials credentials} for the given authentication scope. + * + * @param map the credentials hash map + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + */ + private static Credentials matchCredentials( + final HashMap<AuthScope, Credentials> map, + final AuthScope authscope) { + // see if we get a direct hit + Credentials creds = map.get(authscope); + if (creds == null) { + // Nope. + // Do a full scan + int bestMatchFactor = -1; + AuthScope bestMatch = null; + for (AuthScope current: map.keySet()) { + int factor = authscope.match(current); + if (factor > bestMatchFactor) { + bestMatchFactor = factor; + bestMatch = current; + } + } + if (bestMatch != null) { + creds = map.get(bestMatch); + } + } + return creds; + } + + /** + * Get the {@link Credentials credentials} for the given authentication scope. + * + * @param authscope the {@link AuthScope authentication scope} + * @return the credentials + * + * @see #setCredentials(AuthScope, Credentials) + */ + public synchronized Credentials getCredentials(final AuthScope authscope) { + if (authscope == null) { + throw new IllegalArgumentException("Authentication scope may not be null"); + } + return matchCredentials(this.credMap, authscope); + } + + @Override + public String toString() { + return credMap.toString(); + } + + /** + * Clears all credentials. + */ + public synchronized void clear() { + this.credMap.clear(); + } + +} diff --git a/src/org/apache/http/impl/client/BasicResponseHandler.java b/src/org/apache/http/impl/client/BasicResponseHandler.java new file mode 100644 index 0000000..f17d30d --- /dev/null +++ b/src/org/apache/http/impl/client/BasicResponseHandler.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/BasicResponseHandler.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.HttpResponseException; +import org.apache.http.util.EntityUtils; + +/** + * A {@link ResponseHandler} that returns the response body as a String + * for successful (2xx) responses. If the response code was >= 300, the response + * body is consumed and an {@link HttpResponseException} is thrown. + * + * If this is used with + * {@link org.apache.http.client.HttpClient#execute( + * org.apache.http.client.methods.HttpUriRequest, ResponseHandler), + * HttpClient may handle redirects (3xx responses) internally. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 677240 $ + * + * @since 4.0 + */ +public class BasicResponseHandler implements ResponseHandler<String> { + + /** + * Returns the response body as a String if the response was successful (a + * 2xx status code). If no response body exists, this returns null. If the + * response was unsuccessful (>= 300 status code), throws an + * {@link HttpResponseException}. + */ + public String handleResponse(final HttpResponse response) + throws HttpResponseException, IOException { + StatusLine statusLine = response.getStatusLine(); + if (statusLine.getStatusCode() >= 300) { + throw new HttpResponseException(statusLine.getStatusCode(), + statusLine.getReasonPhrase()); + } + + HttpEntity entity = response.getEntity(); + return entity == null ? null : EntityUtils.toString(entity); + } + +} diff --git a/src/org/apache/http/impl/client/ClientParamsStack.java b/src/org/apache/http/impl/client/ClientParamsStack.java new file mode 100644 index 0000000..a017e5d --- /dev/null +++ b/src/org/apache/http/impl/client/ClientParamsStack.java @@ -0,0 +1,282 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/ClientParamsStack.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.params.AbstractHttpParams; + + +/** + * Represents a stack of parameter collections. + * When retrieving a parameter, the stack is searched in a fixed order + * and the first match returned. Setting parameters via the stack is + * not supported. To minimize overhead, the stack has a fixed size and + * does not maintain an internal array. + * The supported stack entries, sorted by increasing priority, are: + * <ol> + * <li>Application parameters: + * expected to be the same for all clients used by an application. + * These provide "global", that is application-wide, defaults. + * </li> + * <li>Client parameters: + * specific to an instance of + * {@link org.apache.http.client.HttpClient HttpClient}. + * These provide client specific defaults. + * </li> + * <li>Request parameters: + * specific to a single request execution. + * For overriding client and global defaults. + * </li> + * <li>Override parameters: + * specific to an instance of + * {@link org.apache.http.client.HttpClient HttpClient}. + * These can be used to set parameters that cannot be overridden + * on a per-request basis. + * </li> + * </ol> + * Each stack entry may be <code>null</code>. That is preferable over + * an empty params collection, since it avoids searching the empty collection + * when looking up parameters. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * @version $Revision: 673450 $ + */ +public class ClientParamsStack extends AbstractHttpParams { + + private final Log log = LogFactory.getLog(getClass()); + + /** The application parameter collection, or <code>null</code>. */ + protected final HttpParams applicationParams; + + /** The client parameter collection, or <code>null</code>. */ + protected final HttpParams clientParams; + + /** The request parameter collection, or <code>null</code>. */ + protected final HttpParams requestParams; + + /** The override parameter collection, or <code>null</code>. */ + protected final HttpParams overrideParams; + + + /** + * Creates a new parameter stack from elements. + * The arguments will be stored as-is, there is no copying to + * prevent modification. + * + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(HttpParams aparams, HttpParams cparams, + HttpParams rparams, HttpParams oparams) { + applicationParams = aparams; + clientParams = cparams; + requestParams = rparams; + overrideParams = oparams; + } + + + /** + * Creates a copy of a parameter stack. + * The new stack will have the exact same entries as the argument stack. + * There is no copying of parameters. + * + * @param stack the stack to copy + */ + public ClientParamsStack(ClientParamsStack stack) { + this(stack.getApplicationParams(), + stack.getClientParams(), + stack.getRequestParams(), + stack.getOverrideParams()); + } + + + /** + * Creates a modified copy of a parameter stack. + * The new stack will contain the explicitly passed elements. + * For elements where the explicit argument is <code>null</code>, + * the corresponding element from the argument stack is used. + * There is no copying of parameters. + * + * @param stack the stack to modify + * @param aparams application parameters, or <code>null</code> + * @param cparams client parameters, or <code>null</code> + * @param rparams request parameters, or <code>null</code> + * @param oparams override parameters, or <code>null</code> + */ + public ClientParamsStack(ClientParamsStack stack, + HttpParams aparams, HttpParams cparams, + HttpParams rparams, HttpParams oparams) { + this((aparams != null) ? aparams : stack.getApplicationParams(), + (cparams != null) ? cparams : stack.getClientParams(), + (rparams != null) ? rparams : stack.getRequestParams(), + (oparams != null) ? oparams : stack.getOverrideParams()); + } + + + /** + * Obtains the application parameters of this stack. + * + * @return the application parameters, or <code>null</code> + */ + public final HttpParams getApplicationParams() { + return applicationParams; + } + + /** + * Obtains the client parameters of this stack. + * + * @return the client parameters, or <code>null</code> + */ + public final HttpParams getClientParams() { + return clientParams; + } + + /** + * Obtains the request parameters of this stack. + * + * @return the request parameters, or <code>null</code> + */ + public final HttpParams getRequestParams() { + return requestParams; + } + + /** + * Obtains the override parameters of this stack. + * + * @return the override parameters, or <code>null</code> + */ + public final HttpParams getOverrideParams() { + return overrideParams; + } + + + /** + * Obtains a parameter from this stack. + * See class comment for search order. + * + * @param name the name of the parameter to obtain + * + * @return the highest-priority value for that parameter, or + * <code>null</code> if it is not set anywhere in this stack + */ + public Object getParameter(String name) { + if (name == null) { + throw new IllegalArgumentException + ("Parameter name must not be null."); + } + + Object result = null; + + if (overrideParams != null) { + result = overrideParams.getParameter(name); + } + if ((result == null) && (requestParams != null)) { + result = requestParams.getParameter(name); + } + if ((result == null) && (clientParams != null)) { + result = clientParams.getParameter(name); + } + if ((result == null) && (applicationParams != null)) { + result = applicationParams.getParameter(name); + } + if (this.log.isDebugEnabled()) { + this.log.debug("'" + name + "': " + result); + } + + return result; + } + + /** + * Does <i>not</i> set a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * @param value ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public HttpParams setParameter(String name, Object value) + throws UnsupportedOperationException { + + throw new UnsupportedOperationException + ("Setting parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> remove a parameter. + * Parameter stacks are read-only. It is possible, though discouraged, + * to access and modify specific stack entries. + * Derived classes may change this behavior. + * + * @param name ignored + * + * @return nothing + * + * @throws UnsupportedOperationException always + */ + public boolean removeParameter(String name) { + throw new UnsupportedOperationException + ("Removing parameters in a stack is not supported."); + } + + + /** + * Does <i>not</i> copy parameters. + * Parameter stacks are lightweight objects, expected to be instantiated + * as needed and to be used only in a very specific context. On top of + * that, they are read-only. The typical copy operation to prevent + * accidental modification of parameters passed by the application to + * a framework object is therefore pointless and disabled. + * Create a new stack if you really need a copy. + * <br/> + * Derived classes may change this behavior. + * + * @return <code>this</code> parameter stack + */ + public HttpParams copy() { + return this; + } + + +} diff --git a/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java b/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java new file mode 100644 index 0000000..c7641d2 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultConnectionKeepAliveStrategy.java @@ -0,0 +1,76 @@ +/* + * $HeadURL: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.client; + +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.HttpResponse; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; + +/** + * Default implementation of a strategy deciding duration + * that a connection can remain idle. + * + * The default implementation looks solely at the 'Keep-Alive' + * header's timeout token. + * + * @author <a href="mailto:sberlin at gmail.com">Sam Berlin</a> + * + * @version $Revision: $ + * + * @since 4.0 + */ +public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy { + + public long getKeepAliveDuration(HttpResponse response, HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + HeaderElementIterator it = new BasicHeaderElementIterator( + response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String param = he.getName(); + String value = he.getValue(); + if (value != null && param.equalsIgnoreCase("timeout")) { + try { + return Long.parseLong(value) * 1000; + } catch(NumberFormatException ignore) { + } + } + } + return -1; + } + +} diff --git a/src/org/apache/http/impl/client/DefaultHttpClient.java b/src/org/apache/http/impl/client/DefaultHttpClient.java new file mode 100644 index 0000000..f0b694e --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultHttpClient.java @@ -0,0 +1,332 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpClient.java $ + * $Revision: 677250 $ + * $Date: 2008-07-16 04:45:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpVersion; +import org.apache.http.auth.AuthSchemeRegistry; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.CookieStore; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.params.AuthPolicy; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.CookiePolicy; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.protocol.RequestAddCookies; +import org.apache.http.client.protocol.RequestDefaultHeaders; +import org.apache.http.client.protocol.RequestProxyAuthentication; +import org.apache.http.client.protocol.RequestTargetAuthentication; +import org.apache.http.client.protocol.ResponseProcessCookies; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionManagerFactory; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +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.cookie.CookieSpecRegistry; +import org.apache.http.impl.DefaultConnectionReuseStrategy; +import org.apache.http.impl.auth.BasicSchemeFactory; +import org.apache.http.impl.auth.DigestSchemeFactory; +import org.apache.http.impl.conn.DefaultHttpRoutePlanner; +import org.apache.http.impl.conn.SingleClientConnManager; +import org.apache.http.impl.cookie.BestMatchSpecFactory; +import org.apache.http.impl.cookie.BrowserCompatSpecFactory; +import org.apache.http.impl.cookie.NetscapeDraftSpecFactory; +import org.apache.http.impl.cookie.RFC2109SpecFactory; +import org.apache.http.impl.cookie.RFC2965SpecFactory; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.BasicHttpProcessor; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestExecutor; +import org.apache.http.protocol.RequestConnControl; +import org.apache.http.protocol.RequestContent; +import org.apache.http.protocol.RequestExpectContinue; +import org.apache.http.protocol.RequestTargetHost; +import org.apache.http.protocol.RequestUserAgent; +import org.apache.http.util.VersionInfo; + + + +/** + * Default implementation of an HTTP client. + * <br/> + * This class replaces <code>HttpClient</code> in HttpClient 3. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 677250 $ + * + * @since 4.0 + */ +public class DefaultHttpClient extends AbstractHttpClient { + + + /** + * Creates a new HTTP client from parameters and a connection manager. + * + * @param params the parameters + * @param conman the connection manager + */ + public DefaultHttpClient( + final ClientConnectionManager conman, + final HttpParams params) { + super(conman, params); + } + + + public DefaultHttpClient(final HttpParams params) { + super(null, params); + } + + + public DefaultHttpClient() { + super(null, null); + } + + + @Override + protected HttpParams createHttpParams() { + HttpParams params = new BasicHttpParams(); + HttpProtocolParams.setVersion(params, + HttpVersion.HTTP_1_1); + HttpProtocolParams.setContentCharset(params, + HTTP.DEFAULT_CONTENT_CHARSET); + HttpProtocolParams.setUseExpectContinue(params, + true); + + // determine the release version from packaged version info + final VersionInfo vi = VersionInfo.loadVersionInfo + ("org.apache.http.client", getClass().getClassLoader()); + final String release = (vi != null) ? + vi.getRelease() : VersionInfo.UNAVAILABLE; + HttpProtocolParams.setUserAgent(params, + "Apache-HttpClient/" + release + " (java 1.4)"); + + return params; + } + + + @Override + protected HttpRequestExecutor createRequestExecutor() { + return new HttpRequestExecutor(); + } + + + @Override + protected ClientConnectionManager createClientConnectionManager() { + SchemeRegistry registry = new SchemeRegistry(); + registry.register( + new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + registry.register( + new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); + + ClientConnectionManager connManager = null; + HttpParams params = getParams(); + + ClientConnectionManagerFactory factory = null; + + // Try first getting the factory directly as an object. + factory = (ClientConnectionManagerFactory) params + .getParameter(ClientPNames.CONNECTION_MANAGER_FACTORY); + if (factory == null) { // then try getting its class name. + String className = (String) params.getParameter( + ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME); + if (className != null) { + try { + Class<?> clazz = Class.forName(className); + factory = (ClientConnectionManagerFactory) clazz.newInstance(); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException("Invalid class name: " + className); + } catch (IllegalAccessException ex) { + throw new IllegalAccessError(ex.getMessage()); + } catch (InstantiationException ex) { + throw new InstantiationError(ex.getMessage()); + } + } + } + + if(factory != null) { + connManager = factory.newInstance(params, registry); + } else { + connManager = new SingleClientConnManager(getParams(), registry); + } + + return connManager; + } + + + @Override + protected HttpContext createHttpContext() { + HttpContext context = new BasicHttpContext(); + context.setAttribute( + ClientContext.AUTHSCHEME_REGISTRY, + getAuthSchemes()); + context.setAttribute( + ClientContext.COOKIESPEC_REGISTRY, + getCookieSpecs()); + context.setAttribute( + ClientContext.COOKIE_STORE, + getCookieStore()); + context.setAttribute( + ClientContext.CREDS_PROVIDER, + getCredentialsProvider()); + return context; + } + + + @Override + protected ConnectionReuseStrategy createConnectionReuseStrategy() { + return new DefaultConnectionReuseStrategy(); + } + + @Override + protected ConnectionKeepAliveStrategy createConnectionKeepAliveStrategy() { + return new DefaultConnectionKeepAliveStrategy(); + } + + + @Override + protected AuthSchemeRegistry createAuthSchemeRegistry() { + AuthSchemeRegistry registry = new AuthSchemeRegistry(); + registry.register( + AuthPolicy.BASIC, + new BasicSchemeFactory()); + registry.register( + AuthPolicy.DIGEST, + new DigestSchemeFactory()); + return registry; + } + + + @Override + protected CookieSpecRegistry createCookieSpecRegistry() { + CookieSpecRegistry registry = new CookieSpecRegistry(); + registry.register( + CookiePolicy.BEST_MATCH, + new BestMatchSpecFactory()); + registry.register( + CookiePolicy.BROWSER_COMPATIBILITY, + new BrowserCompatSpecFactory()); + registry.register( + CookiePolicy.NETSCAPE, + new NetscapeDraftSpecFactory()); + registry.register( + CookiePolicy.RFC_2109, + new RFC2109SpecFactory()); + registry.register( + CookiePolicy.RFC_2965, + new RFC2965SpecFactory()); + return registry; + } + + + @Override + protected BasicHttpProcessor createHttpProcessor() { + BasicHttpProcessor httpproc = new BasicHttpProcessor(); + httpproc.addInterceptor(new RequestDefaultHeaders()); + // Required protocol interceptors + httpproc.addInterceptor(new RequestContent()); + httpproc.addInterceptor(new RequestTargetHost()); + // Recommended protocol interceptors + httpproc.addInterceptor(new RequestConnControl()); + httpproc.addInterceptor(new RequestUserAgent()); + httpproc.addInterceptor(new RequestExpectContinue()); + // HTTP state management interceptors + httpproc.addInterceptor(new RequestAddCookies()); + httpproc.addInterceptor(new ResponseProcessCookies()); + // HTTP authentication interceptors + httpproc.addInterceptor(new RequestTargetAuthentication()); + httpproc.addInterceptor(new RequestProxyAuthentication()); + return httpproc; + } + + + @Override + protected HttpRequestRetryHandler createHttpRequestRetryHandler() { + return new DefaultHttpRequestRetryHandler(); + } + + + @Override + protected RedirectHandler createRedirectHandler() { + return new DefaultRedirectHandler(); + } + + + @Override + protected AuthenticationHandler createTargetAuthenticationHandler() { + return new DefaultTargetAuthenticationHandler(); + } + + + @Override + protected AuthenticationHandler createProxyAuthenticationHandler() { + return new DefaultProxyAuthenticationHandler(); + } + + + @Override + protected CookieStore createCookieStore() { + return new BasicCookieStore(); + } + + + @Override + protected CredentialsProvider createCredentialsProvider() { + return new BasicCredentialsProvider(); + } + + + @Override + protected HttpRoutePlanner createHttpRoutePlanner() { + return new DefaultHttpRoutePlanner + (getConnectionManager().getSchemeRegistry()); + } + + + @Override + protected UserTokenHandler createUserTokenHandler() { + return new DefaultUserTokenHandler(); + } + +} // class DefaultHttpClient diff --git a/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java b/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java new file mode 100644 index 0000000..7f66990 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java @@ -0,0 +1,134 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java $ + * $Revision: 652726 $ + * $Date: 2008-05-01 18:16:51 -0700 (Thu, 01 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLHandshakeException; + +import org.apache.http.NoHttpResponseException; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + +/** + * The default {@link HttpRequestRetryHandler} used by request executors. + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler { + + /** the number of times a method will be retried */ + private final int retryCount; + + /** Whether or not methods that have successfully sent their request will be retried */ + private final boolean requestSentRetryEnabled; + + /** + * Default constructor + */ + public DefaultHttpRequestRetryHandler(int retryCount, boolean requestSentRetryEnabled) { + super(); + this.retryCount = retryCount; + this.requestSentRetryEnabled = requestSentRetryEnabled; + } + + /** + * Default constructor + */ + public DefaultHttpRequestRetryHandler() { + this(3, false); + } + /** + * Used <code>retryCount</code> and <code>requestSentRetryEnabled</code> to determine + * if the given method should be retried. + */ + public boolean retryRequest( + final IOException exception, + int executionCount, + final HttpContext context) { + if (exception == null) { + throw new IllegalArgumentException("Exception parameter may not be null"); + } + if (context == null) { + throw new IllegalArgumentException("HTTP context may not be null"); + } + if (executionCount > this.retryCount) { + // Do not retry if over max retry count + return false; + } + if (exception instanceof NoHttpResponseException) { + // Retry if the server dropped connection on us + return true; + } + if (exception instanceof InterruptedIOException) { + // Timeout + return false; + } + if (exception instanceof UnknownHostException) { + // Unknown host + return false; + } + if (exception instanceof SSLHandshakeException) { + // SSL handshake exception + return false; + } + Boolean b = (Boolean) + context.getAttribute(ExecutionContext.HTTP_REQ_SENT); + boolean sent = (b != null && b.booleanValue()); + if (!sent || this.requestSentRetryEnabled) { + // Retry if the request has not been sent fully or + // if it's OK to retry methods that have been sent + return true; + } + // otherwise do not retry + return false; + } + + /** + * @return <code>true</code> if this handler will retry methods that have + * successfully sent their request, <code>false</code> otherwise + */ + public boolean isRequestSentRetryEnabled() { + return requestSentRetryEnabled; + } + + /** + * @return the maximum number of times a method will be retried + */ + public int getRetryCount() { + return retryCount; + } +} diff --git a/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java b/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java new file mode 100644 index 0000000..c188da8 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultProxyAuthenticationHandler.java $ + * $Revision: 603615 $ + * $Date: 2007-12-12 06:03:21 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultProxyAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultProxyAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + Header[] headers = response.getHeaders(AUTH.PROXY_AUTH); + return parseChallenges(headers); + } + +} diff --git a/src/org/apache/http/impl/client/DefaultRedirectHandler.java b/src/org/apache/http/impl/client/DefaultRedirectHandler.java new file mode 100644 index 0000000..0811b28 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultRedirectHandler.java @@ -0,0 +1,183 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultRedirectHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.ProtocolException; +import org.apache.http.client.CircularRedirectException; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.ExecutionContext; + + +/** + * Default implementation of {@link RedirectHandler}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class DefaultRedirectHandler implements RedirectHandler { + + private final Log log = LogFactory.getLog(getClass()); + + private static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; + + public DefaultRedirectHandler() { + super(); + } + + public boolean isRedirectRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int statusCode = response.getStatusLine().getStatusCode(); + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_SEE_OTHER: + case HttpStatus.SC_TEMPORARY_REDIRECT: + return true; + default: + return false; + } //end of switch + } + + public URI getLocationURI( + final HttpResponse response, + final HttpContext context) throws ProtocolException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + //get the location header to find out where to redirect to + Header locationHeader = response.getFirstHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + throw new ProtocolException( + "Received redirect response " + response.getStatusLine() + + " but no location header"); + } + String location = locationHeader.getValue(); + if (this.log.isDebugEnabled()) { + this.log.debug("Redirect requested to location '" + location + "'"); + } + + URI uri; + try { + uri = new URI(location); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid redirect URI: " + location, ex); + } + + HttpParams params = response.getParams(); + // rfc2616 demands the location value be a complete URI + // Location = "Location" ":" absoluteURI + if (!uri.isAbsolute()) { + if (params.isParameterTrue(ClientPNames.REJECT_RELATIVE_REDIRECT)) { + throw new ProtocolException("Relative redirect location '" + + uri + "' not allowed"); + } + // Adjust location URI + HttpHost target = (HttpHost) context.getAttribute( + ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + throw new IllegalStateException("Target host not available " + + "in the HTTP context"); + } + + HttpRequest request = (HttpRequest) context.getAttribute( + ExecutionContext.HTTP_REQUEST); + + try { + URI requestURI = new URI(request.getRequestLine().getUri()); + URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, true); + uri = URIUtils.resolve(absoluteRequestURI, uri); + } catch (URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } + + if (params.isParameterFalse(ClientPNames.ALLOW_CIRCULAR_REDIRECTS)) { + + RedirectLocations redirectLocations = (RedirectLocations) context.getAttribute( + REDIRECT_LOCATIONS); + + if (redirectLocations == null) { + redirectLocations = new RedirectLocations(); + context.setAttribute(REDIRECT_LOCATIONS, redirectLocations); + } + + URI redirectURI; + if (uri.getFragment() != null) { + try { + HttpHost target = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + redirectURI = URIUtils.rewriteURI(uri, target, true); + } catch (URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + } else { + redirectURI = uri; + } + + if (redirectLocations.contains(redirectURI)) { + throw new CircularRedirectException("Circular redirect to '" + + redirectURI + "'"); + } else { + redirectLocations.add(redirectURI); + } + } + + return uri; + } + +} diff --git a/src/org/apache/http/impl/client/DefaultRequestDirector.java b/src/org/apache/http/impl/client/DefaultRequestDirector.java new file mode 100644 index 0000000..511f8a0 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultRequestDirector.java @@ -0,0 +1,1088 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultRequestDirector.java $ + * $Revision: 676023 $ + * $Date: 2008-07-11 09:40:56 -0700 (Fri, 11 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolException; +import org.apache.http.ProtocolVersion; +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.client.AuthenticationHandler; +import org.apache.http.client.RequestDirector; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.NonRepeatableRequestException; +import org.apache.http.client.RedirectException; +import org.apache.http.client.RedirectHandler; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.methods.AbortableHttpRequest; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.params.ClientPNames; +import org.apache.http.client.params.HttpClientParams; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.conn.BasicManagedEntity; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.params.ConnManagerParams; +import org.apache.http.conn.routing.BasicRouteDirector; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRouteDirector; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.message.BasicHttpRequest; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpProcessor; +import org.apache.http.protocol.HttpRequestExecutor; + +/** + * Default implementation of {@link RequestDirector}. + * <br/> + * This class replaces the <code>HttpMethodDirector</code> in HttpClient 3. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 676023 $ + * + * @since 4.0 + */ +public class DefaultRequestDirector implements RequestDirector { + + private final Log log = LogFactory.getLog(getClass()); + + /** The connection manager. */ + protected final ClientConnectionManager connManager; + + /** The route planner. */ + protected final HttpRoutePlanner routePlanner; + + /** The connection re-use strategy. */ + protected final ConnectionReuseStrategy reuseStrategy; + + /** The keep-alive duration strategy. */ + protected final ConnectionKeepAliveStrategy keepAliveStrategy; + + /** The request executor. */ + protected final HttpRequestExecutor requestExec; + + /** The HTTP protocol processor. */ + protected final HttpProcessor httpProcessor; + + /** The request retry handler. */ + protected final HttpRequestRetryHandler retryHandler; + + /** The redirect handler. */ + protected final RedirectHandler redirectHandler; + + /** The target authentication handler. */ + private final AuthenticationHandler targetAuthHandler; + + /** The proxy authentication handler. */ + private final AuthenticationHandler proxyAuthHandler; + + /** The user token handler. */ + private final UserTokenHandler userTokenHandler; + + /** The HTTP parameters. */ + protected final HttpParams params; + + /** The currently allocated connection. */ + protected ManagedClientConnection managedConn; + + private int redirectCount; + + private int maxRedirects; + + private final AuthState targetAuthState; + + private final AuthState proxyAuthState; + + public DefaultRequestDirector( + final HttpRequestExecutor requestExec, + final ClientConnectionManager conman, + final ConnectionReuseStrategy reustrat, + final ConnectionKeepAliveStrategy kastrat, + final HttpRoutePlanner rouplan, + final HttpProcessor httpProcessor, + final HttpRequestRetryHandler retryHandler, + final RedirectHandler redirectHandler, + final AuthenticationHandler targetAuthHandler, + final AuthenticationHandler proxyAuthHandler, + final UserTokenHandler userTokenHandler, + final HttpParams params) { + + if (requestExec == null) { + throw new IllegalArgumentException + ("Request executor may not be null."); + } + if (conman == null) { + throw new IllegalArgumentException + ("Client connection manager may not be null."); + } + if (reustrat == null) { + throw new IllegalArgumentException + ("Connection reuse strategy may not be null."); + } + if (kastrat == null) { + throw new IllegalArgumentException + ("Connection keep alive strategy may not be null."); + } + if (rouplan == null) { + throw new IllegalArgumentException + ("Route planner may not be null."); + } + if (httpProcessor == null) { + throw new IllegalArgumentException + ("HTTP protocol processor may not be null."); + } + if (retryHandler == null) { + throw new IllegalArgumentException + ("HTTP request retry handler may not be null."); + } + if (redirectHandler == null) { + throw new IllegalArgumentException + ("Redirect handler may not be null."); + } + if (targetAuthHandler == null) { + throw new IllegalArgumentException + ("Target authentication handler may not be null."); + } + if (proxyAuthHandler == null) { + throw new IllegalArgumentException + ("Proxy authentication handler may not be null."); + } + if (userTokenHandler == null) { + throw new IllegalArgumentException + ("User token handler may not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("HTTP parameters may not be null"); + } + this.requestExec = requestExec; + this.connManager = conman; + this.reuseStrategy = reustrat; + this.keepAliveStrategy = kastrat; + this.routePlanner = rouplan; + this.httpProcessor = httpProcessor; + this.retryHandler = retryHandler; + this.redirectHandler = redirectHandler; + this.targetAuthHandler = targetAuthHandler; + this.proxyAuthHandler = proxyAuthHandler; + this.userTokenHandler = userTokenHandler; + this.params = params; + + this.managedConn = null; + + this.redirectCount = 0; + this.maxRedirects = this.params.getIntParameter(ClientPNames.MAX_REDIRECTS, 100); + this.targetAuthState = new AuthState(); + this.proxyAuthState = new AuthState(); + } // constructor + + + private RequestWrapper wrapRequest( + final HttpRequest request) throws ProtocolException { + if (request instanceof HttpEntityEnclosingRequest) { + return new EntityEnclosingRequestWrapper( + (HttpEntityEnclosingRequest) request); + } else { + return new RequestWrapper( + request); + } + } + + + protected void rewriteRequestURI( + final RequestWrapper request, + final HttpRoute route) throws ProtocolException { + try { + + URI uri = request.getURI(); + if (route.getProxyHost() != null && !route.isTunnelled()) { + // Make sure the request URI is absolute + if (!uri.isAbsolute()) { + HttpHost target = route.getTargetHost(); + uri = URIUtils.rewriteURI(uri, target); + request.setURI(uri); + } + } else { + // Make sure the request URI is relative + if (uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, null); + request.setURI(uri); + } + } + + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid URI: " + + request.getRequestLine().getUri(), ex); + } + } + + + // non-javadoc, see interface ClientRequestDirector + public HttpResponse execute(HttpHost target, HttpRequest request, + HttpContext context) + throws HttpException, IOException { + + HttpRequest orig = request; + RequestWrapper origWrapper = wrapRequest(orig); + origWrapper.setParams(params); + HttpRoute origRoute = determineRoute(target, origWrapper, context); + + RoutedRequest roureq = new RoutedRequest(origWrapper, origRoute); + + long timeout = ConnManagerParams.getTimeout(params); + + int execCount = 0; + + boolean reuse = false; + HttpResponse response = null; + boolean done = false; + try { + while (!done) { + // In this loop, the RoutedRequest may be replaced by a + // followup request and route. The request and route passed + // in the method arguments will be replaced. The original + // request is still available in 'orig'. + + RequestWrapper wrapper = roureq.getRequest(); + HttpRoute route = roureq.getRoute(); + + // See if we have a user token bound to the execution context + Object userToken = context.getAttribute(ClientContext.USER_TOKEN); + + // Allocate connection if needed + if (managedConn == null) { + ClientConnectionRequest connRequest = connManager.requestConnection( + route, userToken); + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setConnectionRequest(connRequest); + } + + try { + managedConn = connRequest.getConnection(timeout, TimeUnit.MILLISECONDS); + } catch(InterruptedException interrupted) { + InterruptedIOException iox = new InterruptedIOException(); + iox.initCause(interrupted); + throw iox; + } + + if (HttpConnectionParams.isStaleCheckingEnabled(params)) { + // validate connection + this.log.debug("Stale connection check"); + if (managedConn.isStale()) { + this.log.debug("Stale connection detected"); + managedConn.close(); + } + } + } + + if (orig instanceof AbortableHttpRequest) { + ((AbortableHttpRequest) orig).setReleaseTrigger(managedConn); + } + + // Reopen connection if needed + if (!managedConn.isOpen()) { + managedConn.open(route, context, params); + } + + try { + establishRoute(route, context); + } catch (TunnelRefusedException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage()); + } + response = ex.getResponse(); + break; + } + + // Reset headers on the request wrapper + wrapper.resetHeaders(); + + // Re-write request URI if needed + rewriteRequestURI(wrapper, route); + + // Use virtual host if set + target = (HttpHost) wrapper.getParams().getParameter( + ClientPNames.VIRTUAL_HOST); + + if (target == null) { + target = route.getTargetHost(); + } + + HttpHost proxy = route.getProxyHost(); + + // Populate the execution context + context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, + target); + context.setAttribute(ExecutionContext.HTTP_PROXY_HOST, + proxy); + context.setAttribute(ExecutionContext.HTTP_CONNECTION, + managedConn); + context.setAttribute(ClientContext.TARGET_AUTH_STATE, + targetAuthState); + context.setAttribute(ClientContext.PROXY_AUTH_STATE, + proxyAuthState); + + // Run request protocol interceptors + requestExec.preProcess(wrapper, httpProcessor, context); + + context.setAttribute(ExecutionContext.HTTP_REQUEST, + wrapper); + + boolean retrying = true; + while (retrying) { + // Increment total exec count (with redirects) + execCount++; + // Increment exec count for this particular request + wrapper.incrementExecCount(); + if (wrapper.getExecCount() > 1 && !wrapper.isRepeatable()) { + throw new NonRepeatableRequestException("Cannot retry request " + + "with a non-repeatable request entity"); + } + + try { + if (this.log.isDebugEnabled()) { + this.log.debug("Attempt " + execCount + " to execute request"); + } + response = requestExec.execute(wrapper, managedConn, context); + retrying = false; + + } catch (IOException ex) { + this.log.debug("Closing the connection."); + managedConn.close(); + if (retryHandler.retryRequest(ex, execCount, context)) { + if (this.log.isInfoEnabled()) { + this.log.info("I/O exception ("+ ex.getClass().getName() + + ") caught when processing request: " + + ex.getMessage()); + } + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + this.log.info("Retrying request"); + } else { + throw ex; + } + + // If we have a direct route to the target host + // just re-open connection and re-try the request + if (route.getHopCount() == 1) { + this.log.debug("Reopening the direct connection."); + managedConn.open(route, context, params); + } else { + // otherwise give up + retrying = false; + } + + } + + } + + // Run response protocol interceptors + response.setParams(params); + requestExec.postProcess(response, httpProcessor, context); + + + // The connection is in or can be brought to a re-usable state. + reuse = reuseStrategy.keepAlive(response, context); + if(reuse) { + // Set the idle duration of this connection + long duration = keepAliveStrategy.getKeepAliveDuration(response, context); + managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS); + } + + RoutedRequest followup = handleResponse(roureq, response, context); + if (followup == null) { + done = true; + } else { + if (reuse) { + this.log.debug("Connection kept alive"); + // Make sure the response body is fully consumed, if present + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.consumeContent(); + } + // entity consumed above is not an auto-release entity, + // need to mark the connection re-usable explicitly + managedConn.markReusable(); + } else { + managedConn.close(); + } + // check if we can use the same connection for the followup + if (!followup.getRoute().equals(roureq.getRoute())) { + releaseConnection(); + } + roureq = followup; + } + + userToken = this.userTokenHandler.getUserToken(context); + context.setAttribute(ClientContext.USER_TOKEN, userToken); + if (managedConn != null) { + managedConn.setState(userToken); + } + } // while not done + + + // check for entity, release connection if possible + if ((response == null) || (response.getEntity() == null) || + !response.getEntity().isStreaming()) { + // connection not needed and (assumed to be) in re-usable state + if (reuse) + managedConn.markReusable(); + releaseConnection(); + } else { + // install an auto-release entity + HttpEntity entity = response.getEntity(); + entity = new BasicManagedEntity(entity, managedConn, reuse); + response.setEntity(entity); + } + + return response; + + } catch (HttpException ex) { + abortConnection(); + throw ex; + } catch (IOException ex) { + abortConnection(); + throw ex; + } catch (RuntimeException ex) { + abortConnection(); + throw ex; + } + } // execute + + /** + * Returns the connection back to the connection manager + * and prepares for retrieving a new connection during + * the next request. + */ + protected void releaseConnection() { + // Release the connection through the ManagedConnection instead of the + // ConnectionManager directly. This lets the connection control how + // it is released. + try { + managedConn.releaseConnection(); + } catch(IOException ignored) { + this.log.debug("IOException releasing connection", ignored); + } + managedConn = null; + } + + /** + * Determines the route for a request. + * Called by {@link #execute} + * to determine the route for either the original or a followup request. + * + * @param target the target host for the request. + * Implementations may accept <code>null</code> + * if they can still determine a route, for example + * to a default target or by inspecting the request. + * @param request the request to execute + * @param context the context to use for the execution, + * never <code>null</code> + * + * @return the route the request should take + * + * @throws HttpException in case of a problem + */ + protected HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (target == null) { + target = (HttpHost) request.getParams().getParameter( + ClientPNames.DEFAULT_HOST); + } + if (target == null) { + throw new IllegalStateException + ("Target host must not be null, or set in parameters."); + } + + return this.routePlanner.determineRoute(target, request, context); + } + + + /** + * Establishes the target route. + * + * @param route the route to establish + * @param context the context for the request execution + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected void establishRoute(HttpRoute route, HttpContext context) + throws HttpException, IOException { + + //@@@ how to handle CONNECT requests for tunnelling? + //@@@ refuse to send external CONNECT via director? special handling? + + //@@@ should the request parameters already be used below? + //@@@ probably yes, but they're not linked yet + //@@@ will linking above cause problems with linking in reqExec? + //@@@ probably not, because the parent is replaced + //@@@ just make sure we don't link parameters to themselves + + HttpRouteDirector rowdy = new BasicRouteDirector(); + int step; + do { + HttpRoute fact = managedConn.getRoute(); + step = rowdy.nextStep(route, fact); + + switch (step) { + + case HttpRouteDirector.CONNECT_TARGET: + case HttpRouteDirector.CONNECT_PROXY: + managedConn.open(route, context, this.params); + break; + + case HttpRouteDirector.TUNNEL_TARGET: { + boolean secure = createTunnelToTarget(route, context); + this.log.debug("Tunnel to target created."); + managedConn.tunnelTarget(secure, this.params); + } break; + + case HttpRouteDirector.TUNNEL_PROXY: { + // The most simple example for this case is a proxy chain + // of two proxies, where P1 must be tunnelled to P2. + // route: Source -> P1 -> P2 -> Target (3 hops) + // fact: Source -> P1 -> Target (2 hops) + final int hop = fact.getHopCount()-1; // the hop to establish + boolean secure = createTunnelToProxy(route, hop, context); + this.log.debug("Tunnel to proxy created."); + managedConn.tunnelProxy(route.getHopTarget(hop), + secure, this.params); + } break; + + + case HttpRouteDirector.LAYER_PROTOCOL: + managedConn.layerProtocol(context, this.params); + break; + + case HttpRouteDirector.UNREACHABLE: + throw new IllegalStateException + ("Unable to establish route." + + "\nplanned = " + route + + "\ncurrent = " + fact); + + case HttpRouteDirector.COMPLETE: + // do nothing + break; + + default: + throw new IllegalStateException + ("Unknown step indicator "+step+" from RouteDirector."); + } // switch + + } while (step > HttpRouteDirector.COMPLETE); + + } // establishConnection + + + /** + * Creates a tunnel to the target server. + * The connection must be established to the (last) proxy. + * A CONNECT request for tunnelling through the proxy will + * be created and sent, the response received and checked. + * This method does <i>not</i> update the connection with + * information about the tunnel, that is left to the caller. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return <code>true</code> if the tunnelled route is secure, + * <code>false</code> otherwise. + * The implementation here always returns <code>false</code>, + * but derived classes may override. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToTarget(HttpRoute route, + HttpContext context) + throws HttpException, IOException { + + HttpHost proxy = route.getProxyHost(); + HttpHost target = route.getTargetHost(); + HttpResponse response = null; + + boolean done = false; + while (!done) { + + done = true; + + if (!this.managedConn.isOpen()) { + this.managedConn.open(route, context, this.params); + } + + HttpRequest connect = createConnectRequest(route, context); + + String agent = HttpProtocolParams.getUserAgent(params); + if (agent != null) { + connect.addHeader(HTTP.USER_AGENT, agent); + } + connect.addHeader(HTTP.TARGET_HOST, target.toHostString()); + + AuthScheme authScheme = this.proxyAuthState.getAuthScheme(); + AuthScope authScope = this.proxyAuthState.getAuthScope(); + Credentials creds = this.proxyAuthState.getCredentials(); + if (creds != null) { + if (authScope != null || !authScheme.isConnectionBased()) { + try { + connect.addHeader(authScheme.authenticate(creds, connect)); + } catch (AuthenticationException ex) { + if (this.log.isErrorEnabled()) { + this.log.error("Proxy authentication error: " + ex.getMessage()); + } + } + } + } + + response = requestExec.execute(connect, this.managedConn, context); + + int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + + CredentialsProvider credsProvider = (CredentialsProvider) + context.getAttribute(ClientContext.CREDS_PROVIDER); + + if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { + if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { + + this.log.debug("Proxy requested authentication"); + Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( + response, context); + try { + processChallenges( + challenges, this.proxyAuthState, this.proxyAuthHandler, + response, context); + } catch (AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication error: " + ex.getMessage()); + break; + } + } + updateAuthState(this.proxyAuthState, proxy, credsProvider); + + if (this.proxyAuthState.getCredentials() != null) { + done = false; + + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + this.log.debug("Connection kept alive"); + // Consume response content + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.consumeContent(); + } + } else { + this.managedConn.close(); + } + + } + + } else { + // Reset proxy auth scope + this.proxyAuthState.setAuthScope(null); + } + } + } + + int status = response.getStatusLine().getStatusCode(); + + if (status > 299) { + + // Buffer response content + HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + this.managedConn.close(); + throw new TunnelRefusedException("CONNECT refused by proxy: " + + response.getStatusLine(), response); + } + + this.managedConn.markReusable(); + + // How to decide on security of the tunnelled connection? + // The socket factory knows only about the segment to the proxy. + // Even if that is secure, the hop to the target may be insecure. + // Leave it to derived classes, consider insecure by default here. + return false; + + } // createTunnelToTarget + + + + /** + * Creates a tunnel to an intermediate proxy. + * This method is <i>not</i> implemented in this class. + * It just throws an exception here. + * + * @param route the route to establish + * @param hop the hop in the route to establish now. + * <code>route.getHopTarget(hop)</code> + * will return the proxy to tunnel to. + * @param context the context for request execution + * + * @return <code>true</code> if the partially tunnelled connection + * is secure, <code>false</code> otherwise. + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected boolean createTunnelToProxy(HttpRoute route, int hop, + HttpContext context) + throws HttpException, IOException { + + // Have a look at createTunnelToTarget and replicate the parts + // you need in a custom derived class. If your proxies don't require + // authentication, it is not too hard. But for the stock version of + // HttpClient, we cannot make such simplifying assumptions and would + // have to include proxy authentication code. The HttpComponents team + // is currently not in a position to support rarely used code of this + // complexity. Feel free to submit patches that refactor the code in + // createTunnelToTarget to facilitate re-use for proxy tunnelling. + + throw new UnsupportedOperationException + ("Proxy chains are not supported."); + } + + + + /** + * Creates the CONNECT request for tunnelling. + * Called by {@link #createTunnelToTarget createTunnelToTarget}. + * + * @param route the route to establish + * @param context the context for request execution + * + * @return the CONNECT request for tunnelling + */ + protected HttpRequest createConnectRequest(HttpRoute route, + HttpContext context) { + // see RFC 2817, section 5.2 and + // INTERNET-DRAFT: Tunneling TCP based protocols through + // Web proxy servers + + HttpHost target = route.getTargetHost(); + + String host = target.getHostName(); + int port = target.getPort(); + if (port < 0) { + Scheme scheme = connManager.getSchemeRegistry(). + getScheme(target.getSchemeName()); + port = scheme.getDefaultPort(); + } + + StringBuilder buffer = new StringBuilder(host.length() + 6); + buffer.append(host); + buffer.append(':'); + buffer.append(Integer.toString(port)); + + String authority = buffer.toString(); + ProtocolVersion ver = HttpProtocolParams.getVersion(params); + HttpRequest req = new BasicHttpRequest + ("CONNECT", authority, ver); + + return req; + } + + + /** + * Analyzes a response to check need for a followup. + * + * @param roureq the request and route. + * @param response the response to analayze + * @param context the context used for the current request execution + * + * @return the followup request and route if there is a followup, or + * <code>null</code> if the response should be returned as is + * + * @throws HttpException in case of a problem + * @throws IOException in case of an IO problem + */ + protected RoutedRequest handleResponse(RoutedRequest roureq, + HttpResponse response, + HttpContext context) + throws HttpException, IOException { + + HttpRoute route = roureq.getRoute(); + HttpHost proxy = route.getProxyHost(); + RequestWrapper request = roureq.getRequest(); + + HttpParams params = request.getParams(); + if (HttpClientParams.isRedirecting(params) && + this.redirectHandler.isRedirectRequested(response, context)) { + + if (redirectCount >= maxRedirects) { + throw new RedirectException("Maximum redirects (" + + maxRedirects + ") exceeded"); + } + redirectCount++; + + URI uri = this.redirectHandler.getLocationURI(response, context); + + HttpHost newTarget = new HttpHost( + uri.getHost(), + uri.getPort(), + uri.getScheme()); + + HttpGet redirect = new HttpGet(uri); + + HttpRequest orig = request.getOriginal(); + redirect.setHeaders(orig.getAllHeaders()); + + RequestWrapper wrapper = new RequestWrapper(redirect); + wrapper.setParams(params); + + HttpRoute newRoute = determineRoute(newTarget, wrapper, context); + RoutedRequest newRequest = new RoutedRequest(wrapper, newRoute); + + if (this.log.isDebugEnabled()) { + this.log.debug("Redirecting to '" + uri + "' via " + newRoute); + } + + return newRequest; + } + + CredentialsProvider credsProvider = (CredentialsProvider) + context.getAttribute(ClientContext.CREDS_PROVIDER); + + if (credsProvider != null && HttpClientParams.isAuthenticating(params)) { + + if (this.targetAuthHandler.isAuthenticationRequested(response, context)) { + + HttpHost target = (HttpHost) + context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); + if (target == null) { + target = route.getTargetHost(); + } + + this.log.debug("Target requested authentication"); + Map<String, Header> challenges = this.targetAuthHandler.getChallenges( + response, context); + try { + processChallenges(challenges, + this.targetAuthState, this.targetAuthHandler, + response, context); + } catch (AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication error: " + ex.getMessage()); + return null; + } + } + updateAuthState(this.targetAuthState, target, credsProvider); + + if (this.targetAuthState.getCredentials() != null) { + // Re-try the same request via the same route + return roureq; + } else { + return null; + } + } else { + // Reset target auth scope + this.targetAuthState.setAuthScope(null); + } + + if (this.proxyAuthHandler.isAuthenticationRequested(response, context)) { + + this.log.debug("Proxy requested authentication"); + Map<String, Header> challenges = this.proxyAuthHandler.getChallenges( + response, context); + try { + processChallenges(challenges, + this.proxyAuthState, this.proxyAuthHandler, + response, context); + } catch (AuthenticationException ex) { + if (this.log.isWarnEnabled()) { + this.log.warn("Authentication error: " + ex.getMessage()); + return null; + } + } + updateAuthState(this.proxyAuthState, proxy, credsProvider); + + if (this.proxyAuthState.getCredentials() != null) { + // Re-try the same request via the same route + return roureq; + } else { + return null; + } + } else { + // Reset proxy auth scope + this.proxyAuthState.setAuthScope(null); + } + } + return null; + } // handleResponse + + + /** + * Shuts down the connection. + * This method is called from a <code>catch</code> block in + * {@link #execute execute} during exception handling. + */ + private void abortConnection() { + ManagedClientConnection mcc = managedConn; + if (mcc != null) { + // we got here as the result of an exception + // no response will be returned, release the connection + managedConn = null; + try { + mcc.abortConnection(); + } catch (IOException ex) { + if (this.log.isDebugEnabled()) { + this.log.debug(ex.getMessage(), ex); + } + } + // ensure the connection manager properly releases this connection + try { + mcc.releaseConnection(); + } catch(IOException ignored) { + this.log.debug("Error releasing connection", ignored); + } + } + } // abortConnection + + + private void processChallenges( + final Map<String, Header> challenges, + final AuthState authState, + final AuthenticationHandler authHandler, + final HttpResponse response, + final HttpContext context) + throws MalformedChallengeException, AuthenticationException { + + AuthScheme authScheme = authState.getAuthScheme(); + if (authScheme == null) { + // Authentication not attempted before + authScheme = authHandler.selectScheme(challenges, response, context); + authState.setAuthScheme(authScheme); + } + String id = authScheme.getSchemeName(); + + Header challenge = challenges.get(id.toLowerCase(Locale.ENGLISH)); + if (challenge == null) { + throw new AuthenticationException(id + + " authorization challenge expected, but not found"); + } + authScheme.processChallenge(challenge); + this.log.debug("Authorization challenge processed"); + } + + + private void updateAuthState( + final AuthState authState, + final HttpHost host, + final CredentialsProvider credsProvider) { + + if (!authState.isValid()) { + return; + } + + String hostname = host.getHostName(); + int port = host.getPort(); + if (port < 0) { + Scheme scheme = connManager.getSchemeRegistry().getScheme(host); + port = scheme.getDefaultPort(); + } + + AuthScheme authScheme = authState.getAuthScheme(); + AuthScope authScope = new AuthScope( + hostname, + port, + authScheme.getRealm(), + authScheme.getSchemeName()); + + if (this.log.isDebugEnabled()) { + this.log.debug("Authentication scope: " + authScope); + } + Credentials creds = authState.getCredentials(); + if (creds == null) { + creds = credsProvider.getCredentials(authScope); + if (this.log.isDebugEnabled()) { + if (creds != null) { + this.log.debug("Found credentials"); + } else { + this.log.debug("Credentials not found"); + } + } + } else { + if (authScheme.isComplete()) { + this.log.debug("Authentication failed"); + creds = null; + } + } + authState.setAuthScope(authScope); + authState.setCredentials(creds); + } + +} // class DefaultClientRequestDirector diff --git a/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java b/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java new file mode 100644 index 0000000..5794549 --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java @@ -0,0 +1,72 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultTargetAuthenticationHandler.java $ + * $Revision: 603615 $ + * $Date: 2007-12-12 06:03:21 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.protocol.HttpContext; + +/** + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class DefaultTargetAuthenticationHandler extends AbstractAuthenticationHandler { + + public DefaultTargetAuthenticationHandler() { + super(); + } + + public boolean isAuthenticationRequested( + final HttpResponse response, + final HttpContext context) { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + int status = response.getStatusLine().getStatusCode(); + return status == HttpStatus.SC_UNAUTHORIZED; + } + + public Map<String, Header> getChallenges( + final HttpResponse response, + final HttpContext context) throws MalformedChallengeException { + if (response == null) { + throw new IllegalArgumentException("HTTP response may not be null"); + } + Header[] headers = response.getHeaders(AUTH.WWW_AUTH); + return parseChallenges(headers); + } + +} diff --git a/src/org/apache/http/impl/client/DefaultUserTokenHandler.java b/src/org/apache/http/impl/client/DefaultUserTokenHandler.java new file mode 100644 index 0000000..c8a409f --- /dev/null +++ b/src/org/apache/http/impl/client/DefaultUserTokenHandler.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/DefaultUserTokenHandler.java $ + * $Revision: 659971 $ + * $Date: 2008-05-25 05:01:22 -0700 (Sun, 25 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.security.Principal; + +import javax.net.ssl.SSLSession; + +import org.apache.http.auth.AuthScheme; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.client.UserTokenHandler; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.protocol.ExecutionContext; +import org.apache.http.protocol.HttpContext; + +public class DefaultUserTokenHandler implements UserTokenHandler { + + public Object getUserToken(final HttpContext context) { + + Principal userPrincipal = null; + + AuthState targetAuthState = (AuthState) context.getAttribute( + ClientContext.TARGET_AUTH_STATE); + if (targetAuthState != null) { + userPrincipal = getAuthPrincipal(targetAuthState); + if (userPrincipal == null) { + AuthState proxyAuthState = (AuthState) context.getAttribute( + ClientContext.PROXY_AUTH_STATE); + userPrincipal = getAuthPrincipal(proxyAuthState); + } + } + + if (userPrincipal == null) { + ManagedClientConnection conn = (ManagedClientConnection) context.getAttribute( + ExecutionContext.HTTP_CONNECTION); + if (conn.isOpen()) { + SSLSession sslsession = conn.getSSLSession(); + if (sslsession != null) { + userPrincipal = sslsession.getLocalPrincipal(); + } + } + } + + return userPrincipal; + } + + private static Principal getAuthPrincipal(final AuthState authState) { + AuthScheme scheme = authState.getAuthScheme(); + if (scheme != null && scheme.isComplete() && scheme.isConnectionBased()) { + Credentials creds = authState.getCredentials(); + if (creds != null) { + return creds.getUserPrincipal(); + } + } + return null; + } + +} diff --git a/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java b/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java new file mode 100644 index 0000000..05098cf --- /dev/null +++ b/src/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/EntityEnclosingRequestWrapper.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.ProtocolException; +import org.apache.http.protocol.HTTP; + +/** + * A wrapper class for {@link HttpEntityEnclosingRequest}s that can + * be used to change properties of the current request without + * modifying the original object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public class EntityEnclosingRequestWrapper extends RequestWrapper + implements HttpEntityEnclosingRequest { + + private HttpEntity entity; + + public EntityEnclosingRequestWrapper(final HttpEntityEnclosingRequest request) + throws ProtocolException { + super(request); + this.entity = request.getEntity(); + } + + public HttpEntity getEntity() { + return this.entity; + } + + public void setEntity(final HttpEntity entity) { + this.entity = entity; + } + + public boolean expectContinue() { + Header expect = getFirstHeader(HTTP.EXPECT_DIRECTIVE); + return expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue()); + } + + @Override + public boolean isRepeatable() { + return this.entity == null || this.entity.isRepeatable(); + } + +} diff --git a/src/org/apache/http/impl/client/RedirectLocations.java b/src/org/apache/http/impl/client/RedirectLocations.java new file mode 100644 index 0000000..d5c47e7 --- /dev/null +++ b/src/org/apache/http/impl/client/RedirectLocations.java @@ -0,0 +1,71 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RedirectLocations.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.net.URI; +import java.util.HashSet; +import java.util.Set; + +/** + * A collection of URIs that were used as redirects. + */ +public class RedirectLocations { + + private final Set<URI> uris; + + public RedirectLocations() { + super(); + this.uris = new HashSet<URI>(); + } + + /** + * Returns true if this collection contains the given URI. + */ + public boolean contains(final URI uri) { + return this.uris.contains(uri); + } + + /** + * Adds a new URI to the list of redirects. + */ + public void add(final URI uri) { + this.uris.add(uri); + } + + /** + * Removes a URI from the list of redirects. + */ + public boolean remove(final URI uri) { + return this.uris.remove(uri); + } + +} diff --git a/src/org/apache/http/impl/client/RequestWrapper.java b/src/org/apache/http/impl/client/RequestWrapper.java new file mode 100644 index 0000000..04a641d --- /dev/null +++ b/src/org/apache/http/impl/client/RequestWrapper.java @@ -0,0 +1,170 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RequestWrapper.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.http.HttpRequest; +import org.apache.http.ProtocolException; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.AbstractHttpMessage; +import org.apache.http.message.BasicRequestLine; +import org.apache.http.params.HttpProtocolParams; + +/** + * A wrapper class for {@link HttpRequest}s that can be used to change + * properties of the current request without modifying the original + * object. + * </p> + * This class is also capable of resetting the request headers to + * the state of the original request. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 674186 $ + * + * @since 4.0 + */ +public class RequestWrapper extends AbstractHttpMessage implements HttpUriRequest { + + private final HttpRequest original; + + private URI uri; + private String method; + private ProtocolVersion version; + private int execCount; + + public RequestWrapper(final HttpRequest request) throws ProtocolException { + super(); + if (request == null) { + throw new IllegalArgumentException("HTTP request may not be null"); + } + this.original = request; + setParams(request.getParams()); + // Make a copy of the original URI + if (request instanceof HttpUriRequest) { + this.uri = ((HttpUriRequest) request).getURI(); + this.method = ((HttpUriRequest) request).getMethod(); + this.version = null; + } else { + RequestLine requestLine = request.getRequestLine(); + try { + this.uri = new URI(requestLine.getUri()); + } catch (URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + + requestLine.getUri(), ex); + } + this.method = requestLine.getMethod(); + this.version = request.getProtocolVersion(); + } + this.execCount = 0; + } + + public void resetHeaders() { + // Make a copy of original headers + this.headergroup.clear(); + setHeaders(this.original.getAllHeaders()); + } + + public String getMethod() { + return this.method; + } + + public void setMethod(final String method) { + if (method == null) { + throw new IllegalArgumentException("Method name may not be null"); + } + this.method = method; + } + + public ProtocolVersion getProtocolVersion() { + if (this.version != null) { + return this.version; + } else { + return HttpProtocolParams.getVersion(getParams()); + } + } + + public void setProtocolVersion(final ProtocolVersion version) { + this.version = version; + } + + + public URI getURI() { + return this.uri; + } + + public void setURI(final URI uri) { + this.uri = uri; + } + + public RequestLine getRequestLine() { + String method = getMethod(); + ProtocolVersion ver = getProtocolVersion(); + String uritext = null; + if (uri != null) { + uritext = uri.toASCIIString(); + } + if (uritext == null || uritext.length() == 0) { + uritext = "/"; + } + return new BasicRequestLine(method, uritext, ver); + } + + public void abort() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public boolean isAborted() { + return false; + } + + public HttpRequest getOriginal() { + return this.original; + } + + public boolean isRepeatable() { + return true; + } + + public int getExecCount() { + return this.execCount; + } + + public void incrementExecCount() { + this.execCount++; + } + +} diff --git a/src/org/apache/http/impl/client/RoutedRequest.java b/src/org/apache/http/impl/client/RoutedRequest.java new file mode 100644 index 0000000..954ebe5 --- /dev/null +++ b/src/org/apache/http/impl/client/RoutedRequest.java @@ -0,0 +1,73 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/RoutedRequest.java $ + * $Revision: 645846 $ + * $Date: 2008-04-08 03:53:39 -0700 (Tue, 08 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.conn.routing.HttpRoute; + + +/** + * A request with the route along which it should be sent. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 645846 $ + * + * @since 4.0 + */ +public class RoutedRequest { + + protected final RequestWrapper request; + protected final HttpRoute route; + + /** + * Creates a new routed request. + * + * @param req the request + * @param route the route + */ + public RoutedRequest(final RequestWrapper req, final HttpRoute route) { + super(); + this.request = req; + this.route = route; + } + + public final RequestWrapper getRequest() { + return request; + } + + public final HttpRoute getRoute() { + return route; + } + +} // interface RoutedRequest diff --git a/src/org/apache/http/impl/client/TunnelRefusedException.java b/src/org/apache/http/impl/client/TunnelRefusedException.java new file mode 100644 index 0000000..601626f --- /dev/null +++ b/src/org/apache/http/impl/client/TunnelRefusedException.java @@ -0,0 +1,52 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/client/TunnelRefusedException.java $ + * $Revision: 537650 $ + * $Date: 2007-05-13 12:58:22 -0700 (Sun, 13 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.client; + +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; + +public class TunnelRefusedException extends HttpException { + + private static final long serialVersionUID = -8646722842745617323L; + + private final HttpResponse response; + + public TunnelRefusedException(final String message, final HttpResponse response) { + super(message); + this.response = response; + } + + public HttpResponse getResponse() { + return this.response; + } + +} diff --git a/src/org/apache/http/impl/client/package.html b/src/org/apache/http/impl/client/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/client/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + diff --git a/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java b/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java new file mode 100644 index 0000000..5cbe010 --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java @@ -0,0 +1,399 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java $ + * $Revision: 672969 $ + * $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSession; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.ClientConnectionManager; + + +/** + * Abstract adapter from {@link OperatedClientConnection operated} to + * {@link ManagedClientConnection managed} client connections. + * Read and write methods are delegated to the wrapped connection. + * Operations affecting the connection state have to be implemented + * by derived classes. Operations for querying the connection state + * are delegated to the wrapped connection if there is one, or + * return a default value if there is none. + * <br/> + * This adapter tracks the checkpoints for reusable communication states, + * as indicated by {@link #markReusable markReusable} and queried by + * {@link #isMarkedReusable isMarkedReusable}. + * All send and receive operations will automatically clear the mark. + * <br/> + * Connection release calls are delegated to the connection manager, + * if there is one. {@link #abortConnection abortConnection} will + * clear the reusability mark first. The connection manager is + * expected to tolerate multiple calls to the release method. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672969 $ $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * @since 4.0 + */ +public abstract class AbstractClientConnAdapter + implements ManagedClientConnection { + + /** Thread that requested this connection. */ + private final Thread executionThread; + + /** + * The connection manager, if any. + * This attribute MUST NOT be final, so the adapter can be detached + * from the connection manager without keeping a hard reference there. + */ + private volatile ClientConnectionManager connManager; + + /** The wrapped connection. */ + private volatile OperatedClientConnection wrappedConnection; + + /** The reusability marker. */ + private volatile boolean markedReusable; + + /** True if the connection has been aborted. */ + private volatile boolean aborted; + + /** The duration this is valid for while idle (in ms). */ + private volatile long duration; + + /** + * Creates a new connection adapter. + * The adapter is initially <i>not</i> + * {@link #isMarkedReusable marked} as reusable. + * + * @param mgr the connection manager, or <code>null</code> + * @param conn the connection to wrap, or <code>null</code> + */ + protected AbstractClientConnAdapter(ClientConnectionManager mgr, + OperatedClientConnection conn) { + super(); + executionThread = Thread.currentThread(); + connManager = mgr; + wrappedConnection = conn; + markedReusable = false; + aborted = false; + duration = Long.MAX_VALUE; + } // <constructor> + + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + protected void detach() { + wrappedConnection = null; + connManager = null; // base class attribute + duration = Long.MAX_VALUE; + } + + protected OperatedClientConnection getWrappedConnection() { + return wrappedConnection; + } + + protected ClientConnectionManager getManager() { + return connManager; + } + + /** + * Asserts that the connection has not been aborted. + * + * @throws InterruptedIOException if the connection has been aborted + */ + protected final void assertNotAborted() throws InterruptedIOException { + if (aborted) { + throw new InterruptedIOException("Connection has been shut down."); + } + } + + /** + * Asserts that there is a wrapped connection to delegate to. + * + * @throws IllegalStateException if there is no wrapped connection + * or connection has been aborted + */ + protected final void assertValid( + final OperatedClientConnection wrappedConn) { + if (wrappedConn == null) { + throw new IllegalStateException("No wrapped connection."); + } + } + + // non-javadoc, see interface HttpConnection + public boolean isOpen() { + OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) + return false; + + return conn.isOpen(); + } + + + // non-javadoc, see interface HttpConnection + public boolean isStale() { + if (aborted) + return true; + OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) + return true; + + return conn.isStale(); + } + + + // non-javadoc, see interface HttpConnection + public void setSocketTimeout(int timeout) { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + conn.setSocketTimeout(timeout); + } + + + // non-javadoc, see interface HttpConnection + public int getSocketTimeout() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getSocketTimeout(); + } + + + // non-javadoc, see interface HttpConnection + public HttpConnectionMetrics getMetrics() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getMetrics(); + } + + + // non-javadoc, see interface HttpClientConnection + public void flush() + throws IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + conn.flush(); + } + + + // non-javadoc, see interface HttpClientConnection + public boolean isResponseAvailable(int timeout) + throws IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + return conn.isResponseAvailable(timeout); + } + + + // non-javadoc, see interface HttpClientConnection + public void receiveResponseEntity(HttpResponse response) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.receiveResponseEntity(response); + } + + + // non-javadoc, see interface HttpClientConnection + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + return conn.receiveResponseHeader(); + } + + + // non-javadoc, see interface HttpClientConnection + public void sendRequestEntity(HttpEntityEnclosingRequest request) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.sendRequestEntity(request); + } + + + // non-javadoc, see interface HttpClientConnection + public void sendRequestHeader(HttpRequest request) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.sendRequestHeader(request); + } + + + // non-javadoc, see interface HttpInetConnection + public InetAddress getLocalAddress() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalAddress(); + } + + // non-javadoc, see interface HttpInetConnection + public int getLocalPort() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalPort(); + } + + + // non-javadoc, see interface HttpInetConnection + public InetAddress getRemoteAddress() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemoteAddress(); + } + + // non-javadoc, see interface HttpInetConnection + public int getRemotePort() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemotePort(); + } + + // non-javadoc, see interface ManagedClientConnection + public boolean isSecure() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.isSecure(); + } + + // non-javadoc, see interface ManagedClientConnection + public SSLSession getSSLSession() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (!isOpen()) + return null; + + SSLSession result = null; + Socket sock = conn.getSocket(); + if (sock instanceof SSLSocket) { + result = ((SSLSocket)sock).getSession(); + } + return result; + } + + // non-javadoc, see interface ManagedClientConnection + public void markReusable() { + markedReusable = true; + } + + // non-javadoc, see interface ManagedClientConnection + public void unmarkReusable() { + markedReusable = false; + } + + // non-javadoc, see interface ManagedClientConnection + public boolean isMarkedReusable() { + return markedReusable; + } + + public void setIdleDuration(long duration, TimeUnit unit) { + if(duration > 0) { + this.duration = unit.toMillis(duration); + } else { + this.duration = -1; + } + } + + // non-javadoc, see interface ConnectionReleaseTrigger + public void releaseConnection() { + if (connManager != null) { + connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS); + } + } + + // non-javadoc, see interface ConnectionReleaseTrigger + public void abortConnection() { + if (aborted) { + return; + } + aborted = true; + unmarkReusable(); + try { + shutdown(); + } catch (IOException ignore) { + } + // Usually #abortConnection() is expected to be called from + // a helper thread in order to unblock the main execution thread + // blocked in an I/O operation. It may be unsafe to call + // #releaseConnection() from the helper thread, so we have to rely + // on an IOException thrown by the closed socket on the main thread + // to trigger the release of the connection back to the + // connection manager. + // + // However, if this method is called from the main execution thread + // it should be safe to release the connection immediately. Besides, + // this also helps ensure the connection gets released back to the + // manager if #abortConnection() is called from the main execution + // thread while there is no blocking I/O operation. + if (executionThread.equals(Thread.currentThread())) { + releaseConnection(); + } + } + +} // class AbstractClientConnAdapter diff --git a/src/org/apache/http/impl/conn/AbstractPoolEntry.java b/src/org/apache/http/impl/conn/AbstractPoolEntry.java new file mode 100644 index 0000000..0e7d95f --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractPoolEntry.java @@ -0,0 +1,322 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java $ + * $Revision: 658775 $ + * $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.RouteTracker; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.OperatedClientConnection; + + + +/** + * A pool entry for use by connection manager implementations. + * Pool entries work in conjunction with an + * {@link AbstractClientConnAdapter adapter}. + * The adapter is handed out to applications that obtain a connection. + * The pool entry stores the underlying connection and tracks the + * {@link HttpRoute route} established. + * The adapter delegates methods for establishing the route to + * it's pool entry. + * <br/> + * If the managed connections is released or revoked, the adapter + * gets disconnected, but the pool entry still contains the + * underlying connection and the established route. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 658775 $ + * + * @since 4.0 + */ +public abstract class AbstractPoolEntry { + + /** The connection operator. */ + protected final ClientConnectionOperator connOperator; + + /** The underlying connection being pooled or used. */ + protected final OperatedClientConnection connection; + + /** The route for which this entry gets allocated. */ + //@@@ currently accessed from connection manager(s) as attribute + //@@@ avoid that, derived classes should decide whether update is allowed + //@@@ SCCM: yes, TSCCM: no + protected volatile HttpRoute route; + + /** Connection state object */ + protected volatile Object state; + + /** The tracked route, or <code>null</code> before tracking starts. */ + protected volatile RouteTracker tracker; + + + /** + * Creates a new pool entry. + * + * @param connOperator the Connection Operator for this entry + * @param route the planned route for the connection, + * or <code>null</code> + */ + protected AbstractPoolEntry(ClientConnectionOperator connOperator, + HttpRoute route) { + super(); + if (connOperator == null) { + throw new IllegalArgumentException("Connection operator may not be null"); + } + this.connOperator = connOperator; + this.connection = connOperator.createConnection(); + this.route = route; + this.tracker = null; + } + + /** + * Returns the state object associated with this pool entry. + * + * @return The state object + */ + public Object getState() { + return state; + } + + /** + * Assigns a state object to this pool entry. + * + * @param state The state object + */ + public void setState(final Object state) { + this.state = state; + } + + /** + * Opens the underlying connection. + * + * @param route the route along which to open the connection + * @param context the context for opening the connection + * @param params the parameters for opening the connection + * + * @throws IOException in case of a problem + */ + public void open(HttpRoute route, + HttpContext context, HttpParams params) + throws IOException { + + if (route == null) { + throw new IllegalArgumentException + ("Route must not be null."); + } + //@@@ is context allowed to be null? depends on operator? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if ((this.tracker != null) && this.tracker.isConnected()) { + throw new IllegalStateException("Connection already open."); + } + + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // opening of the connection will be tracked. + + //@@@ verify route against planned route? + + this.tracker = new RouteTracker(route); + final HttpHost proxy = route.getProxyHost(); + + connOperator.openConnection + (this.connection, + (proxy != null) ? proxy : route.getTargetHost(), + route.getLocalAddress(), + context, params); + + RouteTracker localTracker = tracker; // capture volatile + + // If this tracker was reset while connecting, + // fail early. + if (localTracker == null) { + throw new IOException("Request aborted"); + } + + if (proxy == null) { + localTracker.connectTarget(this.connection.isSecure()); + } else { + localTracker.connectProxy(proxy, this.connection.isSecure()); + } + + } // open + + + /** + * Tracks tunnelling of the connection to the target. + * The tunnel has to be established outside by sending a CONNECT + * request to the (last) proxy. + * + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelTarget(boolean secure, HttpParams params) + throws IOException { + + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + //@@@ check for proxy in planned route? + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + if (this.tracker.isTunnelled()) { + throw new IllegalStateException + ("Connection is already tunnelled."); + } + + // LOG.debug? + + this.connection.update(null, tracker.getTargetHost(), + secure, params); + this.tracker.tunnelTarget(secure); + + } // tunnelTarget + + + /** + * Tracks tunnelling of the connection to a chained proxy. + * The tunnel has to be established outside by sending a CONNECT + * request to the previous proxy. + * + * @param next the proxy to which the tunnel was established. + * See {@link org.apache.http.conn.ManagedClientConnection#tunnelProxy + * ManagedClientConnection.tunnelProxy} + * for details. + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException { + + if (next == null) { + throw new IllegalArgumentException + ("Next proxy must not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + //@@@ check for proxy in planned route? + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + + // LOG.debug? + + this.connection.update(null, next, secure, params); + this.tracker.tunnelProxy(next, secure); + + } // tunnelProxy + + + /** + * Layers a protocol on top of an established tunnel. + * + * @param context the context for layering + * @param params the parameters for layering + * + * @throws IOException in case of a problem + */ + public void layerProtocol(HttpContext context, HttpParams params) + throws IOException { + + //@@@ is context allowed to be null? depends on operator? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + if (!this.tracker.isTunnelled()) { + //@@@ allow this? + throw new IllegalStateException + ("Protocol layering without a tunnel not supported."); + } + if (this.tracker.isLayered()) { + throw new IllegalStateException + ("Multiple protocol layering not supported."); + } + + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // layering on top of the connection will be tracked. + + final HttpHost target = tracker.getTargetHost(); + + connOperator.updateSecureConnection(this.connection, target, + context, params); + + this.tracker.layerProtocol(this.connection.isSecure()); + + } // layerProtocol + + + /** + * Shuts down the entry. + * + * If {@link #open(HttpRoute, HttpContext, HttpParams)} is in progress, + * this will cause that open to possibly throw an {@link IOException}. + */ + protected void shutdownEntry() { + tracker = null; + } + + +} // class AbstractPoolEntry + diff --git a/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java b/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java new file mode 100644 index 0000000..2c5fd30 --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java @@ -0,0 +1,188 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java $ + * $Revision: 658775 $ + * $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.OperatedClientConnection; + + + +/** + * Abstract adapter from pool {@link AbstractPoolEntry entries} to + * {@link org.apache.http.conn.ManagedClientConnection managed} + * client connections. + * The connection in the pool entry is used to initialize the base class. + * In addition, methods to establish a route are delegated to the + * pool entry. {@link #shutdown shutdown} and {@link #close close} + * will clear the tracked route in the pool entry and call the + * respective method of the wrapped connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 658775 $ $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * @since 4.0 + */ +public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapter { + + /** The wrapped pool entry. */ + protected volatile AbstractPoolEntry poolEntry; + + + /** + * Creates a new connection adapter. + * + * @param manager the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected AbstractPooledConnAdapter(ClientConnectionManager manager, + AbstractPoolEntry entry) { + super(manager, entry.connection); + this.poolEntry = entry; + } + + + /** + * Asserts that this adapter is still attached. + * + * @throws IllegalStateException + * if it is {@link #detach detach}ed + */ + protected final void assertAttached() { + if (poolEntry == null) { + throw new IllegalStateException("Adapter is detached."); + } + } + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + @Override + protected void detach() { + super.detach(); + poolEntry = null; + } + + + // non-javadoc, see interface ManagedHttpConnection + public HttpRoute getRoute() { + + assertAttached(); + return (poolEntry.tracker == null) ? + null : poolEntry.tracker.toRoute(); + } + + // non-javadoc, see interface ManagedHttpConnection + public void open(HttpRoute route, + HttpContext context, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.open(route, context, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void tunnelTarget(boolean secure, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.tunnelTarget(secure, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.tunnelProxy(next, secure, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void layerProtocol(HttpContext context, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.layerProtocol(context, params); + } + + + + // non-javadoc, see interface HttpConnection + public void close() throws IOException { + if (poolEntry != null) + poolEntry.shutdownEntry(); + + OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.close(); + } + } + + // non-javadoc, see interface HttpConnection + public void shutdown() throws IOException { + if (poolEntry != null) + poolEntry.shutdownEntry(); + + OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.shutdown(); + } + } + + + // non-javadoc, see interface ManagedClientConnection + public Object getState() { + assertAttached(); + return poolEntry.getState(); + } + + + // non-javadoc, see interface ManagedClientConnection + public void setState(final Object state) { + assertAttached(); + poolEntry.setState(state); + } + + +} // class AbstractPooledConnAdapter diff --git a/src/org/apache/http/impl/conn/DefaultClientConnection.java b/src/org/apache/http/impl/conn/DefaultClientConnection.java new file mode 100644 index 0000000..a41f57a --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultClientConnection.java @@ -0,0 +1,259 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; +import java.net.Socket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.impl.SocketHttpClientConnection; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; + +import org.apache.http.conn.OperatedClientConnection; + + +/** + * Default implementation of an operated client connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * @since 4.0 + */ +public class DefaultClientConnection extends SocketHttpClientConnection + implements OperatedClientConnection { + + private final Log log = LogFactory.getLog(getClass()); + private final Log headerLog = LogFactory.getLog("org.apache.http.headers"); + private final Log wireLog = LogFactory.getLog("org.apache.http.wire"); + + /** The unconnected socket */ + private volatile Socket socket; + + /** The target host of this connection. */ + private HttpHost targetHost; + + /** Whether this connection is secure. */ + private boolean connSecure; + + /** True if this connection was shutdown. */ + private volatile boolean shutdown; + + public DefaultClientConnection() { + super(); + } + + + // non-javadoc, see interface OperatedClientConnection + public final HttpHost getTargetHost() { + return this.targetHost; + } + + + // non-javadoc, see interface OperatedClientConnection + public final boolean isSecure() { + return this.connSecure; + } + + + @Override + public final Socket getSocket() { + return this.socket; + } + + + public void opening(Socket sock, HttpHost target) throws IOException { + assertNotOpen(); + this.socket = sock; + this.targetHost = target; + + // Check for shutdown after assigning socket, so that + if (this.shutdown) { + sock.close(); // allow this to throw... + // ...but if it doesn't, explicitly throw one ourselves. + throw new IOException("Connection already shutdown"); + } + } + + + public void openCompleted(boolean secure, HttpParams params) throws IOException { + assertNotOpen(); + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + this.connSecure = secure; + bind(this.socket, params); + } + + /** + * Force-closes this connection. + * If the connection is still in the process of being open (the method + * {@link #opening opening} was already called but + * {@link #openCompleted openCompleted} was not), the associated + * socket that is being connected to a remote address will be closed. + * That will interrupt a thread that is blocked on connecting + * the socket. + * If the connection is not yet open, this will prevent the connection + * from being opened. + * + * @throws IOException in case of a problem + */ + @Override + public void shutdown() throws IOException { + log.debug("Connection shut down"); + shutdown = true; + + super.shutdown(); + Socket sock = this.socket; // copy volatile attribute + if (sock != null) + sock.close(); + + } // shutdown + + + @Override + public void close() throws IOException { + log.debug("Connection closed"); + super.close(); + } + + + @Override + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + SessionInputBuffer inbuffer = super.createSessionInputBuffer( + socket, + buffersize, + params); + if (wireLog.isDebugEnabled()) { + inbuffer = new LoggingSessionInputBuffer(inbuffer, new Wire(wireLog)); + } + return inbuffer; + } + + + @Override + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + SessionOutputBuffer outbuffer = super.createSessionOutputBuffer( + socket, + buffersize, + params); + if (wireLog.isDebugEnabled()) { + outbuffer = new LoggingSessionOutputBuffer(outbuffer, new Wire(wireLog)); + } + return outbuffer; + } + + + @Override + protected HttpMessageParser createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new DefaultResponseParser + (buffer, null, responseFactory, params); + } + + + // non-javadoc, see interface OperatedClientConnection + public void update(Socket sock, HttpHost target, + boolean secure, HttpParams params) + throws IOException { + + assertOpen(); + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + if (sock != null) { + this.socket = sock; + bind(sock, params); + } + targetHost = target; + connSecure = secure; + + } // update + + + @Override + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + HttpResponse response = super.receiveResponseHeader(); + if (headerLog.isDebugEnabled()) { + headerLog.debug("<< " + response.getStatusLine().toString()); + Header[] headers = response.getAllHeaders(); + for (Header header : headers) { + headerLog.debug("<< " + header.toString()); + } + } + return response; + } + + + @Override + public void sendRequestHeader(HttpRequest request) throws HttpException, IOException { + super.sendRequestHeader(request); + if (headerLog.isDebugEnabled()) { + headerLog.debug(">> " + request.getRequestLine().toString()); + Header[] headers = request.getAllHeaders(); + for (Header header : headers) { + headerLog.debug(">> " + header.toString()); + } + } + } + +} // class DefaultClientConnection diff --git a/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java b/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java new file mode 100644 index 0000000..41488e1 --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java @@ -0,0 +1,216 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java $ + * $Revision: 652193 $ + * $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.Socket; +import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.scheme.LayeredSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.SocketFactory; + + +/** + * Default implementation of a + * {@link ClientConnectionOperator ClientConnectionOperator}. + * It uses a {@link SchemeRegistry SchemeRegistry} to look up + * {@link SocketFactory SocketFactory} objects. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 652193 $ $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $ + * + * @since 4.0 + */ +public class DefaultClientConnectionOperator + implements ClientConnectionOperator { + + + /** The scheme registry for looking up socket factories. */ + protected SchemeRegistry schemeRegistry; + + + /** + * Creates a new client connection operator for the given scheme registry. + * + * @param schemes the scheme registry + */ + public DefaultClientConnectionOperator(SchemeRegistry schemes) { + if (schemes == null) { + throw new IllegalArgumentException + ("Scheme registry must not be null."); + } + schemeRegistry = schemes; + } + + + // non-javadoc, see interface ClientConnectionOperator + public OperatedClientConnection createConnection() { + return new DefaultClientConnection(); + } + + + // non-javadoc, see interface ClientConnectionOperator + public void openConnection(OperatedClientConnection conn, + HttpHost target, + InetAddress local, + HttpContext context, + HttpParams params) + throws IOException { + + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + // local address may be null + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must not be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + final SocketFactory sf = schm.getSocketFactory(); + + Socket sock = sf.createSocket(); + conn.opening(sock, target); + + try { + sock = sf.connectSocket(sock, target.getHostName(), + schm.resolvePort(target.getPort()), + local, 0, params); + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.openCompleted(sf.isSecure(sock), params); + } // openConnection + + + // non-javadoc, see interface ClientConnectionOperator + public void updateSecureConnection(OperatedClientConnection conn, + HttpHost target, + HttpContext context, + HttpParams params) + throws IOException { + + + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (!conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + if (!(schm.getSocketFactory() instanceof LayeredSocketFactory)) { + throw new IllegalArgumentException + ("Target scheme (" + schm.getName() + + ") must have layered socket factory."); + } + + final LayeredSocketFactory lsf = (LayeredSocketFactory) schm.getSocketFactory(); + final Socket sock; + try { + sock = lsf.createSocket + (conn.getSocket(), target.getHostName(), target.getPort(), true); + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.update(sock, target, lsf.isSecure(sock), params); + //@@@ error handling: close the layered socket in case of exception? + + } // updateSecureConnection + + + /** + * Performs standard initializations on a newly created socket. + * + * @param sock the socket to prepare + * @param context the context for the connection + * @param params the parameters from which to prepare the socket + * + * @throws IOException in case of an IO problem + */ + protected void prepareSocket(Socket sock, HttpContext context, + HttpParams params) + throws IOException { + + // context currently not used, but derived classes may need it + //@@@ is context allowed to be null? + + sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + sock.setSoLinger(linger > 0, linger); + } + + } // prepareSocket + + +} // class DefaultClientConnectionOperator + diff --git a/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java b/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java new file mode 100644 index 0000000..90fd55f --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.net.InetAddress; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; + +import org.apache.http.conn.params.ConnRouteParams; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on + * {@link org.apache.http.conn.params.ConnRoutePNames parameters}. + * It will not make use of any Java system properties, + * nor of system or browser proxy settings. + */ +public class DefaultHttpRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected SchemeRegistry schemeRegistry; + + + /** + * Creates a new default route planner. + * + * @param schreg the scheme registry + */ + public DefaultHttpRoutePlanner(SchemeRegistry schreg) { + if (schreg == null) { + throw new IllegalArgumentException + ("SchemeRegistry must not be null."); + } + schemeRegistry = schreg; + } + + + // non-javadoc, see interface HttpRoutePlanner + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (request == null) { + throw new IllegalStateException + ("Request must not be null."); + } + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) + return route; + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + if (target == null) { + throw new IllegalStateException + ("Target host must not be null."); + } + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = + ConnRouteParams.getDefaultProxy(request.getParams()); + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + +} diff --git a/src/org/apache/http/impl/conn/DefaultResponseParser.java b/src/org/apache/http/impl/conn/DefaultResponseParser.java new file mode 100644 index 0000000..f817a10 --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultResponseParser.java @@ -0,0 +1,103 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java $ + * $Revision: 617638 $ + * $Date: 2008-02-01 12:49:26 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponseFactory; +import org.apache.http.NoHttpResponseException; +import org.apache.http.ProtocolException; +import org.apache.http.StatusLine; +import org.apache.http.conn.params.ConnConnectionPNames; +import org.apache.http.impl.io.AbstractMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class DefaultResponseParser extends AbstractMessageParser { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + private final int maxGarbageLines; + + public DefaultResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + if (responseFactory == null) { + throw new IllegalArgumentException + ("Response factory may not be null"); + } + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + this.maxGarbageLines = params.getIntParameter( + ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, Integer.MAX_VALUE); + } + + + @Override + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) throws IOException, HttpException { + // clear the buffer + this.lineBuf.clear(); + //read out the HTTP status string + int count = 0; + ParserCursor cursor = null; + do { + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1 && count == 0) { + // The server just dropped connection on us + throw new NoHttpResponseException("The target server failed to respond"); + } + cursor = new ParserCursor(0, this.lineBuf.length()); + if (lineParser.hasProtocolVersion(this.lineBuf, cursor)) { + // Got one + break; + } else if (i == -1 || count >= this.maxGarbageLines) { + // Giving up + throw new ProtocolException("The server failed to respond with a " + + "valid HTTP response"); + } + count++; + } while(true); + //create the status line from the status string + StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/src/org/apache/http/impl/conn/IdleConnectionHandler.java b/src/org/apache/http/impl/conn/IdleConnectionHandler.java new file mode 100644 index 0000000..2cacda3 --- /dev/null +++ b/src/org/apache/http/impl/conn/IdleConnectionHandler.java @@ -0,0 +1,190 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpConnection; + + +/** + * A helper class for connection managers to track idle connections. + * + * <p>This class is not synchronized.</p> + * + * @see org.apache.http.conn.ClientConnectionManager#closeIdleConnections + * + * @since 4.0 + */ +public class IdleConnectionHandler { + + private final Log log = LogFactory.getLog(getClass()); + + /** Holds connections and the time they were added. */ + private final Map<HttpConnection,TimeValues> connectionToTimes; + + + public IdleConnectionHandler() { + super(); + connectionToTimes = new HashMap<HttpConnection,TimeValues>(); + } + + /** + * Registers the given connection with this handler. The connection will be held until + * {@link #remove} or {@link #closeIdleConnections} is called. + * + * @param connection the connection to add + * + * @see #remove + */ + public void add(HttpConnection connection, long validDuration, TimeUnit unit) { + + Long timeAdded = Long.valueOf(System.currentTimeMillis()); + + if (log.isDebugEnabled()) { + log.debug("Adding connection at: " + timeAdded); + } + + connectionToTimes.put(connection, new TimeValues(timeAdded, validDuration, unit)); + } + + /** + * Removes the given connection from the list of connections to be closed when idle. + * This will return true if the connection is still valid, and false + * if the connection should be considered expired and not used. + * + * @param connection + * @return True if the connection is still valid. + */ + public boolean remove(HttpConnection connection) { + TimeValues times = connectionToTimes.remove(connection); + if(times == null) { + log.warn("Removing a connection that never existed!"); + return true; + } else { + return System.currentTimeMillis() <= times.timeExpires; + } + } + + /** + * Removes all connections referenced by this handler. + */ + public void removeAll() { + this.connectionToTimes.clear(); + } + + /** + * Closes connections that have been idle for at least the given amount of time. + * + * @param idleTime the minimum idle time, in milliseconds, for connections to be closed + */ + //@@@ add TimeUnit argument here? + public void closeIdleConnections(long idleTime) { + + // the latest time for which connections will be closed + long idleTimeout = System.currentTimeMillis() - idleTime; + + if (log.isDebugEnabled()) { + log.debug("Checking for connections, idleTimeout: " + idleTimeout); + } + + Iterator<HttpConnection> connectionIter = + connectionToTimes.keySet().iterator(); + + while (connectionIter.hasNext()) { + HttpConnection conn = connectionIter.next(); + TimeValues times = connectionToTimes.get(conn); + Long connectionTime = times.timeAdded; + if (connectionTime.longValue() <= idleTimeout) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, connection time: " + connectionTime); + } + connectionIter.remove(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + + public void closeExpiredConnections() { + long now = System.currentTimeMillis(); + if (log.isDebugEnabled()) { + log.debug("Checking for expired connections, now: " + now); + } + + Iterator<HttpConnection> connectionIter = + connectionToTimes.keySet().iterator(); + + while (connectionIter.hasNext()) { + HttpConnection conn = connectionIter.next(); + TimeValues times = connectionToTimes.get(conn); + if(times.timeExpires <= now) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, expired @: " + times.timeExpires); + } + connectionIter.remove(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + private static class TimeValues { + private final long timeAdded; + private final long timeExpires; + + /** + * @param now The current time in milliseconds + * @param validDuration The duration this connection is valid for + * @param validUnit The unit of time the duration is specified in. + */ + TimeValues(long now, long validDuration, TimeUnit validUnit) { + this.timeAdded = now; + if(validDuration > 0) { + this.timeExpires = now + validUnit.toMillis(validDuration); + } else { + this.timeExpires = Long.MAX_VALUE; + } + } + } +} diff --git a/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java b/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java new file mode 100644 index 0000000..4f6477e --- /dev/null +++ b/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java @@ -0,0 +1,117 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Logs all data read to the wire LOG. + * + * @author Ortwin Glueck + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class LoggingSessionInputBuffer implements SessionInputBuffer { + + /** Original session input buffer. */ + private final SessionInputBuffer in; + + /** The wire log to use for writing. */ + private final Wire wire; + + /** + * Create an instance that wraps the specified session input buffer. + * @param in The session input buffer. + * @param wire The wire log to use. + */ + public LoggingSessionInputBuffer(final SessionInputBuffer in, final Wire wire) { + super(); + this.in = in; + this.wire = wire; + } + + public boolean isDataAvailable(int timeout) throws IOException { + return this.in.isDataAvailable(timeout); + } + + public int read(byte[] b, int off, int len) throws IOException { + int l = this.in.read(b, off, len); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, off, l); + } + return l; + } + + public int read() throws IOException { + int l = this.in.read(); + if (this.wire.enabled() && l > 0) { + this.wire.input(l); + } + return l; + } + + public int read(byte[] b) throws IOException { + int l = this.in.read(b); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, 0, l); + } + return l; + } + + public String readLine() throws IOException { + String s = this.in.readLine(); + if (this.wire.enabled() && s != null) { + this.wire.input(s + "[EOL]"); + } + return s; + } + + public int readLine(final CharArrayBuffer buffer) throws IOException { + int l = this.in.readLine(buffer); + if (this.wire.enabled() && l > 0) { + int pos = buffer.length() - l; + String s = new String(buffer.buffer(), pos, l); + this.wire.input(s + "[EOL]"); + } + return l; + } + + public HttpTransportMetrics getMetrics() { + return this.in.getMetrics(); + } + +} diff --git a/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java b/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java new file mode 100644 index 0000000..1afab7d --- /dev/null +++ b/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java @@ -0,0 +1,109 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Logs all data written to the wire LOG. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class LoggingSessionOutputBuffer implements SessionOutputBuffer { + + /** Original data transmitter. */ + private final SessionOutputBuffer out; + + /** The wire log to use. */ + private final Wire wire; + + /** + * Create an instance that wraps the specified session output buffer. + * @param out The session output buffer. + * @param wire The Wire log to use. + */ + public LoggingSessionOutputBuffer(final SessionOutputBuffer out, final Wire wire) { + super(); + this.out = out; + this.wire = wire; + } + + public void write(byte[] b, int off, int len) throws IOException { + this.out.write(b, off, len); + if (this.wire.enabled()) { + this.wire.output(b, off, len); + } + } + + public void write(int b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void write(byte[] b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void writeLine(final CharArrayBuffer buffer) throws IOException { + this.out.writeLine(buffer); + if (this.wire.enabled()) { + String s = new String(buffer.buffer(), 0, buffer.length()); + this.wire.output(s + "[EOL]"); + } + } + + public void writeLine(final String s) throws IOException { + this.out.writeLine(s); + if (this.wire.enabled()) { + this.wire.output(s + "[EOL]"); + } + } + + public HttpTransportMetrics getMetrics() { + return this.out.getMetrics(); + } + +} diff --git a/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java b/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java new file mode 100644 index 0000000..136caf4 --- /dev/null +++ b/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java @@ -0,0 +1,290 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; + +import org.apache.http.conn.params.ConnRouteParams; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on {@link java.net.ProxySelector}. + * By default, it will pick up the proxy settings of the JVM, either + * from system properties or from the browser running the application. + * Additionally, it interprets some + * {@link org.apache.http.conn.params.ConnRoutePNames parameters}, + * though not the {@link + * org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}. + */ +public class ProxySelectorRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected SchemeRegistry schemeRegistry; + + /** The proxy selector to use, or <code>null</code> for system default. */ + protected ProxySelector proxySelector; + + + /** + * Creates a new proxy selector route planner. + * + * @param schreg the scheme registry + * @param prosel the proxy selector, or + * <code>null</code> for the system default + */ + public ProxySelectorRoutePlanner(SchemeRegistry schreg, + ProxySelector prosel) { + + if (schreg == null) { + throw new IllegalArgumentException + ("SchemeRegistry must not be null."); + } + schemeRegistry = schreg; + proxySelector = prosel; + } + + + /** + * Obtains the proxy selector to use. + * + * @return the proxy selector, or <code>null</code> for the system default + */ + public ProxySelector getProxySelector() { + return this.proxySelector; + } + + + /** + * Sets the proxy selector to use. + * + * @param prosel the proxy selector, or + * <code>null</code> to use the system default + */ + public void setProxySelector(ProxySelector prosel) { + this.proxySelector = prosel; + } + + + + // non-javadoc, see interface HttpRoutePlanner + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (request == null) { + throw new IllegalStateException + ("Request must not be null."); + } + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) + return route; + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + if (target == null) { + throw new IllegalStateException + ("Target host must not be null."); + } + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = determineProxy(target, request, context); + + final Scheme schm = + this.schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + + /** + * Determines a proxy for the given target. + * + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return the proxy to use, or <code>null</code> for a direct route + * + * @throws HttpException + * in case of system proxy settings that cannot be handled + */ + protected HttpHost determineProxy(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + // the proxy selector can be 'unset', so we better deal with null here + ProxySelector psel = this.proxySelector; + if (psel == null) + psel = ProxySelector.getDefault(); + if (psel == null) + return null; + + URI targetURI = null; + try { + targetURI = new URI(target.toURI()); + } catch (URISyntaxException usx) { + throw new HttpException + ("Cannot convert host to URI: " + target, usx); + } + List<Proxy> proxies = psel.select(targetURI); + + Proxy p = chooseProxy(proxies, target, request, context); + + HttpHost result = null; + if (p.type() == Proxy.Type.HTTP) { + // convert the socket address to an HttpHost + if (!(p.address() instanceof InetSocketAddress)) { + throw new HttpException + ("Unable to handle non-Inet proxy address: "+p.address()); + } + final InetSocketAddress isa = (InetSocketAddress) p.address(); + // assume default scheme (http) + result = new HttpHost(getHost(isa), isa.getPort()); + } + + return result; + } + + + /** + * Obtains a host from an {@link InetSocketAddress}. + * + * @param isa the socket address + * + * @return a host string, either as a symbolic name or + * as a literal IP address string + * <br/> + * (TODO: determine format for IPv6 addresses, with or without [brackets]) + */ + protected String getHost(InetSocketAddress isa) { + + //@@@ Will this work with literal IPv6 addresses, or do we + //@@@ need to wrap these in [] for the string representation? + //@@@ Having it in this method at least allows for easy workarounds. + return isa.isUnresolved() ? + isa.getHostName() : isa.getAddress().getHostAddress(); + + } + + + /* + * Chooses a proxy from a list of available proxies. + * The default implementation just picks the first non-SOCKS proxy + * from the list. If there are only SOCKS proxies, + * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned. + * Derived classes may implement more advanced strategies, + * such as proxy rotation if there are multiple options. + * + * @param proxies the list of proxies to choose from, + * never <code>null</code> or empty + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return a proxy of type {@link Proxy.Type#DIRECT DIRECT} + * or {@link Proxy.Type#HTTP HTTP}, never <code>null</code> + */ + protected Proxy chooseProxy(List<Proxy> proxies, + HttpHost target, + HttpRequest request, + HttpContext context) { + + if ((proxies == null) || proxies.isEmpty()) { + throw new IllegalArgumentException + ("Proxy list must not be empty."); + } + + Proxy result = null; + + // check the list for one we can use + for (int i=0; (result == null) && (i < proxies.size()); i++) { + + Proxy p = proxies.get(i); + switch (p.type()) { + + case DIRECT: + case HTTP: + result = p; + break; + + case SOCKS: + // SOCKS hosts are not handled on the route level. + // The socket may make use of the SOCKS host though. + break; + } + } + + if (result == null) { + //@@@ log as warning or info that only a socks proxy is available? + // result can only be null if all proxies are socks proxies + // socks proxies are not handled on the route planning level + result = Proxy.NO_PROXY; + } + + return result; + } + +} // class ProxySelectorRoutePlanner + diff --git a/src/org/apache/http/impl/conn/SingleClientConnManager.java b/src/org/apache/http/impl/conn/SingleClientConnManager.java new file mode 100644 index 0000000..7999f3e --- /dev/null +++ b/src/org/apache/http/impl/conn/SingleClientConnManager.java @@ -0,0 +1,444 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.RouteTracker; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.params.HttpParams; + + +/** + * A connection "manager" for a single connection. + * This manager is good only for single-threaded use. + * Allocation <i>always</i> returns the connection immediately, + * even if it has not been released after the previous allocation. + * In that case, a {@link #MISUSE_MESSAGE warning} is logged + * and the previously issued connection is revoked. + * <p> + * This class is derived from <code>SimpleHttpConnectionManager</code> + * in HttpClient 3. See there for original authors. + * </p> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class SingleClientConnManager implements ClientConnectionManager { + + private final Log log = LogFactory.getLog(getClass()); + + /** The message to be logged on multiple allocation. */ + public final static String MISUSE_MESSAGE = + "Invalid use of SingleClientConnManager: connection still allocated.\n" + + "Make sure to release the connection before allocating another one."; + + + /** The schemes supported by this connection manager. */ + protected SchemeRegistry schemeRegistry; + + /** The operator for opening and updating connections. */ + protected ClientConnectionOperator connOperator; + + /** The one and only entry in this pool. */ + protected PoolEntry uniquePoolEntry; + + /** The currently issued managed connection, if any. */ + protected ConnAdapter managedConn; + + /** The time of the last connection release, or -1. */ + protected long lastReleaseTime; + + /** The time the last released connection expires and shouldn't be reused. */ + protected long connectionExpiresTime; + + /** Whether the connection should be shut down on release. */ + protected boolean alwaysShutDown; + + /** Indicates whether this connection manager is shut down. */ + protected volatile boolean isShutDown; + + + + + /** + * Creates a new simple connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry, or + * <code>null</code> for the default registry + */ + public SingleClientConnManager(HttpParams params, + SchemeRegistry schreg) { + + if (schreg == null) { + throw new IllegalArgumentException + ("Scheme registry must not be null."); + } + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.uniquePoolEntry = new PoolEntry(); + this.managedConn = null; + this.lastReleaseTime = -1L; + this.alwaysShutDown = false; //@@@ from params? as argument? + this.isShutDown = false; + + } // <constructor> + + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + + // non-javadoc, see interface ClientConnectionManager + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg); + } + + + /** + * Asserts that this manager is not shut down. + * + * @throws IllegalStateException if this manager is shut down + */ + protected final void assertStillUp() + throws IllegalStateException { + + if (this.isShutDown) + throw new IllegalStateException("Manager is shut down."); + } + + + public final ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + return new ClientConnectionRequest() { + + public void abortRequest() { + // Nothing to abort, since requests are immediate. + } + + public ManagedClientConnection getConnection( + long timeout, TimeUnit tunit) { + return SingleClientConnManager.this.getConnection( + route, state); + } + + }; + } + + + /** + * Obtains a connection. + * This method does not block. + * + * @param route where the connection should point to + * + * @return a connection that can be used to communicate + * along the given route + */ + public ManagedClientConnection getConnection(HttpRoute route, Object state) { + + if (route == null) { + throw new IllegalArgumentException("Route may not be null."); + } + assertStillUp(); + + if (log.isDebugEnabled()) { + log.debug("Get connection for route " + route); + } + + if (managedConn != null) + revokeConnection(); + + // check re-usability of the connection + boolean recreate = false; + boolean shutdown = false; + + // Kill the connection if it expired. + closeExpiredConnections(); + + if (uniquePoolEntry.connection.isOpen()) { + RouteTracker tracker = uniquePoolEntry.tracker; + shutdown = (tracker == null || // can happen if method is aborted + !tracker.toRoute().equals(route)); + } else { + // If the connection is not open, create a new PoolEntry, + // as the connection may have been marked not reusable, + // due to aborts -- and the PoolEntry should not be reused + // either. There's no harm in recreating an entry if + // the connection is closed. + recreate = true; + } + + if (shutdown) { + recreate = true; + try { + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + log.debug("Problem shutting down connection.", iox); + } + } + + if (recreate) + uniquePoolEntry = new PoolEntry(); + + managedConn = new ConnAdapter(uniquePoolEntry, route); + + return managedConn; + } + + + // non-javadoc, see interface ClientConnectionManager + public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { + assertStillUp(); + + if (!(conn instanceof ConnAdapter)) { + throw new IllegalArgumentException + ("Connection class mismatch, " + + "connection not obtained from this manager."); + } + + if (log.isDebugEnabled()) { + log.debug("Releasing connection " + conn); + } + + ConnAdapter sca = (ConnAdapter) conn; + if (sca.poolEntry == null) + return; // already released + ClientConnectionManager manager = sca.getManager(); + if (manager != null && manager != this) { + throw new IllegalArgumentException + ("Connection not obtained from this manager."); + } + + try { + // make sure that the response has been read completely + if (sca.isOpen() && (this.alwaysShutDown || + !sca.isMarkedReusable()) + ) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not reusable."); + } + + // make sure this connection will not be re-used + // we might have gotten here because of a shutdown trigger + // shutdown of the adapter also clears the tracked route + sca.shutdown(); + } + } catch (IOException iox) { + //@@@ log as warning? let pass? + if (log.isDebugEnabled()) + log.debug("Exception shutting down released connection.", + iox); + } finally { + sca.detach(); + managedConn = null; + lastReleaseTime = System.currentTimeMillis(); + if(validDuration > 0) + connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; + else + connectionExpiresTime = Long.MAX_VALUE; + } + } // releaseConnection + + public void closeExpiredConnections() { + if(System.currentTimeMillis() >= connectionExpiresTime) { + closeIdleConnections(0, TimeUnit.MILLISECONDS); + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void closeIdleConnections(long idletime, TimeUnit tunit) { + assertStillUp(); + + // idletime can be 0 or negative, no problem there + if (tunit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + + if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { + final long cutoff = + System.currentTimeMillis() - tunit.toMillis(idletime); + if (lastReleaseTime <= cutoff) { + try { + uniquePoolEntry.close(); + } catch (IOException iox) { + // ignore + log.debug("Problem closing idle connection.", iox); + } + } + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void shutdown() { + + this.isShutDown = true; + + if (managedConn != null) + managedConn.detach(); + + try { + if (uniquePoolEntry != null) // and connection open? + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + // ignore + log.debug("Problem while shutting down manager.", iox); + } finally { + uniquePoolEntry = null; + } + } + + + /** + * Revokes the currently issued connection. + * The adapter gets disconnected, the connection will be shut down. + */ + protected void revokeConnection() { + if (managedConn == null) + return; + + log.warn(MISUSE_MESSAGE); + + managedConn.detach(); + + try { + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + // ignore + log.debug("Problem while shutting down connection.", iox); + } + } + + + /** + * The pool entry for this connection manager. + */ + protected class PoolEntry extends AbstractPoolEntry { + + /** + * Creates a new pool entry. + * + */ + protected PoolEntry() { + super(SingleClientConnManager.this.connOperator, null); + } + + /** + * Closes the connection in this pool entry. + */ + protected void close() + throws IOException { + + shutdownEntry(); + if (connection.isOpen()) + connection.close(); + } + + + /** + * Shuts down the connection in this pool entry. + */ + protected void shutdown() + throws IOException { + + shutdownEntry(); + if (connection.isOpen()) + connection.shutdown(); + } + + } // class PoolEntry + + + + /** + * The connection adapter used by this manager. + */ + protected class ConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new connection adapter. + * + * @param entry the pool entry for the connection being wrapped + * @param route the planned route for this connection + */ + protected ConnAdapter(PoolEntry entry, HttpRoute route) { + super(SingleClientConnManager.this, entry); + markReusable(); + entry.route = route; + } + + } + + +} // class SingleClientConnManager diff --git a/src/org/apache/http/impl/conn/Wire.java b/src/org/apache/http/impl/conn/Wire.java new file mode 100644 index 0000000..147b7f5 --- /dev/null +++ b/src/org/apache/http/impl/conn/Wire.java @@ -0,0 +1,160 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/Wire.java,v 1.9 2004/06/24 21:39:52 mbecke Exp $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import org.apache.commons.logging.Log; + +/** + * Logs data to the wire LOG. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class Wire { + + private final Log log; + + public Wire(Log log) { + this.log = log; + } + + private void wire(String header, InputStream instream) + throws IOException { + StringBuilder buffer = new StringBuilder(); + int ch; + while ((ch = instream.read()) != -1) { + if (ch == 13) { + buffer.append("[\\r]"); + } else if (ch == 10) { + buffer.append("[\\n]\""); + buffer.insert(0, "\""); + buffer.insert(0, header); + log.debug(buffer.toString()); + buffer.setLength(0); + } else if ((ch < 32) || (ch > 127)) { + buffer.append("[0x"); + buffer.append(Integer.toHexString(ch)); + buffer.append("]"); + } else { + buffer.append((char) ch); + } + } + if (buffer.length() > 0) { + buffer.append('\"'); + buffer.insert(0, '\"'); + buffer.insert(0, header); + log.debug(buffer.toString()); + } + } + + + public boolean enabled() { + return log.isDebugEnabled(); + } + + public void output(InputStream outstream) + throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", outstream); + } + + public void input(InputStream instream) + throws IOException { + if (instream == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", instream); + } + + public void output(byte[] b, int off, int len) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", new ByteArrayInputStream(b, off, len)); + } + + public void input(byte[] b, int off, int len) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", new ByteArrayInputStream(b, off, len)); + } + + public void output(byte[] b) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", new ByteArrayInputStream(b)); + } + + public void input(byte[] b) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", new ByteArrayInputStream(b)); + } + + public void output(int b) + throws IOException { + output(new byte[] {(byte) b}); + } + + public void input(int b) + throws IOException { + input(new byte[] {(byte) b}); + } + + public void output(final String s) + throws IOException { + if (s == null) { + throw new IllegalArgumentException("Output may not be null"); + } + output(s.getBytes()); + } + + public void input(final String s) + throws IOException { + if (s == null) { + throw new IllegalArgumentException("Input may not be null"); + } + input(s.getBytes()); + } +} diff --git a/src/org/apache/http/impl/conn/package.html b/src/org/apache/http/impl/conn/package.html new file mode 100644 index 0000000..54eb3c2 --- /dev/null +++ b/src/org/apache/http/impl/conn/package.html @@ -0,0 +1,5 @@ +<body> + + +</body> + diff --git a/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java new file mode 100644 index 0000000..2b37d72 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java @@ -0,0 +1,332 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.conn.IdleConnectionHandler; + + +/** + * An abstract connection pool. + * It is used by the {@link ThreadSafeClientConnManager}. + * The abstract pool includes a {@link #poolLock}, which is used to + * synchronize access to the internal pool datastructures. + * Don't use <code>synchronized</code> for that purpose! + */ +public abstract class AbstractConnPool implements RefQueueHandler { + + private final Log log = LogFactory.getLog(getClass()); + + /** + * The global lock for this pool. + */ + protected final Lock poolLock; + + + /** + * References to issued connections. + * Objects in this set are of class + * {@link BasicPoolEntryRef BasicPoolEntryRef}, + * and point to the pool entry for the issued connection. + * GCed connections are detected by the missing pool entries. + */ + protected Set<BasicPoolEntryRef> issuedConnections; + + /** The handler for idle connections. */ + protected IdleConnectionHandler idleConnHandler; + + /** The current total number of connections. */ + protected int numConnections; + + /** + * A reference queue to track loss of pool entries to GC. + * The same queue is used to track loss of the connection manager, + * so we cannot specialize the type. + */ + protected ReferenceQueue<Object> refQueue; + + /** A worker (thread) to track loss of pool entries to GC. */ + private RefQueueWorker refWorker; + + + /** Indicates whether this pool is shut down. */ + protected volatile boolean isShutDown; + + /** + * Creates a new connection pool. + */ + protected AbstractConnPool() { + issuedConnections = new HashSet<BasicPoolEntryRef>(); + idleConnHandler = new IdleConnectionHandler(); + + boolean fair = false; //@@@ check parameters to decide + poolLock = new ReentrantLock(fair); + } + + + /** + * Enables connection garbage collection (GC). + * This method must be called immediately after creating the + * connection pool. It is not possible to enable connection GC + * after pool entries have been created. Neither is it possible + * to disable connection GC. + * + * @throws IllegalStateException + * if connection GC is already enabled, or if it cannot be + * enabled because there already are pool entries + */ + public void enableConnectionGC() + throws IllegalStateException { + + if (refQueue != null) { + throw new IllegalStateException("Connection GC already enabled."); + } + poolLock.lock(); + try { + if (numConnections > 0) { //@@@ is this check sufficient? + throw new IllegalStateException("Pool already in use."); + } + } finally { + poolLock.unlock(); + } + + refQueue = new ReferenceQueue<Object>(); + refWorker = new RefQueueWorker(refQueue, this); + Thread t = new Thread(refWorker); //@@@ use a thread factory + t.setDaemon(true); + t.setName("RefQueueWorker@" + this); + t.start(); + } + + + /** + * Obtains a pool entry with a connection within the given timeout. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + public final + BasicPoolEntry getEntry( + HttpRoute route, + Object state, + long timeout, + TimeUnit tunit) + throws ConnectionPoolTimeoutException, InterruptedException { + return requestPoolEntry(route, state).getPoolEntry(timeout, tunit); + } + + /** + * Returns a new {@link PoolEntryRequest}, from which a {@link BasicPoolEntry} + * can be obtained, or the request can be aborted. + */ + public abstract PoolEntryRequest requestPoolEntry(HttpRoute route, Object state); + + + /** + * Returns an entry into the pool. + * The connection of the entry is expected to be in a suitable state, + * either open and re-usable, or closed. The pool will not make any + * attempt to determine whether it can be re-used or not. + * + * @param entry the entry for the connection to release + * @param reusable <code>true</code> if the entry is deemed + * reusable, <code>false</code> otherwise. + * @param validDuration The duration that the entry should remain free and reusable. + * @param timeUnit The unit of time the duration is measured in. + */ + public abstract void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) + ; + + + + // non-javadoc, see interface RefQueueHandler +// BEGIN android-changed + public void handleReference(Reference ref) { +// END android-changed + poolLock.lock(); + try { + + if (ref instanceof BasicPoolEntryRef) { + // check if the GCed pool entry was still in use + //@@@ find a way to detect this without lookup + //@@@ flag in the BasicPoolEntryRef, to be reset when freed? + final boolean lost = issuedConnections.remove(ref); + if (lost) { + final HttpRoute route = + ((BasicPoolEntryRef)ref).getRoute(); + if (log.isDebugEnabled()) { + log.debug("Connection garbage collected. " + route); + } + handleLostEntry(route); + } + } + + } finally { + poolLock.unlock(); + } + } + + + /** + * Handles cleaning up for a lost pool entry with the given route. + * A lost pool entry corresponds to a connection that was + * garbage collected instead of being properly released. + * + * @param route the route of the pool entry that was lost + */ + protected abstract void handleLostEntry(HttpRoute route) + ; + + + /** + * Closes idle connections. + * + * @param idletime the time the connections should have been idle + * in order to be closed now + * @param tunit the unit for the <code>idletime</code> + */ + public void closeIdleConnections(long idletime, TimeUnit tunit) { + + // idletime can be 0 or negative, no problem there + if (tunit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + + poolLock.lock(); + try { + idleConnHandler.closeIdleConnections(tunit.toMillis(idletime)); + } finally { + poolLock.unlock(); + } + } + + public void closeExpiredConnections() { + poolLock.lock(); + try { + idleConnHandler.closeExpiredConnections(); + } finally { + poolLock.unlock(); + } + } + + + //@@@ revise this cleanup stuff (closeIdle+deleteClosed), it's not good + + /** + * Deletes all entries for closed connections. + */ + public abstract void deleteClosedConnections() + ; + + + /** + * Shuts down this pool and all associated resources. + * Overriding methods MUST call the implementation here! + */ + public void shutdown() { + + poolLock.lock(); + try { + + if (isShutDown) + return; + + // no point in monitoring GC anymore + if (refWorker != null) + refWorker.shutdown(); + + // close all connections that are issued to an application + Iterator<BasicPoolEntryRef> iter = issuedConnections.iterator(); + while (iter.hasNext()) { + BasicPoolEntryRef per = iter.next(); + iter.remove(); + BasicPoolEntry entry = per.get(); + if (entry != null) { + closeConnection(entry.getConnection()); + } + } + + // remove all references to connections + //@@@ use this for shutting them down instead? + idleConnHandler.removeAll(); + + isShutDown = true; + + } finally { + poolLock.unlock(); + } + } + + + /** + * Closes a connection from this pool. + * + * @param conn the connection to close, or <code>null</code> + */ + protected void closeConnection(final OperatedClientConnection conn) { + if (conn != null) { + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + + + + + +} // class AbstractConnPool + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java new file mode 100644 index 0000000..dded360 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java $ + * $Revision: 652721 $ + * $Date: 2008-05-01 17:32:20 -0700 (Thu, 01 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.lang.ref.ReferenceQueue; + +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.conn.AbstractPoolEntry; + + + +/** + * Basic implementation of a connection pool entry. + */ +public class BasicPoolEntry extends AbstractPoolEntry { + + /** + * A weak reference to <code>this</code> used to detect GC of entries. + * Pool entries can only be GCed when they are allocated by an application + * and therefore not referenced with a hard link in the manager. + */ + private final BasicPoolEntryRef reference; + + /** + * Creates a new pool entry. + * + * @param op the connection operator + * @param route the planned route for the connection + * @param queue the reference queue for tracking GC of this entry, + * or <code>null</code> + */ + public BasicPoolEntry(ClientConnectionOperator op, + HttpRoute route, + ReferenceQueue<Object> queue) { + super(op, route); + if (route == null) { + throw new IllegalArgumentException("HTTP route may not be null"); + } + this.reference = new BasicPoolEntryRef(this, queue); + } + + protected final OperatedClientConnection getConnection() { + return super.connection; + } + + protected final HttpRoute getPlannedRoute() { + return super.route; + } + + protected final BasicPoolEntryRef getWeakRef() { + return this.reference; + } + + +} // class BasicPoolEntry + + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java new file mode 100644 index 0000000..32df8a5 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java @@ -0,0 +1,80 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; + +import org.apache.http.conn.routing.HttpRoute; + + + +/** + * A weak reference to a {@link BasicPoolEntry BasicPoolEntry}. + * This reference explicitly keeps the planned route, so the connection + * can be reclaimed if it is lost to garbage collection. + */ +public class BasicPoolEntryRef extends WeakReference<BasicPoolEntry> { + + /** The planned route of the entry. */ + private final HttpRoute route; + + + /** + * Creates a new reference to a pool entry. + * + * @param entry the pool entry, must not be <code>null</code> + * @param queue the reference queue, or <code>null</code> + */ + public BasicPoolEntryRef(BasicPoolEntry entry, + ReferenceQueue<Object> queue) { + super(entry, queue); + if (entry == null) { + throw new IllegalArgumentException + ("Pool entry must not be null."); + } + route = entry.getPlannedRoute(); + } + + + /** + * Obtain the planned route for the referenced entry. + * The planned route is still available, even if the entry is gone. + * + * @return the planned route + */ + public final HttpRoute getRoute() { + return this.route; + } + +} // class BasicPoolEntryRef + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java new file mode 100644 index 0000000..29455d0 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java $ + * $Revision: 653214 $ + * $Date: 2008-05-04 07:12:13 -0700 (Sun, 04 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.conn.AbstractPoolEntry; +import org.apache.http.impl.conn.AbstractPooledConnAdapter; + + + +/** + * A connection wrapper and callback handler. + * All connections given out by the manager are wrappers which + * can be {@link #detach detach}ed to prevent further use on release. + */ +public class BasicPooledConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new adapter. + * + * @param tsccm the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected BasicPooledConnAdapter(ThreadSafeClientConnManager tsccm, + AbstractPoolEntry entry) { + super(tsccm, entry); + markReusable(); + } + + + @Override + protected ClientConnectionManager getManager() { + // override needed only to make method visible in this package + return super.getManager(); + } + + + /** + * Obtains the pool entry. + * + * @return the pool entry, or <code>null</code> if detached + */ + protected AbstractPoolEntry getPoolEntry() { + return super.poolEntry; + } + + + // non-javadoc, see base class + @Override + protected void detach() { + // override needed only to make method visible in this package + super.detach(); + } +} diff --git a/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java new file mode 100644 index 0000000..cf59129 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java @@ -0,0 +1,698 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Queue; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.params.ConnPerRoute; +import org.apache.http.conn.params.ConnManagerParams; +import org.apache.http.params.HttpParams; + + +/** + * A connection pool that maintains connections by route. + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3.x, see there for original authors. It implements the same + * algorithm for connection re-use and connection-per-host enforcement: + * <ul> + * <li>connections are re-used only for the exact same route</li> + * <li>connection limits are enforced per route rather than per host</li> + * </ul> + * Note that access to the pool datastructures is synchronized via the + * {@link AbstractConnPool#poolLock poolLock} in the base class, + * not via <code>synchronized</code> methods. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author and others + */ +public class ConnPoolByRoute extends AbstractConnPool { + + private final Log log = LogFactory.getLog(getClass()); + + /** Connection operator for this pool */ + protected final ClientConnectionOperator operator; + + /** The list of free connections */ + protected Queue<BasicPoolEntry> freeConnections; + + /** The list of WaitingThreads waiting for a connection */ + protected Queue<WaitingThread> waitingThreads; + + /** + * A map of route-specific pools. + * Keys are of class {@link HttpRoute}, + * values of class {@link RouteSpecificPool}. + */ + protected final Map<HttpRoute, RouteSpecificPool> routeToPool; + + protected final int maxTotalConnections; + + private final ConnPerRoute connPerRoute; + + /** + * Creates a new connection pool, managed by route. + */ + public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) { + super(); + if (operator == null) { + throw new IllegalArgumentException("Connection operator may not be null"); + } + this.operator = operator; + + freeConnections = createFreeConnQueue(); + waitingThreads = createWaitingThreadQueue(); + routeToPool = createRouteToPoolMap(); + maxTotalConnections = ConnManagerParams + .getMaxTotalConnections(params); + connPerRoute = ConnManagerParams + .getMaxConnectionsPerRoute(params); + } + + + /** + * Creates the queue for {@link #freeConnections}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<BasicPoolEntry> createFreeConnQueue() { + return new LinkedList<BasicPoolEntry>(); + } + + /** + * Creates the queue for {@link #waitingThreads}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<WaitingThread> createWaitingThreadQueue() { + return new LinkedList<WaitingThread>(); + } + + /** + * Creates the map for {@link #routeToPool}. + * Called once by the constructor. + * + * @return a map + */ + protected Map<HttpRoute, RouteSpecificPool> createRouteToPoolMap() { + return new HashMap<HttpRoute, RouteSpecificPool>(); + } + + + /** + * Creates a new route-specific pool. + * Called by {@link #getRoutePool} when necessary. + * + * @param route the route + * + * @return the new pool + */ + protected RouteSpecificPool newRouteSpecificPool(HttpRoute route) { + return new RouteSpecificPool(route, connPerRoute.getMaxForRoute(route)); + } + + + /** + * Creates a new waiting thread. + * Called by {@link #getRoutePool} when necessary. + * + * @param cond the condition to wait for + * @param rospl the route specific pool, or <code>null</code> + * + * @return a waiting thread representation + */ + protected WaitingThread newWaitingThread(Condition cond, + RouteSpecificPool rospl) { + return new WaitingThread(cond, rospl); + } + + + /** + * Get a route-specific pool of available connections. + * + * @param route the route + * @param create whether to create the pool if it doesn't exist + * + * @return the pool for the argument route, + * never <code>null</code> if <code>create</code> is <code>true</code> + */ + protected RouteSpecificPool getRoutePool(HttpRoute route, + boolean create) { + RouteSpecificPool rospl = null; + poolLock.lock(); + try { + + rospl = routeToPool.get(route); + if ((rospl == null) && create) { + // no pool for this route yet (or anymore) + rospl = newRouteSpecificPool(route); + routeToPool.put(route, rospl); + } + + } finally { + poolLock.unlock(); + } + + return rospl; + } + + + //@@@ consider alternatives for gathering statistics + public int getConnectionsInPool(HttpRoute route) { + + poolLock.lock(); + try { + // don't allow a pool to be created here! + RouteSpecificPool rospl = getRoutePool(route, false); + return (rospl != null) ? rospl.getEntryCount() : 0; + + } finally { + poolLock.unlock(); + } + } + + @Override + public PoolEntryRequest requestPoolEntry( + final HttpRoute route, + final Object state) { + + final WaitingThreadAborter aborter = new WaitingThreadAborter(); + + return new PoolEntryRequest() { + + public void abortRequest() { + poolLock.lock(); + try { + aborter.abort(); + } finally { + poolLock.unlock(); + } + } + + public BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) + throws InterruptedException, ConnectionPoolTimeoutException { + return getEntryBlocking(route, state, timeout, tunit, aborter); + } + + }; + } + + /** + * Obtains a pool entry with a connection within the given timeout. + * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)} + * must be called before blocking, to allow the thread to be interrupted. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * @param aborter an object which can abort a {@link WaitingThread}. + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + protected BasicPoolEntry getEntryBlocking( + HttpRoute route, Object state, + long timeout, TimeUnit tunit, + WaitingThreadAborter aborter) + throws ConnectionPoolTimeoutException, InterruptedException { + + Date deadline = null; + if (timeout > 0) { + deadline = new Date + (System.currentTimeMillis() + tunit.toMillis(timeout)); + } + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + WaitingThread waitingThread = null; + + while (entry == null) { + + if (isShutDown) { + throw new IllegalStateException + ("Connection pool shut down."); + } + + if (log.isDebugEnabled()) { + log.debug("Total connections kept alive: " + freeConnections.size()); + log.debug("Total issued connections: " + issuedConnections.size()); + log.debug("Total allocated connection: " + numConnections + " out of " + maxTotalConnections); + } + + // the cases to check for: + // - have a free connection for that route + // - allowed to create a free connection for that route + // - can delete and replace a free connection for another route + // - need to wait for one of the things above to come true + + entry = getFreeEntry(rospl, state); + if (entry != null) { + break; + } + + boolean hasCapacity = rospl.getCapacity() > 0; + + if (log.isDebugEnabled()) { + log.debug("Available capacity: " + rospl.getCapacity() + + " out of " + rospl.getMaxEntries() + + " [" + route + "][" + state + "]"); + } + + if (hasCapacity && numConnections < maxTotalConnections) { + + entry = createEntry(rospl, operator); + + } else if (hasCapacity && !freeConnections.isEmpty()) { + + deleteLeastUsedEntry(); + entry = createEntry(rospl, operator); + + } else { + + if (log.isDebugEnabled()) { + log.debug("Need to wait for connection" + + " [" + route + "][" + state + "]"); + } + + if (waitingThread == null) { + waitingThread = + newWaitingThread(poolLock.newCondition(), rospl); + aborter.setWaitingThread(waitingThread); + } + + boolean success = false; + try { + rospl.queueThread(waitingThread); + waitingThreads.add(waitingThread); + success = waitingThread.await(deadline); + + } finally { + // In case of 'success', we were woken up by the + // connection pool and should now have a connection + // waiting for us, or else we're shutting down. + // Just continue in the loop, both cases are checked. + rospl.removeThread(waitingThread); + waitingThreads.remove(waitingThread); + } + + // check for spurious wakeup vs. timeout + if (!success && (deadline != null) && + (deadline.getTime() <= System.currentTimeMillis())) { + throw new ConnectionPoolTimeoutException + ("Timeout waiting for connection"); + } + } + } // while no entry + + } finally { + poolLock.unlock(); + } + + return entry; + + } // getEntry + + + // non-javadoc, see base class AbstractConnPool + @Override + public void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) { + + HttpRoute route = entry.getPlannedRoute(); + if (log.isDebugEnabled()) { + log.debug("Freeing connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + if (isShutDown) { + // the pool is shut down, release the + // connection's resources and get out of here + closeConnection(entry.getConnection()); + return; + } + + // no longer issued, we keep a hard reference now + issuedConnections.remove(entry.getWeakRef()); + + RouteSpecificPool rospl = getRoutePool(route, true); + + if (reusable) { + rospl.freeEntry(entry); + freeConnections.add(entry); + idleConnHandler.add(entry.getConnection(), validDuration, timeUnit); + } else { + rospl.dropEntry(); + numConnections--; + } + + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + + } // freeEntry + + + + /** + * If available, get a free pool entry for a route. + * + * @param rospl the route-specific pool from which to get an entry + * + * @return an available pool entry for the given route, or + * <code>null</code> if none is available + */ + protected BasicPoolEntry getFreeEntry(RouteSpecificPool rospl, Object state) { + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + boolean done = false; + while(!done) { + + entry = rospl.allocEntry(state); + + if (entry != null) { + if (log.isDebugEnabled()) { + log.debug("Getting free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + + } + freeConnections.remove(entry); + boolean valid = idleConnHandler.remove(entry.getConnection()); + if(!valid) { + // If the free entry isn't valid anymore, get rid of it + // and loop to find another one that might be valid. + if(log.isDebugEnabled()) + log.debug("Closing expired free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + closeConnection(entry.getConnection()); + // We use dropEntry instead of deleteEntry because the entry + // is no longer "free" (we just allocated it), and deleteEntry + // can only be used to delete free entries. + rospl.dropEntry(); + numConnections--; + } else { + issuedConnections.add(entry.getWeakRef()); + done = true; + } + + } else { + done = true; + if (log.isDebugEnabled()) { + log.debug("No free connections" + + " [" + rospl.getRoute() + "][" + state + "]"); + } + } + } + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Creates a new pool entry. + * This method assumes that the new connection will be handed + * out immediately. + * + * @param rospl the route-specific pool for which to create the entry + * @param op the operator for creating a connection + * + * @return the new pool entry for a new connection + */ + protected BasicPoolEntry createEntry(RouteSpecificPool rospl, + ClientConnectionOperator op) { + + if (log.isDebugEnabled()) { + log.debug("Creating new connection [" + rospl.getRoute() + "]"); + } + + // the entry will create the connection when needed + BasicPoolEntry entry = + new BasicPoolEntry(op, rospl.getRoute(), refQueue); + + poolLock.lock(); + try { + + rospl.createdEntry(entry); + numConnections++; + + issuedConnections.add(entry.getWeakRef()); + + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Deletes a given pool entry. + * This closes the pooled connection and removes all references, + * so that it can be GCed. + * + * <p><b>Note:</b> Does not remove the entry from the freeConnections list. + * It is assumed that the caller has already handled this step.</p> + * <!-- @@@ is that a good idea? or rather fix it? --> + * + * @param entry the pool entry for the connection to delete + */ + protected void deleteEntry(BasicPoolEntry entry) { + + HttpRoute route = entry.getPlannedRoute(); + + if (log.isDebugEnabled()) { + log.debug("Deleting connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + + closeConnection(entry.getConnection()); + + RouteSpecificPool rospl = getRoutePool(route, true); + rospl.deleteEntry(entry); + numConnections--; + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + idleConnHandler.remove(entry.getConnection());// not idle, but dead + + } finally { + poolLock.unlock(); + } + } + + + /** + * Delete an old, free pool entry to make room for a new one. + * Used to replace pool entries with ones for a different route. + */ + protected void deleteLeastUsedEntry() { + + try { + poolLock.lock(); + + //@@@ with get() instead of remove, we could + //@@@ leave the removing to deleteEntry() + BasicPoolEntry entry = freeConnections.remove(); + + if (entry != null) { + deleteEntry(entry); + } else if (log.isDebugEnabled()) { + log.debug("No free connection to delete."); + } + + } finally { + poolLock.unlock(); + } + } + + + // non-javadoc, see base class AbstractConnPool + @Override + protected void handleLostEntry(HttpRoute route) { + + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + rospl.dropEntry(); + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + numConnections--; + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + } + + + /** + * Notifies a waiting thread that a connection is available. + * This will wake a thread waiting in the specific route pool, + * if there is one. + * Otherwise, a thread in the connection pool will be notified. + * + * @param rospl the pool in which to notify, or <code>null</code> + */ + protected void notifyWaitingThread(RouteSpecificPool rospl) { + + //@@@ while this strategy provides for best connection re-use, + //@@@ is it fair? only do this if the connection is open? + // Find the thread we are going to notify. We want to ensure that + // each waiting thread is only interrupted once, so we will remove + // it from all wait queues before interrupting. + WaitingThread waitingThread = null; + + poolLock.lock(); + try { + + if ((rospl != null) && rospl.hasThread()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on pool" + + " [" + rospl.getRoute() + "]"); + } + waitingThread = rospl.nextThread(); + } else if (!waitingThreads.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on any pool"); + } + waitingThread = waitingThreads.remove(); + } else if (log.isDebugEnabled()) { + log.debug("Notifying no-one, there are no waiting threads"); + } + + if (waitingThread != null) { + waitingThread.wakeup(); + } + + } finally { + poolLock.unlock(); + } + } + + + //@@@ revise this cleanup stuff + //@@@ move method to base class when deleteEntry() is fixed + // non-javadoc, see base class AbstractConnPool + @Override + public void deleteClosedConnections() { + + poolLock.lock(); + try { + + Iterator<BasicPoolEntry> iter = freeConnections.iterator(); + while (iter.hasNext()) { + BasicPoolEntry entry = iter.next(); + if (!entry.getConnection().isOpen()) { + iter.remove(); + deleteEntry(entry); + } + } + + } finally { + poolLock.unlock(); + } + } + + + // non-javadoc, see base class AbstractConnPool + @Override + public void shutdown() { + + poolLock.lock(); + try { + + super.shutdown(); + + // close all free connections + //@@@ move this to base class? + Iterator<BasicPoolEntry> ibpe = freeConnections.iterator(); + while (ibpe.hasNext()) { + BasicPoolEntry entry = ibpe.next(); + ibpe.remove(); + closeConnection(entry.getConnection()); + } + + // wake up all waiting threads + Iterator<WaitingThread> iwth = waitingThreads.iterator(); + while (iwth.hasNext()) { + WaitingThread waiter = iwth.next(); + iwth.remove(); + waiter.wakeup(); + } + + routeToPool.clear(); + + } finally { + poolLock.unlock(); + } + } + + +} // class ConnPoolByRoute + diff --git a/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java new file mode 100644 index 0000000..faf5e3b --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.conn.ConnectionPoolTimeoutException; + +/** + * Encapsulates a request for a {@link BasicPoolEntry}. + */ +public interface PoolEntryRequest { + + /** + * Obtains a pool entry with a connection within the given timeout. + * If {@link #abortRequest()} is called before this completes + * an {@link InterruptedException} is thrown. + * + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted or the request was aborted + */ + BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException; + + /** + * Aborts the active or next call to + * {@link #getPoolEntry(long, TimeUnit)}. + */ + void abortRequest(); + +} diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java new file mode 100644 index 0000000..3af28cc --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java @@ -0,0 +1,48 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java $ + * $Revision: 603874 $ + * $Date: 2007-12-13 02:42:41 -0800 (Thu, 13 Dec 2007) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.lang.ref.Reference; + + +/** + * Callback handler for {@link RefQueueWorker RefQueueWorker}. + */ +public interface RefQueueHandler { + + /** + * Invoked when a reference is found on the queue. + * + * @param ref the reference to handle + */ + public void handleReference(Reference<?> ref) + ; +} diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java new file mode 100644 index 0000000..9ad5c77 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java @@ -0,0 +1,139 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + + +/** + * A worker thread for processing queued references. + * {@link Reference Reference}s can be + * {@link ReferenceQueue queued} + * automatically by the garbage collector. + * If that feature is used, a daemon thread should be executing + * this worker. It will pick up the queued references and pass them + * on to a handler for appropriate processing. + */ +public class RefQueueWorker implements Runnable { + + private final Log log = LogFactory.getLog(getClass()); + + /** The reference queue to monitor. */ + protected final ReferenceQueue<?> refQueue; + + /** The handler for the references found. */ + protected final RefQueueHandler refHandler; + + + /** + * The thread executing this handler. + * This attribute is also used as a shutdown indicator. + */ + protected volatile Thread workerThread; + + + /** + * Instantiates a new worker to listen for lost connections. + * + * @param queue the queue on which to wait for references + * @param handler the handler to pass the references to + */ + public RefQueueWorker(ReferenceQueue<?> queue, RefQueueHandler handler) { + if (queue == null) { + throw new IllegalArgumentException("Queue must not be null."); + } + if (handler == null) { + throw new IllegalArgumentException("Handler must not be null."); + } + + refQueue = queue; + refHandler = handler; + } + + + /** + * The main loop of this worker. + * If initialization succeeds, this method will only return + * after {@link #shutdown shutdown()}. Only one thread can + * execute the main loop at any time. + */ + public void run() { + + if (this.workerThread == null) { + this.workerThread = Thread.currentThread(); + } + + while (this.workerThread == Thread.currentThread()) { + try { + // remove the next reference and process it + Reference<?> ref = refQueue.remove(); + refHandler.handleReference(ref); + } catch (InterruptedException e) { + //@@@ is logging really necessary? this here is the + //@@@ only reason for having a log in this class + if (log.isDebugEnabled()) { + log.debug(this.toString() + " interrupted", e); + } + } + } + } + + + /** + * Shuts down this worker. + * It can be re-started afterwards by another call to {@link #run run()}. + */ + public void shutdown() { + Thread wt = this.workerThread; + if (wt != null) { + this.workerThread = null; // indicate shutdown + wt.interrupt(); + } + } + + + /** + * Obtains a description of this worker. + * + * @return a descriptive string for this worker + */ + @Override + public String toString() { + return "RefQueueWorker::" + this.workerThread; + } + +} // class RefQueueWorker + diff --git a/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java new file mode 100644 index 0000000..5c63933 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java @@ -0,0 +1,301 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.util.ListIterator; +import java.util.Queue; +import java.util.LinkedList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.util.LangUtils; + + +/** + * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}. + * The methods in this class are unsynchronized. It is expected that the + * containing pool takes care of synchronization. + */ +public class RouteSpecificPool { + + private final Log log = LogFactory.getLog(getClass()); + + /** The route this pool is for. */ + protected final HttpRoute route; + + /** the maximum number of entries allowed for this pool */ + protected final int maxEntries; + + /** + * The list of free entries. + * This list is managed LIFO, to increase idle times and + * allow for closing connections that are not really needed. + */ + protected final LinkedList<BasicPoolEntry> freeEntries; + + /** The list of threads waiting for this pool. */ + protected final Queue<WaitingThread> waitingThreads; + + /** The number of created entries. */ + protected int numEntries; + + + /** + * Creates a new route-specific pool. + * + * @param route the route for which to pool + * @param maxEntries the maximum number of entries allowed for this pool + */ + public RouteSpecificPool(HttpRoute route, int maxEntries) { + this.route = route; + this.maxEntries = maxEntries; + this.freeEntries = new LinkedList<BasicPoolEntry>(); + this.waitingThreads = new LinkedList<WaitingThread>(); + this.numEntries = 0; + } + + + /** + * Obtains the route for which this pool is specific. + * + * @return the route + */ + public final HttpRoute getRoute() { + return route; + } + + + /** + * Obtains the maximum number of entries allowed for this pool. + * + * @return the max entry number + */ + public final int getMaxEntries() { + return maxEntries; + } + + + /** + * Indicates whether this pool is unused. + * A pool is unused if there is neither an entry nor a waiting thread. + * All entries count, not only the free but also the allocated ones. + * + * @return <code>true</code> if this pool is unused, + * <code>false</code> otherwise + */ + public boolean isUnused() { + return (numEntries < 1) && waitingThreads.isEmpty(); + } + + + /** + * Return remaining capacity of this pool + * + * @return capacity + */ + public int getCapacity() { + return maxEntries - numEntries; + } + + + /** + * Obtains the number of entries. + * This includes not only the free entries, but also those that + * have been created and are currently issued to an application. + * + * @return the number of entries for the route of this pool + */ + public final int getEntryCount() { + return numEntries; + } + + + /** + * Obtains a free entry from this pool, if one is available. + * + * @return an available pool entry, or <code>null</code> if there is none + */ + public BasicPoolEntry allocEntry(final Object state) { + if (!freeEntries.isEmpty()) { + ListIterator<BasicPoolEntry> it = freeEntries.listIterator(freeEntries.size()); + while (it.hasPrevious()) { + BasicPoolEntry entry = it.previous(); + if (LangUtils.equals(state, entry.getState())) { + it.remove(); + return entry; + } + } + } + if (!freeEntries.isEmpty()) { + BasicPoolEntry entry = freeEntries.remove(); + entry.setState(null); + OperatedClientConnection conn = entry.getConnection(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + return entry; + } + return null; + } + + + /** + * Returns an allocated entry to this pool. + * + * @param entry the entry obtained from {@link #allocEntry allocEntry} + * or presented to {@link #createdEntry createdEntry} + */ + public void freeEntry(BasicPoolEntry entry) { + + if (numEntries < 1) { + throw new IllegalStateException + ("No entry created for this pool. " + route); + } + if (numEntries <= freeEntries.size()) { + throw new IllegalStateException + ("No entry allocated from this pool. " + route); + } + freeEntries.add(entry); + } + + + /** + * Indicates creation of an entry for this pool. + * The entry will <i>not</i> be added to the list of free entries, + * it is only recognized as belonging to this pool now. It can then + * be passed to {@link #freeEntry freeEntry}. + * + * @param entry the entry that was created for this pool + */ + public void createdEntry(BasicPoolEntry entry) { + + if (!route.equals(entry.getPlannedRoute())) { + throw new IllegalArgumentException + ("Entry not planned for this pool." + + "\npool: " + route + + "\nplan: " + entry.getPlannedRoute()); + } + + numEntries++; + } + + + /** + * Deletes an entry from this pool. + * Only entries that are currently free in this pool can be deleted. + * Allocated entries can not be deleted. + * + * @param entry the entry to delete from this pool + * + * @return <code>true</code> if the entry was found and deleted, or + * <code>false</code> if the entry was not found + */ + public boolean deleteEntry(BasicPoolEntry entry) { + + final boolean found = freeEntries.remove(entry); + if (found) + numEntries--; + return found; + } + + + /** + * Forgets about an entry from this pool. + * This method is used to indicate that an entry + * {@link #allocEntry allocated} + * from this pool has been lost and will not be returned. + */ + public void dropEntry() { + if (numEntries < 1) { + throw new IllegalStateException + ("There is no entry that could be dropped."); + } + numEntries--; + } + + + /** + * Adds a waiting thread. + * This pool makes no attempt to match waiting threads with pool entries. + * It is the caller's responsibility to check that there is no entry + * before adding a waiting thread. + * + * @param wt the waiting thread + */ + public void queueThread(WaitingThread wt) { + if (wt == null) { + throw new IllegalArgumentException + ("Waiting thread must not be null."); + } + this.waitingThreads.add(wt); + } + + + /** + * Checks whether there is a waiting thread in this pool. + * + * @return <code>true</code> if there is a waiting thread, + * <code>false</code> otherwise + */ + public boolean hasThread() { + return !this.waitingThreads.isEmpty(); + } + + + /** + * Returns the next thread in the queue. + * + * @return a waiting thread, or <code>null</code> if there is none + */ + public WaitingThread nextThread() { + return this.waitingThreads.peek(); + } + + + /** + * Removes a waiting thread, if it is queued. + * + * @param wt the waiting thread + */ + public void removeThread(WaitingThread wt) { + if (wt == null) + return; + + this.waitingThreads.remove(wt); + } + + +} // class RouteSpecificPool diff --git a/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java new file mode 100644 index 0000000..0781e05 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java @@ -0,0 +1,282 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.params.HttpParams; +import org.apache.http.impl.conn.DefaultClientConnectionOperator; + + + +/** + * Manages a pool of {@link OperatedClientConnection client connections}. + * <p> + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3. See there for original authors. + * </p> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * @since 4.0 + */ +public class ThreadSafeClientConnManager implements ClientConnectionManager { + + private final Log log = LogFactory.getLog(getClass()); + + /** The schemes supported by this connection manager. */ + protected SchemeRegistry schemeRegistry; + + /** The pool of connections being managed. */ + protected final AbstractConnPool connectionPool; + + /** The operator for opening and updating connections. */ + protected ClientConnectionOperator connOperator; + + + + /** + * Creates a new thread safe connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry, or + * <code>null</code> for the default registry + */ + public ThreadSafeClientConnManager(HttpParams params, + SchemeRegistry schreg) { + + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.connectionPool = createConnectionPool(params); + + } // <constructor> + + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + + /** + * Hook for creating the connection pool. + * + * @return the connection pool to use + */ + protected AbstractConnPool createConnectionPool(final HttpParams params) { + + AbstractConnPool acp = new ConnPoolByRoute(connOperator, params); + boolean conngc = true; //@@@ check parameters to decide + if (conngc) { + acp.enableConnectionGC(); + } + return acp; + } + + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg); + } + + + // non-javadoc, see interface ClientConnectionManager + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + + public ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + final PoolEntryRequest poolRequest = connectionPool.requestPoolEntry( + route, state); + + return new ClientConnectionRequest() { + + public void abortRequest() { + poolRequest.abortRequest(); + } + + public ManagedClientConnection getConnection( + long timeout, TimeUnit tunit) throws InterruptedException, + ConnectionPoolTimeoutException { + if (route == null) { + throw new IllegalArgumentException("Route may not be null."); + } + + if (log.isDebugEnabled()) { + log.debug("ThreadSafeClientConnManager.getConnection: " + + route + ", timeout = " + timeout); + } + + BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit); + return new BasicPooledConnAdapter(ThreadSafeClientConnManager.this, entry); + } + + }; + + } + + + // non-javadoc, see interface ClientConnectionManager + public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { + + if (!(conn instanceof BasicPooledConnAdapter)) { + throw new IllegalArgumentException + ("Connection class mismatch, " + + "connection not obtained from this manager."); + } + BasicPooledConnAdapter hca = (BasicPooledConnAdapter) conn; + if ((hca.getPoolEntry() != null) && (hca.getManager() != this)) { + throw new IllegalArgumentException + ("Connection not obtained from this manager."); + } + + try { + // make sure that the response has been read completely + if (hca.isOpen() && !hca.isMarkedReusable()) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not marked reusable."); + } + // In MTHCM, there would be a call to + // SimpleHttpConnectionManager.finishLastResponse(conn); + // Consuming the response is handled outside in 4.0. + + // make sure this connection will not be re-used + // Shut down rather than close, we might have gotten here + // because of a shutdown trigger. + // Shutdown of the adapter also clears the tracked route. + hca.shutdown(); + } + } catch (IOException iox) { + //@@@ log as warning? let pass? + if (log.isDebugEnabled()) + log.debug("Exception shutting down released connection.", + iox); + } finally { + BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry(); + boolean reusable = hca.isMarkedReusable(); + hca.detach(); + if (entry != null) { + connectionPool.freeEntry(entry, reusable, validDuration, timeUnit); + } + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void shutdown() { + connectionPool.shutdown(); + } + + + /** + * Gets the total number of pooled connections for the given route. + * This is the total number of connections that have been created and + * are still in use by this connection manager for the route. + * This value will not exceed the maximum number of connections per host. + * + * @param route the route in question + * + * @return the total number of pooled connections for that route + */ + public int getConnectionsInPool(HttpRoute route) { + return ((ConnPoolByRoute)connectionPool).getConnectionsInPool( + route); + } + + + /** + * Gets the total number of pooled connections. This is the total number of + * connections that have been created and are still in use by this connection + * manager. This value will not exceed the maximum number of connections + * in total. + * + * @return the total number of pooled connections + */ + public int getConnectionsInPool() { + synchronized (connectionPool) { + return connectionPool.numConnections; //@@@ + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void closeIdleConnections(long idleTimeout, TimeUnit tunit) { + // combine these two in a single call? + connectionPool.closeIdleConnections(idleTimeout, tunit); + connectionPool.deleteClosedConnections(); + } + + public void closeExpiredConnections() { + connectionPool.closeExpiredConnections(); + connectionPool.deleteClosedConnections(); + } + + +} // class ThreadSafeClientConnManager + diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThread.java b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java new file mode 100644 index 0000000..a50e11f --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java @@ -0,0 +1,197 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java $ + * $Revision: 649217 $ + * $Date: 2008-04-17 11:32:32 -0700 (Thu, 17 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.util.Date; +import java.util.concurrent.locks.Condition; + + +/** + * Represents a thread waiting for a connection. + * This class implements throwaway objects. It is instantiated whenever + * a thread needs to wait. Instances are not re-used, except if the + * waiting thread experiences a spurious wakeup and continues to wait. + * <br/> + * All methods assume external synchronization on the condition + * passed to the constructor. + * Instances of this class do <i>not</i> synchronize access! + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + */ +public class WaitingThread { + + /** The condition on which the thread is waiting. */ + private final Condition cond; + + /** The route specific pool on which the thread is waiting. */ + //@@@ replace with generic pool interface + private final RouteSpecificPool pool; + + /** The thread that is waiting for an entry. */ + private Thread waiter; + + /** True if this was interrupted. */ + private boolean aborted; + + + /** + * Creates a new entry for a waiting thread. + * + * @param cond the condition for which to wait + * @param pool the pool on which the thread will be waiting, + * or <code>null</code> + */ + public WaitingThread(Condition cond, RouteSpecificPool pool) { + + if (cond == null) { + throw new IllegalArgumentException("Condition must not be null."); + } + + this.cond = cond; + this.pool = pool; + } + + + /** + * Obtains the condition. + * + * @return the condition on which to wait, never <code>null</code> + */ + public final Condition getCondition() { + // not synchronized + return this.cond; + } + + + /** + * Obtains the pool, if there is one. + * + * @return the pool on which a thread is or was waiting, + * or <code>null</code> + */ + public final RouteSpecificPool getPool() { + // not synchronized + return this.pool; + } + + + /** + * Obtains the thread, if there is one. + * + * @return the thread which is waiting, or <code>null</code> + */ + public final Thread getThread() { + // not synchronized + return this.waiter; + } + + + /** + * Blocks the calling thread. + * This method returns when the thread is notified or interrupted, + * if a timeout occurrs, or if there is a spurious wakeup. + * <br/> + * This method assumes external synchronization. + * + * @param deadline when to time out, or <code>null</code> for no timeout + * + * @return <code>true</code> if the condition was satisfied, + * <code>false</code> in case of a timeout. + * Typically, a call to {@link #wakeup} is used to indicate + * that the condition was satisfied. Since the condition is + * accessible outside, this cannot be guaranteed though. + * + * @throws InterruptedException if the waiting thread was interrupted + * + * @see #wakeup + */ + public boolean await(Date deadline) + throws InterruptedException { + + // This is only a sanity check. We cannot synchronize here, + // the lock would not be released on calling cond.await() below. + if (this.waiter != null) { + throw new IllegalStateException + ("A thread is already waiting on this object." + + "\ncaller: " + Thread.currentThread() + + "\nwaiter: " + this.waiter); + } + + if (aborted) + throw new InterruptedException("Operation interrupted"); + + this.waiter = Thread.currentThread(); + + boolean success = false; + try { + if (deadline != null) { + success = this.cond.awaitUntil(deadline); + } else { + this.cond.await(); + success = true; + } + if (aborted) + throw new InterruptedException("Operation interrupted"); + } finally { + this.waiter = null; + } + return success; + + } // await + + + /** + * Wakes up the waiting thread. + * <br/> + * This method assumes external synchronization. + */ + public void wakeup() { + + // If external synchronization and pooling works properly, + // this cannot happen. Just a sanity check. + if (this.waiter == null) { + throw new IllegalStateException + ("Nobody waiting on this object."); + } + + // One condition might be shared by several WaitingThread instances. + // It probably isn't, but just in case: wake all, not just one. + this.cond.signalAll(); + } + + public void interrupt() { + aborted = true; + this.cond.signalAll(); + } + + +} // class WaitingThread diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java new file mode 100644 index 0000000..1844457 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java $ + * $Revision: 649220 $ + * $Date: 2008-04-17 11:40:24 -0700 (Thu, 17 Apr 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +/** A simple class that can interrupt a {@link WaitingThread}. */ +public class WaitingThreadAborter { + + private WaitingThread waitingThread; + private boolean aborted; + + /** + * If a waiting thread has been set, interrupts it. + */ + public void abort() { + aborted = true; + + if (waitingThread != null) + waitingThread.interrupt(); + + } + + /** + * Sets the waiting thread. If this has already been aborted, + * the waiting thread is immediately interrupted. + * + * @param waitingThread The thread to interrupt when aborting. + */ + public void setWaitingThread(WaitingThread waitingThread) { + this.waitingThread = waitingThread; + if (aborted) + waitingThread.interrupt(); + } + +} diff --git a/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png Binary files differnew file mode 100644 index 0000000..2e2820d --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png diff --git a/src/org/apache/http/impl/conn/tsccm/package.html b/src/org/apache/http/impl/conn/tsccm/package.html new file mode 100644 index 0000000..5aca5d4 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/package.html @@ -0,0 +1,205 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/package.html $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> + +The implementation of a thread-safe client connection manager. + +<center> +<img src="doc-files/tsccm-structure.png" alt="Relation Diagram"/> +</center> + +<p> +The implementation is structured into three areas, as illustrated +by the diagram above. +Facing the application is the <i>Manager</i> (green), which internally +maintains a <i>Pool</i> (yellow) of connections and waiting threads. +Both Manager and Pool rely on <i>Operations</i> (cyan) to provide the +actual connections. +</p> +<p> +In order to allow connection garbage collection, it is +imperative that hard object references between the areas are +restricted to the relations indicated by arrows in the diagram: +</p> +<ul> +<li>Applications reference only the Manager objects.</li> +<li>Manager objects reference Pool objects, but not vice versa.</li> +<li>Operations objects do not reference either Manager or Pool objects.</li> +</ul> + +<p> +The following table shows a selection of classes and interfaces, +and their assignment to the three areas. +</p> +<center> +<table border="1"> +<colgroup> + <col width="50%"/> + <col width="50%"/> +</colgroup> + +<tr> +<td style="text-align: center; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager} +</td> +<td style="text-align: center; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.AbstractConnPool} +</td> +</tr> + +<tr> +<td style="text-align: center; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPooledConnAdapter} +</td> +<td style="text-align: center; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute} +</td> +</tr> + +<!-- appears on both sides! --> + +<tr> +<td style="text-align: right; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +</td> +<td style="text-align: left; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +</td> +</tr> + +<!-- ====================== --> + +<tr style="border-width: 5px;"> +</tr> + +<tr> +<td colspan="2" style="text-align: center; background-color: #00ffff;"> +{@link org.apache.http.conn.ClientConnectionOperator} +</td> +</tr> + +<tr> +<td colspan="2" style="text-align: center; background-color: #00ffff;"> +{@link org.apache.http.conn.OperatedClientConnection} +</td> +</tr> + +</table> +</center> + +<p> +The Manager area has implementations for the connection management +interfaces {@link org.apache.http.conn.ClientConnectionManager} +and {@link org.apache.http.conn.ManagedClientConnection}. +The latter is an adapter from managed to operated connections, based on a +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}. +<br/> +The Pool area shows an abstract pool class +{@link org.apache.http.impl.conn.tsccm.AbstractConnPool} +and a concrete implementation +{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute} +which uses the same basic algorithm as the +<code>MultiThreadedHttpConnectionManager</code> +in HttpClient 3.x. +A pool contains instances of +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}. +Most other classes in this package also belong to the Pool area. +<br/> +In the Operations area, you will find only the interfaces for +operated connections as defined in the org.apache.http.conn package. +The connection manager will work with all correct implementations +of these interfaces. This package therefore does not define anything +specific to the Operations area. +</p> + +<p> +As you have surely noticed, the +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +appears in both the Manager and Pool areas. +This is where things get tricky for connection garbage collection. +<br/> +A connection pool may start a background thread to implement cleanup. +In that case, the connection pool will not be garbage collected until +it is shut down, since the background thread keeps a hard reference +to the pool. The pool itself keeps hard references to the pooled entries, +which in turn reference idle connections. Neither of these is subject +to garbage collection. +Only the shutdown of the pool will stop the background thread, +thereby enabling garbage collection of the pool objects. +<br/> +A pool entry that is passed to an application by means of a connection +adapter will move from the Pool area to the Manager area. When the +connection is released by the application, the manager returns the +entry back to the pool. With that step, the pool entry moves from +the Manager area back to the Pool area. +While the entry is in the Manager area, the pool MUST NOT keep a +hard reference to it. +</p> + +<p> +The purpose of connection garbage collection is to detect when an +application fails to return a connection. In order to achieve this, +the only hard reference to the pool entry in the Manager area is +in the connection wrapper. The manager will not keep a hard reference +to the connection wrapper either, since that wrapper is effectively +moving to the Application area. +If the application drops it's reference to the connection wrapper, +that wrapper will be garbage collected, and with it the pool entry. +<br/> +In order to detect garbage collection of pool entries handed out +to the application, the pool keeps a <i>weak reference</i> to the +entry. Instances of +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntryRef} +combine the weak reference with information about the route for +which the pool entry was allocated. If one of these entry references +becomes stale, the pool can accommodate for the lost connection. +This is triggered either by a background thread waiting for the +references to be queued by the garbage collector, or by the +application calling a {@link + org.apache.http.conn.ClientConnectionManager#closeIdleConnections cleanup} +method of the connection manager. +<br/> +Basically the same trick is used for detecting garbage collection +of the connection manager itself. The pool keeps a weak reference +to the connection manager that created it. However, this will work +only if there is a background thread to detect when that reference +is queued by the garbage collector. Otherwise, a finalizer of the +connection manager will shut down the pool and release it's resources. +</p> + + +</body> +</html> diff --git a/src/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java b/src/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java new file mode 100644 index 0000000..1aa4d2c --- /dev/null +++ b/src/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/AbstractCookieAttributeHandler.java $ + * $Revision: 503525 $ + * $Date: 2007-02-04 17:15:08 -0800 (Sun, 04 Feb 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; + +public abstract class AbstractCookieAttributeHandler implements CookieAttributeHandler { + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + // Do nothing + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + // Always match + return true; + } + +} diff --git a/src/org/apache/http/impl/cookie/AbstractCookieSpec.java b/src/org/apache/http/impl/cookie/AbstractCookieSpec.java new file mode 100644 index 0000000..3e47a4d --- /dev/null +++ b/src/org/apache/http/impl/cookie/AbstractCookieSpec.java @@ -0,0 +1,110 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/AbstractCookieSpec.java $ + * $Revision: 617207 $ + * $Date: 2008-01-31 12:14:12 -0800 (Thu, 31 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieSpec; + +/** + * Abstract cookie specification which can delegate the job of parsing, + * validation or matching cookie attributes to a number of arbitrary + * {@link CookieAttributeHandler}s. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public abstract class AbstractCookieSpec implements CookieSpec { + + /** + * Stores attribute name -> attribute handler mappings + */ + private final Map<String, CookieAttributeHandler> attribHandlerMap; + + /** + * Default constructor + * */ + public AbstractCookieSpec() { + super(); + this.attribHandlerMap = new HashMap<String, CookieAttributeHandler>(10); + } + + public void registerAttribHandler( + final String name, final CookieAttributeHandler handler) { + if (name == null) { + throw new IllegalArgumentException("Attribute name may not be null"); + } + if (handler == null) { + throw new IllegalArgumentException("Attribute handler may not be null"); + } + this.attribHandlerMap.put(name, handler); + } + + /** + * Finds an attribute handler {@link CookieAttributeHandler} for the + * given attribute. Returns <tt>null</tt> if no attribute handler is + * found for the specified attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @return an attribute handler or <tt>null</tt> + */ + protected CookieAttributeHandler findAttribHandler(final String name) { + return this.attribHandlerMap.get(name); + } + + /** + * Gets attribute handler {@link CookieAttributeHandler} for the + * given attribute. + * + * @param name attribute name. e.g. Domain, Path, etc. + * @throws IllegalStateException if handler not found for the + * specified attribute. + */ + protected CookieAttributeHandler getAttribHandler(final String name) { + CookieAttributeHandler handler = findAttribHandler(name); + if (handler == null) { + throw new IllegalStateException("Handler not registered for " + + name + " attribute."); + } else { + return handler; + } + } + + protected Collection<CookieAttributeHandler> getAttribHandlers() { + return this.attribHandlerMap.values(); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicClientCookie.java b/src/org/apache/http/impl/cookie/BasicClientCookie.java new file mode 100644 index 0000000..6ec6c2b --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicClientCookie.java @@ -0,0 +1,376 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie.java $ + * $Revision: 659191 $ + * $Date: 2008-05-22 11:26:53 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.SetCookie; + +/** + * HTTP "magic-cookie" represents a piece of state information + * that the HTTP agent and the target server can exchange to maintain + * a session. + * + * @author B.C. Holmes + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a> + * @author Rod Waldhoff + * @author dIon Gillard + * @author Sean C. Sullivan + * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a> + * @author Marc A. Saegesser + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @version $Revision: 659191 $ + */ +public class BasicClientCookie implements SetCookie, ClientCookie, Cloneable { + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicClientCookie(final String name, final String value) { + super(); + if (name == null) { + throw new IllegalArgumentException("Name may not be null"); + } + this.name = name; + this.attribs = new HashMap<String, String>(); + this.value = value; + } + + /** + * Returns the name. + * + * @return String name The name + */ + public String getName() { + return this.name; + } + + /** + * Returns the value. + * + * @return String value The current value. + */ + public String getValue() { + return this.value; + } + + /** + * Sets the value + * + * @param value + */ + public void setValue(final String value) { + this.value = value; + } + + /** + * Returns the comment describing the purpose of this cookie, or + * <tt>null</tt> if no such comment has been defined. + * + * @return comment + * + * @see #setComment(String) + */ + public String getComment() { + return cookieComment; + } + + /** + * If a user agent (web browser) presents this cookie to a user, the + * cookie's purpose will be described using this comment. + * + * @param comment + * + * @see #getComment() + */ + public void setComment(String comment) { + cookieComment = comment; + } + + + /** + * Returns null. Cookies prior to RFC2965 do not set this attribute + */ + public String getCommentURL() { + return null; + } + + + /** + * Returns the expiration {@link Date} of the cookie, or <tt>null</tt> + * if none exists. + * <p><strong>Note:</strong> the object returned by this method is + * considered immutable. Changing it (e.g. using setTime()) could result + * in undefined behaviour. Do so at your peril. </p> + * @return Expiration {@link Date}, or <tt>null</tt>. + * + * @see #setExpiryDate(java.util.Date) + * + */ + public Date getExpiryDate() { + return cookieExpiryDate; + } + + /** + * Sets expiration date. + * <p><strong>Note:</strong> the object returned by this method is considered + * immutable. Changing it (e.g. using setTime()) could result in undefined + * behaviour. Do so at your peril.</p> + * + * @param expiryDate the {@link Date} after which this cookie is no longer valid. + * + * @see #getExpiryDate + * + */ + public void setExpiryDate (Date expiryDate) { + cookieExpiryDate = expiryDate; + } + + + /** + * Returns <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise. + * + * @return <tt>false</tt> if the cookie should be discarded at the end + * of the "session"; <tt>true</tt> otherwise + */ + public boolean isPersistent() { + return (null != cookieExpiryDate); + } + + + /** + * Returns domain attribute of the cookie. + * + * @return the value of the domain attribute + * + * @see #setDomain(java.lang.String) + */ + public String getDomain() { + return cookieDomain; + } + + /** + * Sets the domain attribute. + * + * @param domain The value of the domain attribute + * + * @see #getDomain + */ + public void setDomain(String domain) { + if (domain != null) { + cookieDomain = domain.toLowerCase(Locale.ENGLISH); + } else { + cookieDomain = null; + } + } + + + /** + * Returns the path attribute of the cookie + * + * @return The value of the path attribute. + * + * @see #setPath(java.lang.String) + */ + public String getPath() { + return cookiePath; + } + + /** + * Sets the path attribute. + * + * @param path The value of the path attribute + * + * @see #getPath + * + */ + public void setPath(String path) { + cookiePath = path; + } + + /** + * @return <code>true</code> if this cookie should only be sent over secure connections. + * @see #setSecure(boolean) + */ + public boolean isSecure() { + return isSecure; + } + + /** + * Sets the secure attribute of the cookie. + * <p> + * When <tt>true</tt> the cookie should only be sent + * using a secure protocol (https). This should only be set when + * the cookie's originating server used a secure protocol to set the + * cookie's value. + * + * @param secure The value of the secure attribute + * + * @see #isSecure() + */ + public void setSecure (boolean secure) { + isSecure = secure; + } + + + /** + * Returns null. Cookies prior to RFC2965 do not set this attribute + */ + public int[] getPorts() { + return null; + } + + + /** + * Returns the version of the cookie specification to which this + * cookie conforms. + * + * @return the version of the cookie. + * + * @see #setVersion(int) + * + */ + public int getVersion() { + return cookieVersion; + } + + /** + * Sets the version of the cookie specification to which this + * cookie conforms. + * + * @param version the version of the cookie. + * + * @see #getVersion + */ + public void setVersion(int version) { + cookieVersion = version; + } + + /** + * Returns true if this cookie has expired. + * @param date Current time + * + * @return <tt>true</tt> if the cookie has expired. + */ + public boolean isExpired(final Date date) { + if (date == null) { + throw new IllegalArgumentException("Date may not be null"); + } + return (cookieExpiryDate != null + && cookieExpiryDate.getTime() <= date.getTime()); + } + + public void setAttribute(final String name, final String value) { + this.attribs.put(name, value); + } + + public String getAttribute(final String name) { + return this.attribs.get(name); + } + + public boolean containsAttribute(final String name) { + return this.attribs.get(name) != null; + } + + @Override + public Object clone() throws CloneNotSupportedException { + BasicClientCookie clone = (BasicClientCookie) super.clone(); + clone.attribs = new HashMap<String, String>(this.attribs); + return clone; + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("[version: "); + buffer.append(Integer.toString(this.cookieVersion)); + buffer.append("]"); + buffer.append("[name: "); + buffer.append(this.name); + buffer.append("]"); + buffer.append("[value: "); + buffer.append(this.value); + buffer.append("]"); + buffer.append("[domain: "); + buffer.append(this.cookieDomain); + buffer.append("]"); + buffer.append("[path: "); + buffer.append(this.cookiePath); + buffer.append("]"); + buffer.append("[expiry: "); + buffer.append(this.cookieExpiryDate); + buffer.append("]"); + return buffer.toString(); + } + + // ----------------------------------------------------- Instance Variables + + /** Cookie name */ + private final String name; + + /** Cookie attributes as specified by the origin server */ + private Map<String, String> attribs; + + /** Cookie value */ + private String value; + + /** Comment attribute. */ + private String cookieComment; + + /** Domain attribute. */ + private String cookieDomain; + + /** Expiration {@link Date}. */ + private Date cookieExpiryDate; + + /** Path attribute. */ + private String cookiePath; + + /** My secure flag. */ + private boolean isSecure; + + /** The version of the cookie specification I was created from. */ + private int cookieVersion; + +} + diff --git a/src/org/apache/http/impl/cookie/BasicClientCookie2.java b/src/org/apache/http/impl/cookie/BasicClientCookie2.java new file mode 100644 index 0000000..86ec60d --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicClientCookie2.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicClientCookie2.java $ + * $Revision: 659191 $ + * $Date: 2008-05-22 11:26:53 -0700 (Thu, 22 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Date; + +import org.apache.http.cookie.SetCookie2; + +/** + * HTTP "magic-cookie" represents a piece of state information + * that the HTTP agent and the target server can exchange to maintain + * a session as specified by RFC2965. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public class BasicClientCookie2 extends BasicClientCookie implements SetCookie2 { + + private String commentURL; + private int[] ports; + private boolean discard; + + /** + * Default Constructor taking a name and a value. The value may be null. + * + * @param name The name. + * @param value The value. + */ + public BasicClientCookie2(final String name, final String value) { + super(name, value); + } + + @Override + public int[] getPorts() { + return this.ports; + } + + public void setPorts(final int[] ports) { + this.ports = ports; + } + + @Override + public String getCommentURL() { + return this.commentURL; + } + + public void setCommentURL(final String commentURL) { + this.commentURL = commentURL; + } + + public void setDiscard(boolean discard) { + this.discard = discard; + } + + @Override + public boolean isPersistent() { + return !this.discard && super.isPersistent(); + } + + @Override + public boolean isExpired(final Date date) { + return this.discard || super.isExpired(date); + } + + @Override + public Object clone() throws CloneNotSupportedException { + BasicClientCookie2 clone = (BasicClientCookie2) super.clone(); + clone.ports = this.ports.clone(); + return clone; + } + +} + diff --git a/src/org/apache/http/impl/cookie/BasicCommentHandler.java b/src/org/apache/http/impl/cookie/BasicCommentHandler.java new file mode 100644 index 0000000..ce8baea --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicCommentHandler.java @@ -0,0 +1,50 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicCommentHandler.java $ + * $Revision: 558519 $ + * $Date: 2007-07-22 11:19:49 -0700 (Sun, 22 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicCommentHandler extends AbstractCookieAttributeHandler { + + public BasicCommentHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + cookie.setComment(value); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicDomainHandler.java b/src/org/apache/http/impl/cookie/BasicDomainHandler.java new file mode 100644 index 0000000..267faf8 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicDomainHandler.java @@ -0,0 +1,122 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicDomainHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicDomainHandler implements CookieAttributeHandler { + + public BasicDomainHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for domain attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for domain attribute"); + } + cookie.setDomain(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + // Validate the cookies domain attribute. NOTE: Domains without + // any dots are allowed to support hosts on private LANs that don't + // have DNS names. Since they have no dots, to domain-match the + // request-host and domain must be identical for the cookie to sent + // back to the origin-server. + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + throw new MalformedCookieException("Cookie domain may not be null"); + } + if (host.contains(".")) { + // Not required to have at least two dots. RFC 2965. + // A Set-Cookie2 with Domain=ajax.com will be accepted. + + // domain must match host + if (!host.endsWith(domain)) { + if (domain.startsWith(".")) { + domain = domain.substring(1, domain.length()); + } + if (!host.equals(domain)) { + throw new MalformedCookieException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + } + } else { + if (!host.equals(domain)) { + throw new MalformedCookieException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + if (host.equals(domain)) { + return true; + } + if (!domain.startsWith(".")) { + domain = '.' + domain; + } + return host.endsWith(domain) || host.equals(domain.substring(1)); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicExpiresHandler.java b/src/org/apache/http/impl/cookie/BasicExpiresHandler.java new file mode 100644 index 0000000..a53519e --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicExpiresHandler.java @@ -0,0 +1,65 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicExpiresHandler.java $ + * $Revision: 558519 $ + * $Date: 2007-07-22 11:19:49 -0700 (Sun, 22 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + + +public class BasicExpiresHandler extends AbstractCookieAttributeHandler { + + /** Valid date patterns */ + private final String[] datepatterns; + + public BasicExpiresHandler(final String[] datepatterns) { + if (datepatterns == null) { + throw new IllegalArgumentException("Array of date patterns may not be null"); + } + this.datepatterns = datepatterns; + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for expires attribute"); + } + try { + cookie.setExpiryDate(DateUtils.parseDate(value, this.datepatterns)); + } catch (DateParseException dpe) { + throw new MalformedCookieException("Unable to parse expires attribute: " + + value); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicMaxAgeHandler.java b/src/org/apache/http/impl/cookie/BasicMaxAgeHandler.java new file mode 100644 index 0000000..92a5c7d --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicMaxAgeHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicMaxAgeHandler.java $ + * $Revision: 581953 $ + * $Date: 2007-10-04 08:53:50 -0700 (Thu, 04 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import java.util.Date; + +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicMaxAgeHandler extends AbstractCookieAttributeHandler { + + public BasicMaxAgeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for max-age attribute"); + } + int age; + try { + age = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new MalformedCookieException ("Invalid max-age attribute: " + + value); + } + if (age < 0) { + throw new MalformedCookieException ("Negative max-age attribute: " + + value); + } + cookie.setExpiryDate(new Date(System.currentTimeMillis() + age * 1000L)); + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicPathHandler.java b/src/org/apache/http/impl/cookie/BasicPathHandler.java new file mode 100644 index 0000000..43a12c8 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicPathHandler.java @@ -0,0 +1,91 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicPathHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicPathHandler implements CookieAttributeHandler { + + public BasicPathHandler() { + super(); + } + + public void parse(final SetCookie cookie, String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null || value.trim().length() == 0) { + value = "/"; + } + cookie.setPath(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (!match(cookie, origin)) { + throw new MalformedCookieException( + "Illegal path attribute \"" + cookie.getPath() + + "\". Path of origin: \"" + origin.getPath() + "\""); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String targetpath = origin.getPath(); + String topmostPath = cookie.getPath(); + if (topmostPath == null) { + topmostPath = "/"; + } + if (topmostPath.length() > 1 && topmostPath.endsWith("/")) { + topmostPath = topmostPath.substring(0, topmostPath.length() - 1); + } + boolean match = targetpath.startsWith (topmostPath); + // if there is a match and these values are not exactly the same we have + // to make sure we're not matcing "/foobar" and "/foo" + if (match && targetpath.length() != topmostPath.length()) { + if (!topmostPath.endsWith("/")) { + match = (targetpath.charAt(topmostPath.length()) == '/'); + } + } + return match; + } + +} diff --git a/src/org/apache/http/impl/cookie/BasicSecureHandler.java b/src/org/apache/http/impl/cookie/BasicSecureHandler.java new file mode 100644 index 0000000..9100b9c --- /dev/null +++ b/src/org/apache/http/impl/cookie/BasicSecureHandler.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BasicSecureHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class BasicSecureHandler extends AbstractCookieAttributeHandler { + + public BasicSecureHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + cookie.setSecure(true); + } + + @Override + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + return !cookie.isSecure() || origin.isSecure(); + } + +} diff --git a/src/org/apache/http/impl/cookie/BestMatchSpec.java b/src/org/apache/http/impl/cookie/BestMatchSpec.java new file mode 100644 index 0000000..e33fec3 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BestMatchSpec.java @@ -0,0 +1,186 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java $ + * $Revision: 657334 $ + * $Date: 2008-05-17 04:44:16 -0700 (Sat, 17 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.List; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.MalformedCookieException; + +/** + * 'Meta' cookie specification that selects a cookie policy depending + * on the format of the cookie(s) + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BestMatchSpec implements CookieSpec { + + private final String[] datepatterns; + private final boolean oneHeader; + + private RFC2965Spec strict; + private BrowserCompatSpec compat; + private NetscapeDraftSpec netscape; + + public BestMatchSpec(final String[] datepatterns, boolean oneHeader) { + super(); + this.datepatterns = datepatterns; + this.oneHeader = oneHeader; + } + + public BestMatchSpec() { + this(null, false); + } + + private RFC2965Spec getStrict() { + if (this.strict == null) { + this.strict = new RFC2965Spec(this.datepatterns, this.oneHeader); + } + return strict; + } + + private BrowserCompatSpec getCompat() { + if (this.compat == null) { + this.compat = new BrowserCompatSpec(this.datepatterns); + } + return compat; + } + + private NetscapeDraftSpec getNetscape() { + if (this.netscape == null) { + String[] patterns = this.datepatterns; + if (patterns == null) { + patterns = BrowserCompatSpec.DATE_PATTERNS; + } + this.netscape = new NetscapeDraftSpec(patterns); + } + return netscape; + } + + public List<Cookie> parse( + final Header header, + final CookieOrigin origin) throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + HeaderElement[] helems = header.getElements(); + boolean versioned = false; + boolean netscape = false; + for (HeaderElement helem: helems) { + if (helem.getParameterByName("version") != null) { + versioned = true; + } + if (helem.getParameterByName("expires") != null) { + netscape = true; + } + } + if (netscape) { + + } + // Do we have a cookie with a version attribute? + if (versioned) { + return getStrict().parse(helems, origin); + } else if (netscape) { + // Need to parse the header again, + // because Netscape draft cannot handle + // comma separators + return getNetscape().parse(header, origin); + } else { + return getCompat().parse(helems, origin); + } + } + + public void validate( + final Cookie cookie, + final CookieOrigin origin) throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + if (cookie.getVersion() > 0) { + getStrict().validate(cookie, origin); + } else { + getCompat().validate(cookie, origin); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + if (cookie.getVersion() > 0) { + return getStrict().match(cookie, origin); + } else { + return getCompat().match(cookie, origin); + } + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookie may not be null"); + } + int version = Integer.MAX_VALUE; + for (Cookie cookie: cookies) { + if (cookie.getVersion() < version) { + version = cookie.getVersion(); + } + } + if (version > 0) { + return getStrict().formatCookies(cookies); + } else { + return getCompat().formatCookies(cookies); + } + } + + public int getVersion() { + return getStrict().getVersion(); + } + + public Header getVersionHeader() { + return getStrict().getVersionHeader(); + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/BestMatchSpecFactory.java b/src/org/apache/http/impl/cookie/BestMatchSpecFactory.java new file mode 100644 index 0000000..cb632bb --- /dev/null +++ b/src/org/apache/http/impl/cookie/BestMatchSpecFactory.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BestMatchSpecFactory.java $ + * $Revision: 613707 $ + * $Date: 2008-01-20 16:28:37 -0800 (Sun, 20 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BestMatchSpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new BestMatchSpec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS), + params.getBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, false)); + } else { + return new BestMatchSpec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/BrowserCompatSpec.java b/src/org/apache/http/impl/cookie/BrowserCompatSpec.java new file mode 100644 index 0000000..d7bc0da --- /dev/null +++ b/src/org/apache/http/impl/cookie/BrowserCompatSpec.java @@ -0,0 +1,188 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java $ + * $Revision: 657334 $ + * $Date: 2008-05-17 04:44:16 -0700 (Sat, 17 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +/** + * Cookie specification that strives to closely mimic (mis)behavior of + * common web browser applications such as Microsoft Internet Explorer + * and Mozilla FireFox. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BrowserCompatSpec extends CookieSpecBase { + + /** Valid date patterns used per default */ + protected static final String[] DATE_PATTERNS = new String[] { + DateUtils.PATTERN_RFC1123, + DateUtils.PATTERN_RFC1036, + DateUtils.PATTERN_ASCTIME, + "EEE, dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MMM-yyyy HH-mm-ss z", + "EEE, dd MMM yy HH:mm:ss z", + "EEE dd-MMM-yyyy HH:mm:ss z", + "EEE dd MMM yyyy HH:mm:ss z", + "EEE dd-MMM-yyyy HH-mm-ss z", + "EEE dd-MMM-yy HH:mm:ss z", + "EEE dd MMM yy HH:mm:ss z", + "EEE,dd-MMM-yy HH:mm:ss z", + "EEE,dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MM-yyyy HH:mm:ss z", + }; + + private final String[] datepatterns; + + /** Default constructor */ + public BrowserCompatSpec(final String[] datepatterns) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = DATE_PATTERNS; + } + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new BasicDomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public BrowserCompatSpec() { + this(null); + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String headervalue = header.getValue(); + boolean isNetscapeCookie = false; + int i1 = headervalue.toLowerCase(Locale.ENGLISH).indexOf("expires="); + if (i1 != -1) { + i1 += "expires=".length(); + int i2 = headervalue.indexOf(';', i1); + if (i2 == -1) { + i2 = headervalue.length(); + } + try { + DateUtils.parseDate(headervalue.substring(i1, i2), this.datepatterns); + isNetscapeCookie = true; + } catch (DateParseException e) { + // Does not look like a valid expiry date + } + } + HeaderElement[] elems = null; + if (isNetscapeCookie) { + NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + CharArrayBuffer buffer; + ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + elems = new HeaderElement[] { parser.parseHeader(buffer, cursor) }; + } else { + elems = header.getElements(); + } + return parse(elems, origin); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookies may not be null"); + } + if (cookies.isEmpty()) { + throw new IllegalArgumentException("List of cookies may not be empty"); + } + CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + for (int i = 0; i < cookies.size(); i++) { + Cookie cookie = cookies.get(i); + if (i > 0) { + buffer.append("; "); + } + buffer.append(cookie.getName()); + buffer.append("="); + String s = cookie.getValue(); + if (s != null) { + buffer.append(s); + } + } + List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + public int getVersion() { + return 0; + } + + public Header getVersionHeader() { + return null; + } + +} diff --git a/src/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java b/src/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java new file mode 100644 index 0000000..71c0c05 --- /dev/null +++ b/src/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java @@ -0,0 +1,56 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpecFactory.java $ + * $Revision: 576068 $ + * $Date: 2007-09-16 03:25:01 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class BrowserCompatSpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new BrowserCompatSpec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS)); + } else { + return new BrowserCompatSpec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/CookieSpecBase.java b/src/org/apache/http/impl/cookie/CookieSpecBase.java new file mode 100644 index 0000000..8e70bb1 --- /dev/null +++ b/src/org/apache/http/impl/cookie/CookieSpecBase.java @@ -0,0 +1,131 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/CookieSpecBase.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; + +/** + * Cookie management functions shared by all specification. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public abstract class CookieSpecBase extends AbstractCookieSpec { + + protected static String getDefaultPath(final CookieOrigin origin) { + String defaultPath = origin.getPath(); + int lastSlashIndex = defaultPath.lastIndexOf('/'); + if (lastSlashIndex >= 0) { + if (lastSlashIndex == 0) { + //Do not remove the very first slash + lastSlashIndex = 1; + } + defaultPath = defaultPath.substring(0, lastSlashIndex); + } + return defaultPath; + } + + protected static String getDefaultDomain(final CookieOrigin origin) { + return origin.getHost(); + } + + protected List<Cookie> parse(final HeaderElement[] elems, final CookieOrigin origin) + throws MalformedCookieException { + List<Cookie> cookies = new ArrayList<Cookie>(elems.length); + for (HeaderElement headerelement : elems) { + String name = headerelement.getName(); + String value = headerelement.getValue(); + if (name == null || name.length() == 0) { + throw new MalformedCookieException("Cookie name may not be empty"); + } + + BasicClientCookie cookie = new BasicClientCookie(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + + // cycle through the parameters + NameValuePair[] attribs = headerelement.getParameters(); + for (int j = attribs.length - 1; j >= 0; j--) { + NameValuePair attrib = attribs[j]; + String s = attrib.getName().toLowerCase(Locale.ENGLISH); + + cookie.setAttribute(s, attrib.getValue()); + + CookieAttributeHandler handler = findAttribHandler(s); + if (handler != null) { + handler.parse(cookie, attrib.getValue()); + } + } + cookies.add(cookie); + } + return cookies; + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + for (CookieAttributeHandler handler: getAttribHandlers()) { + handler.validate(cookie, origin); + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + for (CookieAttributeHandler handler: getAttribHandlers()) { + if (!handler.match(cookie, origin)) { + return false; + } + } + return true; + } + +} diff --git a/src/org/apache/http/impl/cookie/DateParseException.java b/src/org/apache/http/impl/cookie/DateParseException.java new file mode 100644 index 0000000..c80b669 --- /dev/null +++ b/src/org/apache/http/impl/cookie/DateParseException.java @@ -0,0 +1,60 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/DateParseException.java $ + * $Revision: 609105 $ + * $Date: 2008-01-05 00:55:00 -0800 (Sat, 05 Jan 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + + +/** + * An exception to indicate an error parsing a date string. + * + * @see DateUtils + * + * @author Michael Becke + */ +public class DateParseException extends Exception { + + private static final long serialVersionUID = 4417696455000643370L; + + /** + * + */ + public DateParseException() { + super(); + } + + /** + * @param message the exception message + */ + public DateParseException(String message) { + super(message); + } + +} diff --git a/src/org/apache/http/impl/cookie/DateUtils.java b/src/org/apache/http/impl/cookie/DateUtils.java new file mode 100644 index 0000000..a0a056c --- /dev/null +++ b/src/org/apache/http/impl/cookie/DateUtils.java @@ -0,0 +1,261 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/DateUtils.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.lang.ref.SoftReference; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +/** + * A utility class for parsing and formatting HTTP dates as used in cookies and + * other headers. This class handles dates as defined by RFC 2616 section + * 3.3.1 as well as some other common non-standard formats. + * + * @author Christopher Brown + * @author Michael Becke + */ +public final class DateUtils { + + /** + * Date format pattern used to parse HTTP date headers in RFC 1123 format. + */ + public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in RFC 1036 format. + */ + public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in ANSI C + * <code>asctime()</code> format. + */ + public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; + + private static final String[] DEFAULT_PATTERNS = new String[] { + PATTERN_RFC1036, + PATTERN_RFC1123, + PATTERN_ASCTIME + }; + + private static final Date DEFAULT_TWO_DIGIT_YEAR_START; + + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + static { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(GMT); + calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); + } + + /** + * Parses a date value. The formats used for parsing the date value are retrieved from + * the default http params. + * + * @param dateValue the date value to parse + * + * @return the parsed date + * + * @throws DateParseException if the value could not be parsed using any of the + * supported date formats + */ + public static Date parseDate(String dateValue) throws DateParseException { + return parseDate(dateValue, null, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * + * @return the parsed date + * + * @throws DateParseException if none of the dataFormats could parse the dateValue + */ + public static Date parseDate(final String dateValue, String[] dateFormats) + throws DateParseException { + return parseDate(dateValue, dateFormats, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * @param startDate During parsing, two digit years will be placed in the range + * <code>startDate</code> to <code>startDate + 100 years</code>. This value may + * be <code>null</code>. When <code>null</code> is given as a parameter, year + * <code>2000</code> will be used. + * + * @return the parsed date + * + * @throws DateParseException if none of the dataFormats could parse the dateValue + */ + public static Date parseDate( + String dateValue, + String[] dateFormats, + Date startDate + ) throws DateParseException { + + if (dateValue == null) { + throw new IllegalArgumentException("dateValue is null"); + } + if (dateFormats == null) { + dateFormats = DEFAULT_PATTERNS; + } + if (startDate == null) { + startDate = DEFAULT_TWO_DIGIT_YEAR_START; + } + // trim single quotes around date if present + // see issue #5279 + if (dateValue.length() > 1 + && dateValue.startsWith("'") + && dateValue.endsWith("'") + ) { + dateValue = dateValue.substring (1, dateValue.length() - 1); + } + + for (String dateFormat : dateFormats) { + SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); + dateParser.set2DigitYearStart(startDate); + + try { + return dateParser.parse(dateValue); + } catch (ParseException pe) { + // ignore this exception, we will try the next format + } + } + + // we were unable to parse the date + throw new DateParseException("Unable to parse the date " + dateValue); + } + + /** + * Formats the given date according to the RFC 1123 pattern. + * + * @param date The date to format. + * @return An RFC 1123 formatted date string. + * + * @see #PATTERN_RFC1123 + */ + public static String formatDate(Date date) { + return formatDate(date, PATTERN_RFC1123); + } + + /** + * Formats the given date according to the specified pattern. The pattern + * must conform to that used by the {@link SimpleDateFormat simple date + * format} class. + * + * @param date The date to format. + * @param pattern The pattern to use for formatting the date. + * @return A formatted date string. + * + * @throws IllegalArgumentException If the given date pattern is invalid. + * + * @see SimpleDateFormat + */ + public static String formatDate(Date date, String pattern) { + if (date == null) throw new IllegalArgumentException("date is null"); + if (pattern == null) throw new IllegalArgumentException("pattern is null"); + + SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern); + return formatter.format(date); + } + + /** This class should not be instantiated. */ + private DateUtils() { + } + + /** + * A factory for {@link SimpleDateFormat}s. The instances are stored in a + * threadlocal way because SimpleDateFormat is not threadsafe as noted in + * {@link SimpleDateFormat its javadoc}. + * + * @author Daniel Mueller + */ + final static class DateFormatHolder { + + private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> + THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() { + + @Override + protected SoftReference<Map<String, SimpleDateFormat>> initialValue() { + return new SoftReference<Map<String, SimpleDateFormat>>( + new HashMap<String, SimpleDateFormat>()); + } + + }; + + /** + * creates a {@link SimpleDateFormat} for the requested format string. + * + * @param pattern + * a non-<code>null</code> format String according to + * {@link SimpleDateFormat}. The format is not checked against + * <code>null</code> since all paths go through + * {@link DateUtils}. + * @return the requested format. This simple dateformat should not be used + * to {@link SimpleDateFormat#applyPattern(String) apply} to a + * different pattern. + */ + public static SimpleDateFormat formatFor(String pattern) { + SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get(); + Map<String, SimpleDateFormat> formats = ref.get(); + if (formats == null) { + formats = new HashMap<String, SimpleDateFormat>(); + THREADLOCAL_FORMATS.set( + new SoftReference<Map<String, SimpleDateFormat>>(formats)); + } + + SimpleDateFormat format = formats.get(pattern); + if (format == null) { + format = new SimpleDateFormat(pattern, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + formats.put(pattern, format); + } + + return format; + } + + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDomainHandler.java b/src/org/apache/http/impl/cookie/NetscapeDomainHandler.java new file mode 100644 index 0000000..8b785ae --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDomainHandler.java @@ -0,0 +1,106 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDomainHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import java.util.Locale; +import java.util.StringTokenizer; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; + +public class NetscapeDomainHandler extends BasicDomainHandler { + + public NetscapeDomainHandler() { + super(); + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + super.validate(cookie, origin); + // Perform Netscape Cookie draft specific validation + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (host.contains(".")) { + int domainParts = new StringTokenizer(domain, ".").countTokens(); + + if (isSpecialDomain(domain)) { + if (domainParts < 2) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates the Netscape cookie specification for " + + "special domains"); + } + } else { + if (domainParts < 3) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates the Netscape cookie specification"); + } + } + } + } + + /** + * Checks if the given domain is in one of the seven special + * top level domains defined by the Netscape cookie specification. + * @param domain The domain. + * @return True if the specified domain is "special" + */ + private static boolean isSpecialDomain(final String domain) { + final String ucDomain = domain.toUpperCase(Locale.ENGLISH); + return ucDomain.endsWith(".COM") + || ucDomain.endsWith(".EDU") + || ucDomain.endsWith(".NET") + || ucDomain.endsWith(".GOV") + || ucDomain.endsWith(".MIL") + || ucDomain.endsWith(".ORG") + || ucDomain.endsWith(".INT"); + } + + @Override +public boolean match(Cookie cookie, CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + return host.endsWith(domain); + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java b/src/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java new file mode 100644 index 0000000..ca6b7fa --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java @@ -0,0 +1,78 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java $ + * $Revision: 603563 $ + * $Date: 2007-12-12 03:17:55 -0800 (Wed, 12 Dec 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.message.BasicHeaderElement; +import org.apache.http.message.BasicHeaderValueParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +public class NetscapeDraftHeaderParser { + + public final static NetscapeDraftHeaderParser DEFAULT = new NetscapeDraftHeaderParser(); + + private final static char[] DELIMITERS = new char[] { ';' }; + + private final BasicHeaderValueParser nvpParser; + + public NetscapeDraftHeaderParser() { + super(); + this.nvpParser = BasicHeaderValueParser.DEFAULT; + } + + public HeaderElement parseHeader( + final CharArrayBuffer buffer, + final ParserCursor cursor) throws ParseException { + if (buffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + if (cursor == null) { + throw new IllegalArgumentException("Parser cursor may not be null"); + } + NameValuePair nvp = this.nvpParser.parseNameValuePair(buffer, cursor, DELIMITERS); + List<NameValuePair> params = new ArrayList<NameValuePair>(); + while (!cursor.atEnd()) { + NameValuePair param = this.nvpParser.parseNameValuePair(buffer, cursor, DELIMITERS); + params.add(param); + } + return new BasicHeaderElement( + nvp.getName(), + nvp.getValue(), params.toArray(new NameValuePair[params.size()])); + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDraftSpec.java b/src/org/apache/http/impl/cookie/NetscapeDraftSpec.java new file mode 100644 index 0000000..3bc4f9f --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDraftSpec.java @@ -0,0 +1,182 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.http.FormattedHeader; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; + +/** + * Netscape cookie draft compliant cookie policy + * + * @author B.C. Holmes + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a> + * @author Rod Waldhoff + * @author dIon Gillard + * @author Sean C. Sullivan + * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a> + * @author Marc A. Saegesser + * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ +public class NetscapeDraftSpec extends CookieSpecBase { + + protected static final String EXPIRES_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z"; + + private final String[] datepatterns; + + /** Default constructor */ + public NetscapeDraftSpec(final String[] datepatterns) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = new String[] { EXPIRES_PATTERN }; + } + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new NetscapeDomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public NetscapeDraftSpec() { + this(null); + } + + /** + * Parses the Set-Cookie value into an array of <tt>Cookie</tt>s. + * + * <p>Syntax of the Set-Cookie HTTP Response Header:</p> + * + * <p>This is the format a CGI script would use to add to + * the HTTP headers a new piece of data which is to be stored by + * the client for later retrieval.</p> + * + * <PRE> + * Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure + * </PRE> + * + * <p>Please note that Netscape draft specification does not fully + * conform to the HTTP header format. Netscape draft does not specify + * whether multiple cookies may be sent in one header. Hence, comma + * character may be present in unquoted cookie value or unquoted + * parameter value.</p> + * + * @see <a href="http://wp.netscape.com/newsref/std/cookie_spec.html"> + * The Cookie Spec.</a> + * + * @param header the <tt>Set-Cookie</tt> received from the server + * @return an array of <tt>Cookie</tt>s parsed from the Set-Cookie value + * @throws MalformedCookieException if an exception occurs during parsing + */ + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT; + CharArrayBuffer buffer; + ParserCursor cursor; + if (header instanceof FormattedHeader) { + buffer = ((FormattedHeader) header).getBuffer(); + cursor = new ParserCursor( + ((FormattedHeader) header).getValuePos(), + buffer.length()); + } else { + String s = header.getValue(); + if (s == null) { + throw new MalformedCookieException("Header value is null"); + } + buffer = new CharArrayBuffer(s.length()); + buffer.append(s); + cursor = new ParserCursor(0, buffer.length()); + } + return parse(new HeaderElement[] { parser.parseHeader(buffer, cursor) }, origin); + } + + public List<Header> formatCookies(final List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookies may not be null"); + } + if (cookies.isEmpty()) { + throw new IllegalArgumentException("List of cookies may not be empty"); + } + CharArrayBuffer buffer = new CharArrayBuffer(20 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + for (int i = 0; i < cookies.size(); i++) { + Cookie cookie = cookies.get(i); + if (i > 0) { + buffer.append("; "); + } + buffer.append(cookie.getName()); + String s = cookie.getValue(); + if (s != null) { + buffer.append("="); + buffer.append(s); + } + } + List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + public int getVersion() { + return 0; + } + + public Header getVersionHeader() { + return null; + } + +} diff --git a/src/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java b/src/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java new file mode 100644 index 0000000..0dcb187 --- /dev/null +++ b/src/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java @@ -0,0 +1,56 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpecFactory.java $ + * $Revision: 657334 $ + * $Date: 2008-05-17 04:44:16 -0700 (Sat, 17 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class NetscapeDraftSpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new NetscapeDraftSpec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS)); + } else { + return new NetscapeDraftSpec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109DomainHandler.java b/src/org/apache/http/impl/cookie/RFC2109DomainHandler.java new file mode 100644 index 0000000..9cfd484 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109DomainHandler.java @@ -0,0 +1,126 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109DomainHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import java.util.Locale; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class RFC2109DomainHandler implements CookieAttributeHandler { + + public RFC2109DomainHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for domain attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for domain attribute"); + } + cookie.setDomain(value); + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + throw new MalformedCookieException("Cookie domain may not be null"); + } + if (!domain.equals(host)) { + int dotIndex = domain.indexOf('.'); + if (dotIndex == -1) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" does not match the host \"" + + host + "\""); + } + // domain must start with dot + if (!domain.startsWith(".")) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates RFC 2109: domain must start with a dot"); + } + // domain must have at least one embedded dot + dotIndex = domain.indexOf('.', 1); + if (dotIndex < 0 || dotIndex == domain.length() - 1) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates RFC 2109: domain must contain an embedded dot"); + } + host = host.toLowerCase(Locale.ENGLISH); + if (!host.endsWith(domain)) { + throw new MalformedCookieException( + "Illegal domain attribute \"" + domain + + "\". Domain of origin: \"" + host + "\""); + } + // host minus domain may not contain any dots + String hostWithoutDomain = host.substring(0, host.length() - domain.length()); + if (hostWithoutDomain.indexOf('.') != -1) { + throw new MalformedCookieException("Domain attribute \"" + + domain + + "\" violates RFC 2109: host minus domain may not contain any dots"); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost(); + String domain = cookie.getDomain(); + if (domain == null) { + return false; + } + return host.equals(domain) || (domain.startsWith(".") && host.endsWith(domain)); + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109Spec.java b/src/org/apache/http/impl/cookie/RFC2109Spec.java new file mode 100644 index 0000000..9e45408 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109Spec.java @@ -0,0 +1,246 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.CookiePathComparator; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; + +/** + * RFC 2109 compliant cookie policy + * + * @author B.C. Holmes + * @author <a href="mailto:jericho@thinkfree.com">Park, Sung-Gu</a> + * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a> + * @author Rod Waldhoff + * @author dIon Gillard + * @author Sean C. Sullivan + * @author <a href="mailto:JEvans@Cyveillance.com">John Evans</a> + * @author Marc A. Saegesser + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ + +public class RFC2109Spec extends CookieSpecBase { + + private final static CookiePathComparator PATH_COMPARATOR = new CookiePathComparator(); + + private final static String[] DATE_PATTERNS = { + DateUtils.PATTERN_RFC1123, + DateUtils.PATTERN_RFC1036, + DateUtils.PATTERN_ASCTIME + }; + + private final String[] datepatterns; + private final boolean oneHeader; + + /** Default constructor */ + public RFC2109Spec(final String[] datepatterns, boolean oneHeader) { + super(); + if (datepatterns != null) { + this.datepatterns = datepatterns.clone(); + } else { + this.datepatterns = DATE_PATTERNS; + } + this.oneHeader = oneHeader; + registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2109VersionHandler()); + registerAttribHandler(ClientCookie.PATH_ATTR, new BasicPathHandler()); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2109DomainHandler()); + registerAttribHandler(ClientCookie.MAX_AGE_ATTR, new BasicMaxAgeHandler()); + registerAttribHandler(ClientCookie.SECURE_ATTR, new BasicSecureHandler()); + registerAttribHandler(ClientCookie.COMMENT_ATTR, new BasicCommentHandler()); + registerAttribHandler(ClientCookie.EXPIRES_ATTR, new BasicExpiresHandler( + this.datepatterns)); + } + + /** Default constructor */ + public RFC2109Spec() { + this(null, false); + } + + public List<Cookie> parse(final Header header, final CookieOrigin origin) + throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + HeaderElement[] elems = header.getElements(); + return parse(elems, origin); + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + String name = cookie.getName(); + if (name.indexOf(' ') != -1) { + throw new MalformedCookieException("Cookie name may not contain blanks"); + } + if (name.startsWith("$")) { + throw new MalformedCookieException("Cookie name may not start with $"); + } + super.validate(cookie, origin); + } + + public List<Header> formatCookies(List<Cookie> cookies) { + if (cookies == null) { + throw new IllegalArgumentException("List of cookies may not be null"); + } + if (cookies.isEmpty()) { + throw new IllegalArgumentException("List of cookies may not be empty"); + } + if (cookies.size() > 1) { + // Create a mutable copy and sort the copy. + cookies = new ArrayList<Cookie>(cookies); + Collections.sort(cookies, PATH_COMPARATOR); + } + if (this.oneHeader) { + return doFormatOneHeader(cookies); + } else { + return doFormatManyHeaders(cookies); + } + } + + private List<Header> doFormatOneHeader(final List<Cookie> cookies) { + int version = Integer.MAX_VALUE; + // Pick the lowest common denominator + for (Cookie cookie : cookies) { + if (cookie.getVersion() < version) { + version = cookie.getVersion(); + } + } + CharArrayBuffer buffer = new CharArrayBuffer(40 * cookies.size()); + buffer.append(SM.COOKIE); + buffer.append(": "); + buffer.append("$Version="); + buffer.append(Integer.toString(version)); + for (Cookie cooky : cookies) { + buffer.append("; "); + Cookie cookie = cooky; + formatCookieAsVer(buffer, cookie, version); + } + List<Header> headers = new ArrayList<Header>(1); + headers.add(new BufferedHeader(buffer)); + return headers; + } + + private List<Header> doFormatManyHeaders(final List<Cookie> cookies) { + List<Header> headers = new ArrayList<Header>(cookies.size()); + for (Cookie cookie : cookies) { + int version = cookie.getVersion(); + CharArrayBuffer buffer = new CharArrayBuffer(40); + buffer.append("Cookie: "); + buffer.append("$Version="); + buffer.append(Integer.toString(version)); + buffer.append("; "); + formatCookieAsVer(buffer, cookie, version); + headers.add(new BufferedHeader(buffer)); + } + return headers; + } + + /** + * Return a name/value string suitable for sending in a <tt>"Cookie"</tt> + * header as defined in RFC 2109 for backward compatibility with cookie + * version 0 + * @param buffer The char array buffer to use for output + * @param name The cookie name + * @param value The cookie value + * @param version The cookie version + */ + protected void formatParamAsVer(final CharArrayBuffer buffer, + final String name, final String value, int version) { + buffer.append(name); + buffer.append("="); + if (value != null) { + if (version > 0) { + buffer.append('\"'); + buffer.append(value); + buffer.append('\"'); + } else { + buffer.append(value); + } + } + } + + /** + * Return a string suitable for sending in a <tt>"Cookie"</tt> header + * as defined in RFC 2109 for backward compatibility with cookie version 0 + * @param buffer The char array buffer to use for output + * @param cookie The {@link Cookie} to be formatted as string + * @param version The version to use. + */ + protected void formatCookieAsVer(final CharArrayBuffer buffer, + final Cookie cookie, int version) { + formatParamAsVer(buffer, cookie.getName(), cookie.getValue(), version); + if (cookie.getPath() != null) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PATH_ATTR)) { + buffer.append("; "); + formatParamAsVer(buffer, "$Path", cookie.getPath(), version); + } + } + if (cookie.getDomain() != null) { + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + buffer.append("; "); + formatParamAsVer(buffer, "$Domain", cookie.getDomain(), version); + } + } + } + + public int getVersion() { + return 1; + } + + public Header getVersionHeader() { + return null; + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109SpecFactory.java b/src/org/apache/http/impl/cookie/RFC2109SpecFactory.java new file mode 100644 index 0000000..35c506e --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109SpecFactory.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109SpecFactory.java $ + * $Revision: 576068 $ + * $Date: 2007-09-16 03:25:01 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class RFC2109SpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new RFC2109Spec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS), + params.getBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, false)); + } else { + return new RFC2109Spec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2109VersionHandler.java b/src/org/apache/http/impl/cookie/RFC2109VersionHandler.java new file mode 100644 index 0000000..d2c4955 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2109VersionHandler.java @@ -0,0 +1,74 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2109VersionHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +public class RFC2109VersionHandler extends AbstractCookieAttributeHandler { + + public RFC2109VersionHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException("Missing value for version attribute"); + } + if (value.trim().length() == 0) { + throw new MalformedCookieException("Blank value for version attribute"); + } + try { + cookie.setVersion(Integer.parseInt(value)); + } catch (NumberFormatException e) { + throw new MalformedCookieException("Invalid version: " + + e.getMessage()); + } + } + + @Override + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie.getVersion() < 0) { + throw new MalformedCookieException("Cookie version may not be negative"); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java new file mode 100644 index 0000000..aa3a1c5 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965CommentUrlAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"CommantURL"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ + public class RFC2965CommentUrlAttributeHandler implements CookieAttributeHandler { + + public RFC2965CommentUrlAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + cookie2.setCommentURL(commenturl); + } + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + + }
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java new file mode 100644 index 0000000..aa81145 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java @@ -0,0 +1,66 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DiscardAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"Discard"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ + public class RFC2965DiscardAttributeHandler implements CookieAttributeHandler { + + public RFC2965DiscardAttributeHandler() { + super(); + } + + public void parse(final SetCookie cookie, final String commenturl) + throws MalformedCookieException { + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + cookie2.setDiscard(true); + } + } + + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + + }
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java new file mode 100644 index 0000000..b07e5e9 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java @@ -0,0 +1,195 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965DomainAttributeHandler.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.Locale; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; + +/** + * <tt>"Domain"</tt> cookie attribute handler for RFC 2965 cookie spec. + * + * @author jain.samit@gmail.com (Samit Jain) + * + * @since 3.1 + */ +public class RFC2965DomainAttributeHandler implements CookieAttributeHandler { + + public RFC2965DomainAttributeHandler() { + super(); + } + + /** + * Parse cookie domain attribute. + */ + public void parse(final SetCookie cookie, String domain) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (domain == null) { + throw new MalformedCookieException( + "Missing value for domain attribute"); + } + if (domain.trim().length() == 0) { + throw new MalformedCookieException( + "Blank value for domain attribute"); + } + domain = domain.toLowerCase(Locale.ENGLISH); + if (!domain.startsWith(".")) { + // Per RFC 2965 section 3.2.2 + // "... If an explicitly specified value does not start with + // a dot, the user agent supplies a leading dot ..." + // That effectively implies that the domain attribute + // MAY NOT be an IP address of a host name + domain = '.' + domain; + } + cookie.setDomain(domain); + } + + /** + * Performs domain-match as defined by the RFC2965. + * <p> + * Host A's name domain-matches host B's if + * <ol> + * <ul>their host name strings string-compare equal; or</ul> + * <ul>A is a HDN string and has the form NB, where N is a non-empty + * name string, B has the form .B', and B' is a HDN string. (So, + * x.y.com domain-matches .Y.com but not Y.com.)</ul> + * </ol> + * + * @param host host name where cookie is received from or being sent to. + * @param domain The cookie domain attribute. + * @return true if the specified host matches the given domain. + */ + public boolean domainMatch(String host, String domain) { + boolean match = host.equals(domain) + || (domain.startsWith(".") && host.endsWith(domain)); + + return match; + } + + /** + * Validate cookie domain attribute. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost().toLowerCase(Locale.ENGLISH); + if (cookie.getDomain() == null) { + throw new MalformedCookieException("Invalid cookie state: " + + "domain not specified"); + } + String cookieDomain = cookie.getDomain().toLowerCase(Locale.ENGLISH); + + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.DOMAIN_ATTR)) { + // Domain attribute must start with a dot + if (!cookieDomain.startsWith(".")) { + throw new MalformedCookieException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2109: domain must start with a dot"); + } + + // Domain attribute must contain at least one embedded dot, + // or the value must be equal to .local. + int dotIndex = cookieDomain.indexOf('.', 1); + if (((dotIndex < 0) || (dotIndex == cookieDomain.length() - 1)) + && (!cookieDomain.equals(".local"))) { + throw new MalformedCookieException( + "Domain attribute \"" + cookie.getDomain() + + "\" violates RFC 2965: the value contains no embedded dots " + + "and the value is not .local"); + } + + // The effective host name must domain-match domain attribute. + if (!domainMatch(host, cookieDomain)) { + throw new MalformedCookieException( + "Domain attribute \"" + cookie.getDomain() + + "\" violates RFC 2965: effective host name does not " + + "domain-match domain attribute."); + } + + // effective host name minus domain must not contain any dots + String effectiveHostWithoutDomain = host.substring( + 0, host.length() - cookieDomain.length()); + if (effectiveHostWithoutDomain.indexOf('.') != -1) { + throw new MalformedCookieException("Domain attribute \"" + + cookie.getDomain() + "\" violates RFC 2965: " + + "effective host minus domain may not contain any dots"); + } + } else { + // Domain was not specified in header. In this case, domain must + // string match request host (case-insensitive). + if (!cookie.getDomain().equals(host)) { + throw new MalformedCookieException("Illegal domain attribute: \"" + + cookie.getDomain() + "\"." + + "Domain of origin: \"" + + host + "\""); + } + } + } + + /** + * Match cookie domain attribute. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + String host = origin.getHost().toLowerCase(Locale.ENGLISH); + String cookieDomain = cookie.getDomain(); + + // The effective host name MUST domain-match the Domain + // attribute of the cookie. + if (!domainMatch(host, cookieDomain)) { + return false; + } + // effective host name minus domain must not contain any dots + String effectiveHostWithoutDomain = host.substring( + 0, host.length() - cookieDomain.length()); + return effectiveHostWithoutDomain.indexOf('.') == -1; + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java new file mode 100644 index 0000000..b881cda --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java @@ -0,0 +1,168 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965PortAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.StringTokenizer; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"Port"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ +public class RFC2965PortAttributeHandler implements CookieAttributeHandler { + + public RFC2965PortAttributeHandler() { + super(); + } + + /** + * Parses the given Port attribute value (e.g. "8000,8001,8002") + * into an array of ports. + * + * @param portValue port attribute value + * @return parsed array of ports + * @throws MalformedCookieException if there is a problem in + * parsing due to invalid portValue. + */ + private static int[] parsePortAttribute(final String portValue) + throws MalformedCookieException { + StringTokenizer st = new StringTokenizer(portValue, ","); + int[] ports = new int[st.countTokens()]; + try { + int i = 0; + while(st.hasMoreTokens()) { + ports[i] = Integer.parseInt(st.nextToken().trim()); + if (ports[i] < 0) { + throw new MalformedCookieException ("Invalid Port attribute."); + } + ++i; + } + } catch (NumberFormatException e) { + throw new MalformedCookieException ("Invalid Port " + + "attribute: " + e.getMessage()); + } + return ports; + } + + /** + * Returns <tt>true</tt> if the given port exists in the given + * ports list. + * + * @param port port of host where cookie was received from or being sent to. + * @param ports port list + * @return true returns <tt>true</tt> if the given port exists in + * the given ports list; <tt>false</tt> otherwise. + */ + private static boolean portMatch(int port, int[] ports) { + boolean portInList = false; + for (int i = 0, len = ports.length; i < len; i++) { + if (port == ports[i]) { + portInList = true; + break; + } + } + return portInList; + } + + /** + * Parse cookie port attribute. + */ + public void parse(final SetCookie cookie, final String portValue) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie instanceof SetCookie2) { + SetCookie2 cookie2 = (SetCookie2) cookie; + if (portValue != null && portValue.trim().length() > 0) { + int[] ports = parsePortAttribute(portValue); + cookie2.setPorts(ports); + } + } + } + + /** + * Validate cookie port attribute. If the Port attribute was specified + * in header, the request port must be in cookie's port list. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (!portMatch(port, cookie.getPorts())) { + throw new MalformedCookieException( + "Port attribute violates RFC 2965: " + + "Request port not found in cookie's port list."); + } + } + } + + /** + * Match cookie port attribute. If the Port attribute is not specified + * in header, the cookie can be sent to any port. Otherwise, the request port + * must be in the cookie's port list. + */ + public boolean match(final Cookie cookie, final CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + int port = origin.getPort(); + if (cookie instanceof ClientCookie + && ((ClientCookie) cookie).containsAttribute(ClientCookie.PORT_ATTR)) { + if (cookie.getPorts() == null) { + // Invalid cookie state: port not specified + return false; + } + if (!portMatch(port, cookie.getPorts())) { + return false; + } + } + return true; + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2965Spec.java b/src/org/apache/http/impl/cookie/RFC2965Spec.java new file mode 100644 index 0000000..9422fdf --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965Spec.java @@ -0,0 +1,259 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965Spec.java $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.NameValuePair; +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SM; +import org.apache.http.message.BufferedHeader; +import org.apache.http.util.CharArrayBuffer; + +/** + * <p>RFC 2965 specific cookie management functions.</p> + * + * @author jain.samit@gmail.com (Samit Jain) + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 3.1 + */ +public class RFC2965Spec extends RFC2109Spec { + + /** + * Default constructor + * + */ + public RFC2965Spec() { + this(null, false); + } + + public RFC2965Spec(final String[] datepatterns, boolean oneHeader) { + super(datepatterns, oneHeader); + registerAttribHandler(ClientCookie.DOMAIN_ATTR, new RFC2965DomainAttributeHandler()); + registerAttribHandler(ClientCookie.PORT_ATTR, new RFC2965PortAttributeHandler()); + registerAttribHandler(ClientCookie.COMMENTURL_ATTR, new RFC2965CommentUrlAttributeHandler()); + registerAttribHandler(ClientCookie.DISCARD_ATTR, new RFC2965DiscardAttributeHandler()); + registerAttribHandler(ClientCookie.VERSION_ATTR, new RFC2965VersionAttributeHandler()); + } + + private BasicClientCookie createCookie( + final String name, final String value, final CookieOrigin origin) { + BasicClientCookie cookie = new BasicClientCookie(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + return cookie; + } + + private BasicClientCookie createCookie2( + final String name, final String value, final CookieOrigin origin) { + BasicClientCookie2 cookie = new BasicClientCookie2(name, value); + cookie.setPath(getDefaultPath(origin)); + cookie.setDomain(getDefaultDomain(origin)); + cookie.setPorts(new int [] { origin.getPort() }); + return cookie; + } + + @Override + public List<Cookie> parse( + final Header header, + CookieOrigin origin) throws MalformedCookieException { + if (header == null) { + throw new IllegalArgumentException("Header may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + + origin = adjustEffectiveHost(origin); + + HeaderElement[] elems = header.getElements(); + + List<Cookie> cookies = new ArrayList<Cookie>(elems.length); + for (HeaderElement headerelement : elems) { + String name = headerelement.getName(); + String value = headerelement.getValue(); + if (name == null || name.length() == 0) { + throw new MalformedCookieException("Cookie name may not be empty"); + } + + BasicClientCookie cookie; + if (header.getName().equals(SM.SET_COOKIE2)) { + cookie = createCookie2(name, value, origin); + } else { + cookie = createCookie(name, value, origin); + } + + // cycle through the parameters + NameValuePair[] attribs = headerelement.getParameters(); + + // Eliminate duplicate attributes. The first occurrence takes precedence + // See RFC2965: 3.2 Origin Server Role + Map<String, NameValuePair> attribmap = + new HashMap<String, NameValuePair>(attribs.length); + for (int j = attribs.length - 1; j >= 0; j--) { + NameValuePair param = attribs[j]; + attribmap.put(param.getName().toLowerCase(Locale.ENGLISH), param); + } + for (Map.Entry<String, NameValuePair> entry : attribmap.entrySet()) { + NameValuePair attrib = entry.getValue(); + String s = attrib.getName().toLowerCase(Locale.ENGLISH); + + cookie.setAttribute(s, attrib.getValue()); + + CookieAttributeHandler handler = findAttribHandler(s); + if (handler != null) { + handler.parse(cookie, attrib.getValue()); + } + } + cookies.add(cookie); + } + return cookies; + } + + @Override + public void validate(final Cookie cookie, CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + origin = adjustEffectiveHost(origin); + super.validate(cookie, origin); + } + + @Override + public boolean match(final Cookie cookie, CookieOrigin origin) { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (origin == null) { + throw new IllegalArgumentException("Cookie origin may not be null"); + } + origin = adjustEffectiveHost(origin); + return super.match(cookie, origin); + } + + /** + * Adds valid Port attribute value, e.g. "8000,8001,8002" + */ + @Override + protected void formatCookieAsVer(final CharArrayBuffer buffer, + final Cookie cookie, int version) { + super.formatCookieAsVer(buffer, cookie, version); + // format port attribute + if (cookie instanceof ClientCookie) { + // Test if the port attribute as set by the origin server is not blank + String s = ((ClientCookie) cookie).getAttribute(ClientCookie.PORT_ATTR); + if (s != null) { + buffer.append("; $Port"); + buffer.append("=\""); + if (s.trim().length() > 0) { + int[] ports = cookie.getPorts(); + if (ports != null) { + for (int i = 0, len = ports.length; i < len; i++) { + if (i > 0) { + buffer.append(","); + } + buffer.append(Integer.toString(ports[i])); + } + } + } + buffer.append("\""); + } + } + } + + /** + * Set 'effective host name' as defined in RFC 2965. + * <p> + * If a host name contains no dots, the effective host name is + * that name with the string .local appended to it. Otherwise + * the effective host name is the same as the host name. Note + * that all effective host names contain at least one dot. + * + * @param origin origin where cookie is received from or being sent to. + * @return + */ + private static CookieOrigin adjustEffectiveHost(final CookieOrigin origin) { + String host = origin.getHost(); + + // Test if the host name appears to be a fully qualified DNS name, + // IPv4 address or IPv6 address + boolean isLocalHost = true; + for (int i = 0; i < host.length(); i++) { + char ch = host.charAt(i); + if (ch == '.' || ch == ':') { + isLocalHost = false; + break; + } + } + if (isLocalHost) { + host += ".local"; + return new CookieOrigin( + host, + origin.getPort(), + origin.getPath(), + origin.isSecure()); + } else { + return origin; + } + } + + @Override + public int getVersion() { + return 1; + } + + @Override + public Header getVersionHeader() { + CharArrayBuffer buffer = new CharArrayBuffer(40); + buffer.append(SM.COOKIE2); + buffer.append(": "); + buffer.append("$Version="); + buffer.append(Integer.toString(getVersion())); + return new BufferedHeader(buffer); + } + +} + diff --git a/src/org/apache/http/impl/cookie/RFC2965SpecFactory.java b/src/org/apache/http/impl/cookie/RFC2965SpecFactory.java new file mode 100644 index 0000000..4b3cc4d --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965SpecFactory.java @@ -0,0 +1,57 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965SpecFactory.java $ + * $Revision: 581953 $ + * $Date: 2007-10-04 08:53:50 -0700 (Thu, 04 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.CookieSpec; +import org.apache.http.cookie.CookieSpecFactory; +import org.apache.http.cookie.params.CookieSpecPNames; +import org.apache.http.params.HttpParams; + +/** + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class RFC2965SpecFactory implements CookieSpecFactory { + + public CookieSpec newInstance(final HttpParams params) { + if (params != null) { + return new RFC2965Spec( + (String []) params.getParameter(CookieSpecPNames.DATE_PATTERNS), + params.getBooleanParameter(CookieSpecPNames.SINGLE_COOKIE_HEADER, false)); + } else { + return new RFC2965Spec(); + } + } + +} diff --git a/src/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java b/src/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java new file mode 100644 index 0000000..8ea8481 --- /dev/null +++ b/src/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java @@ -0,0 +1,96 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/cookie/RFC2965VersionAttributeHandler.java $ + * $Revision: 590695 $ + * $Date: 2007-10-31 07:55:41 -0700 (Wed, 31 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.cookie; + +import org.apache.http.cookie.ClientCookie; +import org.apache.http.cookie.Cookie; +import org.apache.http.cookie.CookieAttributeHandler; +import org.apache.http.cookie.CookieOrigin; +import org.apache.http.cookie.MalformedCookieException; +import org.apache.http.cookie.SetCookie; +import org.apache.http.cookie.SetCookie2; + +/** + * <tt>"Version"</tt> cookie attribute handler for RFC 2965 cookie spec. + */ +public class RFC2965VersionAttributeHandler implements CookieAttributeHandler { + + public RFC2965VersionAttributeHandler() { + super(); + } + + /** + * Parse cookie version attribute. + */ + public void parse(final SetCookie cookie, final String value) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (value == null) { + throw new MalformedCookieException( + "Missing value for version attribute"); + } + int version = -1; + try { + version = Integer.parseInt(value); + } catch (NumberFormatException e) { + version = -1; + } + if (version < 0) { + throw new MalformedCookieException("Invalid cookie version."); + } + cookie.setVersion(version); + } + + /** + * validate cookie version attribute. Version attribute is REQUIRED. + */ + public void validate(final Cookie cookie, final CookieOrigin origin) + throws MalformedCookieException { + if (cookie == null) { + throw new IllegalArgumentException("Cookie may not be null"); + } + if (cookie instanceof SetCookie2) { + if (cookie instanceof ClientCookie + && !((ClientCookie) cookie).containsAttribute(ClientCookie.VERSION_ATTR)) { + throw new MalformedCookieException( + "Violates RFC 2965. Version attribute is required."); + } + } + } + + public boolean match(final Cookie cookie, final CookieOrigin origin) { + return true; + } + +}
\ No newline at end of file diff --git a/src/org/apache/http/impl/cookie/package.html b/src/org/apache/http/impl/cookie/package.html new file mode 100644 index 0000000..e301283 --- /dev/null +++ b/src/org/apache/http/impl/cookie/package.html @@ -0,0 +1,4 @@ +<body> + +</body> + diff --git a/src/org/apache/http/impl/entity/EntityDeserializer.java b/src/org/apache/http/impl/entity/EntityDeserializer.java new file mode 100644 index 0000000..12c4756 --- /dev/null +++ b/src/org/apache/http/impl/entity/EntityDeserializer.java @@ -0,0 +1,115 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/EntityDeserializer.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import java.io.IOException; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.impl.io.ChunkedInputStream; +import org.apache.http.impl.io.ContentLengthInputStream; +import org.apache.http.impl.io.IdentityInputStream; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.protocol.HTTP; + +/** + * Default implementation of an entity deserializer. + * <p> + * This entity deserializer currently supports only "chunked" and "identitiy" transfer-coding</a> + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class EntityDeserializer { + + private final ContentLengthStrategy lenStrategy; + + public EntityDeserializer(final ContentLengthStrategy lenStrategy) { + super(); + if (lenStrategy == null) { + throw new IllegalArgumentException("Content length strategy may not be null"); + } + this.lenStrategy = lenStrategy; + } + + protected BasicHttpEntity doDeserialize( + final SessionInputBuffer inbuffer, + final HttpMessage message) throws HttpException, IOException { + BasicHttpEntity entity = new BasicHttpEntity(); + + long len = this.lenStrategy.determineLength(message); + if (len == ContentLengthStrategy.CHUNKED) { + entity.setChunked(true); + entity.setContentLength(-1); + entity.setContent(new ChunkedInputStream(inbuffer)); + } else if (len == ContentLengthStrategy.IDENTITY) { + entity.setChunked(false); + entity.setContentLength(-1); + entity.setContent(new IdentityInputStream(inbuffer)); + } else { + entity.setChunked(false); + entity.setContentLength(len); + entity.setContent(new ContentLengthInputStream(inbuffer, len)); + } + + Header contentTypeHeader = message.getFirstHeader(HTTP.CONTENT_TYPE); + if (contentTypeHeader != null) { + entity.setContentType(contentTypeHeader); + } + Header contentEncodingHeader = message.getFirstHeader(HTTP.CONTENT_ENCODING); + if (contentEncodingHeader != null) { + entity.setContentEncoding(contentEncodingHeader); + } + return entity; + } + + public HttpEntity deserialize( + final SessionInputBuffer inbuffer, + final HttpMessage message) throws HttpException, IOException { + if (inbuffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + return doDeserialize(inbuffer, message); + } + +} diff --git a/src/org/apache/http/impl/entity/EntitySerializer.java b/src/org/apache/http/impl/entity/EntitySerializer.java new file mode 100644 index 0000000..3221980 --- /dev/null +++ b/src/org/apache/http/impl/entity/EntitySerializer.java @@ -0,0 +1,101 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/EntitySerializer.java $ + * $Revision: 560343 $ + * $Date: 2007-07-27 11:18:19 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.impl.io.ChunkedOutputStream; +import org.apache.http.impl.io.ContentLengthOutputStream; +import org.apache.http.impl.io.IdentityOutputStream; +import org.apache.http.io.SessionOutputBuffer; + +/** + * Default implementation of an entity serializer. + * <p> + * This entity serializer currently supports only "chunked" and "identitiy" transfer-coding</a> + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560343 $ + * + * @since 4.0 + */ +public class EntitySerializer { + + private final ContentLengthStrategy lenStrategy; + + public EntitySerializer(final ContentLengthStrategy lenStrategy) { + super(); + if (lenStrategy == null) { + throw new IllegalArgumentException("Content length strategy may not be null"); + } + this.lenStrategy = lenStrategy; + } + + protected OutputStream doSerialize( + final SessionOutputBuffer outbuffer, + final HttpMessage message) throws HttpException, IOException { + long len = this.lenStrategy.determineLength(message); + if (len == ContentLengthStrategy.CHUNKED) { + return new ChunkedOutputStream(outbuffer); + } else if (len == ContentLengthStrategy.IDENTITY) { + return new IdentityOutputStream(outbuffer); + } else { + return new ContentLengthOutputStream(outbuffer, len); + } + } + + public void serialize( + final SessionOutputBuffer outbuffer, + final HttpMessage message, + final HttpEntity entity) throws HttpException, IOException { + if (outbuffer == null) { + throw new IllegalArgumentException("Session output buffer may not be null"); + } + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + if (entity == null) { + throw new IllegalArgumentException("HTTP entity may not be null"); + } + OutputStream outstream = doSerialize(outbuffer, message); + entity.writeTo(outstream); + outstream.close(); + } + +} diff --git a/src/org/apache/http/impl/entity/LaxContentLengthStrategy.java b/src/org/apache/http/impl/entity/LaxContentLengthStrategy.java new file mode 100644 index 0000000..9a0d238 --- /dev/null +++ b/src/org/apache/http/impl/entity/LaxContentLengthStrategy.java @@ -0,0 +1,260 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/LaxContentLengthStrategy.java $ + * $Revision: 576073 $ + * $Date: 2007-09-16 03:53:13 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.ParseException; +import org.apache.http.ProtocolException; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.params.HttpParams; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.protocol.HTTP; + +/** + * The lax implementation of the content length strategy. + * <p> + * This strategy conforms to the entity transfer rules outlined in + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec4.4">Section 4.4</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41">Section 14.41</a> + * and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec14.13">Section 14.13</a> + * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, but is lenient + * about unsupported transfer codecs and malformed content-length headers. + * </p> + * <h>4.4 Message Length</h> + * <p> + * The transfer-length of a message is the length of the message-body as it appears in the + * message; that is, after any transfer-codings have been applied. When a message-body is + * included with a message, the transfer-length of that body is determined by one of the + * following (in order of precedence): + * </p> + * <p> + * 1.Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, + * and 304 responses and any response to a HEAD request) is always terminated by the first + * empty line after the header fields, regardless of the entity-header fields present in the + * message. + * </p> + * <p> + * 2.If a Transfer-Encoding header field (section 14.41) is present and has any value other + * than "identity", then the transfer-length is defined by use of the "chunked" transfer- + * coding (section 3.6), unless the message is terminated by closing the connection. + * </p> + * <p> + * 3.If a Content-Length header field (section 14.13) is present, its decimal value in + * OCTETs represents both the entity-length and the transfer-length. The Content-Length + * header field MUST NOT be sent if these two lengths are different (i.e., if a + * Transfer-Encoding + * </p> + * <pre> + * header field is present). If a message is received with both a + * Transfer-Encoding header field and a Content-Length header field, + * the latter MUST be ignored. + * </pre> + * <p> + * 4.If the message uses the media type "multipart/byteranges", and the ransfer-length is not + * otherwise specified, then this self- elimiting media type defines the transfer-length. + * This media type UST NOT be used unless the sender knows that the recipient can arse it; the + * presence in a request of a Range header with ultiple byte- range specifiers from a 1.1 + * client implies that the lient can parse multipart/byteranges responses. + * </p> + * <pre> + * A range header might be forwarded by a 1.0 proxy that does not + * understand multipart/byteranges; in this case the server MUST + * delimit the message using methods defined in items 1,3 or 5 of + * this section. + * </pre> + * <p> + * 5.By the server closing the connection. (Closing the connection cannot be used to indicate + * the end of a request body, since that would leave no possibility for the server to send back + * a response.) + * </p> + * <p> + * For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body + * MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 + * compliant. If a request contains a message-body and a Content-Length is not given, the + * server SHOULD respond with 400 (bad request) if it cannot determine the length of the + * message, or with 411 (length required) if it wishes to insist on receiving a valid + * Content-Length. + * </p> + * <p>All HTTP/1.1 applications that receive entities MUST accept the "chunked" transfer-coding + * (section 3.6), thus allowing this mechanism to be used for messages when the message + * length cannot be determined in advance. + * </p> + * <h>3.6 Transfer Codings</h> + * <p> + * Transfer-coding values are used to indicate an encoding transformation that + * has been, can be, or may need to be applied to an entity-body in order to ensure + * "safe transport" through the network. This differs from a content coding in that + * the transfer-coding is a property of the message, not of the original entity. + * </p> + * <pre> + * transfer-coding = "chunked" | transfer-extension + * transfer-extension = token *( ";" parameter ) + * </pre> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * <p> + * All transfer-coding values are case-insensitive. HTTP/1.1 uses transfer-coding values in + * the TE header field (section 14.39) and in the Transfer-Encoding header field (section 14.41). + * </p> + * <p> + * Whenever a transfer-coding is applied to a message-body, the set of transfer-codings MUST + * include "chunked", unless the message is terminated by closing the connection. When the + * "chunked" transfer-coding is used, it MUST be the last transfer-coding applied to the + * message-body. The "chunked" transfer-coding MUST NOT be applied more than once to a + * message-body. These rules allow the recipient to determine the transfer-length of the + * message (section 4.4). + * </p> + * <h>14.41 Transfer-Encoding</h> + * <p> + * The Transfer-Encoding general-header field indicates what (if any) type of transformation has + * been applied to the message body in order to safely transfer it between the sender and the + * recipient. This differs from the content-coding in that the transfer-coding is a property of + * the message, not of the entity. + * </p> + * <pre> + * Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding + * </pre> + * <p> + * If multiple encodings have been applied to an entity, the transfer- codings MUST be listed in + * the order in which they were applied. Additional information about the encoding parameters + * MAY be provided by other entity-header fields not defined by this specification. + * </p> + * <h>14.13 Content-Length</h> + * <p> + * The Content-Length entity-header field indicates the size of the entity-body, in decimal + * number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of + * the entity-body that would have been sent had the request been a GET. + * </p> + * <pre> + * Content-Length = "Content-Length" ":" 1*DIGIT + * </pre> + * <p> + * Applications SHOULD use this field to indicate the transfer-length of the message-body, + * unless this is prohibited by the rules in section 4.4. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 576073 $ + * + * @since 4.0 + */ +public class LaxContentLengthStrategy implements ContentLengthStrategy { + + public LaxContentLengthStrategy() { + super(); + } + + public long determineLength(final HttpMessage message) throws HttpException { + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + + HttpParams params = message.getParams(); + boolean strict = params.isParameterTrue(CoreProtocolPNames.STRICT_TRANSFER_ENCODING); + + Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING); + Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN); + // We use Transfer-Encoding if present and ignore Content-Length. + // RFC2616, 4.4 item number 3 + if (transferEncodingHeader != null) { + HeaderElement[] encodings = null; + try { + encodings = transferEncodingHeader.getElements(); + } catch (ParseException px) { + throw new ProtocolException + ("Invalid Transfer-Encoding header value: " + + transferEncodingHeader, px); + } + if (strict) { + // Currently only chunk and identity are supported + for (int i = 0; i < encodings.length; i++) { + String encoding = encodings[i].getName(); + if (encoding != null && encoding.length() > 0 + && !encoding.equalsIgnoreCase(HTTP.CHUNK_CODING) + && !encoding.equalsIgnoreCase(HTTP.IDENTITY_CODING)) { + throw new ProtocolException("Unsupported transfer encoding: " + encoding); + } + } + } + // The chunked encoding must be the last one applied RFC2616, 14.41 + int len = encodings.length; + if (HTTP.IDENTITY_CODING.equalsIgnoreCase(transferEncodingHeader.getValue())) { + return IDENTITY; + } else if ((len > 0) && (HTTP.CHUNK_CODING.equalsIgnoreCase( + encodings[len - 1].getName()))) { + return CHUNKED; + } else { + if (strict) { + throw new ProtocolException("Chunk-encoding must be the last one applied"); + } + return IDENTITY; + } + } else if (contentLengthHeader != null) { + long contentlen = -1; + Header[] headers = message.getHeaders(HTTP.CONTENT_LEN); + if (strict && headers.length > 1) { + throw new ProtocolException("Multiple content length headers"); + } + for (int i = headers.length - 1; i >= 0; i--) { + Header header = headers[i]; + try { + contentlen = Long.parseLong(header.getValue()); + break; + } catch (NumberFormatException e) { + if (strict) { + throw new ProtocolException("Invalid content length: " + header.getValue()); + } + } + // See if we can have better luck with another header, if present + } + if (contentlen >= 0) { + return contentlen; + } else { + return IDENTITY; + } + } else { + return IDENTITY; + } + } + +} diff --git a/src/org/apache/http/impl/entity/StrictContentLengthStrategy.java b/src/org/apache/http/impl/entity/StrictContentLengthStrategy.java new file mode 100644 index 0000000..30be8e2 --- /dev/null +++ b/src/org/apache/http/impl/entity/StrictContentLengthStrategy.java @@ -0,0 +1,220 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/StrictContentLengthStrategy.java $ + * $Revision: 573949 $ + * $Date: 2007-09-08 22:46:25 -0700 (Sat, 08 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.entity; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpVersion; +import org.apache.http.ProtocolException; +import org.apache.http.entity.ContentLengthStrategy; +import org.apache.http.protocol.HTTP; + +/** + * The strict implementation of the content length strategy. + * <p> + * This entity generator comforms to the entity transfer rules outlined in the + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec4.4">Section 4.4</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.41">Section 14.41</a> + * and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec14.13">Section 14.13</a> + * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a> + * </p> + * <h>4.4 Message Length</h> + * <p> + * The transfer-length of a message is the length of the message-body as it appears in the + * message; that is, after any transfer-codings have been applied. When a message-body is + * included with a message, the transfer-length of that body is determined by one of the + * following (in order of precedence): + * </p> + * <p> + * 1.Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, + * and 304 responses and any response to a HEAD request) is always terminated by the first + * empty line after the header fields, regardless of the entity-header fields present in the + * message. + * </p> + * <p> + * 2.If a Transfer-Encoding header field (section 14.41) is present and has any value other + * than "identity", then the transfer-length is defined by use of the "chunked" transfer- + * coding (section 3.6), unless the message is terminated by closing the connection. + * </p> + * <p> + * 3.If a Content-Length header field (section 14.13) is present, its decimal value in + * OCTETs represents both the entity-length and the transfer-length. The Content-Length + * header field MUST NOT be sent if these two lengths are different (i.e., if a + * Transfer-Encoding + * </p> + * <pre> + * header field is present). If a message is received with both a + * Transfer-Encoding header field and a Content-Length header field, + * the latter MUST be ignored. + * </pre> + * <p> + * 4.If the message uses the media type "multipart/byteranges", and the ransfer-length is not + * otherwise specified, then this self- elimiting media type defines the transfer-length. + * This media type UST NOT be used unless the sender knows that the recipient can arse it; the + * presence in a request of a Range header with ultiple byte- range specifiers from a 1.1 + * client implies that the lient can parse multipart/byteranges responses. + * </p> + * <pre> + * A range header might be forwarded by a 1.0 proxy that does not + * understand multipart/byteranges; in this case the server MUST + * delimit the message using methods defined in items 1,3 or 5 of + * this section. + * </pre> + * <p> + * 5.By the server closing the connection. (Closing the connection cannot be used to indicate + * the end of a request body, since that would leave no possibility for the server to send back + * a response.) + * </p> + * <p> + * For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing a message-body + * MUST include a valid Content-Length header field unless the server is known to be HTTP/1.1 + * compliant. If a request contains a message-body and a Content-Length is not given, the + * server SHOULD respond with 400 (bad request) if it cannot determine the length of the + * message, or with 411 (length required) if it wishes to insist on receiving a valid + * Content-Length. + * </p> + * <p>All HTTP/1.1 applications that receive entities MUST accept the "chunked" transfer-coding + * (section 3.6), thus allowing this mechanism to be used for messages when the message + * length cannot be determined in advance. + * </p> + * <h>3.6 Transfer Codings</h> + * <p> + * Transfer-coding values are used to indicate an encoding transformation that + * has been, can be, or may need to be applied to an entity-body in order to ensure + * "safe transport" through the network. This differs from a content coding in that + * the transfer-coding is a property of the message, not of the original entity. + * </p> + * <pre> + * transfer-coding = "chunked" | transfer-extension + * transfer-extension = token *( ";" parameter ) + * </pre> + * <p> + * Parameters are in the form of attribute/value pairs. + * </p> + * <pre> + * parameter = attribute "=" value + * attribute = token + * value = token | quoted-string + * </pre> + * <p> + * All transfer-coding values are case-insensitive. HTTP/1.1 uses transfer-coding values in + * the TE header field (section 14.39) and in the Transfer-Encoding header field (section 14.41). + * </p> + * <p> + * Whenever a transfer-coding is applied to a message-body, the set of transfer-codings MUST + * include "chunked", unless the message is terminated by closing the connection. When the + * "chunked" transfer-coding is used, it MUST be the last transfer-coding applied to the + * message-body. The "chunked" transfer-coding MUST NOT be applied more than once to a + * message-body. These rules allow the recipient to determine the transfer-length of the + * message (section 4.4). + * </p> + * <h>14.41 Transfer-Encoding</h> + * <p> + * The Transfer-Encoding general-header field indicates what (if any) type of transformation has + * been applied to the message body in order to safely transfer it between the sender and the + * recipient. This differs from the content-coding in that the transfer-coding is a property of + * the message, not of the entity. + * </p> + * <pre> + * Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding + * </pre> + * <p> + * If multiple encodings have been applied to an entity, the transfer- codings MUST be listed in + * the order in which they were applied. Additional information about the encoding parameters + * MAY be provided by other entity-header fields not defined by this specification. + * </p> + * <h>14.13 Content-Length</h> + * <p> + * The Content-Length entity-header field indicates the size of the entity-body, in decimal + * number of OCTETs, sent to the recipient or, in the case of the HEAD method, the size of + * the entity-body that would have been sent had the request been a GET. + * </p> + * <pre> + * Content-Length = "Content-Length" ":" 1*DIGIT + * </pre> + * <p> + * Applications SHOULD use this field to indicate the transfer-length of the message-body, + * unless this is prohibited by the rules in section 4.4. + * </p> + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 573949 $ + * + * @since 4.0 + */ +public class StrictContentLengthStrategy implements ContentLengthStrategy { + + public StrictContentLengthStrategy() { + super(); + } + + public long determineLength(final HttpMessage message) throws HttpException { + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + // Although Transfer-Encoding is specified as a list, in practice + // it is either missing or has the single value "chunked". So we + // treat it as a single-valued header here. + Header transferEncodingHeader = message.getFirstHeader(HTTP.TRANSFER_ENCODING); + Header contentLengthHeader = message.getFirstHeader(HTTP.CONTENT_LEN); + if (transferEncodingHeader != null) { + String s = transferEncodingHeader.getValue(); + if (HTTP.CHUNK_CODING.equalsIgnoreCase(s)) { + if (message.getProtocolVersion().lessEquals(HttpVersion.HTTP_1_0)) { + throw new ProtocolException( + "Chunked transfer encoding not allowed for " + + message.getProtocolVersion()); + } + return CHUNKED; + } else if (HTTP.IDENTITY_CODING.equalsIgnoreCase(s)) { + return IDENTITY; + } else { + throw new ProtocolException( + "Unsupported transfer encoding: " + s); + } + } else if (contentLengthHeader != null) { + String s = contentLengthHeader.getValue(); + try { + long len = Long.parseLong(s); + return len; + } catch (NumberFormatException e) { + throw new ProtocolException("Invalid content length: " + s); + } + } else { + return IDENTITY; + } + } + +} diff --git a/src/org/apache/http/impl/entity/package.html b/src/org/apache/http/impl/entity/package.html new file mode 100644 index 0000000..9ac4481 --- /dev/null +++ b/src/org/apache/http/impl/entity/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/entity/package.html $ + * $Revision: 496072 $ + * $Date: 2007-01-14 04:22:55 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Default implementations for interfaces in +{@link org.apache.http.entity org.apache.http.entity}. + +</body> +</html> diff --git a/src/org/apache/http/impl/io/AbstractMessageParser.java b/src/org/apache/http/impl/io/AbstractMessageParser.java new file mode 100644 index 0000000..679bcd1 --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractMessageParser.java @@ -0,0 +1,187 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractMessageParser.java $ + * $Revision: 576077 $ + * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.util.ArrayList; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.ParseException; +import org.apache.http.ProtocolException; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.BasicLineParser; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +/** + * Message parser base class. + * + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + */ +public abstract class AbstractMessageParser implements HttpMessageParser { + + private final SessionInputBuffer sessionBuffer; + private final int maxHeaderCount; + private final int maxLineLen; + protected final LineParser lineParser; + + + public AbstractMessageParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpParams params) { + super(); + if (buffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.sessionBuffer = buffer; + this.maxHeaderCount = params.getIntParameter( + CoreConnectionPNames.MAX_HEADER_COUNT, -1); + this.maxLineLen = params.getIntParameter( + CoreConnectionPNames.MAX_LINE_LENGTH, -1); + this.lineParser = (parser != null) ? parser : BasicLineParser.DEFAULT; + } + + /** + * Parses HTTP headers from the data receiver stream according to the generic + * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. + * + * @param inbuffer Session input buffer + * @param maxHeaderCount maximum number of headers allowed. If the number + * of headers received from the data stream exceeds maxCount value, an + * IOException will be thrown. Setting this parameter to a negative value + * or zero will disable the check. + * @param maxLineLen maximum number of characters for a header line, + * including the continuation lines + * @return array of HTTP headers + * + * @throws HttpException + * @throws IOException + */ + public static Header[] parseHeaders( + final SessionInputBuffer inbuffer, + int maxHeaderCount, + int maxLineLen, + LineParser parser) + throws HttpException, IOException { + + if (inbuffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + if (parser == null) + parser = BasicLineParser.DEFAULT; + + ArrayList headerLines = new ArrayList(); + + CharArrayBuffer current = null; + CharArrayBuffer previous = null; + for (;;) { + if (current == null) { + current = new CharArrayBuffer(64); + } else { + current.clear(); + } + int l = inbuffer.readLine(current); + if (l == -1 || current.length() < 1) { + break; + } + // Parse the header name and value + // Check for folded headers first + // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 + // discussion on folded headers + if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) { + // we have continuation folded header + // so append value + int i = 0; + while (i < current.length()) { + char ch = current.charAt(i); + if (ch != ' ' && ch != '\t') { + break; + } + i++; + } + if (maxLineLen > 0 + && previous.length() + 1 + current.length() - i > maxLineLen) { + throw new IOException("Maximum line length limit exceeded"); + } + previous.append(' '); + previous.append(current, i, current.length() - i); + } else { + headerLines.add(current); + previous = current; + current = null; + } + if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) { + throw new IOException("Maximum header count exceeded"); + } + } + Header[] headers = new Header[headerLines.size()]; + for (int i = 0; i < headerLines.size(); i++) { + CharArrayBuffer buffer = (CharArrayBuffer) headerLines.get(i); + try { + headers[i] = parser.parseHeader(buffer); + } catch (ParseException ex) { + throw new ProtocolException(ex.getMessage()); + } + } + return headers; + } + + protected abstract HttpMessage parseHead(SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException; + + public HttpMessage parse() throws IOException, HttpException { + HttpMessage message = null; + try { + message = parseHead(this.sessionBuffer); + } catch (ParseException px) { + throw new ProtocolException(px.getMessage(), px); + } + Header[] headers = AbstractMessageParser.parseHeaders( + this.sessionBuffer, + this.maxHeaderCount, + this.maxLineLen, + this.lineParser); + message.setHeaders(headers); + return message; + } + +} diff --git a/src/org/apache/http/impl/io/AbstractMessageWriter.java b/src/org/apache/http/impl/io/AbstractMessageWriter.java new file mode 100644 index 0000000..f9644ce --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractMessageWriter.java @@ -0,0 +1,85 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractMessageWriter.java $ + * $Revision: 569673 $ + * $Date: 2007-08-25 06:58:51 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.util.Iterator; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.io.HttpMessageWriter; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.message.LineFormatter; +import org.apache.http.message.BasicLineFormatter; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public abstract class AbstractMessageWriter implements HttpMessageWriter { + + protected final SessionOutputBuffer sessionBuffer; + protected final CharArrayBuffer lineBuf; + protected final LineFormatter lineFormatter; + + public AbstractMessageWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(); + if (buffer == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.sessionBuffer = buffer; + this.lineBuf = new CharArrayBuffer(128); + this.lineFormatter = (formatter != null) ? + formatter : BasicLineFormatter.DEFAULT; + } + + protected abstract void writeHeadLine(HttpMessage message) + throws IOException + ; + + public void write( + final HttpMessage message) throws IOException, HttpException { + if (message == null) { + throw new IllegalArgumentException("HTTP message may not be null"); + } + writeHeadLine(message); + for (Iterator it = message.headerIterator(); it.hasNext(); ) { + Header header = (Header) it.next(); + this.sessionBuffer.writeLine + (lineFormatter.formatHeader(this.lineBuf, header)); + } + this.lineBuf.clear(); + this.sessionBuffer.writeLine(this.lineBuf); + } + +} diff --git a/src/org/apache/http/impl/io/AbstractSessionInputBuffer.java b/src/org/apache/http/impl/io/AbstractSessionInputBuffer.java new file mode 100644 index 0000000..eb007a9 --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractSessionInputBuffer.java @@ -0,0 +1,271 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java $ + * $Revision: 576077 $ + * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.ByteArrayBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract base class for session input buffers that stream data + * from a {@link InputStream}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + */ +public abstract class AbstractSessionInputBuffer implements SessionInputBuffer { + + private InputStream instream; + private byte[] buffer; + private int bufferpos; + private int bufferlen; + + private ByteArrayBuffer linebuffer = null; + + private String charset = HTTP.US_ASCII; + private boolean ascii = true; + private int maxLineLen = -1; + + private HttpTransportMetricsImpl metrics; + + protected void init(final InputStream instream, int buffersize, final HttpParams params) { + if (instream == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("Buffer size may not be negative or zero"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.instream = instream; + this.buffer = new byte[buffersize]; + this.bufferpos = 0; + this.bufferlen = 0; + this.linebuffer = new ByteArrayBuffer(buffersize); + this.charset = HttpProtocolParams.getHttpElementCharset(params); + this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII) + || this.charset.equalsIgnoreCase(HTTP.ASCII); + this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1); + this.metrics = new HttpTransportMetricsImpl(); + } + + protected int fillBuffer() throws IOException { + // compact the buffer if necessary + if (this.bufferpos > 0) { + int len = this.bufferlen - this.bufferpos; + if (len > 0) { + System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len); + } + this.bufferpos = 0; + this.bufferlen = len; + } + int l; + int off = this.bufferlen; + int len = this.buffer.length - off; + l = this.instream.read(this.buffer, off, len); + if (l == -1) { + return -1; + } else { + this.bufferlen = off + l; + this.metrics.incrementBytesTransferred(l); + return l; + } + } + + protected boolean hasBufferedData() { + return this.bufferpos < this.bufferlen; + } + + public int read() throws IOException { + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + return this.buffer[this.bufferpos++] & 0xff; + } + + public int read(final byte[] b, int off, int len) throws IOException { + if (b == null) { + return 0; + } + int noRead = 0; + while (!hasBufferedData()) { + noRead = fillBuffer(); + if (noRead == -1) { + return -1; + } + } + int chunk = this.bufferlen - this.bufferpos; + if (chunk > len) { + chunk = len; + } + System.arraycopy(this.buffer, this.bufferpos, b, off, chunk); + this.bufferpos += chunk; + return chunk; + } + + public int read(final byte[] b) throws IOException { + if (b == null) { + return 0; + } + return read(b, 0, b.length); + } + + private int locateLF() { + for (int i = this.bufferpos; i < this.bufferlen; i++) { + if (this.buffer[i] == HTTP.LF) { + return i; + } + } + return -1; + } + + public int readLine(final CharArrayBuffer charbuffer) throws IOException { + if (charbuffer == null) { + throw new IllegalArgumentException("Char array buffer may not be null"); + } + this.linebuffer.clear(); + int noRead = 0; + boolean retry = true; + while (retry) { + // attempt to find end of line (LF) + int i = locateLF(); + if (i != -1) { + // end of line found. + if (this.linebuffer.isEmpty()) { + // the entire line is preset in the read buffer + return lineFromReadBuffer(charbuffer, i); + } + retry = false; + int len = i + 1 - this.bufferpos; + this.linebuffer.append(this.buffer, this.bufferpos, len); + this.bufferpos = i + 1; + } else { + // end of line not found + if (hasBufferedData()) { + int len = this.bufferlen - this.bufferpos; + this.linebuffer.append(this.buffer, this.bufferpos, len); + this.bufferpos = this.bufferlen; + } + noRead = fillBuffer(); + if (noRead == -1) { + retry = false; + } + } + if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) { + throw new IOException("Maximum line length limit exceeded"); + } + } + if (noRead == -1 && this.linebuffer.isEmpty()) { + // indicate the end of stream + return -1; + } + return lineFromLineBuffer(charbuffer); + } + + private int lineFromLineBuffer(final CharArrayBuffer charbuffer) + throws IOException { + // discard LF if found + int l = this.linebuffer.length(); + if (l > 0) { + if (this.linebuffer.byteAt(l - 1) == HTTP.LF) { + l--; + this.linebuffer.setLength(l); + } + // discard CR if found + if (l > 0) { + if (this.linebuffer.byteAt(l - 1) == HTTP.CR) { + l--; + this.linebuffer.setLength(l); + } + } + } + l = this.linebuffer.length(); + if (this.ascii) { + charbuffer.append(this.linebuffer, 0, l); + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + String s = new String(this.linebuffer.buffer(), 0, l, this.charset); + charbuffer.append(s); + } + return l; + } + + private int lineFromReadBuffer(final CharArrayBuffer charbuffer, int pos) + throws IOException { + int off = this.bufferpos; + int len; + this.bufferpos = pos + 1; + if (pos > 0 && this.buffer[pos - 1] == HTTP.CR) { + // skip CR if found + pos--; + } + len = pos - off; + if (this.ascii) { + charbuffer.append(this.buffer, off, len); + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + String s = new String(this.buffer, off, len, this.charset); + charbuffer.append(s); + } + return len; + } + + public String readLine() throws IOException { + CharArrayBuffer charbuffer = new CharArrayBuffer(64); + int l = readLine(charbuffer); + if (l != -1) { + return charbuffer.toString(); + } else { + return null; + } + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/io/AbstractSessionOutputBuffer.java b/src/org/apache/http/impl/io/AbstractSessionOutputBuffer.java new file mode 100644 index 0000000..bf4e56e --- /dev/null +++ b/src/org/apache/http/impl/io/AbstractSessionOutputBuffer.java @@ -0,0 +1,179 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/AbstractSessionOutputBuffer.java $ + * $Revision: 652091 $ + * $Date: 2008-04-29 13:41:07 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.ByteArrayBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Abstract base class for session output buffers that stream data + * to an {@link OutputStream}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + */ +public abstract class AbstractSessionOutputBuffer implements SessionOutputBuffer { + + private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF}; + + private static final int MAX_CHUNK = 256; + + private OutputStream outstream; + private ByteArrayBuffer buffer; + + private String charset = HTTP.US_ASCII; + private boolean ascii = true; + + private HttpTransportMetricsImpl metrics; + + protected void init(final OutputStream outstream, int buffersize, final HttpParams params) { + if (outstream == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (buffersize <= 0) { + throw new IllegalArgumentException("Buffer size may not be negative or zero"); + } + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.outstream = outstream; + this.buffer = new ByteArrayBuffer(buffersize); + this.charset = HttpProtocolParams.getHttpElementCharset(params); + this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII) + || this.charset.equalsIgnoreCase(HTTP.ASCII); + this.metrics = new HttpTransportMetricsImpl(); + } + + protected void flushBuffer() throws IOException { + int len = this.buffer.length(); + if (len > 0) { + this.outstream.write(this.buffer.buffer(), 0, len); + this.buffer.clear(); + this.metrics.incrementBytesTransferred(len); + } + } + + public void flush() throws IOException { + flushBuffer(); + this.outstream.flush(); + } + + public void write(final byte[] b, int off, int len) throws IOException { + if (b == null) { + return; + } + // Do not want to buffer largish chunks + // if the byte array is larger then MAX_CHUNK + // write it directly to the output stream + if (len > MAX_CHUNK || len > this.buffer.capacity()) { + // flush the buffer + flushBuffer(); + // write directly to the out stream + this.outstream.write(b, off, len); + this.metrics.incrementBytesTransferred(len); + } else { + // Do not let the buffer grow unnecessarily + int freecapacity = this.buffer.capacity() - this.buffer.length(); + if (len > freecapacity) { + // flush the buffer + flushBuffer(); + } + // buffer + this.buffer.append(b, off, len); + } + } + + public void write(final byte[] b) throws IOException { + if (b == null) { + return; + } + write(b, 0, b.length); + } + + public void write(int b) throws IOException { + if (this.buffer.isFull()) { + flushBuffer(); + } + this.buffer.append(b); + } + + public void writeLine(final String s) throws IOException { + if (s == null) { + return; + } + if (s.length() > 0) { + write(s.getBytes(this.charset)); + } + write(CRLF); + } + + public void writeLine(final CharArrayBuffer s) throws IOException { + if (s == null) { + return; + } + if (this.ascii) { + int off = 0; + int remaining = s.length(); + while (remaining > 0) { + int chunk = this.buffer.capacity() - this.buffer.length(); + chunk = Math.min(chunk, remaining); + if (chunk > 0) { + this.buffer.append(s, off, chunk); + } + if (this.buffer.isFull()) { + flushBuffer(); + } + off += chunk; + remaining -= chunk; + } + } else { + // This is VERY memory inefficient, BUT since non-ASCII charsets are + // NOT meant to be used anyway, there's no point optimizing it + byte[] tmp = s.toString().getBytes(this.charset); + write(tmp); + } + write(CRLF); + } + + public HttpTransportMetrics getMetrics() { + return this.metrics; + } + +} diff --git a/src/org/apache/http/impl/io/ChunkedInputStream.java b/src/org/apache/http/impl/io/ChunkedInputStream.java new file mode 100644 index 0000000..60cae90 --- /dev/null +++ b/src/org/apache/http/impl/io/ChunkedInputStream.java @@ -0,0 +1,294 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java $ + * $Revision: 569843 $ + * $Date: 2007-08-26 10:05:40 -0700 (Sun, 26 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.MalformedChunkCodingException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.ExceptionUtils; + +/** + * Implements chunked transfer coding. + * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>. + * It transparently coalesces chunks of a HTTP stream that uses chunked + * transfer coding. After the stream is read to the end, it provides access + * to the trailers, if any. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * </p> + * + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author Martin Elwin + * @author Eric Johnson + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + * + */ +public class ChunkedInputStream extends InputStream { + + /** The session input buffer */ + private SessionInputBuffer in; + + private final CharArrayBuffer buffer; + + /** The chunk size */ + private int chunkSize; + + /** The current position within the current chunk */ + private int pos; + + /** True if we'are at the beginning of stream */ + private boolean bof = true; + + /** True if we've reached the end of stream */ + private boolean eof = false; + + /** True if this stream is closed */ + private boolean closed = false; + + private Header[] footers = new Header[] {}; + + public ChunkedInputStream(final SessionInputBuffer in) { + super(); + if (in == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.in = in; + this.pos = 0; + this.buffer = new CharArrayBuffer(16); + } + + /** + * <p> Returns all the data in a chunked stream in coalesced form. A chunk + * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 + * is detected.</p> + * + * <p> Trailer headers are read automcatically at the end of the stream and + * can be obtained with the getResponseFooters() method.</p> + * + * @return -1 of the end of the stream has been reached or the next data + * byte + * @throws IOException If an IO problem occurs + */ + public int read() throws IOException { + if (this.closed) { + throw new IOException("Attempted read from closed stream."); + } + if (this.eof) { + return -1; + } + if (this.pos >= this.chunkSize) { + nextChunk(); + if (this.eof) { + return -1; + } + } + pos++; + return in.read(); + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @param off The offset into the byte array at which bytes will start to be + * placed. + * @param len the maximum number of bytes that can be returned. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[], int, int) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b, int off, int len) throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (eof) { + return -1; + } + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } + } + len = Math.min(len, chunkSize - pos); + int count = in.read(b, off, len); + pos += count; + return count; + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[]) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the next chunk. + * @throws IOException If an IO error occurs. + */ + private void nextChunk() throws IOException { + chunkSize = getChunkSize(); + if (chunkSize < 0) { + throw new MalformedChunkCodingException("Negative chunk size"); + } + bof = false; + pos = 0; + if (chunkSize == 0) { + eof = true; + parseTrailerHeaders(); + } + } + + /** + * Expects the stream to start with a chunksize in hex with optional + * comments after a semicolon. The line must end with a CRLF: "a3; some + * comment\r\n" Positions the stream at the start of the next line. + * + * @param in The new input stream. + * @param required <tt>true<tt/> if a valid chunk must be present, + * <tt>false<tt/> otherwise. + * + * @return the chunk size as integer + * + * @throws IOException when the chunk size could not be parsed + */ + private int getChunkSize() throws IOException { + // skip CRLF + if (!bof) { + int cr = in.read(); + int lf = in.read(); + if ((cr != HTTP.CR) || (lf != HTTP.LF)) { + throw new MalformedChunkCodingException( + "CRLF expected at end of chunk"); + } + } + //parse data + this.buffer.clear(); + int i = this.in.readLine(this.buffer); + if (i == -1) { + throw new MalformedChunkCodingException( + "Chunked stream ended unexpectedly"); + } + int separator = this.buffer.indexOf(';'); + if (separator < 0) { + separator = this.buffer.length(); + } + try { + return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16); + } catch (NumberFormatException e) { + throw new MalformedChunkCodingException("Bad chunk header"); + } + } + + /** + * Reads and stores the Trailer headers. + * @throws IOException If an IO problem occurs + */ + private void parseTrailerHeaders() throws IOException { + try { + this.footers = AbstractMessageParser.parseHeaders + (in, -1, -1, null); + } catch (HttpException e) { + IOException ioe = new MalformedChunkCodingException("Invalid footer: " + + e.getMessage()); + ExceptionUtils.initCause(ioe, e); + throw ioe; + } + } + + /** + * Upon close, this reads the remainder of the chunked message, + * leaving the underlying socket at a position to start reading the + * next response without scanning. + * @throws IOException If an IO problem occurs. + */ + public void close() throws IOException { + if (!closed) { + try { + if (!eof) { + exhaustInputStream(this); + } + } finally { + eof = true; + closed = true; + } + } + } + + public Header[] getFooters() { + return (Header[])this.footers.clone(); + } + + /** + * Exhaust an input stream, reading until EOF has been encountered. + * + * <p>Note that this function is intended as a non-public utility. + * This is a little weird, but it seemed silly to make a utility + * class for this one function, so instead it is just static and + * shared that way.</p> + * + * @param inStream The {@link InputStream} to exhaust. + * @throws IOException If an IO problem occurs + */ + static void exhaustInputStream(final InputStream inStream) throws IOException { + // read and discard the remainder of the message + byte buffer[] = new byte[1024]; + while (inStream.read(buffer) >= 0) { + ; + } + } + +} diff --git a/src/org/apache/http/impl/io/ChunkedOutputStream.java b/src/org/apache/http/impl/io/ChunkedOutputStream.java new file mode 100644 index 0000000..5ee7dd6 --- /dev/null +++ b/src/org/apache/http/impl/io/ChunkedOutputStream.java @@ -0,0 +1,191 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java $ + * $Revision: 645081 $ + * $Date: 2008-04-05 04:36:42 -0700 (Sat, 05 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; + +/** + * Implements chunked transfer coding. + * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>. + * Writes are buffered to an internal buffer (2048 default size). + * + * @author Mohammad Rezaei (Goldman, Sachs & Co.) + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class ChunkedOutputStream extends OutputStream { + + // ----------------------------------------------------- Instance Variables + private final SessionOutputBuffer out; + + private byte[] cache; + + private int cachePosition = 0; + + private boolean wroteLastChunk = false; + + /** True if the stream is closed. */ + private boolean closed = false; + + // ----------------------------------------------------------- Constructors + /** + * Wraps a session output buffer and chunks the output. + * @param out the session output buffer to wrap + * @param bufferSize minimum chunk size (excluding last chunk) + * @throws IOException + */ + public ChunkedOutputStream(final SessionOutputBuffer out, int bufferSize) + throws IOException { + super(); + this.cache = new byte[bufferSize]; + this.out = out; + } + + /** + * Wraps a session output buffer and chunks the output. The default buffer + * size of 2048 was chosen because the chunk overhead is less than 0.5% + * + * @param out the output buffer to wrap + * @throws IOException + */ + public ChunkedOutputStream(final SessionOutputBuffer out) + throws IOException { + this(out, 2048); + } + + // ----------------------------------------------------------- Internal methods + /** + * Writes the cache out onto the underlying stream + * @throws IOException + */ + protected void flushCache() throws IOException { + if (this.cachePosition > 0) { + this.out.writeLine(Integer.toHexString(this.cachePosition)); + this.out.write(this.cache, 0, this.cachePosition); + this.out.writeLine(""); + this.cachePosition = 0; + } + } + + /** + * Writes the cache and bufferToAppend to the underlying stream + * as one large chunk + * @param bufferToAppend + * @param off + * @param len + * @throws IOException + */ + protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws IOException { + this.out.writeLine(Integer.toHexString(this.cachePosition + len)); + this.out.write(this.cache, 0, this.cachePosition); + this.out.write(bufferToAppend, off, len); + this.out.writeLine(""); + this.cachePosition = 0; + } + + protected void writeClosingChunk() throws IOException { + // Write the final chunk. + this.out.writeLine("0"); + this.out.writeLine(""); + } + + // ----------------------------------------------------------- Public Methods + /** + * Must be called to ensure the internal cache is flushed and the closing chunk is written. + * @throws IOException + */ + public void finish() throws IOException { + if (!this.wroteLastChunk) { + flushCache(); + writeClosingChunk(); + this.wroteLastChunk = true; + } + } + + // -------------------------------------------- OutputStream Methods + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.cache[this.cachePosition] = (byte) b; + this.cachePosition++; + if (this.cachePosition == this.cache.length) flushCache(); + } + + /** + * Writes the array. If the array does not fit within the buffer, it is + * not split, but rather written out as one large chunk. + * @param b + * @throws IOException + */ + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + public void write(byte src[], int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (len >= this.cache.length - this.cachePosition) { + flushCacheWithAppend(src, off, len); + } else { + System.arraycopy(src, off, cache, this.cachePosition, len); + this.cachePosition += len; + } + } + + /** + * Flushes the content buffer and the underlying stream. + * @throws IOException + */ + public void flush() throws IOException { + flushCache(); + this.out.flush(); + } + + /** + * Finishes writing to the underlying stream, but does NOT close the underlying stream. + * @throws IOException + */ + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + finish(); + this.out.flush(); + } + } +} diff --git a/src/org/apache/http/impl/io/ContentLengthInputStream.java b/src/org/apache/http/impl/io/ContentLengthInputStream.java new file mode 100644 index 0000000..3b19c5b --- /dev/null +++ b/src/org/apache/http/impl/io/ContentLengthInputStream.java @@ -0,0 +1,220 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java $ + * $Revision: 652091 $ + * $Date: 2008-04-29 13:41:07 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.io.SessionInputBuffer; + +/** + * Stream that cuts off after a specified number of bytes. + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * + * <p>Implementation note: Choices abound. One approach would pass + * through the {@link InputStream#mark} and {@link InputStream#reset} calls to + * the underlying stream. That's tricky, though, because you then have to + * start duplicating the work of keeping track of how much a reset rewinds. + * Further, you have to watch out for the "readLimit", and since the semantics + * for the readLimit leave room for differing implementations, you might get + * into a lot of trouble.</p> + * + * <p>Alternatively, you could make this class extend + * {@link java.io.BufferedInputStream} + * and then use the protected members of that class to avoid duplicated effort. + * That solution has the side effect of adding yet another possible layer of + * buffering.</p> + * + * <p>Then, there is the simple choice, which this takes - simply don't + * support {@link InputStream#mark} and {@link InputStream#reset}. That choice + * has the added benefit of keeping this class very simple.</p> + * + * @author Ortwin Glueck + * @author Eric Johnson + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * + * @since 4.0 + */ +public class ContentLengthInputStream extends InputStream { + + private static final int BUFFER_SIZE = 2048; + /** + * The maximum number of bytes that can be read from the stream. Subsequent + * read operations will return -1. + */ + private long contentLength; + + /** The current position */ + private long pos = 0; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Wrapped input stream that all calls are delegated to. + */ + private SessionInputBuffer in = null; + + /** + * Creates a new length limited stream + * + * @param in The session input buffer to wrap + * @param contentLength The maximum number of bytes that can be read from + * the stream. Subsequent read operations will return -1. + */ + public ContentLengthInputStream(final SessionInputBuffer in, long contentLength) { + super(); + if (in == null) { + throw new IllegalArgumentException("Input stream may not be null"); + } + if (contentLength < 0) { + throw new IllegalArgumentException("Content length may not be negative"); + } + this.in = in; + this.contentLength = contentLength; + } + + /** + * <p>Reads until the end of the known length of content.</p> + * + * <p>Does not close the underlying socket input, but instead leaves it + * primed to parse the next response.</p> + * @throws IOException If an IO problem occurs. + */ + public void close() throws IOException { + if (!closed) { + try { + byte buffer[] = new byte[BUFFER_SIZE]; + while (read(buffer) >= 0) { + } + } finally { + // close after above so that we don't throw an exception trying + // to read after closed! + closed = true; + } + } + } + + + /** + * Read the next byte from the stream + * @return The next byte or -1 if the end of stream has been reached. + * @throws IOException If an IO problem occurs + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (pos >= contentLength) { + return -1; + } + pos++; + return this.in.read(); + } + + /** + * Does standard {@link InputStream#read(byte[], int, int)} behavior, but + * also notifies the watcher when the contents have been consumed. + * + * @param b The byte array to fill. + * @param off Start filling at this position. + * @param len The number of bytes to attempt to read. + * @return The number of bytes read, or -1 if the end of content has been + * reached. + * + * @throws java.io.IOException Should an error occur on the wrapped stream. + */ + public int read (byte[] b, int off, int len) throws java.io.IOException { + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (pos >= contentLength) { + return -1; + } + + if (pos + len > contentLength) { + len = (int) (contentLength - pos); + } + int count = this.in.read(b, off, len); + pos += count; + return count; + } + + + /** + * Read more bytes from the stream. + * @param b The byte array to put the new data in. + * @return The number of bytes read into the buffer. + * @throws IOException If an IO problem occurs + * @see java.io.InputStream#read(byte[]) + */ + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Skips and discards a number of bytes from the input stream. + * @param n The number of bytes to skip. + * @return The actual number of bytes skipped. <= 0 if no bytes + * are skipped. + * @throws IOException If an error occurs while skipping bytes. + * @see InputStream#skip(long) + */ + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + byte[] buffer = new byte[BUFFER_SIZE]; + // make sure we don't skip more bytes than are + // still available + long remaining = Math.min(n, this.contentLength - this.pos); + // skip and keep track of the bytes actually skipped + long count = 0; + while (remaining > 0) { + int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); + if (l == -1) { + break; + } + count += l; + remaining -= l; + } + this.pos += count; + return count; + } +} diff --git a/src/org/apache/http/impl/io/ContentLengthOutputStream.java b/src/org/apache/http/impl/io/ContentLengthOutputStream.java new file mode 100644 index 0000000..afcb883 --- /dev/null +++ b/src/org/apache/http/impl/io/ContentLengthOutputStream.java @@ -0,0 +1,132 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ContentLengthOutputStream.java $ + * $Revision: 560343 $ + * $Date: 2007-07-27 11:18:19 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; + +/** + * A stream wrapper that closes itself after a defined number of bytes. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560343 $ + * + * @since 4.0 + */ +public class ContentLengthOutputStream extends OutputStream { + + /** + * Wrapped session outbut buffer. + */ + private final SessionOutputBuffer out; + + /** + * The maximum number of bytes that can be written the stream. Subsequent + * write operations will be ignored. + */ + private final long contentLength; + + /** Total bytes written */ + private long total = 0; + + /** True if the stream is closed. */ + private boolean closed = false; + + /** + * Creates a new length limited stream + * + * @param out The data transmitter to wrap + * @param contentLength The maximum number of bytes that can be written to + * the stream. Subsequent write operations will be ignored. + * + * @since 4.0 + */ + public ContentLengthOutputStream(final SessionOutputBuffer out, long contentLength) { + super(); + if (out == null) { + throw new IllegalArgumentException("Session output buffer may not be null"); + } + if (contentLength < 0) { + throw new IllegalArgumentException("Content length may not be negative"); + } + this.out = out; + this.contentLength = contentLength; + } + + /** + * <p>Does not close the underlying socket output.</p> + * + * @throws IOException If an I/O problem occurs. + */ + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + this.out.flush(); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (this.total < this.contentLength) { + long max = this.contentLength - this.total; + if (len > max) { + len = (int) max; + } + this.out.write(b, off, len); + this.total += len; + } + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + if (this.total < this.contentLength) { + this.out.write(b); + this.total++; + } + } + +} diff --git a/src/org/apache/http/impl/io/HttpRequestParser.java b/src/org/apache/http/impl/io/HttpRequestParser.java new file mode 100644 index 0000000..a7bae6d --- /dev/null +++ b/src/org/apache/http/impl/io/HttpRequestParser.java @@ -0,0 +1,80 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpRequestParser.java $ + * $Revision: 589374 $ + * $Date: 2007-10-28 09:25:07 -0700 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.ConnectionClosedException; +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpRequestFactory; +import org.apache.http.RequestLine; +import org.apache.http.ParseException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpRequestParser extends AbstractMessageParser { + + private final HttpRequestFactory requestFactory; + private final CharArrayBuffer lineBuf; + + public HttpRequestParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpRequestFactory requestFactory, + final HttpParams params) { + super(buffer, parser, params); + if (requestFactory == null) { + throw new IllegalArgumentException("Request factory may not be null"); + } + this.requestFactory = requestFactory; + this.lineBuf = new CharArrayBuffer(128); + } + + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new ConnectionClosedException("Client closed connection"); + } + ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + RequestLine requestline = this.lineParser.parseRequestLine(this.lineBuf, cursor); + return this.requestFactory.newHttpRequest(requestline); + } + +} diff --git a/src/org/apache/http/impl/io/HttpRequestWriter.java b/src/org/apache/http/impl/io/HttpRequestWriter.java new file mode 100644 index 0000000..b784e2d --- /dev/null +++ b/src/org/apache/http/impl/io/HttpRequestWriter.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpRequestWriter.java $ + * $Revision: 569673 $ + * $Date: 2007-08-25 06:58:51 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.HttpMessage; +import org.apache.http.HttpRequest; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.message.LineFormatter; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpRequestWriter extends AbstractMessageWriter { + + public HttpRequestWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(buffer, formatter, params); + } + + protected void writeHeadLine(final HttpMessage message) + throws IOException { + + final CharArrayBuffer buffer = lineFormatter.formatRequestLine + (this.lineBuf, ((HttpRequest) message).getRequestLine()); + this.sessionBuffer.writeLine(buffer); + } + +} diff --git a/src/org/apache/http/impl/io/HttpResponseParser.java b/src/org/apache/http/impl/io/HttpResponseParser.java new file mode 100644 index 0000000..575aa18 --- /dev/null +++ b/src/org/apache/http/impl/io/HttpResponseParser.java @@ -0,0 +1,81 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpResponseParser.java $ + * $Revision: 589374 $ + * $Date: 2007-10-28 09:25:07 -0700 (Sun, 28 Oct 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponseFactory; +import org.apache.http.NoHttpResponseException; +import org.apache.http.StatusLine; +import org.apache.http.ParseException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpResponseParser extends AbstractMessageParser { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + + public HttpResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + if (responseFactory == null) { + throw new IllegalArgumentException("Response factory may not be null"); + } + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + } + + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) + throws IOException, HttpException, ParseException { + + this.lineBuf.clear(); + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1) { + throw new NoHttpResponseException("The target server failed to respond"); + } + //create the status line from the status string + ParserCursor cursor = new ParserCursor(0, this.lineBuf.length()); + StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/src/org/apache/http/impl/io/HttpResponseWriter.java b/src/org/apache/http/impl/io/HttpResponseWriter.java new file mode 100644 index 0000000..f88791e --- /dev/null +++ b/src/org/apache/http/impl/io/HttpResponseWriter.java @@ -0,0 +1,59 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpResponseWriter.java $ + * $Revision: 569673 $ + * $Date: 2007-08-25 06:58:51 -0700 (Sat, 25 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; + +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponse; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.message.LineFormatter; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class HttpResponseWriter extends AbstractMessageWriter { + + public HttpResponseWriter(final SessionOutputBuffer buffer, + final LineFormatter formatter, + final HttpParams params) { + super(buffer, formatter, params); + } + + protected void writeHeadLine(final HttpMessage message) + throws IOException { + + final CharArrayBuffer buffer = lineFormatter.formatStatusLine + (this.lineBuf, ((HttpResponse) message).getStatusLine()); + this.sessionBuffer.writeLine(buffer); + } + +} diff --git a/src/org/apache/http/impl/io/HttpTransportMetricsImpl.java b/src/org/apache/http/impl/io/HttpTransportMetricsImpl.java new file mode 100644 index 0000000..53e6772 --- /dev/null +++ b/src/org/apache/http/impl/io/HttpTransportMetricsImpl.java @@ -0,0 +1,63 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/HttpTransportMetricsImpl.java $ + * $Revision: 539755 $ + * $Date: 2007-05-19 07:05:02 -0700 (Sat, 19 May 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import org.apache.http.io.HttpTransportMetrics; + +/** + * Default implementation of {@link HttpTransportMetrics}. + */ +public class HttpTransportMetricsImpl implements HttpTransportMetrics { + + private long bytesTransferred = 0; + + public HttpTransportMetricsImpl() { + super(); + } + + public long getBytesTransferred() { + return this.bytesTransferred; + } + + public void setBytesTransferred(long count) { + this.bytesTransferred = count; + } + + public void incrementBytesTransferred(long count) { + this.bytesTransferred += count; + } + + public void reset() { + this.bytesTransferred = 0; + } + +} diff --git a/src/org/apache/http/impl/io/IdentityInputStream.java b/src/org/apache/http/impl/io/IdentityInputStream.java new file mode 100644 index 0000000..390d5b7 --- /dev/null +++ b/src/org/apache/http/impl/io/IdentityInputStream.java @@ -0,0 +1,90 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/IdentityInputStream.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.io.SessionInputBuffer; + +/** + * A stream for reading from a {@link SessionInputBuffer session input buffer}. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class IdentityInputStream extends InputStream { + + private final SessionInputBuffer in; + + private boolean closed = false; + + public IdentityInputStream(final SessionInputBuffer in) { + super(); + if (in == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.in = in; + } + + public int available() throws IOException { + if (!this.closed && this.in.isDataAvailable(10)) { + return 1; + } else { + return 0; + } + } + + public void close() throws IOException { + this.closed = true; + } + + public int read() throws IOException { + if (this.closed) { + return -1; + } else { + return this.in.read(); + } + } + + public int read(final byte[] b, int off, int len) throws IOException { + if (this.closed) { + return -1; + } else { + return this.in.read(b, off, len); + } + } + +} diff --git a/src/org/apache/http/impl/io/IdentityOutputStream.java b/src/org/apache/http/impl/io/IdentityOutputStream.java new file mode 100644 index 0000000..10b64f7 --- /dev/null +++ b/src/org/apache/http/impl/io/IdentityOutputStream.java @@ -0,0 +1,100 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/IdentityOutputStream.java $ + * $Revision: 560343 $ + * $Date: 2007-07-27 11:18:19 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.io.SessionOutputBuffer; + +/** + * A stream for writing with an "identity" transport encoding. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560343 $ + * + * @since 4.0 + */ +public class IdentityOutputStream extends OutputStream { + + /** + * Wrapped session output buffer. + */ + private final SessionOutputBuffer out; + + /** True if the stream is closed. */ + private boolean closed = false; + + public IdentityOutputStream(final SessionOutputBuffer out) { + super(); + if (out == null) { + throw new IllegalArgumentException("Session output buffer may not be null"); + } + this.out = out; + } + + /** + * <p>Does not close the underlying socket output.</p> + * + * @throws IOException If an I/O problem occurs. + */ + public void close() throws IOException { + if (!this.closed) { + this.closed = true; + this.out.flush(); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void write(byte[] b, int off, int len) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.out.write(b, off, len); + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public void write(int b) throws IOException { + if (this.closed) { + throw new IOException("Attempted write to closed stream."); + } + this.out.write(b); + } + +} diff --git a/src/org/apache/http/impl/io/SocketInputBuffer.java b/src/org/apache/http/impl/io/SocketInputBuffer.java new file mode 100644 index 0000000..925e80a --- /dev/null +++ b/src/org/apache/http/impl/io/SocketInputBuffer.java @@ -0,0 +1,115 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/SocketInputBuffer.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Socket; + +import org.apache.http.params.HttpParams; + + +/** + * {@link Socket} bound session input buffer. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class SocketInputBuffer extends AbstractSessionInputBuffer { + + static private final Class SOCKET_TIMEOUT_CLASS = SocketTimeoutExceptionClass(); + + /** + * Returns <code>SocketTimeoutExceptionClass<code> or <code>null</code> if the class + * does not exist. + * + * @return <code>SocketTimeoutExceptionClass<code>, or <code>null</code> if unavailable. + */ + static private Class SocketTimeoutExceptionClass() { + try { + return Class.forName("java.net.SocketTimeoutException"); + } catch (ClassNotFoundException e) { + return null; + } + } + + private static boolean isSocketTimeoutException(final InterruptedIOException e) { + if (SOCKET_TIMEOUT_CLASS != null) { + return SOCKET_TIMEOUT_CLASS.isInstance(e); + } else { + return true; + } + } + + private final Socket socket; + + public SocketInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + super(); + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + this.socket = socket; + if (buffersize < 0) { + buffersize = socket.getReceiveBufferSize(); + } + if (buffersize < 1024) { + buffersize = 1024; + } + init(socket.getInputStream(), buffersize, params); + } + + public boolean isDataAvailable(int timeout) throws IOException { + boolean result = hasBufferedData(); + if (!result) { + int oldtimeout = this.socket.getSoTimeout(); + try { + this.socket.setSoTimeout(timeout); + fillBuffer(); + result = hasBufferedData(); + } catch (InterruptedIOException e) { + if (!isSocketTimeoutException(e)) { + throw e; + } + } finally { + socket.setSoTimeout(oldtimeout); + } + } + return result; + } + +} diff --git a/src/org/apache/http/impl/io/SocketOutputBuffer.java b/src/org/apache/http/impl/io/SocketOutputBuffer.java new file mode 100644 index 0000000..efb91e9 --- /dev/null +++ b/src/org/apache/http/impl/io/SocketOutputBuffer.java @@ -0,0 +1,79 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/SocketOutputBuffer.java $ + * $Revision: 560358 $ + * $Date: 2007-07-27 12:30:42 -0700 (Fri, 27 Jul 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.io; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.http.params.HttpParams; + + +/** + * {@link Socket} bound session output buffer. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @version $Revision: 560358 $ + * + * @since 4.0 + */ +public class SocketOutputBuffer extends AbstractSessionOutputBuffer { + + public SocketOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + super(); + if (socket == null) { + throw new IllegalArgumentException("Socket may not be null"); + } + if (buffersize < 0) { + buffersize = socket.getReceiveBufferSize(); +// BEGIN android-changed + // Workaround for http://b/issue?id=1083103. + if (buffersize > 8096) { + buffersize = 8096; + } +// END android-changed + } + if (buffersize < 1024) { + buffersize = 1024; + } + +// BEGIN android-changed + socket.setSendBufferSize(buffersize * 3); +// END andrdoid-changed + + init(socket.getOutputStream(), buffersize, params); + } + +} diff --git a/src/org/apache/http/impl/io/package.html b/src/org/apache/http/impl/io/package.html new file mode 100644 index 0000000..48eb2c1 --- /dev/null +++ b/src/org/apache/http/impl/io/package.html @@ -0,0 +1,48 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/package.html $ + * $Revision: 567360 $ + * $Date: 2007-08-18 23:49:21 -0700 (Sat, 18 Aug 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Default implementations for interfaces in +{@link org.apache.http.io org.apache.http.io}. + +<br/> + +There are implementations of the transport encodings used by HTTP, +in particular the chunked encoding for +{@link org.apache.http.impl.io.ChunkedOutputStream sending} and +{@link org.apache.http.impl.io.ChunkedInputStream receiving} entities. + +</body> +</html> diff --git a/src/org/apache/http/impl/package.html b/src/org/apache/http/impl/package.html new file mode 100644 index 0000000..6cec586 --- /dev/null +++ b/src/org/apache/http/impl/package.html @@ -0,0 +1,41 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/package.html $ + * $Revision: 496072 $ + * $Date: 2007-01-14 04:22:55 -0800 (Sun, 14 Jan 2007) $ + * + * ==================================================================== + * 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> +Default implementations for interfaces in +{@link org.apache.http org.apache.http}. + +</body> +</html> |