/*
* 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.
*/
/*
* @(#)HttpTunnelClientDriver.java 1.14 06/28/07
*/
package com.sun.messaging.jmq.transport.httptunnel.client;
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 java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
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 client side. It also uses a dedicated thread to continuously
* send HTTP pull requests to fetch packets sent by the server.
*/
public class HttpTunnelClientDriver extends Thread implements HttpTunnelDefaults,
HttpTunnelDriver {
private static boolean DEBUG = Boolean.getBoolean("httptunnel.debug");
private boolean stopThread = false;
private String urlString = null;
private String urlParam = null;
private URL pushUrl = null;
private URL pullUrl = null;
private URLConnection uc = null;
private HttpTunnelPush pushWorker = null;
private int connId;
private HttpTunnelConnection conn = null;
private long lastConnectTime = 0;
private Logger logger = Logger.getLogger("Http Tunneling");
public HttpTunnelClientDriver(String urlString) {
int index = urlString.lastIndexOf('?');
if (index >= 0) {
this.urlString = urlString.substring(0, index);
urlParam = "&" + urlString.substring(index + 1);
} else {
this.urlString = urlString;
urlParam = "";
}
setName("HttpTunnelClientDriver");
setDaemon(true);
pushWorker = new HttpTunnelPush();
}
private void handleConnInitAck(HttpTunnelPacket p) {
if (conn != null) {
return;
}
conn = new HttpTunnelConnection(connId, this);
}
private void handleConnClose(HttpTunnelPacket p) {
if (conn == null) {
return;
}
conn.handleClose(p);
}
private void handleConnAbort(HttpTunnelPacket p) {
if (conn == null) {
return;
}
conn.handleAbort(p);
}
private void handleConnOption(HttpTunnelPacket p) {
if (conn == null) {
return;
}
conn.handleConnOption(p);
}
/**
* Handles data packets from the network.
*/
private void handlePacket(HttpTunnelPacket p, boolean moreData) {
if (conn == null) {
// TBD : Error - Tell the client ???
return;
}
conn.receivePacket(p, moreData);
}
private void handleDummyPacket(HttpTunnelPacket p) {
if (DEBUG) {
log("#### Received dummy packet :");
log(p + "\n");
}
}
/**
* Implements the connection establishment protocol.
*/
public HttpTunnelConnection doConnect() throws IOException {
URL connUrl = new URL(urlString + "?Type=connect" + urlParam);
HttpTunnelPacket p = new HttpTunnelPacket();
p.setPacketType(CONN_INIT_PACKET);
p.setPacketBody(null);
p.setConnId(0);
p.setSequence(0);
p.setWinsize(0);
p.setChecksum(0);
try {
HttpTunnelPacket resp = pushWorker.sendPacketDirect(connUrl, p, true);
connId = resp.getConnId();
String serverName = new String(resp.getPacketBody(), "UTF8");
String newurlParam = "&" + serverName;
if (!urlParam.equals("") && !newurlParam.equals(urlParam)) {
throw new IOException("Unexpected new ServerName: " +
serverName);
}
if (urlParam.equals("")) {
urlParam = newurlParam;
}
pushUrl = new URL(urlString + "?Type=push" + urlParam);
pullUrl = new URL(urlString + "?Type=pull&ConnId=" + connId +
urlParam);
while (conn == null) {
Vector v = pullPackets();
if ((v == null) || (v.size() == 0)) {
continue;
}
HttpTunnelPacket ack = (HttpTunnelPacket) v.elementAt(0);
if (ack != null) {
switch (ack.getPacketType()) {
case CONN_SHUTDOWN:
throw new IOException("Connection refused");
case CONN_INIT_ACK:
handleConnInitAck(ack);
break;
case CONN_CLOSE_PACKET:
handleConnClose(p);
break;
}
}
}
} catch (Exception e) {
String message = "Connection refused : ";
if (e instanceof EOFException) {
message = "Connection refused : " +
"Make sure that the broker is running and " +
"its HTTP service is active...";
}
// Concatenate the underlying exception's message.
if (e.getMessage() != null) {
message = message + e.getMessage();
}
ConnectException ce = new ConnectException(message);
try {
ce.initCause(e);
} catch (Throwable t) {
// e.g. NoSuchMethodError when running with ancient
// JDKs that do not have Throwable.initCause(). In
// this case we will not be able to provide the full
// stack trace of the underlying cause. The users will
// have to rely on the concatenated message string.
// Too bad. They should upgrade...
}
throw ce;
}
pushWorker.startPushThread(pushUrl);
start();
return conn;
}
/**
* Sends a packet.
*/
public void sendPacket(HttpTunnelPacket p) {
pushWorker.sendPacket(p);
if (DEBUG) {
log("Sending packet" + p);
}
}
/**
* Shutdown the driver. Stops accepting packets from the peer.
*/
public void shutdown(int connId) {
pushWorker.shutdown();
stopThread = true;
if ((uc != null) && uc instanceof HttpURLConnection) {
try {
((HttpURLConnection) uc).disconnect();
} catch (Throwable t) {
}
}
conn = null;
}
/**
* Send a HTTP pull request to fetch any packets sent by the
* server. The server-side driver can send multiple packets
* with each HTTP pull response.
* @return A <code>Vector</code> containing received packets.
*/
private Vector pullPackets() throws Exception {
Vector v = null;
int responseCode = HttpURLConnection.HTTP_OK;
try {
uc = pullUrl.openConnection();
uc.setDoInput(true);
uc.setDoOutput(false);
uc.setUseCaches(false);
uc.connect();
if (uc instanceof HttpURLConnection) {
responseCode = ((HttpURLConnection) uc).getResponseCode();
} else {
uc.getContentType();
// We don't really need the content type. This just forces
// the uc to do its job.
}
} catch (IOException e) {
handleHTTPConnectError();
throw e;
}
if (responseCode != HttpURLConnection.HTTP_OK) {
handleHTTPConnectError();
} else {
lastConnectTime = System.currentTimeMillis();
v = new Vector();
InputStream is = uc.getInputStream();
while (true) {
try {
HttpTunnelPacket p = new HttpTunnelPacket();
p.readPacket(is);
v.addElement(p);
} catch (Exception e) {
if (v.size() > 1) {
break;
}
if (v.size() == 1) {
HttpTunnelPacket p = (HttpTunnelPacket) v.elementAt(0);
if (p.getPacketType() != NO_OP_PACKET) {
break;
}
}
// The 'pull' request came back empty handed..
int pullPeriod = conn.getPullPeriod();
if (pullPeriod > 0) {
try {
Thread.sleep(pullPeriod * 1000L);
} catch (Exception se) {
}
break;
} else {
throw e;
}
}
}
is.close();
}
uc = null;
return v;
}
private void handleHTTPConnectError() {
if (conn.getConnectionTimeout() <= 0) {
return;
}
if (lastConnectTime == 0) {
return;
}
if ((System.currentTimeMillis() - lastConnectTime) > (conn.getConnectionTimeout() * 1000)) {
// Abort the connection by generating a CONN_ABORT_PACKET.
HttpTunnelPacket p = new HttpTunnelPacket();
p.setPacketType(CONN_ABORT_PACKET);
p.setConnId(connId);
p.setSequence(0);
p.setWinsize(0);
p.setChecksum(0);
p.setPacketBody(null);
handleConnAbort(p);
}
}
private void handleHttpPullError() {
}
public void run() {
while (!stopThread) {
try {
Vector v = pullPackets();
if ((v == null) || (v.size() == 0)) {
handleHttpPullError();
continue;
}
int i;
int j;
for (j = v.size() - 1; j >= 0; j--) {
HttpTunnelPacket p = (HttpTunnelPacket) v.elementAt(j);
if (p.getPacketType() == DATA_PACKET) {
break;
}
}
// Now j points to the last data packet in v
for (i = 0; i < v.size(); i++) {
HttpTunnelPacket p = (HttpTunnelPacket) v.elementAt(i);
if (p.getPacketType() == CONN_SHUTDOWN) {
// TBD: Connection aborted...
}
if (DEBUG) {
log("Received packet:" + p);
}
switch (p.getPacketType()) {
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, (i != j));
// i == j is true for the last data packet in v
break;
case DUMMY_PACKET:
handleDummyPacket(p);
break;
default:
break;
}
// receivePacket(p);
}
} catch (Exception e) {
try {
Thread.sleep(1000);
} catch (Exception se) {
}
handleHttpPullError();
}
}
}
public Hashtable getDebugState() {
return new Hashtable();
}
private void log(String msg) {
logger.log(Level.INFO, msg);
}
}
/*
* EOF
*/