| /* |
| * 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); |
| } |
| } |
| } |
| } |