// ======================================================================== // Copyright (c) 2003-2009 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.ByteChannel; import java.nio.channels.SelectionKey; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Set; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.ConnectedEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.nio.ChannelEndPoint; import org.eclipse.jetty.server.BlockingHttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.ConcurrentHashSet; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------------------------- */ /** Blocking NIO connector. * This connector uses efficient NIO buffers with a traditional blocking thread model. * Direct NIO buffers are used and a thread is allocated per connections. * * This connector is best used when there are a few very active connections. * * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector" * * * */ public class BlockingChannelConnector extends AbstractNIOConnector { private static final Logger LOG = Log.getLogger(BlockingChannelConnector.class); private transient ServerSocketChannel _acceptChannel; private final Set<BlockingChannelEndPoint> _endpoints = new ConcurrentHashSet<BlockingChannelEndPoint>(); /* ------------------------------------------------------------ */ /** Constructor. * */ public BlockingChannelConnector() { } /* ------------------------------------------------------------ */ public Object getConnection() { return _acceptChannel; } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.server.AbstractConnector#doStart() */ @Override protected void doStart() throws Exception { super.doStart(); getThreadPool().dispatch(new Runnable() { public void run() { while (isRunning()) { try { Thread.sleep(400); long now=System.currentTimeMillis(); for (BlockingChannelEndPoint endp : _endpoints) { endp.checkIdleTimestamp(now); } } catch(InterruptedException e) { LOG.ignore(e); } catch(Exception e) { LOG.warn(e); } } } }); } /* ------------------------------------------------------------ */ public void open() throws IOException { // Create a new server socket and set to non blocking mode _acceptChannel= ServerSocketChannel.open(); _acceptChannel.configureBlocking(true); // Bind the server socket to the local host and port InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort()); _acceptChannel.socket().bind(addr,getAcceptQueueSize()); } /* ------------------------------------------------------------ */ public void close() throws IOException { if (_acceptChannel != null) _acceptChannel.close(); _acceptChannel=null; } /* ------------------------------------------------------------ */ @Override public void accept(int acceptorID) throws IOException, InterruptedException { SocketChannel channel = _acceptChannel.accept(); channel.configureBlocking(true); Socket socket=channel.socket(); configure(socket); BlockingChannelEndPoint connection=new BlockingChannelEndPoint(channel); connection.dispatch(); } /* ------------------------------------------------------------------------------- */ @Override public void customize(EndPoint endpoint, Request request) throws IOException { super.customize(endpoint, request); endpoint.setMaxIdleTime(_maxIdleTime); configure(((SocketChannel)endpoint.getTransport()).socket()); } /* ------------------------------------------------------------------------------- */ public int getLocalPort() { if (_acceptChannel==null || !_acceptChannel.isOpen()) return -1; return _acceptChannel.socket().getLocalPort(); } /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */ private class BlockingChannelEndPoint extends ChannelEndPoint implements Runnable, ConnectedEndPoint { private Connection _connection; private int _timeout; private volatile long _idleTimestamp; BlockingChannelEndPoint(ByteChannel channel) throws IOException { super(channel,BlockingChannelConnector.this._maxIdleTime); _connection = new BlockingHttpConnection(BlockingChannelConnector.this,this,getServer()); } /* ------------------------------------------------------------ */ /** Get the connection. * @return the connection */ public Connection getConnection() { return _connection; } /* ------------------------------------------------------------ */ public void setConnection(Connection connection) { _connection=connection; } /* ------------------------------------------------------------ */ public void checkIdleTimestamp(long now) { if (_idleTimestamp!=0 && _timeout>0 && now>(_idleTimestamp+_timeout)) { idleExpired(); } } /* ------------------------------------------------------------ */ protected void idleExpired() { try { super.close(); } catch (IOException e) { LOG.ignore(e); } } /* ------------------------------------------------------------ */ void dispatch() throws IOException { if (!getThreadPool().dispatch(this)) { LOG.warn("dispatch failed for {}",_connection); super.close(); } } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.io.nio.ChannelEndPoint#fill(org.eclipse.jetty.io.Buffer) */ @Override public int fill(Buffer buffer) throws IOException { _idleTimestamp=System.currentTimeMillis(); return super.fill(buffer); } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer) */ @Override public int flush(Buffer buffer) throws IOException { _idleTimestamp=System.currentTimeMillis(); return super.flush(buffer); } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer) */ @Override public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException { _idleTimestamp=System.currentTimeMillis(); return super.flush(header,buffer,trailer); } /* ------------------------------------------------------------ */ public void run() { try { _timeout=getMaxIdleTime(); connectionOpened(_connection); _endpoints.add(this); while (isOpen()) { _idleTimestamp=System.currentTimeMillis(); if (_connection.isIdle()) { if (getServer().getThreadPool().isLowOnThreads()) { int lrmit = getLowResourcesMaxIdleTime(); if (lrmit>=0 && _timeout!= lrmit) { _timeout=lrmit; } } } else { if (_timeout!=getMaxIdleTime()) { _timeout=getMaxIdleTime(); } } _connection = _connection.handle(); } } catch (EofException e) { LOG.debug("EOF", e); try{BlockingChannelEndPoint.this.close();} catch(IOException e2){LOG.ignore(e2);} } catch (HttpException e) { LOG.debug("BAD", e); try{super.close();} catch(IOException e2){LOG.ignore(e2);} } catch(Throwable e) { LOG.warn("handle failed",e); try{super.close();} catch(IOException e2){LOG.ignore(e2);} } finally { connectionClosed(_connection); _endpoints.remove(this); // wait for client to close, but if not, close ourselves. try { if (!_socket.isClosed()) { long timestamp=System.currentTimeMillis(); int max_idle=getMaxIdleTime(); _socket.setSoTimeout(getMaxIdleTime()); int c=0; do { c = _socket.getInputStream().read(); } while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle); if (!_socket.isClosed()) _socket.close(); } } catch(IOException e) { LOG.ignore(e); } } } /* ------------------------------------------------------------ */ @Override public String toString() { return String.format("BCEP@%x{l(%s)<->r(%s),open=%b,ishut=%b,oshut=%b}-{%s}", hashCode(), _socket.getRemoteSocketAddress(), _socket.getLocalSocketAddress(), isOpen(), isInputShutdown(), isOutputShutdown(), _connection); } } }