/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.synapse.transport.nhttp; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.transport.base.MetricsCollector; import org.apache.axis2.transport.base.threads.WorkerPool; import org.apache.axis2.transport.base.threads.WorkerPoolFactory; import org.apache.axis2.util.JavaUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.*; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.DefaultConnectionReuseStrategy; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.message.BasicHttpResponse; import org.apache.http.nio.ContentDecoder; import org.apache.http.nio.ContentEncoder; import org.apache.http.nio.NHttpServerConnection; import org.apache.http.nio.NHttpServerEventHandler; import org.apache.http.nio.entity.ContentInputStream; import org.apache.http.nio.entity.ContentOutputStream; import org.apache.http.nio.util.*; import org.apache.http.protocol.*; import org.apache.http.util.EncodingUtils; import org.apache.synapse.commons.evaluators.EvaluatorContext; import org.apache.synapse.commons.evaluators.Parser; import org.apache.synapse.commons.executors.PriorityExecutor; import org.apache.synapse.commons.jmx.ThreadingView; import org.apache.synapse.transport.http.conn.Scheme; import org.apache.synapse.transport.nhttp.debug.ServerConnectionDebug; import org.apache.synapse.transport.nhttp.util.LatencyCollector; import org.apache.synapse.transport.nhttp.util.LatencyView; import org.apache.synapse.transport.nhttp.util.NhttpMetricsCollector; import org.apache.synapse.transport.nhttp.util.NhttpUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The server connection handler. An instance of this class is used by each IOReactor, to * process every connection. Hence this class should not store any data related to a single * connection - as this is being shared. */ public class ServerHandler implements NHttpServerEventHandler { private static final Log log = LogFactory.getLog(ServerHandler.class); /** the Axis2 configuration context */ private final ConfigurationContext cfgCtx; /** the protocol scheme */ private final Scheme scheme; /** the listener context */ private final ListenerContext listenerContext; /** the factory to create HTTP responses */ private final HttpResponseFactory responseFactory; /** the HTTP response processor */ private final HttpProcessor httpProcessor; /** the strategy to re-use connections */ private final ConnectionReuseStrategy connStrategy; /** the buffer allocator */ private final ByteBufferAllocator allocator; /** the thread pool to process requests */ private final WorkerPool workerPool; /** the metrics collector */ private final NhttpMetricsCollector metrics; /** * This parset is used by the priority executor to parse a given HTTP message and * determine the priority of the message */ private final Parser parser; /** * An executor capable of exucuting the Server Worker according the priority assigned * to a particular message */ private final PriorityExecutor executor; /** keeps track of the connection that are alive in the system */ private final List<NHttpServerConnection> activeConnections; private final LatencyView latencyView; private final LatencyView s2sLatencyView; private final ThreadingView threadingView; public static final String REQUEST_SINK_BUFFER = "synapse.request-sink-buffer"; public static final String RESPONSE_SOURCE_BUFFER = "synapse.response-source-buffer"; public static final String CONNECTION_CREATION_TIME = "synapse.connectionCreationTime"; public static final String SERVER_CONNECTION_DEBUG = "synapse.server-connection-debug"; /** * Used to obtain http request from context */ public static final String HTTP_REQUEST = "http.request"; public ServerHandler( final ConfigurationContext cfgCtx, final Scheme scheme, final ListenerContext listenerContext, final NhttpMetricsCollector metrics) { super(); this.cfgCtx = cfgCtx; this.scheme = scheme; this.listenerContext = listenerContext; this.metrics = metrics; this.responseFactory = new DefaultHttpResponseFactory(); this.httpProcessor = getHttpProcessor(); this.connStrategy = new DefaultConnectionReuseStrategy(); this.allocator = new HeapByteBufferAllocator(); this.activeConnections = new ArrayList<NHttpServerConnection>(); String strNamePostfix = ""; if (listenerContext.getTransportIn() != null && listenerContext.getTransportIn().getName() != null) { strNamePostfix = "-" + listenerContext.getTransportIn().getName(); } NHttpConfiguration cfg = NHttpConfiguration.getInstance(); boolean enableAdvancedForLatencyView = cfg.getBooleanValue("synapse.nhttp.latency_view.enable_advanced_view", false); boolean enableAdvancedForS2SView = cfg.getBooleanValue("synapse.nhttp.s2slatency_view.enable_advanced_view", false); this.latencyView = new LatencyView("NHTTPLatencyView", scheme.isSSL(), strNamePostfix, enableAdvancedForLatencyView); this.s2sLatencyView = new LatencyView("NHTTPS2SLatencyView", scheme.isSSL(), strNamePostfix, enableAdvancedForS2SView); this.threadingView = new ThreadingView("HttpServerWorker", true, 50); if (listenerContext.getExecutor() == null) { this.workerPool = WorkerPoolFactory.getWorkerPool( cfg.getServerCoreThreads(), cfg.getServerMaxThreads(), cfg.getServerKeepalive(), cfg.getServerQueueLen(), "Server Worker thread group", "HttpServerWorker"); this.executor = null; this.parser = null; } else { this.workerPool = null; this.executor = listenerContext.getExecutor(); this.parser = listenerContext.getParser(); } } /** * Process a new incoming request * @param conn the connection */ public void requestReceived(final NHttpServerConnection conn) { HttpContext context = conn.getContext(); context.setAttribute(NhttpConstants.REQ_ARRIVAL_TIME, System.currentTimeMillis()); context.setAttribute(NhttpConstants.REQ_FROM_CLIENT_READ_START_TIME, System.currentTimeMillis()); HttpRequest request = conn.getHttpRequest(); context.setAttribute(ExecutionContext.HTTP_REQUEST, request); context.setAttribute(NhttpConstants.MESSAGE_IN_FLIGHT, "true"); // prepare to collect debug information conn.getContext().setAttribute( ServerHandler.SERVER_CONNECTION_DEBUG, new ServerConnectionDebug(conn)); NHttpConfiguration cfg = NHttpConfiguration.getInstance(); try { InputStream is; // Only create an input buffer and ContentInputStream if the request has content if (request instanceof HttpEntityEnclosingRequest) { // Mark request as not yet fully read, to detect timeouts from harmless keepalive deaths conn.getContext().setAttribute(NhttpConstants.REQUEST_READ, Boolean.FALSE); ContentInputBuffer inputBuffer = new SharedInputBuffer(cfg.getBufferSize(), conn, allocator); context.setAttribute(REQUEST_SINK_BUFFER, inputBuffer); is = new ContentInputStream(inputBuffer); } else { is = null; conn.getContext().removeAttribute(NhttpConstants.REQUEST_READ); } ContentOutputBuffer outputBuffer = new SharedOutputBuffer(cfg.getBufferSize(), conn, allocator); context.setAttribute(RESPONSE_SOURCE_BUFFER, outputBuffer); OutputStream os = new ContentOutputStream(outputBuffer); // create the default response to this request ProtocolVersion httpVersion = request.getRequestLine().getProtocolVersion(); HttpResponse response = responseFactory.newHttpResponse( httpVersion, HttpStatus.SC_OK, context); // create a basic HttpEntity using the source channel of the response pipe BasicHttpEntity entity = new BasicHttpEntity(); if (httpVersion.greaterEquals(HttpVersion.HTTP_1_1)) { entity.setChunked(true); } response.setEntity(entity); if (metrics != null) { metrics.incrementMessagesReceived(); } // hand off processing of the request to a thread off the pool ServerWorker worker = new ServerWorker( cfgCtx, scheme.getName(), metrics, conn, this, request, is, response, os, listenerContext.isRestDispatching(), listenerContext.getHttpGetRequestProcessor()); if (workerPool != null) { workerPool.execute(worker); } else if (executor != null) { Map<String, String> headers = new HashMap<String, String>(); for (Header header : request.getAllHeaders()) { headers.put(header.getName(), header.getValue()); } EvaluatorContext evaluatorContext = new EvaluatorContext(request.getRequestLine().getUri(), headers); int priority = parser.parse(evaluatorContext); executor.execute(worker, priority); } // See if the client expects a 100-Continue Header expect = request.getFirstHeader(HTTP.EXPECT_DIRECTIVE); if (expect != null && HTTP.EXPECT_CONTINUE.equalsIgnoreCase(expect.getValue())) { HttpResponse ack = new BasicHttpResponse(request.getProtocolVersion(), HttpStatus.SC_CONTINUE, "Continue"); conn.submitResponse(ack); if (log.isDebugEnabled()) { log.debug(conn + ": Expect :100 Continue hit, sending ack back to the server"); } return; } } catch (Exception e) { if (metrics != null) { metrics.incrementFaultsReceiving(); } handleException("Error processing request received for : " + request.getRequestLine().getUri(), e, conn); } } /** * Process ready input by writing it into the Pipe * @param conn the connection being processed * @param decoder the content decoder in use */ public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) { HttpContext context = conn.getContext(); ContentInputBuffer inBuf = (ContentInputBuffer) context.getAttribute(REQUEST_SINK_BUFFER); try { int bytesRead = inBuf.consumeContent(decoder); if (metrics != null && bytesRead > 0) { metrics.incrementBytesReceived(bytesRead); } if (decoder.isCompleted()) { ((ServerConnectionDebug) conn.getContext().getAttribute( SERVER_CONNECTION_DEBUG)).recordRequestCompletionTime(); // remove the request we have fully read, to detect harmless keepalive timeouts from // real timeouts while reading requests context.setAttribute(NhttpConstants.REQUEST_READ, Boolean.TRUE); context.setAttribute(NhttpConstants.REQ_FROM_CLIENT_READ_END_TIME, System.currentTimeMillis()); } } catch (IOException e) { if (metrics != null) { metrics.incrementFaultsReceiving(); } handleException("I/O Error at inputReady : " + e.getMessage(), e, conn); } } private void updateLatencyView(HttpContext context) { if (context == null) { return; } latencyView.notifyTimes(new LatencyCollector(context, false)); s2sLatencyView.notifyTimes(new LatencyCollector(context, true)); LatencyCollector.clearTimestamps(context); } /** * Process ready output by writing into the channel * @param conn the connection being processed * @param encoder the content encoder in use */ public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) { HttpContext context = conn.getContext(); HttpResponse response = conn.getHttpResponse(); ContentOutputBuffer outBuf = (ContentOutputBuffer) context.getAttribute( RESPONSE_SOURCE_BUFFER); if (outBuf == null) { // fix for SYNAPSE 584. This is a temporaly fix becuase of HTTPCORE-208 shutdownConnection(conn, false, null); return; } try { int bytesWritten = outBuf.produceContent(encoder); if (metrics != null && bytesWritten > 0) { metrics.incrementBytesSent(bytesWritten); } if (encoder.isCompleted()) { long currentTime = System.currentTimeMillis(); context.setAttribute(NhttpConstants.RES_TO_CLIENT_WRITE_END_TIME, currentTime); context.setAttribute(NhttpConstants.RES_DEPARTURE_TIME, currentTime); updateLatencyView(context); context.removeAttribute(NhttpConstants.REQ_ARRIVAL_TIME); context.removeAttribute(NhttpConstants.REQ_DEPARTURE_TIME); context.removeAttribute(NhttpConstants.RES_ARRIVAL_TIME); ((ServerConnectionDebug) conn.getContext().getAttribute( SERVER_CONNECTION_DEBUG)).recordResponseCompletionTime(); Boolean reqRead = (Boolean) conn.getContext().getAttribute( NhttpConstants.REQUEST_READ); Boolean forceConnectionClose = (Boolean) conn.getContext().getAttribute( NhttpConstants.FORCE_CONNECTION_CLOSE); if (reqRead != null && !reqRead) { try { // this is a connection we should not re-use conn.close(); } catch (Exception ignore) {} } else if (!connStrategy.keepAlive(response, context)) { conn.close(); } else if (forceConnectionClose != null && forceConnectionClose) { conn.close(); } else { conn.requestInput(); } } } catch (IOException e) { if (metrics != null) { metrics.incrementFaultsSending(); } handleException("I/O Error at outputReady : " + e.getMessage(), e, conn); } } /** * Commit the response to the connection. Processes the response through the configured * HttpProcessor and submits it to be sent out. This method hides any exceptions and is targetted * for non critical (i.e. browser requests etc) requests, which are not core messages * @param conn the connection being processed * @param response the response to commit over the connection */ public void commitResponseHideExceptions( final NHttpServerConnection conn, final HttpResponse response) { try { conn.suspendInput(); httpProcessor.process(response, conn.getContext()); conn.submitResponse(response); } catch (HttpException e) { handleException("Unexpected HTTP protocol error : " + e.getMessage(), e, conn); } catch (IOException e) { handleException("IO error submiting response : " + e.getMessage(), e, conn); } } /** * Commit the response to the connection. Processes the response through the configured * HttpProcessor and submits it to be sent out. Re-Throws exceptions, after closing connections * @param conn the connection being processed * @param response the response to commit over the connection * @throws IOException if an IO error occurs while sending the response * @throws HttpException if a HTTP protocol violation occurs while sending the response */ public void commitResponse(final NHttpServerConnection conn, final HttpResponse response) throws IOException, HttpException { try { BasicHttpEntity entity = (BasicHttpEntity) response.getEntity(); Header[] headers = response.getAllHeaders(); int contentLength = -1; if (canResponseHaveBody(response, conn)) { if (entity == null) { entity = new BasicHttpEntity(); } for (Header header : headers) { if (header.getName().equals(HTTP.CONTENT_LEN) && Integer.parseInt(header.getValue()) > 0) { contentLength = Integer.parseInt(header.getValue()); response.removeHeader(header); } } if (contentLength != -1) { entity.setChunked(false); entity.setContentLength(contentLength); } else { entity.setChunked(true); } } else { if (entity != null) { entity.setChunked(false); entity.setContentLength(contentLength); } } response.setEntity(entity); conn.suspendInput(); HttpContext context = conn.getContext(); httpProcessor.process(response, context); conn.getContext().setAttribute(NhttpConstants.RES_TO_CLIENT_WRITE_START_TIME, System.currentTimeMillis()); conn.submitResponse(response); } catch (HttpException e) { shutdownConnection(conn, true, e.getMessage()); throw e; } catch (IOException e) { shutdownConnection(conn, true, e.getMessage()); throw e; } } /** * Handle connection timeouts by shutting down the connections * @param conn the connection being processed */ public void timeout(final NHttpServerConnection conn) { HttpContext context = conn.getContext(); Boolean read = (Boolean) context.getAttribute(NhttpConstants.REQUEST_READ); if (read == null || read) { if (log.isDebugEnabled()) { log.debug(conn + ": Keepalive connection was closed"); } shutdownConnection(conn, false, null); } else { String msg = "Connection Timeout - before message body was fully read : " + conn; log.error(msg); if (metrics != null) { metrics.incrementTimeoutsReceiving(); } shutdownConnection(conn, true, msg); } } public void endOfInput(final NHttpServerConnection conn) throws IOException { conn.close(); } public void connected(final NHttpServerConnection conn) { if (log.isTraceEnabled()) { log.trace(conn + ": New incoming connection"); } metrics.connected(); // record connection creation time for debug logging conn.getContext().setAttribute(CONNECTION_CREATION_TIME, System.currentTimeMillis()); if (log.isDebugEnabled()) { log.debug(conn + ": Adding a connection : " + conn + " to the pool, existing pool size : " + activeConnections.size()); } synchronized (this) { activeConnections.add(conn); } } public void responseReady(NHttpServerConnection conn) { if (JavaUtils.isTrueExplicitly(conn.getContext().getAttribute(NhttpConstants.FORCE_CLOSING)) && !JavaUtils.isTrueExplicitly(conn.getContext().getAttribute( NhttpConstants.MESSAGE_IN_FLIGHT))) { try { if (log.isDebugEnabled()) { log.debug(conn + ": Closing a persisted connection since it is forced : " + conn); } conn.close(); } catch (IOException ignore) {} return; } metrics.notifyReceivedMessageSize(conn.getMetrics().getReceivedBytesCount()); metrics.notifySentMessageSize(conn.getMetrics().getSentBytesCount()); conn.getMetrics().reset(); conn.getContext().removeAttribute(NhttpConstants.MESSAGE_IN_FLIGHT); if (log.isTraceEnabled()) { log.trace(conn + ": Ready to send response"); } } public void closed(final NHttpServerConnection conn) { HttpContext context = conn.getContext(); shutdownConnection(conn, false, null); context.removeAttribute(REQUEST_SINK_BUFFER); context.removeAttribute(RESPONSE_SOURCE_BUFFER); context.removeAttribute(CONNECTION_CREATION_TIME); context.removeAttribute(SERVER_CONNECTION_DEBUG); if (log.isTraceEnabled()) { log.trace(conn + ": Connection closed"); } metrics.disconnected(); } public void markActiveConnectionsToBeClosed() { log.info("Marking the closing signal on the connection pool of size : " + activeConnections.size()); synchronized (this) { for (NHttpServerConnection conn : activeConnections) { conn.getContext().setAttribute(NhttpConstants.FORCE_CLOSING, "true"); conn.requestOutput(); } } } /** * Handle errors while reading or writing to underlying channels * @param conn the connection being processed * @param e the exception encountered */ public void exception(NHttpServerConnection conn, Exception e) { String errMsg = "I/O error : " + e.getMessage(); if (e instanceof HttpException) { if (metrics != null) { metrics.incrementFaultsReceiving(); } HttpContext context = conn.getContext(); HttpRequest request = conn.getHttpRequest(); ProtocolVersion ver = HttpVersion.HTTP_1_0; if (request != null && request.getRequestLine() != null) { ver = request.getRequestLine().getProtocolVersion(); } HttpResponse response = responseFactory.newHttpResponse( ver, HttpStatus.SC_BAD_REQUEST, context); byte[] msg = EncodingUtils.getAsciiBytes("Malformed HTTP request: " + e.getMessage()); ByteArrayEntity entity = new ByteArrayEntity(msg); entity.setContentType("text/plain; charset=US-ASCII"); response.setEntity(entity); try { commitResponseHideExceptions(conn, response); } catch (Exception ignore) {} } else if (e instanceof ConnectionClosedException || (e.getMessage() != null && (e.getMessage().contains("Connection reset by peer") || e.getMessage().contains("forcibly closed")))) { if (log.isDebugEnabled()) { errMsg = "I/O error (Probably the keepalive connection " + "was closed):" + e.getMessage(); log.debug(errMsg); } shutdownConnection(conn, true, errMsg); } else if (e instanceof IOException && e.getMessage() != null) { errMsg = e.getMessage().toLowerCase(); if (errMsg.indexOf("broken") != -1) { log.warn("I/O error (Probably the connection " + "was closed by the remote party):" + e.getMessage()); } else { log.error("I/O error: " + e.getMessage(), e); } if (metrics != null) { metrics.incrementFaultsReceiving(); } shutdownConnection(conn, true, errMsg); } else { errMsg = "Unexpected I/O error: " + e.getClass().getName(); log.error(errMsg, e); if (metrics != null) { metrics.incrementFaultsReceiving(); } shutdownConnection(conn, true, errMsg); } } /** * Handle IO errors while reading or writing to underlying channels * @param conn the connection being processed * @param e the exception encountered */ public void exception(NHttpServerConnection conn, IOException e) { String errMsg = "I/O error : " + e.getMessage(); if (e instanceof ConnectionClosedException || (e.getMessage() != null && (e.getMessage().contains("Connection reset by peer") || e.getMessage().contains("forcibly closed")))) { if (log.isDebugEnabled()) { errMsg = "I/O error (Probably the keepalive connection " + "was closed):" + e.getMessage(); log.debug(errMsg); } } else if (e.getMessage() != null) { errMsg = e.getMessage().toLowerCase(); if (errMsg.indexOf("broken") != -1) { errMsg = "I/O error (Probably the connection " + "was closed by the remote party):" + e.getMessage(); log.warn(errMsg); } else { errMsg = "I/O error: " + e.getMessage(); log.error(errMsg, e); } if (metrics != null) { metrics.incrementFaultsReceiving(); } } else { log.error("Unexpected I/O error: " + e.getClass().getName(), e); if (metrics != null) { metrics.incrementFaultsReceiving(); } errMsg = "Unexpected I/O error: " + e.getMessage(); } shutdownConnection(conn, true, errMsg); } // ----------- utility methods ----------- private void handleException(String msg, Exception e, NHttpServerConnection conn) { log.error(msg, e); if (conn != null) { shutdownConnection(conn, true, e.getMessage()); } } /** * Shutdown the connection ignoring any IO errors during the process * @param conn the connection to be shutdown * @param isError whether shutdown is due to an error * @param errorMsg error message if shutdown happens on error */ private void shutdownConnection(final NHttpServerConnection conn, boolean isError, String errorMsg) { SharedOutputBuffer outputBuffer = (SharedOutputBuffer) conn.getContext().getAttribute(RESPONSE_SOURCE_BUFFER); if (outputBuffer != null) { outputBuffer.close(); } SharedInputBuffer inputBuffer = (SharedInputBuffer) conn.getContext().getAttribute(REQUEST_SINK_BUFFER); if (inputBuffer != null) { inputBuffer.close(); } if (log.isWarnEnabled() && (isError || log.isDebugEnabled()) && conn instanceof HttpInetConnection) { HttpInetConnection inetConnection = (HttpInetConnection) conn; InetAddress remoteAddress = inetConnection.getRemoteAddress(); int remotePort = inetConnection.getRemotePort(); String msg; if (remotePort != -1 && remoteAddress != null) { // If connection is still alive msg = "Connection from remote address : " + remoteAddress + ":" + remotePort + " to local address : " + inetConnection.getLocalAddress() + ":" + inetConnection.getLocalPort() + " is closed!" + (errorMsg != null ? " - On error : " + errorMsg : ""); } else { // if connection is already closed. obtain params from http context HttpContext httpContext = conn.getContext(); msg = "Connection from remote address : " + httpContext.getAttribute(NhttpConstants.CLIENT_REMOTE_ADDR) + ":" + httpContext.getAttribute(NhttpConstants.CLIENT_REMOTE_PORT) + " to local address : " + inetConnection.getLocalAddress() + ":" + inetConnection.getLocalPort() + " is closed!" + (errorMsg != null ? " - On error : " + errorMsg : ""); } if (isError) { log.warn(msg); } else { log.debug(msg); } } synchronized (this) { if (!activeConnections.isEmpty() && activeConnections.remove(conn) && log.isDebugEnabled()) { log.debug("Removing the connection : " + conn + " from pool of size : " + activeConnections.size()); } } try { conn.shutdown(); } catch (IOException ignore) {} } public int getActiveConnectionsSize() { return activeConnections.size(); } /** * Return the HttpProcessor for responses * @return the HttpProcessor that processes HttpResponses of this server */ private HttpProcessor getHttpProcessor() { BasicHttpProcessor httpProcessor = new BasicHttpProcessor(); httpProcessor.addInterceptor(new ResponseDate()); httpProcessor.addInterceptor(new ResponseServer()); httpProcessor.addInterceptor(new ResponseContent()); httpProcessor.addInterceptor(new ResponseConnControl()); return httpProcessor; } public int getActiveCount() { return workerPool.getActiveCount(); } public int getQueueSize() { return workerPool.getQueueSize(); } public MetricsCollector getMetrics() { return metrics; } public void stop() { latencyView.destroy(); s2sLatencyView.destroy(); threadingView.destroy(); try { if (workerPool != null) { workerPool.shutdown(1000); } else if (executor != null) { executor.destroy(); } } catch (InterruptedException ignore) {} } private boolean canResponseHaveBody(final HttpResponse response, final NHttpServerConnection conn) { HttpRequest httpRequest = (HttpRequest) conn.getContext().getAttribute(HTTP_REQUEST); if (httpRequest != null && NhttpConstants.HTTP_HEAD.equalsIgnoreCase(httpRequest.getRequestLine().getMethod())) { return false; } int status = response.getStatusLine().getStatusCode(); return status >= HttpStatus.SC_OK && status != HttpStatus.SC_NO_CONTENT && status != HttpStatus.SC_NOT_MODIFIED && status != HttpStatus.SC_RESET_CONTENT; } }