/* * 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.felix.httplite.server; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import javax.servlet.ServletException; import org.apache.felix.httplite.osgi.Logger; import org.apache.felix.httplite.osgi.ServiceRegistrationHandler; import org.apache.felix.httplite.osgi.ServiceRegistrationResolver; import org.apache.felix.httplite.servlet.ConcreteServletInputStream; import org.apache.felix.httplite.servlet.HttpConstants; import org.apache.felix.httplite.servlet.HttpServletRequestImpl; import org.apache.felix.httplite.servlet.HttpServletResponseImpl; /** * This class represents an accepted connection between the server and * a client. It supports persistent connections for both HTTP 1.0 and 1.1 * clients. A given persistent connection is limited in the number of * consecutive requests it is allowed to make before having its connection * closed as well as after a period of inactivity. **/ public class Connection { /** * Connection timeout */ public static final int DEFAULT_CONNECTION_TIMEOUT = 10000; /** * Requests per request */ public static final int DEFAULT_CONNECTION_REQUESTLIMIT = 50; private final Socket m_socket; private ConcreteServletInputStream m_is; private OutputStream m_os; private int m_requestCount = 0; private final int m_requestLimit; private final ServiceRegistrationResolver m_resolver; private final Logger m_logger; /** * Constructs a connection with a default inactivity timeout and request limit. * * @param socket Socket connection * @param resolver a resolver to get http request/response and handler. * @param logger Logger * @throws IOException If any I/O error occurs. */ public Connection(final Socket socket, final ServiceRegistrationResolver resolver, final Logger logger) throws IOException { this(socket, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_REQUESTLIMIT, resolver, logger); } /** * Constructs a connection with the specified inactivity timeout and request limit. * @param socket The client socket. * @param timeout The inactivity timeout of the connection in milliseconds. * @param requestLimit The maximum number of consecutive requests. * @param resolver resolves a request URI to a client or servlet registration via the HTTP Service. * @param logger logger instance. * @throws java.io.IOException If any I/O error occurs. */ public Connection(final Socket socket, final int timeout, final int requestLimit, final ServiceRegistrationResolver resolver, final Logger logger) throws IOException { m_socket = socket; m_resolver = resolver; m_logger = logger; m_socket.setSoTimeout(timeout); m_socket.setTcpNoDelay(true); m_requestLimit = requestLimit; try { m_is = new ConcreteServletInputStream(new BufferedInputStream( m_socket.getInputStream())); m_os = new BufferedOutputStream(m_socket.getOutputStream()); } catch (IOException ex) { // Make sure we close any opened socket/streams. try { m_socket.close(); } catch (IOException ex2) { m_logger.log(Logger.LOG_ERROR, "Error closing socket.", ex); } if (m_is != null) { try { m_is.close(); } catch (IOException ex2) { m_logger.log(Logger.LOG_ERROR, "Error closing socket input stream.", ex2); } } if (m_os != null) { try { m_os.close(); } catch (IOException ex2) { m_logger.log(Logger.LOG_ERROR, "Error closing socket output stream.", ex2); } } throw ex; } } /** * Performs the actual servicing of the connection and its subsequent requests. * This method will be called by threads in the thread pool. This method * typically exits when the connection is closed due to either an explicit * connection close, the inactivity timeout expires, the maximum request * limit was reached, or an I/O error occurred. When this method returns, * the associated socket will be closed, regardless of whether or not an * expection was thrown. * @throws java.net.SocketTimeoutException If the inactivity timeout expired * while trying to read from the socket. * @throws java.io.IOException If any I/O error occurs. * @throws ServletException on servlet errors **/ public void process() throws IOException, ServletException { HttpServletRequestImpl request = m_resolver.getServletRequest(m_socket); HttpServletResponseImpl response = m_resolver.getServletResponse(m_os); try { // Loop until we close the connection. boolean close = false; while (!close) { // Read the next request. try { request.parseRequestLine(m_is); } catch (IOException e) { m_logger.log( Logger.LOG_ERROR, "Error with request: " + request.toString() + ": " + e.getMessage()); throw e; } m_requestCount++; // Keep track of whether we have failed or not, // because we still want to read the bytes to clear // the input stream so we can service more requests. boolean error = false; m_logger.log(Logger.LOG_DEBUG, "Processing " + request.getRequestURI() + " (" + (m_requestLimit - m_requestCount) + " remaining)"); // If client is HTTP/1.1, then send continue message. if (request.getProtocol().equals(HttpConstants.HTTP11_VERSION)) { response.sendContinueResponse(); } // Read the header lines of the request. request.parseHeader(m_is); // If we have an HTTP/1.0 request without the connection set to // keep-alive or we explicitly have a request to close the connection, // then set close flag to exit the loop rather than trying to read // more requests. String v = request.getHeader(HttpConstants.HEADER_CONNECTION); if ((request.getProtocol().equals(HttpConstants.HTTP10_VERSION) && ((v == null) || (!v.equalsIgnoreCase(HttpConstants.KEEPALIVE_CONNECTION)))) || ((v != null) && v.equalsIgnoreCase(HttpConstants.CLOSE_CONNECTION))) { close = true; response.setConnectionType("close"); } // If we have serviced the maximum number of requests for // this connection, then set close flag so we exit the loop // and close the connection. else if (m_requestCount >= m_requestLimit) { close = true; response.setConnectionType("close"); } // We do not support OPTIONS method so send // a "not implemented" error in that case. if (!HttpServletRequestImpl.isSupportedMethod(request.getMethod())) { error = true; response.setConnectionType(HttpConstants.CLOSE_CONNECTION); response.sendNotImplementedResponse(); } // Ignore if we have already failed, otherwise send error message // if an HTTP/1.1 client did not include HOST header. if (!error && request.getProtocol().equals(HttpConstants.HTTP11_VERSION) && (request.getHeader(HttpConstants.HOST_HEADER) == null)) { error = true; response.setConnectionType(HttpConstants.CLOSE_CONNECTION); response.sendMissingHostResponse(); } // Read in the request body. request.parseBody(m_is); // Only process the request if there was no error. if (!error) { ServiceRegistrationHandler processor = m_resolver.getProcessor( request, response, request.getRequestURI()); if (processor != null) { processor.handle(close); m_logger.log(Logger.LOG_DEBUG, "Processed " + request.toString()); // TODO: Adding next line to make test cases pass, but not sure if it is correct // and needs further investigation. close = true; continue; } close = true; response.setConnectionType(HttpConstants.CLOSE_CONNECTION); response.sendNotFoundResponse(); } } } finally { try { m_is.close(); } catch (IOException ex) { m_logger.log(Logger.LOG_ERROR, "Error closing socket input stream.", ex); } try { m_os.close(); } catch (IOException ex) { m_logger.log(Logger.LOG_ERROR, "Error closing socket output stream.", ex); } try { m_socket.close(); } catch (IOException ex) { m_logger.log(Logger.LOG_ERROR, "Error closing socket.", ex); } } } }