/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* @(#)HttpTunnelServerDriver.java 1.19 09/11/07
*/
package com.sun.messaging.jmq.transport.httptunnel.server;
import com.sun.messaging.jmq.transport.httptunnel.HttpTunnelConnection;
import com.sun.messaging.jmq.transport.httptunnel.HttpTunnelDefaults;
import com.sun.messaging.jmq.transport.httptunnel.HttpTunnelDriver;
import com.sun.messaging.jmq.transport.httptunnel.HttpTunnelPacket;
import com.sun.messaging.jmq.transport.httptunnel.Link;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class provides unreliable packet delivery mechanism on
* the server side. It also uses a dedicated thread to continuously
* read incoming packets from the servlet over the TCP connection.
*/
public class HttpTunnelServerDriver extends Link implements HttpTunnelDefaults,
HttpTunnelDriver {
private static boolean DEBUG = Boolean.getBoolean("httptunnel.debug");
public static boolean getDEBUG() {
return DEBUG;
}
protected static final boolean DEBUGLINK = Boolean.getBoolean(
"httptunnel.link.debug");
protected Socket serverConn = null;
protected Hashtable connTable = null;
protected String serviceName;
protected InetAddress webServerHost = null;
protected int webServerPort = 0;
protected int inactiveConnAbortInterval = MAX_CONNECTION_RETRY_WAIT;
protected int totalRetryWaited = 0;
private Vector listenQ = null;
private boolean listenState = false;
protected int rxBufSize = 0;
/**
* Creates an HTTP tunnel interface.
*/
public HttpTunnelServerDriver(String serviceName) throws IOException {
this(serviceName, InetAddress.getLocalHost().getHostAddress(),
DEFAULT_HTTP_TUNNEL_PORT);
}
/**
* Creates an HTTP tunnel interface.
*/
public HttpTunnelServerDriver(String serviceName, String webServerHostName,
int webServerPort) throws IOException {
this.serviceName = serviceName;
this.webServerHost = InetAddress.getByName(webServerHostName);
this.webServerPort = webServerPort;
connTable = new Hashtable();
listenQ = new Vector();
setName("HttpTunnelServerDriver");
start();
}
public void setRxBufSize(int rxBufSize) {
this.rxBufSize = rxBufSize;
}
public Vector getListenQ() {
return listenQ;
}
public void listen(boolean listenState) throws IOException {
boolean oldListenState = this.listenState;
this.listenState = listenState;
if (listenState != oldListenState) {
// Inform the servlet now to start or stop accepting
// new connections.
sendListenStatePacket();
}
}
public int getInactiveConnAbortInterval() {
return inactiveConnAbortInterval;
}
public void setInactiveConnAbortInterval(int inactiveConnAbortInterval) {
this.inactiveConnAbortInterval = inactiveConnAbortInterval;
}
/**
* Waits for a TCP connection from the servlet. When
* <code>accept</code> returns successfully, this method sends
* the current state of the connection table to the servlet
* and resumes normal operation.
*/
protected void createLink() {
totalRetryWaited = 0;
if (DEBUG) {
log("http:connecting to " + webServerHost + ":" + webServerPort);
}
while (true) {
try {
/*
* The Socket.setReceiveBufferSize() method must be
* called before the connection is established.
*/
serverConn = new Socket();
if (rxBufSize > 0) {
try {
serverConn.setReceiveBufferSize(rxBufSize);
} catch (SocketException e) {
log(Level.WARNING, "HTTP socket["+webServerHost+":"+webServerPort+
"]setReceiveBufferSize("+rxBufSize+"): "+e.toString(), e);
}
}
InetSocketAddress addr = new InetSocketAddress(webServerHost,
webServerPort);
serverConn.connect(addr);
try {
serverConn.setTcpNoDelay(true);
} catch (SocketException e) {
log(Level.WARNING, "HTTP socket["+webServerHost+":"+webServerPort+
"]setTcpNoDelay: "+e.toString(), e);
}
if (DEBUG) {
log("######## rcvbuf = " +
serverConn.getReceiveBufferSize());
}
is = serverConn.getInputStream();
os = serverConn.getOutputStream();
if (DEBUG || DEBUGLINK) {
log("Broker HTTP link up");
}
totalRetryWaited = 0;
break;
} catch (Exception e) {
}
try {
Thread.sleep(CONNECTION_RETRY_INTERVAL);
totalRetryWaited += CONNECTION_RETRY_INTERVAL;
if (totalRetryWaited >= (inactiveConnAbortInterval * 1000)) {
if (DEBUG || DEBUGLINK) {
log("Retry connect to servlet timeout " +
"- cleanup all (" + connTable.size() +
") connections ...");
}
cleanupAllConns();
totalRetryWaited = 0;
}
} catch (Exception se) {
}
}
sendLinkInitPacket();
sendListenStatePacket();
}
protected void sendListenStatePacket() {
HttpTunnelPacket p = new HttpTunnelPacket();
p.setPacketType(LISTEN_STATE_PACKET);
p.setConnId(0);
p.setSequence(0);
p.setWinsize(0);
p.setChecksum(0);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try {
dos.writeUTF(serviceName);
dos.writeBoolean(listenState);
dos.flush();
bos.flush();
} catch (Exception e) {
if (DEBUG || DEBUGLINK) {
log("Got exception while sending LISTEN_STATE_PACKET " +
"packet: " + e.getMessage());
}
}
byte[] buf = bos.toByteArray();
p.setPacketBody(buf);
sendPacket(p);
}
void sendLinkInitPacket() {
// Help the servlet/web server recreate the connection table...
HttpTunnelPacket p = new HttpTunnelPacket();
p.setPacketType(LINK_INIT_PACKET);
p.setConnId(0);
p.setSequence(0);
p.setWinsize(0);
p.setChecksum(0);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try {
dos.writeUTF(serviceName);
dos.writeInt(connTable.size());
for (Enumeration e = connTable.elements(); e.hasMoreElements();) {
HttpTunnelConnection conn = (HttpTunnelConnection) e.nextElement();
dos.writeInt(conn.getConnId());
dos.writeInt(conn.getPullPeriod());
}
dos.flush();
bos.flush();
} catch (Exception e) {
if (DEBUG || DEBUGLINK) {
log("Got exception while sending LINK_INIT_PACKET " +
"packet: " + e.getMessage());
}
}
byte[] buf = bos.toByteArray();
p.setPacketBody(buf);
sendPacket(p);
}
//to be called only from createLink()
protected void cleanupAllConns() {
Vector connids = new Vector();
synchronized (connTable) {
for (Enumeration e = connTable.keys(); e.hasMoreElements();) {
connids.addElement((String) e.nextElement());
}
}
HttpTunnelConnection conn;
HttpTunnelPacket pkt;
String connId;
for (int i = connids.size() - 1; i >= 0; i--) {
connId = (String) connids.elementAt(i);
conn = (HttpTunnelConnection) connTable.get(connId);
if (conn != null) {
pkt = genAbortPacket(conn.getConnId());
receivePacket(pkt);
}
}
connTable.clear();
connids = null;
}
/**
* Handle TCP connection failure.
*/
protected void handleLinkDown() {
if (DEBUG || DEBUGLINK) {
if (serverConn != null) {
log("Broker HTTP link down");
}
}
try {
serverConn.close();
} catch (Exception e) {
}
serverConn = null;
}
public boolean isLinkReady() {
return (serverConn != null);
}
/**
* Receive a packet from the network side. (i.e. from the servlet).
*/
protected void receivePacket(HttpTunnelPacket p) {
int packetType = p.getPacketType();
if (DEBUG) {
log("Received Packet : " + p);
}
switch (packetType) {
case CONN_INIT_PACKET:
handleNewConn(p);
break;
case CONN_CLOSE_PACKET:
handleConnClose(p);
break;
case CONN_ABORT_PACKET:
handleConnAbort(p);
break;
case CONN_OPTION_PACKET:
handleConnOption(p);
break;
case DATA_PACKET:
case ACK:
handlePacket(p);
break;
case DUMMY_PACKET:
handleDummyPacket(p);
break;
default:
break;
}
}
/**
* Handles new connection requests.
* The new connection is added to the listen queue resulting in
* waking up a thread blocked in accept() if any.
*/
private void handleNewConn(HttpTunnelPacket p) {
int connId = p.getConnId();
String remoteip = null;
byte[] payload = p.getPacketBody();
if (payload != null) {
ByteArrayInputStream bis = new ByteArrayInputStream(payload);
DataInputStream dis = new DataInputStream(bis);
try {
String sname = dis.readUTF();
remoteip = dis.readUTF();
} catch (Exception e) {
log(Level.WARNING, "Got exception while reading CONN_INIT_PACKET " + e.getMessage(), e);
}
try {
dis.close();
} catch (Exception e) {}
}
HttpTunnelConnection conn = new HttpTunnelConnection(connId, this);
conn.setRemoteAddr(remoteip);
synchronized (connTable) {
connTable.put(Integer.toString(connId), conn);
}
HttpTunnelPacket reply = new HttpTunnelPacket();
reply.setPacketType(CONN_INIT_ACK);
reply.setPacketBody(null);
reply.setConnId(connId);
reply.setSequence(0);
reply.setWinsize(0);
reply.setChecksum(0);
sendPacket(reply);
synchronized (listenQ) {
listenQ.addElement(conn);
listenQ.notify();
}
}
/**
* Handle connection close request.
*/
private void handleConnClose(HttpTunnelPacket p) {
int connId = p.getConnId();
HttpTunnelConnection conn;
synchronized (connTable) {
conn = (HttpTunnelConnection) connTable.get(Integer.toString(connId));
}
if (conn == null) {
return;
}
conn.handleClose(p);
}
/**
* Handle connection close request.
*/
private void handleConnAbort(HttpTunnelPacket p) {
int connId = p.getConnId();
HttpTunnelConnection conn;
synchronized (connTable) {
conn = (HttpTunnelConnection) connTable.get(Integer.toString(connId));
}
if (conn == null) {
return;
}
conn.handleAbort(p);
}
/**
* Handle connection close request.
*/
private void handleConnOption(HttpTunnelPacket p) {
int connId = p.getConnId();
HttpTunnelConnection conn;
synchronized (connTable) {
conn = (HttpTunnelConnection) connTable.get(Integer.toString(connId));
}
if (conn == null) {
return;
}
conn.handleConnOption(p);
}
/**
* Handles data packets from the network.
*/
private void handlePacket(HttpTunnelPacket p) {
int connId = p.getConnId();
HttpTunnelConnection conn;
synchronized (connTable) {
conn = (HttpTunnelConnection) connTable.get(Integer.toString(connId));
}
if (conn == null) {
// TBD : Error - Tell the client ???
return;
}
conn.receivePacket(p, false);
}
private void handleDummyPacket(HttpTunnelPacket p) {
if (DEBUG) {
log("#### Received dummy packet :");
log(p + "\n");
}
}
public synchronized void shutdown(int connId) {
synchronized (connTable) {
connTable.remove(Integer.toString(connId));
}
// Also, tell the servlet to do the same...
// Note : this packet is NOT delivered to the other end.
// It just tells the servlet to cleanup its connTable entry..
HttpTunnelPacket p = new HttpTunnelPacket();
p.setPacketType(CONN_SHUTDOWN);
p.setPacketBody(null);
p.setConnId(connId);
p.setSequence(0);
p.setWinsize(0);
p.setChecksum(0);
sendPacket(p);
}
private static HttpTunnelPacket genAbortPacket(int connId) {
HttpTunnelPacket p = new HttpTunnelPacket();
p.setPacketType(CONN_ABORT_PACKET);
p.setConnId(connId);
p.setSequence(0);
p.setWinsize(0);
p.setChecksum(0);
p.setPacketBody(null);
return p;
}
public Hashtable getDebugState() {
return new Hashtable();
}
}
/*
* EOF
*/