/*##############################################################################
Copyright (C) 2011 HPCC Systems.
All rights reserved. This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
############################################################################## */
#pragma warning( disable : 4786 )
#ifdef WIN32
#ifdef ESPHTTP_EXPORTS
#define esp_http_decl __declspec(dllexport)
#endif
#endif
//Jlib
#include "jliball.hpp"
//SCM Interfaces
#include "esp.hpp"
//ESP Core
#include "espthread.hpp"
//ESP Bindings
#include "SOAP/Platform/soapbind.hpp"
#include "SOAP/client/soapclient.hpp"
#include "http/platform/httpprot.hpp"
#include "http/platform/httpservice.hpp"
#include "SOAP/Platform/soapservice.hpp"
#ifdef WIN32
#define ESP_FACTORY __declspec(dllexport)
#else
#define ESP_FACTORY
#endif
CSoapBinding::CSoapBinding()
{
}
CSoapBinding::~CSoapBinding()
{
}
//IEspRpcBinding
const char * CSoapBinding::getRpcType()
{
return "soap";
}
const char * CSoapBinding::getTransportType()
{
return "unknown";
}
int CSoapBinding::processRequest(IRpcMessage* rpc_call, IRpcMessage* rpc_response)
{
return 0;
}
CHttpSoapBinding::CHttpSoapBinding():EspHttpBinding(NULL, NULL, NULL)
{
log_level_=hsl_none;
}
CHttpSoapBinding::CHttpSoapBinding(IPropertyTree* cfg, const char *bindname, const char *procname, http_soap_log_level level)
: EspHttpBinding(cfg, bindname, procname)
{
log_level_=level;
}
CHttpSoapBinding::~CHttpSoapBinding()
{
}
const char * CHttpSoapBinding::getTransportType()
{
return "http";
}
static CSoapFault* makeSoapFault(CHttpRequest* request, IMultiException* me, const char *ns)
{
const char* svcName = request->queryServiceName();
if (svcName && *svcName)
{
const char* method = request->queryServiceMethod();
StringBuffer host;
const char* wsdlAddr = request->queryParameters()->queryProp("__wsdl_address");
if (wsdlAddr && *wsdlAddr)
host.append(wsdlAddr);
else
{
host.append(request->queryHost());
if (request->getPort()>0)
host.append(":").append(request->getPort());
}
VStringBuffer ns_ext("xmlns=\"%s\""
" xsi:schemaLocation=\"%s %s/%s/%s?xsd\"",
ns, ns, host.str(), svcName, method ? method : "");
return new CSoapFault(me, ns_ext);
}
return new CSoapFault(me);
}
int CHttpSoapBinding::onSoapRequest(CHttpRequest* request, CHttpResponse* response)
{
Owned soapFault;
try
{
return HandleSoapRequest(request,response);
}
catch (IMultiException* mex)
{
StringBuffer ns;
soapFault.setown(makeSoapFault(request,mex, generateNamespace(*request->queryContext(), request, request->queryServiceName(), request->queryServiceMethod(), ns).str()));
//SetHTTPErrorStatus(mex->errorCode(),response);
SetHTTPErrorStatus(500,response);
mex->Release();
}
catch (IException* e)
{
StringBuffer ns;
Owned mex = MakeMultiException("Esp");
mex->append(*e); // e is owned by mex
soapFault.setown(makeSoapFault(request,mex, generateNamespace(*request->queryContext(), request, request->queryServiceName(), request->queryServiceMethod(), ns).str()));
SetHTTPErrorStatus(500,response);
}
catch (...)
{
soapFault.setown(new CSoapFault(500,"Internal Server Error"));
SetHTTPErrorStatus(500,response);
}
//response->setContentType(soapFault->get_content_type());
response->setContentType(HTTP_TYPE_TEXT_XML_UTF8);
response->setContent(soapFault->get_text());
DBGLOG("Sending SOAP Fault(%u): %s", response->getContentLength(), response->queryContent());
{
EspTimeSection sendtime("send fault [CHttpSoapBinding::onSoapRequest]");
response->send();
}
return -1;
}
int CHttpSoapBinding::HandleSoapRequest(CHttpRequest* request, CHttpResponse* response)
{
ESP_TIME_SECTION("CHttpSoapBinding::onSoapRequest");
StringBuffer requeststr;
request->getContent(requeststr);
if(requeststr.length() == 0)
throw MakeStringException(-1, "Content read is empty");
Owned soapservice;
Owned soaprequest;
Owned soapresponse;
//soapservice.setown(new CSoapService((IEspSoapBinding*)(this)));
soapservice.setown(new CSoapService(this));
soaprequest.setown(new CSoapRequest);
soaprequest->set_text(requeststr.str());
StringBuffer contenttype;
request->getContentType(contenttype);
soaprequest->set_content_type(contenttype.str());
soaprequest->setContext(request->queryContext());
CMimeMultiPart* multipart = request->queryMultiPart();
if(multipart != NULL)
{
soaprequest->setOwnMultiPart(LINK(multipart));
}
soapresponse.setown(new CSoapResponse);
StringBuffer reqPath;
request->getPath(reqPath);
setRequestPath(reqPath.str());
soapservice->processRequest(*soaprequest.get(), *soapresponse.get());
response->setVersion(HTTP_VERSION);
int status = soapresponse->get_status();
if(status == SOAP_OK)
response->setStatus(HTTP_STATUS_OK);
else if(status == SOAP_SERVER_ERROR || status == SOAP_RPC_ERROR || status == SOAP_CONNECTION_ERROR)
{
StringBuffer msg("Internal Server Error");
const char* detail = soapresponse->get_err();
if (detail && *detail)
msg.appendf(" [%s]", detail);
throw MakeStringException(500, "%s", msg.str());
}
else if(status == SOAP_CLIENT_ERROR || status == SOAP_REQUEST_TYPE_ERROR)
{
StringBuffer msg("Bad Request");
const char* detail = soapresponse->get_err();
if (detail && *detail)
msg.appendf(" [%s]", detail);
throw MakeStringException(400, "%s", msg.str());
}
else if(status == SOAP_AUTHENTICATION_REQUIRED)
response->sendBasicChallenge(m_challenge_realm.str(), false);
else if(status == SOAP_AUTHENTICATION_ERROR)
{
throw MakeStringException(401,"Unauthorized Access");
}
else
response->setStatus(HTTP_STATUS_OK);
response->setContentType(soapresponse->get_content_type());
response->setContent(soapresponse->get_text());
DBGLOG("Sending SOAP Response(%u)", response->getContentLength());
{
EspTimeSection sendtime("send response [CHttpSoapBinding::HandleSoapRequest]");
response->send();
}
return 0;
}
void CSoapRequestBinding::post(const char *proxy, const char* url, IRpcResponseBinding& response, const char *soapaction)
{
CRpcCall rpccall;
CRpcResponse rpcresponse;
rpccall.set_url(url);
rpccall.setProxy(proxy);
serialize(*static_cast(&rpccall));
CSoapClient soapclient; //to add support for handling cookies soapclient(false);
soapclient.setUsernameToken(getUserId(), getPassword(), getRealm());
int result = soapclient.postRequest(soapaction, rpccall, rpcresponse);
if(result == SOAP_OK)
{
response.setRpcState(RPC_MESSAGE_OK);
response.unserialize(rpcresponse, NULL, NULL);
}
else if(result == SOAP_CONNECTION_ERROR)
{
response.setRpcState(RPC_MESSAGE_CONNECTION_ERROR);
}
else
{
response.setRpcState(RPC_MESSAGE_ERROR);
}
}
void CSoapComplexType::appendContent(IEspContext* ctx, MemoryBuffer& buffer, StringBuffer& mimetype)
{
StringBuffer content;
if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
{
content.append('{');
serializeStruct(ctx, content, (const char *)NULL);
content.append('}');
mimetype.set("application/json; charset=UTF-8");
}
else
{
buffer.append(38, "");
serializeStruct(ctx, content, (const char *)NULL);
mimetype.set("text/xml; charset=UTF-8");
}
buffer.append(content.length(), content.str());
}
inline void open_element(IEspContext *ctx, StringBuffer &xml, const char *name, const char *uri, const char *prefix)
{
if (!name || !*name)
return;
xml.append("<");
if (prefix && *prefix)
xml.append(prefix).append(':');
xml.append(name);
if (uri && *uri)
{
xml.append("xmlns");
if (prefix && *prefix)
xml.append(':').append(prefix);
xml.append("=\"").append(uri).append('\"');
}
}
inline void start_child_attributes(IEspContext *ctx, StringBuffer &xml, const char *name)
{
}
inline void start_child_elements(IEspContext *ctx, StringBuffer &xml, const char *name)
{
if (!name || !*name)
return;
xml.append('>');
}
inline void close_element(IEspContext *ctx, StringBuffer &xml, const char *name, const char *prefix)
{
if (!name || !*name)
return;
xml.append("");
if (prefix && *prefix)
xml.append(prefix).append(':');
xml.append(name).append('>');
}
void CSoapComplexType::serializeJSONStruct(IEspContext* ctx, StringBuffer& s, const char *name)
{
if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
{
if (s.length() && !strchr("[{:", s.charAt(s.length()-1)))
s.append(", ");
if (name && *name)
s.append('\"').append(name).append("\": ");
s.append("{");
serializeContent(ctx, s);
s.append("}");
return;
}
}
void CSoapComplexType::serializeStruct(IEspContext* ctx, StringBuffer& s, const char *name)
{
const char *tag = (name && *name) ? name : getRootName();
if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
return serializeJSONStruct(ctx, s, tag);
open_element(ctx, s, tag, getNsURI(), getNsPrefix());
start_child_attributes(ctx, s, name);
serializeAttributes(ctx, s);
start_child_elements(ctx, s, tag);
serializeContent(ctx, s);
close_element(ctx, s, tag, getNsPrefix());
}
void CSoapComplexType::serializeItem(IEspContext* ctx, StringBuffer& s, const char *name)
{
if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
return serializeJSONStruct(ctx, s, NULL);
serializeStruct(ctx, s, name);
}
void CSoapResponseBinding::handleExceptions(IMultiException *me, const char *serv, const char *meth)
{
if (me->ordinality() > 0)
{
StringBuffer text;
me->errorMessage(text);
text.append('\n');
WARNLOG("Exception(s) in %s::%s - %s", serv, meth, text.str());
IArrayOf& exceptions = me->getArray();
ForEachItemIn(i, exceptions)
noteException(*LINK(&exceptions.item(i)));
}
}
void SetHTTPErrorStatus(int ErrorCode,CHttpResponse* response)
{
switch(ErrorCode)
{
case 204:
response->setStatus(HTTP_STATUS_NO_CONTENT);
break;
case 301:
response->setStatus(HTTP_STATUS_MOVED_PERMANENTLY);
break;
case 302:
response->setStatus(HTTP_STATUS_REDIRECT);
break;
case 303:
response->setStatus(HTTP_STATUS_REDIRECT_POST);
break;
case 400:
response->setStatus(HTTP_STATUS_BAD_REQUEST);
break;
case 401:
response->setStatus(HTTP_STATUS_UNAUTHORIZED);
break;
case 403:
response->setStatus(HTTP_STATUS_FORBIDDEN);
break;
case 404:
response->setStatus(HTTP_STATUS_NOT_FOUND);
break;
case 405:
response->setStatus(HTTP_STATUS_NOT_ALLOWED);
break;
case 501:
response->setStatus(HTTP_STATUS_NOT_IMPLEMENTED);
break;
default:
response->setStatus(HTTP_STATUS_INTERNAL_SERVER_ERROR);
}
}