/**
*
*/
package com.taobao.jetty.apr;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.tomcat.jni.Address;
import org.apache.tomcat.jni.Library;
import org.apache.tomcat.jni.OS;
import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.Sockaddr;
import org.apache.tomcat.jni.Socket;
import org.apache.tomcat.jni.Status;
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.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.NIOBuffer;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.log.Log;
/**
* @author lovingprince
* @since 2011-5-20
*
*/
public class AprSocketConnector extends AbstractConnector {
protected final Set<EndPoint> _connections;
protected volatile int _localPort = -1;
/**
* Root APR memory pool.
*/
protected long rootPool = 0;
/**
* Server socket "pointer".
*/
protected long serverSock = 0;
/**
* APR memory pool for the server socket.
*/
protected long serverSockPool = 0;
/* ------------------------------------------------------------ */
/**
* Constructor.
*
*/
public AprSocketConnector() {
_connections = new HashSet<EndPoint>();
}
/* ------------------------------------------------------------ */
public Object getConnection() {
return serverSock == 0 ? null : serverSock;
}
/* ------------------------------------------------------------ */
public void open() throws IOException {
if (!AprLifecycle.isAprAvailable()) {
throw new RuntimeException("apr is not Available!");
}
try {
// Create the root APR memory pool
try {
rootPool = Pool.create(0);
} catch (UnsatisfiedLinkError e) {
throw new IOException(
"AprSocketConnector create rootPool error");
}
// Create the pool for the server socket
serverSockPool = Pool.create(rootPool);
// Create the APR address that will be bound
String addressStr = null;
if (getHost() != null) {
addressStr = getHost();
}
int family = Socket.APR_INET;
if (Library.APR_HAVE_IPV6) {
if (addressStr == null) {
if (!OS.IS_BSD && !OS.IS_WIN32 && !OS.IS_WIN64)
family = Socket.APR_UNSPEC;
} else if (addressStr.indexOf(':') >= 0) {
family = Socket.APR_UNSPEC;
}
}
long inetAddress = Address.info(addressStr, family, getPort(), 0,
rootPool);
// Sockaddr serverSockaddr = Address.getInfo(inetAddress);
// Create the APR server socket
serverSock = Socket.create(family,
Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, rootPool);
if (OS.IS_UNIX) {
Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
}
// Deal with the firewalls that tend to drop the inactive sockets
Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
// Bind the server socket
int ret = Socket.bind(serverSock, inetAddress);
if (ret != Status.APR_SUCCESS) {
throw new Exception("AprSocketConnector bind error");
}
// Start listening on the server socket
ret = Socket.listen(serverSock, getAcceptQueueSize());
if (ret != Status.APR_SUCCESS) {
throw new Exception("AprSocketConnector listen error");
}
if (OS.IS_WIN32 || OS.IS_WIN64) {
// On Windows set the reuseaddr flag after the bind/listen
Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR,
getReuseAddress() ? 1 : 0);
}
long sa = Address.get(Socket.APR_LOCAL, serverSock);
Sockaddr laddr = new Sockaddr();
if (Address.fill(laddr, sa)) {
_localPort = laddr.port;
}
} catch (Exception e) {
throw new IOException(e);
}
}
/* ------------------------------------------------------------ */
public void close() throws IOException {
// if (serverSock != 0) {
// Socket.shutdown(serverSock, Socket.APR_SHUTDOWN_READ);
// serverSock = 0;
// }
// Destroy pool if it was initialised
if (serverSockPool != 0) {
Pool.destroy(serverSockPool);
serverSockPool = 0;
}
// Close server socket if it was initialised
if (serverSock != 0) {
Socket.close(serverSock);
serverSock = 0;
}
// Close all APR memory pools and resources if initialised
if (rootPool != 0) {
Pool.destroy(rootPool);
rootPool = 0;
}
}
/* ------------------------------------------------------------ */
@Override
public void accept(int acceptorID) throws IOException, InterruptedException {
long socket = 0;
try {
socket = Socket.accept(serverSock);
} catch (Exception e) {
throw new IOException(e);
}
configure(socket);
AprEndPoint connection = new AprEndPoint(socket);
connection.dispatch();
}
/**
* Process the specified connection.
*/
protected void configure(long socket) {
try {
// Process the connection
Socket.optSet(socket, Socket.APR_TCP_NODELAY, 1);
Socket.optSet(socket, Socket.APR_SO_NONBLOCK, 0);//blocking
// 1: Set socket options: timeout, linger, etc
if (_soLingerTime >= 0)
Socket.optSet(socket, Socket.APR_SO_LINGER, _soLingerTime);
} catch (Exception e) {
Log.warn("socket configure exception", e);
}
}
/*
* --------------------------------------------------------------------------
* -----
*/
/**
* Allows subclass to override Conection if required.
*/
protected Connection newConnection(EndPoint endpoint) {
return new HttpConnection(this, endpoint, getServer());
}
/*
* --------------------------------------------------------------------------
* -----
*/
@Override
public void customize(EndPoint endpoint, Request request)
throws IOException {
AprEndPoint connection = (AprEndPoint) endpoint;
int lrmit = isLowResources() ? _lowResourceMaxIdleTime : _maxIdleTime;
connection.setMaxIdleTime(lrmit);
super.customize(endpoint, request);
}
/*
* --------------------------------------------------------------------------
* -----
*/
public int getLocalPort() {
return _localPort;
}
/*
* --------------------------------------------------------------------------
* -----
*/
@Override
protected void doStart() throws Exception {
_connections.clear();
super.doStart();
}
/*
* --------------------------------------------------------------------------
* -----
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected void doStop() throws Exception {
super.doStop();
Set set = null;
synchronized (_connections) {
set = new HashSet(_connections);
}
Iterator iter = set.iterator();
while (iter.hasNext()) {
AprEndPoint connection = (AprEndPoint) iter.next();
connection.close();
}
if (AprLifecycle.aprAvailable) {
try {
AprLifecycle.terminateAPR();
} catch (Throwable t) {
Log.info("aprListener.aprDestroy");
}
}
}
protected class AprEndPoint implements Runnable, ConnectedEndPoint,
EndPoint {
private long _sock = 0;
int _maxIdleTime;
boolean _dispatched = false;
volatile Connection _connection;
SocketWrapper remote = null;
SocketWrapper local = null;
public AprEndPoint(long sock) {
_connection = newConnection(this);
_sock = sock;
try {
long sa = Address.get(Socket.APR_REMOTE, _sock);
Sockaddr raddr = new Sockaddr();
if (Address.fill(raddr, sa)) {
remote = new SocketWrapper();
remote.setHost(raddr.hostname);
remote.setAddr(Address.getip(sa));
remote.setPort(raddr.port);
}
sa = Address.get(Socket.APR_LOCAL, _sock);
Sockaddr laddr = new Sockaddr();
if (Address.fill(laddr, sa)) {
local = new SocketWrapper();
local.setHost(laddr.hostname);
local.setAddr(Address.getip(sa));
local.setPort(laddr.port);
}
} catch (Exception e) {
// Ignore
Log.warn("", e);
}
}
@Override
public boolean blockReadable(long arg0) throws IOException {
return true;
}
@Override
public boolean blockWritable(long arg0) throws IOException {
return true;
}
@Override
public void close() throws IOException {
if (_sock == 0)
return;
if (_connection instanceof HttpConnection)
((HttpConnection) _connection).getRequest()
.getAsyncContinuation().cancel();
Socket.destroy(_sock);
_sock = 0;
}
@Override
public int fill(Buffer buffer) throws IOException {
int space = buffer.space();
if (space <= 0) {
if (buffer.hasContent())
return 0;
throw new IOException("FULL");
}
try {
ByteBuffer bb = ((NIOBuffer) buffer).getByteBuffer();
int b=-1;
if(bb.isDirect()){
b = Socket.recvb(_sock, bb, buffer.putIndex(), space);
}else{
b=Socket.recv(_sock, bb.array(), buffer.putIndex(), space);
}
if (b <= 0) {
if ((-b) == Status.ETIMEDOUT || (-b) == Status.TIMEUP) {
throw new SocketTimeoutException("iib.failed read");
} else if ((-b) == Status.APR_EOF||b==0) {
return -1;
} else {
throw new IOException("iib.failed read");
}
} else {
buffer.setPutIndex(buffer.getIndex()+b);
}
return b;
} catch (Exception e) {
Log.ignore(e);
return -1;
}
}
@Override
public void flush() throws IOException {
}
@Override
public int flush(Buffer buffer) throws IOException {
int length = buffer.length();
if (length > 0) {
ByteBuffer bb = ((NIOBuffer) buffer).getByteBuffer();
try {
int loop = 0;
while (buffer.hasContent()) {
bb.position(buffer.getIndex());
bb.limit(buffer.putIndex());
int len = 0;
if (bb.isDirect()) {
len = Socket
.sendb(_sock, bb, bb.position(), length);
} else {
len = Socket.send(_sock, bb.array(), bb.position(),
length);
}
if (len < 0)
break;
else if (len > 0) {
buffer.skip(len);
loop = 0;
} else if (loop++ > 1)
break;
}
} catch (Exception e) {
throw new IOException(e);
} finally {
bb.position(0);
bb.limit(bb.capacity());
}
}
if (!buffer.isImmutable())
buffer.clear();
return length;
}
@Override
public int flush(Buffer header, Buffer buffer, Buffer trailer)
throws IOException {
int len = 0;
if (header != null) {
int tw = header.length();
if (tw > 0) {
int f = flush(header);
len = f;
if (f < tw)
return len;
}
}
if (buffer != null) {
int tw = buffer.length();
if (tw > 0) {
int f = flush(buffer);
if (f < 0)
return len > 0 ? len : f;
len += f;
if (f < tw)
return len;
}
}
if (trailer != null) {
int tw = trailer.length();
if (tw > 0) {
int f = flush(trailer);
if (f < 0)
return len > 0 ? len : f;
len += f;
}
}
return len;
}
@Override
public String getLocalAddr() {
if (local != null && local.getAddr() != null) {
return local.getAddr();
}
return Address.APR_ANYADDR;
}
@Override
public String getLocalHost() {
if (local != null && local.getHost() != null) {
return local.getHost();
}
return Address.APR_ANYADDR;
}
@Override
public int getLocalPort() {
if (local != null) {
return local.getPort();
}
return -1;
}
@Override
public int getMaxIdleTime() {
return _maxIdleTime;
}
@Override
public String getRemoteAddr() {
if (remote != null) {
return remote.getAddr();
}
return null;
}
@Override
public String getRemoteHost() {
if (remote != null) {
return remote.getHost();
}
return null;
}
@Override
public int getRemotePort() {
if (remote != null) {
return remote.getPort();
}
return -1;
}
@Override
public Object getTransport() {
return _sock;
}
@Override
public boolean isBlocking() {
return true;
}
@Override
public boolean isBufferingInput() {
return false;
}
@Override
public boolean isBufferingOutput() {
return false;
}
@Override
public boolean isBufferred() {
return false;
}
@Override
public boolean isOpen() {
return _sock != 0;
}
@Override
public void setMaxIdleTime(int maxIdleTime) throws IOException {
Socket.timeoutSet(_sock, maxIdleTime * 1000);
this._maxIdleTime = maxIdleTime;
}
@Override
public void shutdownOutput() throws IOException {
if (isOpen())
Socket.shutdown(_sock, Socket.APR_SHUTDOWN_WRITE);
}
@Override
public Connection getConnection() {
return this._connection;
}
@Override
public void setConnection(Connection arg0) {
this._connection = arg0;
}
public void dispatch() throws IOException {
if (getThreadPool() == null || !getThreadPool().dispatch(this)) {
Log.warn("dispatch failed for {}", _connection);
close();
}
}
@Override
public void run() {
try {
connectionOpened(_connection);
synchronized (_connections) {
_connections.add(this);
}
while (isStarted() && isOpen()) {
if (_connection.isIdle()) {
if (isLowResources())
setMaxIdleTime(getLowResourcesMaxIdleTime());
}
_connection = _connection.handle();
}
} catch (EofException e) {
Log.debug("EOF", e);
try {
close();
} catch (IOException e2) {
Log.ignore(e2);
}
} catch (HttpException e) {
Log.debug("BAD", e);
try {
close();
} catch (IOException e2) {
Log.ignore(e2);
}
} catch (Exception e) {
Log.warn("handle failed?", e);
try {
close();
} catch (IOException e2) {
Log.ignore(e2);
}
} finally {
connectionClosed(_connection);
synchronized (_connections) {
_connections.remove(this);
}
// wait for client to close, but if not, close ourselves.
try {
if (isOpen()) {
long timestamp = System.currentTimeMillis();
int max_idle = getMaxIdleTime();
Socket.timeoutSet(_sock, getMaxIdleTime() * 1000);
byte[] cc = new byte[1];
int c = 0;
do {
c = Socket.recv(_sock, cc, 0, 1);
} while (c >= 0
&& (System.currentTimeMillis() - timestamp) < max_idle);
if (isOpen())
close();
}
} catch (IOException e) {
Log.ignore(e);
}
}
}
}
public class SocketWrapper {
private String host;
private int port = -1;
private String addr;
public void setAddr(String addr) {
this.addr = addr;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
public String getAddr() {
return addr;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}
private boolean _useDirectBuffers=true;//Ĭ����ֱ��buffer
/* ------------------------------------------------------------------------------- */
public boolean getUseDirectBuffers()
{
return _useDirectBuffers;
}
/* ------------------------------------------------------------------------------- */
/**
* @param direct If True (the default), the connector can use NIO direct buffers.
* Some JVMs have memory management issues (bugs) with direct buffers.
*/
public void setUseDirectBuffers(boolean direct)
{
_useDirectBuffers=direct;
}
/* ------------------------------------------------------------------------------- */
@Override
public Buffer newRequestBuffer(int size)
{
return _useDirectBuffers?new DirectNIOBuffer(size):new IndirectNIOBuffer(size);
}
/* ------------------------------------------------------------------------------- */
@Override
public Buffer newRequestHeader(int size)
{
return new IndirectNIOBuffer(size);
}
/* ------------------------------------------------------------------------------- */
@Override
public Buffer newResponseBuffer(int size)
{
return _useDirectBuffers?new DirectNIOBuffer(size):new IndirectNIOBuffer(size);
}
/* ------------------------------------------------------------------------------- */
@Override
public Buffer newResponseHeader(int size)
{
return new IndirectNIOBuffer(size);
}
/* ------------------------------------------------------------------------------- */
@Override
protected boolean isRequestHeader(Buffer buffer)
{
return buffer instanceof IndirectNIOBuffer;
}
/* ------------------------------------------------------------------------------- */
@Override
protected boolean isResponseHeader(Buffer buffer)
{
return buffer instanceof IndirectNIOBuffer;
}
}