michael@0: /* michael@0: * ==================================================================== michael@0: * Licensed to the Apache Software Foundation (ASF) under one michael@0: * or more contributor license agreements. See the NOTICE file michael@0: * distributed with this work for additional information michael@0: * regarding copyright ownership. The ASF licenses this file michael@0: * to you under the Apache License, Version 2.0 (the michael@0: * "License"); you may not use this file except in compliance michael@0: * with the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, michael@0: * software distributed under the License is distributed on an michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY michael@0: * KIND, either express or implied. See the License for the michael@0: * specific language governing permissions and limitations michael@0: * under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.impl; michael@0: michael@0: import ch.boye.httpclientandroidlib.ConnectionReuseStrategy; michael@0: import ch.boye.httpclientandroidlib.HttpConnection; michael@0: import ch.boye.httpclientandroidlib.HeaderIterator; michael@0: import ch.boye.httpclientandroidlib.HttpEntity; michael@0: import ch.boye.httpclientandroidlib.HttpResponse; michael@0: import ch.boye.httpclientandroidlib.HttpVersion; michael@0: import ch.boye.httpclientandroidlib.ParseException; michael@0: import ch.boye.httpclientandroidlib.ProtocolVersion; michael@0: import ch.boye.httpclientandroidlib.protocol.HTTP; michael@0: import ch.boye.httpclientandroidlib.protocol.HttpContext; michael@0: import ch.boye.httpclientandroidlib.protocol.ExecutionContext; michael@0: import ch.boye.httpclientandroidlib.TokenIterator; michael@0: import ch.boye.httpclientandroidlib.message.BasicTokenIterator; michael@0: michael@0: /** michael@0: * Default implementation of a strategy deciding about connection re-use. michael@0: * The default implementation first checks some basics, for example michael@0: * whether the connection is still open or whether the end of the michael@0: * request entity can be determined without closing the connection. michael@0: * If these checks pass, the tokens in the Connection header will michael@0: * be examined. In the absence of a Connection header, the michael@0: * non-standard but commonly used Proxy-Connection header takes michael@0: * it's role. A token close indicates that the connection cannot michael@0: * be reused. If there is no such token, a token keep-alive michael@0: * indicates that the connection should be re-used. If neither token is found, michael@0: * or if there are no Connection headers, the default policy for michael@0: * the HTTP version is applied. Since HTTP/1.1, connections are michael@0: * re-used by default. Up until HTTP/1.0, connections are not michael@0: * re-used by default. michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: public class DefaultConnectionReuseStrategy implements ConnectionReuseStrategy { michael@0: michael@0: public DefaultConnectionReuseStrategy() { michael@0: super(); michael@0: } michael@0: michael@0: // see interface ConnectionReuseStrategy michael@0: public boolean keepAlive(final HttpResponse response, michael@0: final HttpContext context) { michael@0: if (response == null) { michael@0: throw new IllegalArgumentException michael@0: ("HTTP response may not be null."); michael@0: } michael@0: if (context == null) { michael@0: throw new IllegalArgumentException michael@0: ("HTTP context may not be null."); michael@0: } michael@0: michael@0: HttpConnection conn = (HttpConnection) michael@0: context.getAttribute(ExecutionContext.HTTP_CONNECTION); michael@0: michael@0: if (conn != null && !conn.isOpen()) michael@0: return false; michael@0: // do NOT check for stale connection, that is an expensive operation michael@0: michael@0: // Check for a self-terminating entity. If the end of the entity will michael@0: // be indicated by closing the connection, there is no keep-alive. michael@0: HttpEntity entity = response.getEntity(); michael@0: ProtocolVersion ver = response.getStatusLine().getProtocolVersion(); michael@0: if (entity != null) { michael@0: if (entity.getContentLength() < 0) { michael@0: if (!entity.isChunked() || michael@0: ver.lessEquals(HttpVersion.HTTP_1_0)) { michael@0: // if the content length is not known and is not chunk michael@0: // encoded, the connection cannot be reused michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Check for the "Connection" header. If that is absent, check for michael@0: // the "Proxy-Connection" header. The latter is an unspecified and michael@0: // broken but unfortunately common extension of HTTP. michael@0: HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE); michael@0: if (!hit.hasNext()) michael@0: hit = response.headerIterator("Proxy-Connection"); michael@0: michael@0: // Experimental usage of the "Connection" header in HTTP/1.0 is michael@0: // documented in RFC 2068, section 19.7.1. A token "keep-alive" is michael@0: // used to indicate that the connection should be persistent. michael@0: // Note that the final specification of HTTP/1.1 in RFC 2616 does not michael@0: // include this information. Neither is the "Connection" header michael@0: // mentioned in RFC 1945, which informally describes HTTP/1.0. michael@0: // michael@0: // RFC 2616 specifies "close" as the only connection token with a michael@0: // specific meaning: it disables persistent connections. michael@0: // michael@0: // The "Proxy-Connection" header is not formally specified anywhere, michael@0: // but is commonly used to carry one token, "close" or "keep-alive". michael@0: // The "Connection" header, on the other hand, is defined as a michael@0: // sequence of tokens, where each token is a header name, and the michael@0: // token "close" has the above-mentioned additional meaning. michael@0: // michael@0: // To get through this mess, we treat the "Proxy-Connection" header michael@0: // in exactly the same way as the "Connection" header, but only if michael@0: // the latter is missing. We scan the sequence of tokens for both michael@0: // "close" and "keep-alive". As "close" is specified by RFC 2068, michael@0: // it takes precedence and indicates a non-persistent connection. michael@0: // If there is no "close" but a "keep-alive", we take the hint. michael@0: michael@0: if (hit.hasNext()) { michael@0: try { michael@0: TokenIterator ti = createTokenIterator(hit); michael@0: boolean keepalive = false; michael@0: while (ti.hasNext()) { michael@0: final String token = ti.nextToken(); michael@0: if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) { michael@0: return false; michael@0: } else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) { michael@0: // continue the loop, there may be a "close" afterwards michael@0: keepalive = true; michael@0: } michael@0: } michael@0: if (keepalive) michael@0: return true; michael@0: // neither "close" nor "keep-alive", use default policy michael@0: michael@0: } catch (ParseException px) { michael@0: // invalid connection header means no persistent connection michael@0: // we don't have logging in HttpCore, so the exception is lost michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // default since HTTP/1.1 is persistent, before it was non-persistent michael@0: return !ver.lessEquals(HttpVersion.HTTP_1_0); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Creates a token iterator from a header iterator. michael@0: * This method can be overridden to replace the implementation of michael@0: * the token iterator. michael@0: * michael@0: * @param hit the header iterator michael@0: * michael@0: * @return the token iterator michael@0: */ michael@0: protected TokenIterator createTokenIterator(HeaderIterator hit) { michael@0: return new BasicTokenIterator(hit); michael@0: } michael@0: }