/*
* 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 net.jini.jeri.http;
import com.sun.jini.jeri.internal.http.ConnectionTimer;
import com.sun.jini.jeri.internal.http.HttpServerConnection;
import com.sun.jini.jeri.internal.http.HttpServerManager;
import com.sun.jini.jeri.internal.http.HttpSettings;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.logging.Levels;
import com.sun.jini.logging.LogUtil;
import com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.Endpoint;
import net.jini.jeri.InboundRequest;
import net.jini.jeri.RequestDispatcher;
import net.jini.jeri.ServerEndpoint;
import net.jini.security.Security;
import net.jini.security.SecurityContext;
/**
* An implementation of the {@link ServerEndpoint} abstraction that
* uses HTTP messages sent over TCP sockets (instances of {@link
* ServerSocket}) for the underlying communication mechanism.
*
* <p><code>HttpServerEndpoint</code> instances contain a host name
* and a TCP port number, as well as an optional {@link
* ServerSocketFactory} for customizing the type of
* <code>ServerSocket</code> to use and an optional {@link
* SocketFactory} for customizing the type of {@link Socket} that
* client endpoints will use. The port number is the local TCP port
* to bind to when listening for incoming socket connections. If the
* port number is zero, then each listen operation will bind to a free
* (non-zero) port, which will be the port number contained in the
* resulting {@link HttpEndpoint}. The host name contained in an
* <code>HttpServerEndpoint</code> controls the host name that will be
* contained in the <code>HttpEndpoint</code> instances produced when
* {@link #enumerateListenEndpoints enumerateListenEndpoints} is
* invoked to listen on the <code>HttpServerEndpoint</code> (this host
* name does not affect the behavior of listen operations themselves).
* If the host name in an <code>HttpServerEndpoint</code> is
* <code>null</code>, then the host name in the
* <code>HttpEndpoint</code> instances that it produces will be the IP
* address string obtained from {@link InetAddress#getLocalHost
* InetAddress.getLocalHost} when {@link #enumerateListenEndpoints
* enumerateListenEndpoints} is invoked.
*
* <p><code>HttpServerEndpoint</code> instances map incoming HTTP
* messages to requests; when possible, underlying TCP connections are
* persisted to accommodate multiple non-overlapping incoming
* requests. Inbound request data is received as the
* <code>entity-body</code> of an HTTP POST request; outbound response
* data is sent as the <code>entity-body</code> of the corresponding
* HTTP return message. For information on HTTP, refer to <a
* href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a>. Note that
* providing socket factories that produce SSL sockets does not result
* in endpoints that are fully HTTPS capable.
*
* <p>A <code>ServerSocketFactory</code> used with an
* <code>HttpServerEndpoint</code> must implement {@link Object#equals
* Object.equals} to obey the guidelines that are specified for
* <code>equals</code> methods of {@link
* net.jini.jeri.ServerEndpoint.ListenEndpoint ListenEndpoint}
* instances. A <code>SocketFactory</code> used with a
* <code>HttpServerEndpoint</code> should be serializable and must
* implement <code>Object.equals</code> to obey the guidelines that
* are specified for <code>equals</code> methods of {@link Endpoint}
* instances.
*
* @author Sun Microsystems, Inc.
* @see HttpEndpoint
* @since 2.0
**/
public final class HttpServerEndpoint implements ServerEndpoint {
/**
* pool of threads for executing tasks in system thread group:
* used for socket accept threads
**/
private static final Executor systemThreadPool =
(Executor) Security.doPrivileged(new GetThreadPoolAction(false));
/** HTTP server manager */
private static final HttpServerManager serverManager;
/** idle connection timer */
private static final ConnectionTimer connTimer;
static {
HttpSettings hs = HttpEndpoint.getHttpSettings();
serverManager = new HttpServerManager(hs.getResponseAckTimeout());
connTimer = new ConnectionTimer(hs.getServerConnectionTimeout());
}
/** server transport logger */
private static final Logger logger =
Logger.getLogger("net.jini.jeri.http.server");
/** local host name to fill in to corresponding HttpEndpoints */
private final String host;
/** port to listen on */
private final int port;
/** client socket factory used by corresponding HttpEndpoints */
private final SocketFactory sf;
/** socket factory used to create server sockets */
private final ServerSocketFactory ssf;
/**
* Returns an <code>HttpServerEndpoint</code> instance for the
* given TCP port number.
*
* <p>The host name contained in the returned
* <code>HttpServerEndpoint</code> will be <code>null</code>, so
* that when its {@link #enumerateListenEndpoints
* enumerateListenEndpoints} method produces an {@link
* HttpEndpoint}, the <code>HttpEndpoint</code>'s host name will
* be the IP address string obtained from {@link
* InetAddress#getLocalHost InetAddress.getLocalHost}.
*
* <p>The <code>ServerSocketFactory</code> contained in the
* returned <code>HttpServerEndpoint</code> will be
* <code>null</code>, indicating that this endpoint will create
* <code>ServerSocket</code> objects directly. The
* <code>SocketFactory</code> contained in the returned
* <code>HttpServerEndpoint</code> will also be <code>null</code>.
*
* @param port the TCP port on the local host to listen on
*
* @return an <code>HttpServerEndpoint</code> instance
*
* @throws IllegalArgumentException if the port number is out of
* the range <code>0</code> to <code>65535</code> (inclusive)
**/
public static HttpServerEndpoint getInstance(int port) {
return getInstance(null, port, null, null);
}
/**
* Returns an <code>HttpServerEndpoint</code> instance for the given
* host name and TCP port number.
*
* <p>If <code>host</code> is <code>null</code>, then when the
* returned <code>HttpServerEndpoint</code>'s {@link
* #enumerateListenEndpoints enumerateListenEndpoints} method
* produces an {@link HttpEndpoint}, the
* <code>HttpEndpoint</code>'s host name will be the IP address
* string obtained from {@link InetAddress#getLocalHost
* InetAddress.getLocalHost}.
*
* <p>The <code>ServerSocketFactory</code> contained in the
* returned <code>HttpServerEndpoint</code> will be
* <code>null</code>, indicating that this endpoint will create
* <code>ServerSocket</code> objects directly. The
* <code>SocketFactory</code> contained in the returned
* <code>HttpServerEndpoint</code> will also be <code>null</code>.
*
* @param host the host name to be used in
* <code>HttpEndpoint</code> instances produced by listening on
* the returned <code>HttpServerEndpoint</code>, or
* <code>null</code>
*
* @param port the TCP port on the local host to listen on
*
* @return an <code>HttpServerEndpoint</code> instance
*
* @throws IllegalArgumentException if the port number is out of
* the range <code>0</code> to <code>65535</code> (inclusive)
**/
public static HttpServerEndpoint getInstance(String host, int port) {
return getInstance(host, port, null, null);
}
/**
* Returns an <code>HttpServerEndpoint</code> instance for the
* given host name and TCP port number that contains the given
* <code>SocketFactory</code> and
* <code>ServerSocketFactory</code>.
*
* <p>If <code>host</code> is <code>null</code>, then when the
* returned <code>HttpServerEndpoint</code>'s {@link
* #enumerateListenEndpoints enumerateListenEndpoints} method
* produces an {@link HttpEndpoint}, the
* <code>HttpEndpoint</code>'s host name will be the IP address
* string obtained from {@link InetAddress#getLocalHost
* InetAddress.getLocalHost}.
*
* <p>If the server socket factory argument is <code>null</code>,
* then this endpoint will create <code>ServerSocket</code>
* objects directly.
*
* @param host the host name to be used in
* <code>HttpEndpoint</code> instances produced by listening on
* the returned <code>HttpServerEndpoint</code>, or
* <code>null</code>
*
* @param port the TCP port on the local host to listen on
*
* @param sf the <code>SocketFactory</code> to use for this
* <code>HttpServerEndpoint</code>, or <code>null</code>
*
* @param ssf the <code>ServerSocketFactory</code> to use for this
* <code>HttpServerEndpoint</code>, or <code>null</code>
*
* @return an <code>HttpServerEndpoint</code> instance
*
* @throws IllegalArgumentException if the port number is out of
* the range <code>0</code> to <code>65535</code> (inclusive)
**/
public static HttpServerEndpoint getInstance(String host, int port,
SocketFactory sf,
ServerSocketFactory ssf)
{
return new HttpServerEndpoint(host, port, sf, ssf);
}
/**
* Constructs a new instance.
**/
private HttpServerEndpoint(String host, int port,
SocketFactory sf,
ServerSocketFactory ssf)
{
if (port < 0 || port > 0xFFFF) {
throw new IllegalArgumentException(
"port number out of range: " + port);
}
this.host = host;
this.port = port;
this.sf = sf;
this.ssf = ssf;
}
/**
* Returns the host name that will be used in
* <code>HttpEndpoint</code> instances produced by listening on
* this <code>HttpServerEndpoint</code>, or <code>null</code> if
* the IP address string obtained from {@link
* InetAddress#getLocalHost InetAddress.getLocalHost} will be
* used.
*
* @return the host name to use in <code>HttpEndpoint</code>
* instances produced from this object, or <code>null</code>
**/
public String getHost() {
return host;
}
/**
* Returns the TCP port that this <code>HttpServerEndpoint</code>
* listens on.
*
* @return the TCP port that this endpoint listens on
**/
public int getPort() {
return port;
}
/**
* Returns the <code>SocketFactory</code> that
* <code>HttpEndpoint</code> objects produced by listening on this
* <code>HttpServerEndpoint</code> will use to create
* <code>Socket</code> objects.
*
* @return the socket factory that client-side endpoints
* corresponding to this server endpoint will use to create
* sockets, or <code>null</code> if no factory will be used
**/
public SocketFactory getSocketFactory() {
return sf;
}
/**
* Returns the <code>ServerSocketFactory</code> that this endpoint
* uses to create <code>ServerSocket</code> objects.
*
* @return the socket factory that this endpoint uses to create
* sockets, or <code>null</code> if no factory is used
**/
public ServerSocketFactory getServerSocketFactory() {
return ssf;
}
/**
* {@inheritDoc}
*
* @throws NullPointerException {@inheritDoc}
**/
public InvocationConstraints checkConstraints(
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
return Constraints.check(constraints, true);
}
/**
* Passes the {@link net.jini.jeri.ServerEndpoint.ListenEndpoint
* ListenEndpoint} for this <code>HttpServerEndpoint</code> to
* <code>listenContext</code>, which will ensure an active listen
* operation on the endpoint, and returns an
* <code>HttpEndpoint</code> instance corresponding to the listen
* operation chosen by <code>listenContext</code>.
*
* <p>If this <code>HttpServerEndpoint</code>'s host name is not
* <code>null</code>, then the returned <code>HttpEndpoint</code>
* will contain that host name. If this
* <code>HttpServerEndpoint</code>'s host name is
* <code>null</code>, then this method invokes {@link
* InetAddress#getLocalHost InetAddress.getLocalHost} to obtain an
* <code>InetAddress</code> for the local host. If
* <code>InetAddress.getLocalHost</code> throws an {@link
* UnknownHostException}, this method throws an
* <code>UnknownHostException</code>. The returned
* <code>HttpEndpoint</code>'s host name will be the string
* returned by invoking {@link InetAddress#getHostAddress
* getHostAddress} on that <code>InetAddress</code>. If there is
* a security manager, its {@link
* SecurityManager#checkConnect(String,int) checkConnect} method
* will be invoked with the string returned by invoking {@link
* InetAddress#getHostName getHostName} on that same
* <code>InetAddress</code> as the host argument and
* <code>-1</code> as the port argument; this could result in a
* <code>SecurityException</code>.
*
* <p>This method invokes <code>addListenEndpoint</code> on
* <code>listenContext</code> once, passing a
* <code>ListenEndpoint</code> as described below. If
* <code>addListenEndpoint</code> throws an exception, then this
* method throws that exception. Otherwise, this method returns
* an <code>HttpEndpoint</code> instance with the host name
* described above, the TCP port number bound by the listen
* operation represented by the {@link
* net.jini.jeri.ServerEndpoint.ListenHandle ListenHandle}
* returned by <code>addListenEndpoint</code>, and the same
* <code>SocketFactory</code> as this
* <code>HttpServerEndpoint</code>.
*
* <p>The <code>ListenEndpoint</code> passed to
* <code>addListenEndpoint</code> represents the TCP port number
* and <code>ServerSocketFactory</code> of this
* <code>HttpServerEndpoint</code>. Its methods behave as
* follows:
*
* <p>{@link net.jini.jeri.ServerEndpoint.ListenEndpoint#listen
* ListenHandle listen(RequestDispatcher)}:
*
* <blockquote>
*
* Listens for requests received on this endpoint's TCP port,
* dispatching them to the supplied <code>RequestDispatcher</code>
* in the form of {@link InboundRequest} instances.
*
* <p>When the implementation of this method needs to create a new
* <code>ServerSocket</code>, it will do so by invoking one of the
* <code>createServerSocket</code> methods that returns a bound
* server socket on the contained <code>ServerSocketFactory</code>
* if non-<code>null</code>, or it will create a
* <code>ServerSocket</code> directly otherwise.
*
* <p>If there is a security manager, its {@link
* SecurityManager#checkListen checkListen} method will be invoked
* with this endpoint's TCP port; this could result in a
* <code>SecurityException</code>. Furthermore, before a given
* <code>InboundRequest</code> gets dispatched to the supplied
* request dispatcher, the security manager's {@link
* SecurityManager#checkAccept checkAccept} method must have been
* successfully invoked in the security context of this
* <code>listen</code> invocation with the remote IP address and
* port of the <code>Socket</code> used to receive the request.
* The <code>checkPermissions</code> method of the dispatched
* <code>InboundRequest</code> also performs this latter security
* check. (Note that in some cases, the implementation may carry
* out these security checks indirectly, such as through
* invocations of <code>ServerSocket</code>'s constructors or
* <code>accept</code> method.)
*
* <p>Requests will be dispatched in a {@link PrivilegedAction}
* wrapped by a {@link SecurityContext} obtained when this method
* was invoked, with the {@link AccessControlContext} of that
* <code>SecurityContext</code> in effect.
*
* <p>Dispatched requests will implement {@link
* InboundRequest#populateContext populateContext} to populate the
* supplied collection with context information representing the
* request.
*
* <p>Throws {@link IOException} if an I/O exception occurs while
* performing this operation, such as if the TCP port is already
* in use.
*
* <p>Throws {@link SecurityException} if there is a security
* manager and the invocation of its <code>checkListen</code>
* method fails.
*
* <p>Throws {@link NullPointerException} if
* <code>requestDispatcher</code> is <code>null</code>
*
* </blockquote>
*
* <p>{@link
* net.jini.jeri.ServerEndpoint.ListenEndpoint#checkPermissions
* void checkPermissions()}:
*
* <blockquote>
*
* Verifies that the calling context has all of the security
* permissions necessary to listen for requests on this endpoint.
*
* <p>If there is a security manager, its <code>checkListen</code>
* method will be invoked with this endpoint's TCP port; this
* could result in a <code>SecurityException</code>.
*
* <p>Throws {@link SecurityException} if there is a security
* manager and the invocation of its <code>checkListen</code>
* method fails.
*
* </blockquote>
*
* <p>{@link Object#equals boolean equals(Object)}:
*
* <blockquote>
*
* Compares the specified object with this
* <code>ListenEndpoint</code> for equality.
*
* <p>This method returns <code>true</code> if and only if
*
* <ul>
*
* <li>the specified object is also a <code>ListenEndpoint</code>
* produced by an <code>HttpServerEndpoint</code>,
*
* <li>the port in the specified object is equal to the port in
* this object, and
*
* <li>either this object and the specified object both have no
* <code>ServerSocketFactory</code> or the
* <code>ServerSocketFactory</code> in the specified object has
* the same class and is equal to the one in this object.
*
* </ul>
*
* </blockquote>
*
* @param listenContext the <code>ListenContext</code> to pass
* this <code>HttpServerEndpoint</code>'s
* <code>ListenEndpoint</code> to
*
* @return the <code>HttpEndpoint</code> instance for sending
* requests to this <code>HttpServerEndpoint</code>'s endpoint
* being listened on
*
* @throws UnknownHostException if this
* <code>HttpServerEndpoint</code>'s host name is
* <code>null</code> and <code>InetAddress.getLocalHost</code>
* throws an <code>UnknownHostException</code>
*
* @throws IOException if an I/O exception occurs while performing
* this operation, such as if the TCP port is already in use
*
* @throws SecurityException if there is a security manager and
* either the invocation of its <code>checkListen</code> method
* fails or this <code>HttpServerEndpoint</code>'s host name is
* <code>null</code> and the invocation of the security manager's
* <code>checkConnect</code> method fails
*
* @throws IllegalArgumentException {@inheritDoc}
*
* @throws NullPointerException {@inheritDoc}
**/
public Endpoint enumerateListenEndpoints(ListenContext listenContext)
throws IOException
{
if (listenContext == null) {
throw new NullPointerException();
}
String localHost = host;
if (localHost == null) {
InetAddress localAddr;
try {
localAddr = (InetAddress) Security.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws UnknownHostException {
return InetAddress.getLocalHost();
}
});
} catch (PrivilegedActionException e) {
/*
* Only expose UnknownHostException thrown directly by
* InetAddress.getLocalHost if it would also be thrown
* in the caller's security context; otherwise, throw
* a new UnknownHostException without the host name.
*/
InetAddress.getLocalHost();
throw new UnknownHostException(
"access to resolve local host denied");
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkConnect(localAddr.getHostName(), -1);
} catch (SecurityException e) {
throw new SecurityException(
"access to resolve local host denied");
}
}
localHost = localAddr.getHostAddress();
}
LE listenEndpoint = new LE(); // REMIND: needn't be new?
ListenCookie listenCookie =
listenContext.addListenEndpoint(listenEndpoint);
if (!(listenCookie instanceof LE.Cookie)) {
throw new IllegalArgumentException();
}
LE.Cookie cookie = (LE.Cookie) listenCookie;
if (!listenEndpoint.equals(cookie.getLE())) {
throw new IllegalArgumentException();
}
return HttpEndpoint.getInstance(localHost, cookie.getPort(), sf);
}
/**
* Returns the hash code value for this
* <code>HttpServerEndpoint</code>.
*
* @return the hash code value for this
* <code>HttpServerEndpoint</code>
**/
public int hashCode() {
return port ^
(host != null ? host.hashCode() : 0) ^
(sf != null ? sf.hashCode() : 0) ^
(ssf != null ? ssf.hashCode() : 0);
}
/**
* Compares the specified object with this
* <code>HttpServerEndpoint</code> for equality.
*
* <p>This method returns <code>true</code> if and only if
*
* <ul>
*
* <li>the specified object is also an
* <code>HttpServerEndpoint</code>,
*
* <li>the host and port in the specified object are equal to the
* host and port in this object,
*
* <li>either this object and the specified object both have no
* <code>SocketFactory</code> or the <code>ServerFactory</code> in
* the specified object has the same class and is equal to the one
* in this object, and
*
* <li>either this object and the specified object both have no
* <code>ServerSocketFactory</code> or the
* <code>ServerSocketFactory</code> in the specified object has
* the same class and is equal to the one in this object.
*
* </ul>
*
* @param obj the object to compare with
*
* @return <code>true</code> if <code>obj</code> is equivalent to
* this object; <code>false</code> otherwise
**/
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof HttpServerEndpoint)) {
return false;
}
HttpServerEndpoint other = (HttpServerEndpoint) obj;
return
Util.equals(host, other.host) &&
port == other.port &&
Util.sameClassAndEquals(sf, other.sf) &&
Util.sameClassAndEquals(ssf, other.ssf);
}
/**
* Returns a string representation of this
* <code>HttpServerEndpoint</code>.
*
* @return a string representation of this
* <code>HttpServerEndpoint</code>
**/
public String toString() {
return "HttpServerEndpoint[" +
(host != null ? host + ":" : "") + port +
(ssf != null ? "," + ssf : "") +
(sf != null ? "," + sf : "") + "]";
}
/**
* ListenEndpoint implementation.
**/
private class LE implements ListenEndpoint {
LE() { }
public void checkPermissions() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkListen(port);
}
}
public ListenHandle listen(RequestDispatcher requestDispatcher)
throws IOException
{
if (requestDispatcher == null) {
throw new NullPointerException();
}
ServerSocket serverSocket;
if (ssf != null) {
serverSocket = ssf.createServerSocket(port);
} else {
serverSocket = new ServerSocket(port);
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
(ssf == null ? "created server socket {0}" :
"created server socket {0} using factory {1}"),
new Object[] { serverSocket, ssf });
}
Cookie cookie = new Cookie(serverSocket.getLocalPort());
final LH listenHandle = new LH(requestDispatcher, serverSocket,
Security.getContext(), cookie);
listenHandle.startAccepting();
return listenHandle;
}
// following are required to implement equals:
private int getPort() { return port; }
private ServerSocketFactory getSSF() { return ssf; }
public int hashCode() {
return port ^ (ssf != null ? ssf.hashCode() : 0);
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof LE)) {
return false;
}
LE other = (LE) obj;
return port == other.getPort() &&
Util.sameClassAndEquals(ssf, other.getSSF());
}
public String toString() {
return "HttpServerEndpoint.LE[" + port +
(ssf != null ? "," + ssf : "") + "]";
}
/**
* ListenCookie implementation: identifies a listen operation
* by containing the local port that the server socket is
* bound to.
**/
private class Cookie implements ListenCookie {
private final int port;
Cookie(int port) { this.port = port; }
LE getLE() { return LE.this; }
int getPort() { return port; }
public String toString() {
return "HttpServerEndpoint.LE.Cookie[" + port + "]";
}
}
}
/**
* ListenHandle implementation: represents a listen operation.
**/
private static class LH implements ListenHandle {
private final RequestDispatcher requestDispatcher;
private final ServerSocket serverSocket;
private final SecurityContext context;
private final ListenCookie cookie;
private long acceptFailureTime = 0L; // local to accept thread
private int acceptFailureCount; // local to accept thread
private final Object lock = new Object();
private boolean closed = false;
private final Set conns = new HashSet();
LH(RequestDispatcher requestDispatcher,
ServerSocket serverSocket,
SecurityContext context,
ListenCookie cookie)
{
this.requestDispatcher = requestDispatcher;
this.serverSocket = serverSocket;
this.context = context;
this.cookie = cookie;
}
/**
* Starts the accept loop.
**/
void startAccepting() {
systemThreadPool.execute(new Runnable() {
public void run() {
try {
executeAcceptLoop();
} finally {
/*
* The accept loop is only started once, so
* after no more socket accepts will occur,
* ensure that the server socket is no longer
* listening.
*/
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
}, toString() + " accept loop");
}
/**
* Runs the accept loop in the access control context
* preserved by LE.listen.
**/
private void executeAcceptLoop() {
AccessController.doPrivileged(context.wrap(new PrivilegedAction() {
public Object run() {
executeAcceptLoop0();
return null;
}
}), context.getAccessControlContext());
}
/**
* Executes the accept loop.
**/
private void executeAcceptLoop0() {
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"accepted socket {0} from server socket {1}",
new Object[]{ socket, serverSocket });
}
setSocketOptions(socket);
new Connection(socket);
} catch (Throwable t) {
try {
/*
* If this listen operation has been stopped,
* then we expect the socket accept to throw
* an exception, so just terminate normally.
*/
synchronized (lock) {
if (closed) {
break;
}
}
try {
if (logger.isLoggable(Level.WARNING)) {
LogUtil.logThrow(logger, Level.WARNING,
HttpServerEndpoint.class,
"executeAcceptLoop",
"accept loop for {0} throws",
new Object[] { serverSocket }, t);
}
} catch (Throwable tt) {
}
} finally {
/*
* Always close the accepted socket (if any)
* if an exception occurs, but only after
* logging an unexpected exception.
*/
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
if (!(t instanceof SecurityException)) {
try {
Object[] snapshot;
synchronized (lock) {
snapshot = closed ? null : conns.toArray();
}
if (snapshot != null) {
for (int i = 0; i < snapshot.length; i++) {
((Connection) snapshot[i]).shutdown(false);
}
}
} catch (OutOfMemoryError e) {
} catch (Exception e) {
}
}
/*
* A NoClassDefFoundError can occur if no file
* descriptors are available, in which case this
* loop should not terminate.
*/
boolean knownFailure =
t instanceof Exception ||
t instanceof OutOfMemoryError ||
t instanceof NoClassDefFoundError;
if (knownFailure) {
if (continueAfterAcceptFailure(t)) {
continue;
} else {
return;
}
}
throw (Error) t;
}
}
}
/**
* Stops this listen operation.
**/
public void close() {
synchronized (lock) {
if (closed) {
return;
}
closed = true;
}
try {
serverSocket.close();
} catch (IOException e) {
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"closed server socket {0}", serverSocket);
}
/*
* Iterating over conns without synchronization is safe
* at this point because no other thread will access it
* without verifying that closed is false in a
* synchronized block first.
*/
for (Iterator i = conns.iterator(); i.hasNext(); ) {
((Connection) i.next()).shutdown(true);
}
}
/**
* Returns a cookie to identify this listen operation.
**/
public ListenCookie getCookie() {
return cookie;
}
public String toString() {
return "HttpServerEndpoint.LH[" + serverSocket + "]";
}
/**
* Throttles the accept loop after ServerSocket.accept throws
* an exception, and decides whether to continue at all. The
* current code is borrowed from the JRMP implementation; it
* always continues, but it delays the loop after bursts of
* failed accepts.
**/
private boolean continueAfterAcceptFailure(Throwable t) {
/*
* If we get a burst of NFAIL failures in NMSEC milliseconds,
* then wait for ten seconds. This is to ensure that individual
* failures don't cause hiccups, but sustained failures don't
* hog the CPU in futile accept-fail-retry looping.
*/
final int NFAIL = 10;
final int NMSEC = 5000;
long now = System.currentTimeMillis();
if (acceptFailureTime == 0L ||
(now - acceptFailureTime) > NMSEC)
{
// failure time is very old, or this is first failure
acceptFailureTime = now;
acceptFailureCount = 0;
} else {
// failure window was started recently
acceptFailureCount++;
if (acceptFailureCount >= NFAIL) {
try {
Thread.sleep(10000);
} catch (InterruptedException ignore) {
}
// no need to reset counter/timer
}
}
return true;
}
/**
* HttpServerConnection subclass.
**/
private class Connection extends HttpServerConnection {
private final Socket socket;
private final Object connLock = new Object();
private boolean connClosed;
Connection(Socket socket) throws IOException {
super(socket, requestDispatcher, serverManager);
this.socket = socket;
boolean needShutdown = false;
synchronized (lock) {
if (closed) {
needShutdown = true; // shutdown after releasing lock
} else {
conns.add(this);
}
}
if (needShutdown) {
shutdown(true);
} else {
start();
}
}
public boolean shutdown(boolean force) {
synchronized (connLock) {
if (connClosed) {
return true;
}
connClosed = super.shutdown(force);
if (!connClosed) {
return false;
}
}
connTimer.cancelTimeout(this);
synchronized (lock) {
if (!closed) { // must not mutate set after closed
conns.remove(this);
}
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"shut down connection on socket {0}", socket);
}
return true;
}
protected void checkPermissions() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkAccept(socket.getInetAddress().getHostAddress(),
socket.getPort());
}
}
protected InvocationConstraints checkConstraints(
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
/*
* The transport layer aspects of all constraints
* supported by this transport provider are always
* satisfied by all requests on the server side, so
* this method can have the same static behavior as
* ServerCapabilities.checkConstraints does.
* (Otherwise, this operation would need to be
* parameterized by this connection or the request.)
*/
return Constraints.check(constraints, true);
}
/**
* Populates the context collection with information representing
* the connection.
**/
protected void populateContext(Collection context) {
Util.populateContext(context, socket.getInetAddress());
}
protected void idle() {
connTimer.scheduleTimeout(this, false);
}
protected void busy() {
connTimer.cancelTimeout(this);
}
}
}
/**
* Attempts to set desired socket options for a connected socket
* (TCP_NODELAY and SO_KEEPALIVE); ignores SocketException.
**/
private static void setSocketOptions(Socket socket) {
try {
socket.setTcpNoDelay(true);
} catch (SocketException e) {
if (logger.isLoggable(Levels.HANDLED)) {
LogUtil.logThrow(logger, Levels.HANDLED,
HttpServerEndpoint.class, "setSocketOptions",
"exception setting TCP_NODELAY on socket {0}",
new Object[] { socket }, e);
}
}
try {
socket.setKeepAlive(true);
} catch (SocketException e) {
if (logger.isLoggable(Levels.HANDLED)) {
LogUtil.logThrow(logger, Levels.HANDLED,
HttpServerEndpoint.class, "setSocketOptions",
"exception setting SO_KEEPALIVE on socket {0}",
new Object[] { socket }, e);
}
}
}
}