You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
21 KiB
514 lines
21 KiB
package de.anomic.soap;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.PushbackInputStream;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.Properties;
|
|
|
|
import javax.xml.namespace.QName;
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
|
|
import org.apache.axis.Constants;
|
|
import org.apache.axis.EngineConfiguration;
|
|
import org.apache.axis.Message;
|
|
import org.apache.axis.MessageContext;
|
|
import org.apache.axis.WSDDEngineConfiguration;
|
|
import org.apache.axis.deployment.wsdd.WSDDDeployment;
|
|
import org.apache.axis.deployment.wsdd.WSDDDocument;
|
|
import org.apache.axis.server.AxisServer;
|
|
import org.apache.axis.utils.XMLUtils;
|
|
import org.w3c.dom.Document;
|
|
import org.xml.sax.SAXException;
|
|
|
|
import de.anomic.http.httpHeader;
|
|
import de.anomic.http.httpc;
|
|
import de.anomic.http.httpdAbstractHandler;
|
|
import de.anomic.http.httpdHandler;
|
|
import de.anomic.server.serverClassLoader;
|
|
import de.anomic.server.serverFileUtils;
|
|
import de.anomic.server.serverSwitch;
|
|
|
|
/**
|
|
* Class to accept SOAP Requests and invoke the desired {@link de.anomic.http.httpdSoapService soapService}.
|
|
* An example how to do a soap call from php:
|
|
* <code>
|
|
* <?php
|
|
* $client = new SoapClient("http://192.168.1.201:8080/soap/index?wsdl", array(
|
|
* "trace" => 1,
|
|
* "exceptions" => 1));
|
|
*
|
|
* try
|
|
* {
|
|
* $result = $client->__call("crawling", array("http://test.at"), NULL,
|
|
* new SoapHeader("http://http.anomic.de/header", "Authorization", md5(base64_encode("admin:xxxxxxx"))));
|
|
* }
|
|
* catch (SoapFault $fault)
|
|
* {
|
|
* $result = $fault->faultstring;
|
|
* }
|
|
*
|
|
* print($result);
|
|
* ?>
|
|
* </code>
|
|
*
|
|
*
|
|
*
|
|
* @author Martin Thelian
|
|
*/
|
|
public final class httpdSoapHandler extends httpdAbstractHandler implements httpdHandler
|
|
{
|
|
|
|
/* ===============================================================
|
|
* Constants needed to set some SOAP properties
|
|
* =============================================================== */
|
|
/**
|
|
* SOAP Header Namespace needed to access the soap header field containing
|
|
* the user authentication
|
|
*/
|
|
public static final String serviceHeaderNamespace = "http://http.anomic.de/header";
|
|
|
|
/**
|
|
* define the needed deployment strings
|
|
*/
|
|
public static final String serviceDeploymentString =
|
|
"<deployment "
|
|
+ "xmlns=\"http://xml.apache.org/axis/wsdd/\" "
|
|
+ "xmlns:java=\"http://xml.apache.org/axis/wsdd/providers/java\" >"
|
|
+ "<service name=\"index\" provider=\"java:RPC\" >"
|
|
+ "<parameter name=\"className\" value=\"" + httpdSoapService.class.getName() + "\" />"
|
|
+ "<parameter name=\"allowedMethods\" value=\"*\" />"
|
|
+ "</service>"
|
|
+ "</deployment>";
|
|
|
|
/* ===============================================================
|
|
* Constants needed to set the SOAP message context
|
|
* =============================================================== */
|
|
/**
|
|
* CONSTANT: the http root path
|
|
*/
|
|
public static final String MESSAGE_CONTEXT_HTTP_ROOT_PATH = "htRootPath";
|
|
/**
|
|
* CONSTANT: tge server switchboard
|
|
*/
|
|
public static final String MESSAGE_CONTEXT_SERVER_SWITCH = "serverSwitch";
|
|
/**
|
|
* CONSTANT: received http headers
|
|
*/
|
|
public static final String MESSAGE_CONTEXT_HTTP_HEADER = "httpHeader";
|
|
/**
|
|
* CONSTANT: the server classloader
|
|
*/
|
|
public static final String MESSAGE_CONTEXT_SERVER_CLASSLOADER = "serverClassLoader";
|
|
/**
|
|
* CONSTANT: available templates
|
|
*/
|
|
public static final String MESSAGE_CONTEXT_TEMPLATES = "templates";
|
|
|
|
/* ===============================================================
|
|
* Other object fields
|
|
* =============================================================== */
|
|
private serverClassLoader provider = null;
|
|
private HashMap templates;
|
|
private serverSwitch switchboard;
|
|
|
|
private static AxisServer engine = null;
|
|
private File htRootPath;
|
|
private File htTemplatePath;
|
|
|
|
/*
|
|
* Creating and configuring an apache Axis server.
|
|
* This is only needed once.
|
|
*/
|
|
static
|
|
{
|
|
// create an Axis server
|
|
engine = new AxisServer();
|
|
|
|
// setting some options ...
|
|
engine.setShouldSaveConfig(false);
|
|
|
|
// deploy the service
|
|
deployService(serviceDeploymentString,engine);
|
|
}
|
|
|
|
/**
|
|
* Constructor of this class
|
|
* @param switchboard
|
|
*/
|
|
public httpdSoapHandler(serverSwitch switchboard)
|
|
{
|
|
super();
|
|
|
|
this.switchboard = switchboard;
|
|
|
|
// create a htRootPath: system pages
|
|
if (this.htRootPath == null) {
|
|
this.htRootPath = new File(this.switchboard.getRootPath(), this.switchboard.getConfig("htRootPath","htroot"));
|
|
// if (!(htRootPath.exists())) htRootPath.mkdir();
|
|
}
|
|
|
|
if (this.htTemplatePath == null) {
|
|
this.htTemplatePath = new File(switchboard.getRootPath(), switchboard.getConfig("htTemplatePath","htroot/env/templates"));
|
|
// if (!(this.htTemplatePath.exists())) this.htTemplatePath.mkdir();
|
|
}
|
|
|
|
if (this.provider == null) {
|
|
this.provider = new serverClassLoader(/*this.getClass().getClassLoader()*/);
|
|
}
|
|
|
|
if (this.templates == null) this.templates = loadTemplates(this.htTemplatePath);
|
|
}
|
|
|
|
/**
|
|
* Handle http-GET requests. For soap this is usually a query for the wsdl-file.
|
|
* Therefore we always return the wsdl file for a get request
|
|
*
|
|
* @param conProp
|
|
* @param requestHeader all received http headers
|
|
* @param response {@link OutputStream} to the client
|
|
*
|
|
* @throws IOException
|
|
*
|
|
* @see de.anomic.http.httpdHandler#doGet(java.util.Properties, de.anomic.http.httpHeader, java.io.OutputStream)
|
|
*/
|
|
public void doGet(Properties conProp, httpHeader requestHeader, OutputStream response) throws IOException
|
|
{
|
|
String path = conProp.getProperty("PATH");
|
|
try
|
|
{
|
|
MessageContext msgContext = this.generateMessageContext(path, requestHeader);
|
|
|
|
engine.generateWSDL(msgContext);
|
|
Document doc = (Document) msgContext.getProperty("WSDL");
|
|
|
|
if (doc != null)
|
|
{
|
|
// Converting the the wsdl document into a byte-array
|
|
String responseDoc = XMLUtils.DocumentToString(doc);
|
|
byte[] result = responseDoc.getBytes();
|
|
|
|
/*
|
|
* Setting the response header
|
|
* - Status: 200 OK
|
|
* - Content Type: text/xml
|
|
* - Encoding: UTF8
|
|
* - Date: current Date
|
|
*/
|
|
respondHeader(response, 200, "text/xml; charset=utf-8", result.length, new Date(), null, null);
|
|
|
|
// writing out the data
|
|
Thread.currentThread().join(200);
|
|
serverFileUtils.write(result, response);
|
|
response.flush();
|
|
|
|
if (!(requestHeader.get("Connection", "close").equals("keep-alive"))) {
|
|
// wait a little time until everything closes so that clients can read from the streams/sockets
|
|
try {Thread.currentThread().join(1000);} catch (InterruptedException e) {}
|
|
}
|
|
}
|
|
|
|
// if we where unable to generate the wsdl file ....
|
|
else
|
|
{
|
|
String errorMsg = "Internal Server Error: Unable to generate the WSDL file.";
|
|
respondHeader(response, 500, "text/plain", errorMsg.length(), httpc.nowDate(), null, null);
|
|
response.write(errorMsg.getBytes());
|
|
response.flush();
|
|
}
|
|
|
|
return;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
System.out.println("ERROR: Exception with query: " + path + "; '" + e.toString() + ":" + e.getMessage() + "'\r\n");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* HTTP HEAD method. Not needed for soap.
|
|
* @param conProp
|
|
* @param header
|
|
* @param response
|
|
* @throws IOException
|
|
*
|
|
* @see de.anomic.http.httpdHandler#doHead(java.util.Properties, de.anomic.http.httpHeader, java.io.OutputStream)
|
|
*/
|
|
public void doHead(Properties conProp, httpHeader header, OutputStream response) throws IOException
|
|
{
|
|
// the http HEAD method is not allowed for this SOAP API
|
|
String errorMsg = "Method Not Allowed";
|
|
respondHeader(response, 405, "text/plain", errorMsg.length(), httpc.nowDate(), null, null);
|
|
response.write(errorMsg.getBytes());
|
|
response.flush();
|
|
}
|
|
|
|
/**
|
|
* HTTP Post method. Needed to call a soap service on this server from a soap client
|
|
* @param conProp the connection properties
|
|
* @param requestHeader the received http headers
|
|
* @param response {@link OutputStream} to the client
|
|
* @param body the request body containing the SOAP message
|
|
*
|
|
* @throws IOException
|
|
*
|
|
* @see de.anomic.http.httpdHandler#doPost(java.util.Properties, de.anomic.http.httpHeader, java.io.OutputStream, java.io.PushbackInputStream)
|
|
*/
|
|
public void doPost(Properties conProp, httpHeader requestHeader, OutputStream response, PushbackInputStream body) throws IOException
|
|
{
|
|
String path = conProp.getProperty("PATH");
|
|
try
|
|
{
|
|
/*
|
|
* Converting the request body into a bytehstream needed by the apache
|
|
* axis engine
|
|
*/
|
|
int contentLength = requestHeader.containsKey("CONTENT-LENGTH")?
|
|
Integer.parseInt((String) requestHeader.get("CONTENT-LENGTH")):
|
|
0;
|
|
|
|
byte[] buffer = new byte[contentLength];
|
|
body.read(buffer);
|
|
InputStream soapInputStream = new ByteArrayInputStream(buffer);
|
|
|
|
/*
|
|
* generating the SOAP message context that will be passed over to the invoked
|
|
* service
|
|
*/
|
|
MessageContext msgContext = this.generateMessageContext(path, requestHeader);
|
|
|
|
/*
|
|
* Generating a SOAP Request Message Object from the XML document
|
|
* and store it into the message context
|
|
*/
|
|
Message requestMsg = new Message(soapInputStream, false, "text/xml;charset=\"utf-8\"", "");
|
|
msgContext.setRequestMessage(requestMsg);
|
|
|
|
// invoke the service
|
|
engine.invoke(msgContext);
|
|
|
|
// Retrieve the response from Axis
|
|
Message responseMsg = msgContext.getResponseMessage();
|
|
|
|
if (responseMsg != null)
|
|
{
|
|
respondHeader(response, 200, "text/xml; charset=utf-8", responseMsg.getContentLength(), new Date(), null, null);
|
|
Thread.currentThread().join(200);
|
|
responseMsg.writeTo(response);
|
|
response.flush();
|
|
}
|
|
else
|
|
{
|
|
String errorMsg = "Internal Server Error: Unable to invoke the requested service.";
|
|
respondHeader(response, 500, "text/plain", errorMsg.length(), httpc.nowDate(), null, null);
|
|
response.write(errorMsg.getBytes());
|
|
response.flush();
|
|
}
|
|
|
|
return;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
System.out.println("ERROR: Exception with query: " + path + "; '" + e.toString() + ":" + e.getMessage() + "'\r\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* HTTP Connect Method. Not needed for SOAP
|
|
* @param conProp
|
|
* @param requestHeader
|
|
* @param clientIn
|
|
* @param clientOut
|
|
* @throws IOException
|
|
*
|
|
* @see de.anomic.http.httpdHandler#doConnect(java.util.Properties, de.anomic.http.httpHeader, java.io.InputStream, java.io.OutputStream)
|
|
*/
|
|
public void doConnect(Properties conProp, httpHeader requestHeader, InputStream clientIn, OutputStream clientOut) throws IOException
|
|
{
|
|
// the CONNECT method is not allowed for this SOAP API
|
|
String errorMsg = "Method Not Allowed";
|
|
respondHeader(clientOut, 405, "text/plain", errorMsg.length(), httpc.nowDate(), null, null);
|
|
clientOut.write(errorMsg.getBytes());
|
|
clientOut.flush();
|
|
}
|
|
|
|
/**
|
|
* This function deplays all java classes that should be available via SOAP call.
|
|
*
|
|
* @param deploymentString the deployment string containing detailed information about
|
|
* the java class that should be deployed
|
|
* @param theAxisServer the apache axis engine where the service should be deployed
|
|
*
|
|
* @return <code>true</code> if the deployment was done successfully or <code>false</code>
|
|
* otherwise
|
|
*/
|
|
private static boolean deployService(String deploymentString, AxisServer theAxisServer)
|
|
{
|
|
// convert WSDD file string into bytestream for furhter processing
|
|
InputStream deploymentStream = null;
|
|
if (deploymentString != null)
|
|
{
|
|
deploymentStream = new ByteArrayInputStream(deploymentString.getBytes());
|
|
|
|
Document root = null;
|
|
|
|
try
|
|
{
|
|
// build XML document from stream
|
|
root = XMLUtils.newDocument(deploymentStream);
|
|
|
|
// parse WSDD file
|
|
WSDDDocument wsddDoc = new WSDDDocument(root);
|
|
|
|
// get the configuration of this axis engine
|
|
EngineConfiguration config = theAxisServer.getConfig();
|
|
|
|
if (config instanceof WSDDEngineConfiguration)
|
|
{
|
|
// get the current configuration of the Axis engine
|
|
WSDDDeployment deploymentWSDD =
|
|
((WSDDEngineConfiguration) config).getDeployment();
|
|
|
|
// undeply unneeded standard services
|
|
deploymentWSDD.undeployService(new QName("Version"));
|
|
deploymentWSDD.undeployService(new QName("AdminService"));
|
|
|
|
// deploy the new service
|
|
// an existing service with the same name gets deleted
|
|
wsddDoc.deploy(deploymentWSDD);
|
|
}
|
|
}
|
|
catch (ParserConfigurationException e)
|
|
{
|
|
System.err.println("Could not deploy service.");
|
|
return false;
|
|
}
|
|
catch (SAXException e)
|
|
{
|
|
System.err.println("Could not deploy service.");
|
|
return false;
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
System.err.println("Could not deploy service.");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
System.err.println("Service deployment string is NULL! SOAP Service not deployed.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This function is used to generate the SOAP Message Context that is handed over to
|
|
* the called service.
|
|
* This message context contains some fields needed by the service to fullfil the request.
|
|
*
|
|
* @param path the path of the request
|
|
* @param requestHeader the http headers of the request
|
|
*
|
|
* @return the generated {@link MessageContext}
|
|
*
|
|
* @throws Exception if the {@link MessageContext} could not be generated successfully.
|
|
*/
|
|
private MessageContext generateMessageContext(String path, httpHeader requestHeader)
|
|
throws Exception
|
|
{
|
|
try
|
|
{
|
|
// create and initialize a message context
|
|
MessageContext msgContext = new MessageContext(httpdSoapHandler.engine);
|
|
msgContext.setTransportName("SimpleHTTP");
|
|
msgContext.setProperty(org.apache.axis.Constants.MC_REALPATH, path.toString());
|
|
msgContext.setProperty(Constants.MC_RELATIVE_PATH, path.toString());
|
|
msgContext.setProperty(Constants.MC_JWS_CLASSDIR, "jwsClasses");
|
|
msgContext.setProperty(Constants.MC_HOME_DIR, ".");
|
|
msgContext.setProperty(MessageContext.TRANS_URL, "http://" + requestHeader.get("Host") + ((((String)requestHeader.get("Host")).indexOf(":") > -1)?"":this.switchboard.getConfig("port","8080")) + "/soap/index");
|
|
|
|
msgContext.setProperty(MESSAGE_CONTEXT_HTTP_ROOT_PATH ,this.htRootPath.toString());
|
|
msgContext.setProperty(MESSAGE_CONTEXT_SERVER_SWITCH,this.switchboard);
|
|
msgContext.setProperty(MESSAGE_CONTEXT_HTTP_HEADER ,requestHeader);
|
|
msgContext.setProperty(MESSAGE_CONTEXT_SERVER_CLASSLOADER ,this.provider);
|
|
msgContext.setProperty(MESSAGE_CONTEXT_TEMPLATES ,this.templates);
|
|
|
|
msgContext.setTargetService(path.substring(6));
|
|
|
|
return msgContext;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new Exception ("Unable to generate the message context. ",e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method was copied from {@link httpdFileHandler}. Maybe it would be a good idea
|
|
* to move this function up into {@link httpdAbstractHandler}
|
|
*
|
|
* @param path the path to the template dir
|
|
* @return a hasmap containing all templates
|
|
*/
|
|
private static HashMap loadTemplates(File path) {
|
|
// reads all templates from a path
|
|
// we use only the folder from the given file path
|
|
HashMap result = new HashMap();
|
|
if (path == null) return result;
|
|
if (!(path.isDirectory())) path = path.getParentFile();
|
|
if ((path == null) || (!(path.isDirectory()))) return result;
|
|
String[] templates = path.list();
|
|
int c;
|
|
for (int i = 0; i < templates.length; i++) {
|
|
if (templates[i].endsWith(".template")) try {
|
|
//System.out.println("TEMPLATE " + templates[i].substring(0, templates[i].length() - 9) + ": " + new String(buf, 0, c));
|
|
result.put(templates[i].substring(0, templates[i].length() - 9),
|
|
new String(serverFileUtils.read(new File(path, templates[i]))));
|
|
} catch (Exception e) {}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method was copied from {@link httpdFileHandler}. Maybe it would be a good idea
|
|
* to move this function up into {@link httpdAbstractHandler}
|
|
*
|
|
* @param out the {@link OutputStream} to the client
|
|
* @param retcode the http code
|
|
* @param conttype the content type and encoding
|
|
* @param contlength the content length
|
|
* @param moddate the modification date of the content
|
|
* @param expires the date of expiry of the content
|
|
* @param cookie cookies to be send
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
protected void respondHeader(OutputStream out, int retcode, String conttype, long contlength, Date moddate, Date expires, String cookie) throws IOException
|
|
{
|
|
try {
|
|
out.write(("HTTP/1.1 " + retcode + " OK\r\n").getBytes());
|
|
out.write(("Server: AnomicHTTPD (www.anomic.de)\r\n").getBytes());
|
|
out.write(("Date: " + httpc.dateString(httpc.nowDate()) + "\r\n").getBytes());
|
|
if (expires != null) out.write(("Expires: " + httpc.dateString(expires) + "\r\n").getBytes());
|
|
out.write(("Content-type: " + conttype /* "image/gif", "text/html" */ + "\r\n").getBytes());
|
|
out.write(("Last-modified: " + httpc.dateString(moddate) + "\r\n").getBytes());
|
|
out.write(("Content-length: " + contlength +"\r\n").getBytes());
|
|
out.write(("Pragma: no-cache\r\n").getBytes());
|
|
// out.write(("Accept-ranges: bytes\r\n").getBytes());
|
|
if (cookie != null) out.write(("Set-Cookie: " + cookie + "\r\n").getBytes());
|
|
out.write(("\r\n").getBytes());
|
|
out.flush();
|
|
} catch (Exception e) {
|
|
// any interruption may be caused be network error or because the user has closed
|
|
// the windows during transmission. We simply pass it as IOException
|
|
throw new IOException(e.getMessage());
|
|
}
|
|
}
|
|
}
|