Pārlūkot izejas kodu

HPCC-9259 Add JSON support to ESP

- Create a pull parser interface that will be shared by new parser class
  and original XmlPullParser class
- Implement the pull parser class using jlib JSON and XML pull parsers
- Serialize directly from rpc response to JSON text
- Add JSON test to ESP service form page

Signed-off-by: mayx <yanrui.ma@lexisnexisrisk.com>
mayx 6 gadi atpakaļ
vecāks
revīzija
fb81cb5923

+ 132 - 64
esp/bindings/SOAP/Platform/soapbind.cpp

@@ -32,6 +32,8 @@
 #include "http/platform/httpprot.hpp"
 #include "http/platform/httpservice.hpp"
 #include "SOAP/Platform/soapservice.hpp"
+#include "SOAP/Platform/soapmessage.hpp"
+#include "SOAP/xpp/xjx/xjxpp.hpp"
 
 #define ESP_FACTORY DECL_EXPORT
 
@@ -108,35 +110,71 @@ static CSoapFault* makeSoapFault(CHttpRequest* request, IMultiException* me, con
 
 int CHttpSoapBinding::onSoapRequest(CHttpRequest* request, CHttpResponse* response)
 {
-    Owned<CSoapFault> soapFault;
-    try
-    {
-        return HandleSoapRequest(request,response);
-    }
-    catch (IMultiException* mex)
+    IEspContext* ctx = request->queryContext();
+    if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
     {
-        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();
+        int errcode = 0;
+        StringBuffer msgbuf;
+        try
+        {
+            return HandleSoapRequest(request,response);
+        }
+        catch (IMultiException* mex)
+        {
+            errcode = mex->errorCode();
+            mex->serializeJSON(msgbuf, 0, true, true, true);
+            mex->Release();
+        }
+        catch (IException* e)
+        {
+            errcode = e->errorCode();
+            Owned<IMultiException> mex = MakeMultiException("Esp");
+            mex->append(*e); // e is owned by mex
+            mex->serializeJSON(msgbuf, 0, true, true, true);
+        }
+        catch (...)
+        {
+            errcode = 500;
+            Owned<IMultiException> mex = MakeMultiException("Esp");
+            mex->append(*MakeStringException(500, "Internal Server Error"));
+            mex->serializeJSON(msgbuf, 0, true, true, true);
+        }
+        SetHTTPErrorStatus(errcode, response);
+        response->setContentType(HTTP_TYPE_APPLICATION_JSON_UTF8);
+        response->setContent(msgbuf.str());
     }
-    catch (IException* e)
+    else
     {
-        StringBuffer ns;
-        Owned<IMultiException> 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);
+        Owned<CSoapFault> 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<IMultiException> 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());
     }
-    //response->setContentType(soapFault->get_content_type());
-    response->setContentType(HTTP_TYPE_TEXT_XML_UTF8);
-    response->setContent(soapFault->get_text());
     response->send();
     return -1;
 }
@@ -145,15 +183,15 @@ int CHttpSoapBinding::HandleSoapRequest(CHttpRequest* request, CHttpResponse* re
 {
     StringBuffer requeststr;
     request->getContent(requeststr);
-    
-    if(requeststr.length() == 0)
+    if (requeststr.length() == 0)
         throw MakeStringException(-1, "Content read is empty");
 
+    IEspContext* ctx = request->queryContext();
+
     Owned<CSoapService> soapservice;
     Owned<CSoapRequest> soaprequest;
     Owned<CSoapResponse> soapresponse;
 
-    //soapservice.setown(new CSoapService((IEspSoapBinding*)(this)));
     soapservice.setown(new CSoapService(this));
     soaprequest.setown(new CSoapRequest);
     soaprequest->set_text(requeststr.str());
@@ -161,54 +199,62 @@ int CHttpSoapBinding::HandleSoapRequest(CHttpRequest* request, CHttpResponse* re
     StringBuffer contenttype;
     request->getContentType(contenttype);
     soaprequest->set_content_type(contenttype.str());
-    soaprequest->setContext(request->queryContext());
+    soaprequest->setContext(ctx);
 
     CMimeMultiPart* multipart = request->queryMultiPart();
-    if(multipart != NULL)
-    {
+    if (multipart != nullptr)
         soaprequest->setOwnMultiPart(LINK(multipart));
-    }
-    
+
     soapresponse.setown(new CSoapResponse);
 
+    if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
+    {
+        soaprequest->setHttpReq(request);
+        soapresponse->setHttpResp(response);
+    }
+
     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 MakeStringExceptionDirect(500, msg.str());
-    }
-    else if(status == SOAP_CLIENT_ERROR || status == SOAP_REQUEST_TYPE_ERROR)
+    //For JSON the response would have been sent except for certain errors, which will be thrown below
+    if (!soapresponse->getHttpResp() || !soapresponse->getHttpResp()->getRespSent())
     {
-        StringBuffer msg("Bad Request");
-        const char* detail = soapresponse->get_err();
-        if (detail && *detail)
-            msg.appendf(" [%s]", detail);
-        throw MakeStringExceptionDirect(400, msg.str());
-    }
-    else if(status == SOAP_AUTHENTICATION_REQUIRED)
-        response->sendBasicChallenge(m_challenge_realm.str(), false);
-    else if(status == SOAP_AUTHENTICATION_ERROR)
-    {
-        throw MakeStringExceptionDirect(401,"Unauthorized Access");
-    }
-    else
-        response->setStatus(HTTP_STATUS_OK);
+        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 MakeStringExceptionDirect(500, 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 MakeStringExceptionDirect(400, msg.str());
+        }
+        else if (status == SOAP_AUTHENTICATION_REQUIRED)
+            response->sendBasicChallenge(m_challenge_realm.str(), false);
+        else if (status == SOAP_AUTHENTICATION_ERROR)
+        {
+            throw MakeStringExceptionDirect(401,"Unauthorized Access");
+        }
+        else
+            response->setStatus(HTTP_STATUS_OK);
 
-    response->setContentType(soapresponse->get_content_type());
-    response->setContent(soapresponse->get_text());
-    response->send();
+        response->setContentType(soapresponse->get_content_type());
+        response->setContent(soapresponse->get_text());
+        response->send();
+    }
 
     return 0;
 }
@@ -274,6 +320,28 @@ void CSoapComplexType::appendContent(IEspContext* ctx, MemoryBuffer& buffer, Str
     buffer.append(content.length(), content.str());
 }
 
+void CSoapComplexType::appendContent(IEspContext* ctx, StringBuffer& buffer, StringBuffer& mimetype)
+{
+    if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
+    {
+        const char *jsonp = ctx->queryRequestParameters()->queryProp("jsonp");
+        if (jsonp && *jsonp)
+            buffer.append(jsonp).append('(');
+        buffer.append('{');
+        serializeStruct(ctx, buffer, (const char *)nullptr);
+        buffer.append('}');
+        if (jsonp && *jsonp)
+            buffer.append(");");
+        mimetype.set("application/json; charset=UTF-8");
+    }
+    else
+    {
+        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        serializeStruct(ctx, buffer, (const char *)nullptr);
+        mimetype.set("text/xml; charset=UTF-8");
+    }
+}
+
 inline void open_element(IEspContext *ctx, StringBuffer &xml, const char *name, const char *uri, const char *prefix)
 {
     if (!name || !*name)

+ 1 - 0
esp/bindings/SOAP/Platform/soapbind.hpp

@@ -62,6 +62,7 @@ public:
     void * getThunkHandle(){return thunk_;}
 
     virtual void appendContent(IEspContext* ctx, MemoryBuffer& buffer, StringBuffer& mimetype);
+    virtual void appendContent(IEspContext* ctx, StringBuffer& buffer, StringBuffer& mimetype);
     virtual void serializeJSONStruct(IEspContext* ctx, StringBuffer& s, const char *name);
     virtual void serializeStruct(IEspContext * ctx, StringBuffer & buffer, const char * rootname=NULL);
     virtual void serializeItem(IEspContext* ctx, StringBuffer& s, const char *name);

+ 45 - 7
esp/bindings/SOAP/Platform/soapmessage.cpp

@@ -186,12 +186,48 @@ void CRpcMessage::add_attr(const char * path, const char * name, const char * va
     }
 }
 
-void CRpcMessage::unmarshall(XmlPullParser* xpp)
+void CRpcMessage::preunmarshall(XJXPullParser* xpp)
+{
+    if(!xpp)
+        return;
+    int type;
+    StartTag stag;
+    while((type = xpp->next()) != XmlPullParser::END_DOCUMENT)
+    {
+        if(type == XmlPullParser::START_TAG)
+        {
+            xpp->readStartTag(stag);
+            const char* localname = stag.getLocalName();
+            if(!localname || strieq(localname, "__object__"))
+                continue;
+            set_name(localname);
+
+            StringBuffer ns;
+            const char* qname = stag.getQName();
+
+            if(strlen(qname) > strlen(localname))
+            {
+                const char* semcol = strchr(qname, ':');
+                if(semcol != NULL)
+                {
+                    ns.append(qname, 0, semcol - qname);
+                }
+            }
+            const char* nsuri = stag.getUri();
+
+            set_ns(ns.str());
+            set_nsuri(nsuri);
+            return;
+        }
+    }
+}
+
+void CRpcMessage::unmarshall(XJXPullParser* xpp)
 {
     unmarshall(xpp, m_params, m_name);
 }
 
-void CRpcMessage::unmarshall(XmlPullParser* xpp, CSoapValue* soapvalue, const char* tagname)
+void CRpcMessage::unmarshall(XJXPullParser* xpp, CSoapValue* soapvalue, const char* tagname)
 {
     int type;
     StartTag stag;
@@ -244,12 +280,12 @@ void CRpcMessage::unmarshall(XmlPullParser* xpp, CSoapValue* soapvalue, const ch
     }
 }
 
-void CRpcMessage::unmarshall(XmlPullParser* xpp, CMimeMultiPart* multipart)
+void CRpcMessage::unmarshall(XJXPullParser* xpp, CMimeMultiPart* multipart)
 {
     unmarshall(xpp, m_params, m_name, multipart);
 }
 
-void CRpcMessage::unmarshall(XmlPullParser* xpp, CSoapValue* soapvalue, const char* tagname, CMimeMultiPart* multipart)
+void CRpcMessage::unmarshall(XJXPullParser* xpp, CSoapValue* soapvalue, const char* tagname, CMimeMultiPart* multipart)
 {
     int type;
 
@@ -322,17 +358,19 @@ void CRpcMessage::unmarshall(XmlPullParser* xpp, CSoapValue* soapvalue, const ch
     }
 }
 
+/*
 IRpcMessage* createRpcMessage(const char* rootTag, StringBuffer& xml)
 {
     CRpcMessage* rpc = new  CRpcMessage(rootTag);
 
-    std::unique_ptr<XmlPullParser> xpp(new XmlPullParser(xml.str(), xml.length()));
+    std::unique_ptr<XJXPullParser> xpp(new XmlPullParser(xml.str(), xml.length()));
     xpp->setSupportNamespaces(true);
 
     rpc->unmarshall(xpp.get());
 
     return rpc;
 }
+*/
 
 bool CRpcResponse::handleExceptions(IXslProcessor *xslp, IMultiException *me, const char *serv, const char *meth, const char *errorXslt)
 {
@@ -444,7 +482,7 @@ StringBuffer& CHeader::marshall(StringBuffer& str, CMimeMultiPart* multipart)
     return str;
 }
 
-void CHeader::unmarshall(XmlPullParser* xpp)
+void CHeader::unmarshall(XJXPullParser* xpp)
 {
     int type;
     StartTag stag;
@@ -480,7 +518,7 @@ void CHeader::unmarshall(XmlPullParser* xpp)
     }
 }
 
-void CEnvelope::unmarshall(XmlPullParser* xpp)
+void CEnvelope::unmarshall(XJXPullParser* xpp)
 {
     int type;
     StartTag stag;

+ 32 - 17
esp/bindings/SOAP/Platform/soapmessage.hpp

@@ -26,14 +26,13 @@
 #include "esp.hpp"
 
 #include "http/platform/mime.hpp"
-
 #ifdef ESPHTTP_EXPORTS
     #define esp_http_decl DECL_EXPORT
 #else
     #define esp_http_decl DECL_IMPORT
 #endif
 
-#include "http/platform/httptransport.hpp"
+#include "http/platform/httptransport.ipp"
 
 #include <xpp/XmlPullParser.h>
 
@@ -179,9 +178,13 @@ public:
 
 class CSoapRequest : public CSoapMessage
 {
+private:
+    CHttpRequest* m_httpReq;
 public:
-    CSoapRequest() {};
+    CSoapRequest() : m_httpReq(nullptr) {};
     virtual ~CSoapRequest() {};
+    CHttpRequest* getHttpReq() { return m_httpReq; }
+    void setHttpReq(CHttpRequest* httpReq) { m_httpReq = httpReq; }
 };
 
 class CSoapResponse : public CSoapMessage
@@ -189,14 +192,17 @@ class CSoapResponse : public CSoapMessage
 private:
     int          m_status;
     StringBuffer m_err;
+    CHttpResponse* m_httpResp;
 public:
-    CSoapResponse(){m_status = SOAP_OK;};
+    CSoapResponse() : m_httpResp(nullptr) {m_status = SOAP_OK;};
     virtual ~CSoapResponse() {};
 
     virtual void set_status(int status);
     virtual int get_status();
     virtual void set_err(const char* err);
     virtual const char* get_err();
+    CHttpResponse* getHttpResp() { return m_httpResp; }
+    void setHttpResp(CHttpResponse* httpResp) { m_httpResp = httpResp; }
 };
 
 
@@ -509,10 +515,11 @@ public:
     virtual void set_text(const char* text) {m_text.clear(); m_text.append(text);};
     virtual void append_text(const char* text) {m_text.append(text);};
 
-    virtual void unmarshall(XmlPullParser* xpp);
-    virtual void unmarshall(XmlPullParser* xpp, CSoapValue* soapvalue, const char* tagname);
-    virtual void unmarshall(XmlPullParser* xpp, CMimeMultiPart* multipart);
-    virtual void unmarshall(XmlPullParser* xpp, CSoapValue* soapvalue, const char* tagname, CMimeMultiPart* multipart);
+    void preunmarshall(XJXPullParser* xpp);
+    virtual void unmarshall(XJXPullParser* xpp);
+    virtual void unmarshall(XJXPullParser* xpp, CSoapValue* soapvalue, const char* tagname);
+    virtual void unmarshall(XJXPullParser* xpp, CMimeMultiPart* multipart);
+    virtual void unmarshall(XJXPullParser* xpp, CSoapValue* soapvalue, const char* tagname, CMimeMultiPart* multipart);
 
     virtual void marshall(StringBuffer & outbuf)
     {
@@ -528,10 +535,11 @@ class CRpcCall : public CRpcMessage
 private:
     StringBuffer m_proxy;
     StringAttr m_url;
+    CHttpRequest* m_httpReq;
 
 public:
-    CRpcCall() {};
-    CRpcCall(const char* url) {m_url.set(url);}
+    CRpcCall() : m_httpReq(nullptr) {};
+    CRpcCall(const char* url) : m_httpReq(nullptr) {m_url.set(url);}
 
    virtual ~CRpcCall(){}
     
@@ -540,6 +548,9 @@ public:
 
     virtual const char* getProxy() {return m_proxy.str();}
     virtual void setProxy(const char* proxy) {m_proxy.clear().append(proxy);}
+
+    CHttpRequest* getHttpReq() { return m_httpReq; }
+    void setHttpReq(CHttpRequest* httpReq) { m_httpReq = httpReq; }
 };
 
 class CRpcResponse : public CRpcMessage
@@ -547,9 +558,10 @@ class CRpcResponse : public CRpcMessage
 private:
     int m_status;
     StringBuffer m_err;
+    CHttpResponse* m_httpResp;
 public:
-    CRpcResponse(){ }
-    CRpcResponse(const char* name):CRpcMessage(name) { }
+    CRpcResponse() : m_httpResp(nullptr) { }
+    CRpcResponse(const char* name) : CRpcMessage(name), m_httpResp(nullptr) { }
     virtual ~CRpcResponse(){ }
 
     virtual int get_status() { return m_status; }
@@ -557,6 +569,9 @@ public:
     void set_err(const char* err) { m_err.clear().append(err); }
     const char* get_err() {  return m_err.str(); }
 
+    CHttpResponse* getHttpResp() { return m_httpResp; }
+    void setHttpResp(CHttpResponse* httpResp) { m_httpResp = httpResp; }
+
     bool handleExceptions(IXslProcessor *xslp, IMultiException *me, const char *serv, const char *meth, const char *errorXslt);
 };
 
@@ -572,7 +587,7 @@ public:
     virtual int getNumBlocks();
     virtual IRpcMessage* getHeaderBlock(int seq);
     virtual IRpcMessage* getHeaderBlock(const char* name);
-    virtual void unmarshall(XmlPullParser* xpp);
+    virtual void unmarshall(XJXPullParser* xpp);
     virtual StringBuffer& marshall(StringBuffer& str, CMimeMultiPart* multipart);
     virtual const char * getMessageType() {return "EnvelopeHeader";};
 };
@@ -580,15 +595,15 @@ public:
 class CBody : public CInterface
 {
 private:
-    XmlPullParser* m_xpp;
+    XJXPullParser* m_xpp;
     IArrayOf<IRpcMessage> m_rpcmessages;
 
 public:
     CBody() : m_xpp(NULL) { }
     virtual ~CBody() { }
 
-    virtual XmlPullParser* get_xpp() {return m_xpp;};
-    virtual void set_xpp(XmlPullParser* xpp) {m_xpp = xpp;};
+    virtual XJXPullParser* get_xpp() {return m_xpp;};
+    virtual void set_xpp(XJXPullParser* xpp) {m_xpp = xpp;};
     
     virtual void add_rpcmessage(IRpcMessage* rpcmessage) {
         if(rpcmessage)
@@ -628,7 +643,7 @@ public:
     virtual ~CEnvelope(){};
     virtual CHeader* get_header() {return m_header.get();}
     virtual CBody* get_body() {return m_body.get();}
-    virtual void unmarshall(XmlPullParser* xpp);
+    virtual void unmarshall(XJXPullParser* xpp);
 
     virtual const char * getMessageType() {return "SoapEnvelope";};
 

+ 101 - 70
esp/bindings/SOAP/Platform/soapservice.cpp

@@ -26,6 +26,7 @@
 //ESP Bindings
 #include "platform.h"
 #include <xpp/XmlPullParser.h>
+#include <xjx/xjxpp.hpp>
 #include "SOAP/Platform/soapservice.hpp"
 #include "http/platform/httpservice.hpp"
 #include "bindutil.hpp"
@@ -148,6 +149,10 @@ int CSoapService::processRequest(ISoapMessage &req, ISoapMessage& resp)
     {
         requeststr.append(request.get_text());
     }
+    else if (Utils::strncasecmp(request.get_content_type(), HTTP_TYPE_JSON, strlen(HTTP_TYPE_JSON))==0)
+    {
+        requeststr.append(request.get_text());
+    }
     else if(!Utils::strncasecmp(request.get_content_type(), HTTP_TYPE_MULTIPART_RELATED, strlen(HTTP_TYPE_MULTIPART_RELATED)))
     {
         multipart.setown(LINK(request.queryMultiPart()));
@@ -162,100 +167,126 @@ int CSoapService::processRequest(ISoapMessage &req, ISoapMessage& resp)
         throw MakeStringException(-1, "Request type %s not supported", request.get_content_type());
     }
     
-    //Parse the content
-    std::unique_ptr<XmlPullParser> xpp(new XmlPullParser());
+    OwnedPtr<XJXPullParser> xpp;
     int bufSize = requeststr.length();
-    xpp->setSupportNamespaces(true);
+    //Parse the content
+    if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
+        xpp.setown(new CJsonPullParser(true));
+    else
+    {
+        XmlPullParser* ptr = nullptr;
+        xpp.setown((ptr = new XmlPullParser()));
+        ptr->setSupportNamespaces(true);
+    }
     xpp->setInput(requeststr.str(), bufSize);
 
     int type; 
     StartTag stag;
     EndTag etag;
-
-    Owned<CEnvelope> req_envelope;
-    req_envelope.setown(new CEnvelope);
-
-    while((type = xpp->next()) != XmlPullParser::END_DOCUMENT) 
-    {
-        if(type == XmlPullParser::START_TAG) {
-            xpp->readStartTag(stag);
-            if(!stricmp(stag.getLocalName(), SOAP_ENVELOPE_NAME))
-            {
-                req_envelope->unmarshall(xpp.get());
-                break;
-            }
-        }
-    }
-    
-    CHeader* req_header = req_envelope->get_header();
-    if(req_header != NULL)
-    {
-        // As headers are normally for common uses like authentication and routing, let's process it here
-        // instead of in binding.
-        int ret = processHeader(req_header, ctx);
-        if(ret != 0 )
-        {
-            response.set_status(ret);
-            return 0;
-        }
-    }
-
     StringBuffer peerStr;
     ctx->getPeer(peerStr);
     const char* userId = ctx->queryUserId();
-
-    CBody* req_body = req_envelope->get_body();
     Owned<CRpcResponse> rpc_response;
     rpc_response.setown(new CRpcResponse);
     rpc_response->setContext(req.queryContext());
+    rpc_response->setHttpResp(response.getHttpResp());
     Owned<CRpcCall>rpc_call;
     rpc_call.setown(new CRpcCall);
     rpc_call->setContext(req.queryContext());
+    rpc_call->setHttpReq(request.getHttpReq());
     
-    try {
-        req_body->nextRpcMessage(rpc_call.get());
+    if (ctx && ctx->getResponseFormat()==ESPSerializationJSON)
+    {
+        rpc_call->preunmarshall(xpp.get());
         rpc_call->unmarshall(xpp.get(), multipart.get());
-    } catch (XmlPullParserException& e) {
-        response.set_status(SOAP_CLIENT_ERROR);
-        response.set_err(e.getMessage().c_str());
-        DBGLOG("SOAP request from %s@%s. Parsing xml error: %s. Offending XML: [%s]", (userId&&*userId)?userId:"unknown",
-            (peerStr.length()>0)?peerStr.str():"unknown", e.getMessage().c_str(), requeststr.str());
-        return 0;
-    } catch (...) {
-        response.set_status(SOAP_CLIENT_ERROR);
-        response.set_err("Unknown error when parsing soap body XML");
-        ERRLOG("SOAP request from %s@%s. Unknown error when parsing: %s",  (userId&&*userId)?userId:"unknown",
-            (peerStr.length()>0)?peerStr.str():"unknown", requeststr.str());
-        return 0;
+        DBGLOG("JSON method <%s> from %s@%s.", rpc_call->get_name(),  (userId&&*userId)?userId:"unknown",
+            (peerStr.length()>0)?peerStr.str():"unknown");
+        ctx->setHTTPMethod("JSON");
+    }
+    else
+    {
+        Owned<CEnvelope> req_envelope;
+        req_envelope.setown(new CEnvelope);
+
+        while ((type = xpp->next()) != XmlPullParser::END_DOCUMENT)
+        {
+            if (type == XmlPullParser::START_TAG) {
+                xpp->readStartTag(stag);
+                if (!stricmp(stag.getLocalName(), SOAP_ENVELOPE_NAME))
+                {
+                    req_envelope->unmarshall(xpp.get());
+                    break;
+                }
+            }
+        }
+
+        CHeader* req_header = req_envelope->get_header();
+        if (req_header != NULL)
+        {
+            // As headers are normally for common uses like authentication and routing, let's process it here
+            // instead of in binding.
+            int ret = processHeader(req_header, ctx);
+            if (ret != 0 )
+            {
+                response.set_status(ret);
+                return 0;
+            }
+        }
+
+        CBody* req_body = req_envelope->get_body();
+        try {
+            req_body->nextRpcMessage(rpc_call.get());
+            rpc_call->unmarshall(xpp.get(), multipart.get());
+        } catch (XmlPullParserException& e) {
+            response.set_status(SOAP_CLIENT_ERROR);
+            response.set_err(e.getMessage().c_str());
+            DBGLOG("SOAP request from %s@%s. Parsing xml error: %s. Offending XML: [%s]", (userId&&*userId)?userId:"unknown",
+                (peerStr.length()>0)?peerStr.str():"unknown", e.getMessage().c_str(), requeststr.str());
+            return 0;
+        } catch (...) {
+            response.set_status(SOAP_CLIENT_ERROR);
+            response.set_err("Unknown error when parsing soap body XML");
+            ERRLOG("SOAP request from %s@%s. Unknown error when parsing: %s",  (userId&&*userId)?userId:"unknown",
+                (peerStr.length()>0)?peerStr.str():"unknown", requeststr.str());
+            return 0;
+        }
+
+        DBGLOG("SOAP method <%s> from %s@%s.", rpc_call->get_name(),  (userId&&*userId)?userId:"unknown",
+            (peerStr.length()>0)?peerStr.str():"unknown");
+        ctx->setHTTPMethod("SOAP");
     }
 
-    DBGLOG("SOAP method <%s> from %s@%s.", rpc_call->get_name(),  (userId&&*userId)?userId:"unknown",
-        (peerStr.length()>0)?peerStr.str():"unknown");
-    ctx->setHTTPMethod("SOAP");
     ctx->setServiceMethod(rpc_call->get_name());
 
     // call the rpc and set the response
     if(m_soapbinding != NULL)
         m_soapbinding->processRequest(rpc_call, rpc_response);
-        
-    response.set_status(rpc_response->get_status());
-    response.set_err(rpc_response->get_err());
-
-    Owned<CBody> res_body = new CBody;
-    res_body->add_rpcmessage(rpc_response.get());
-
-    Owned<CEnvelope> res_envelope;
-    res_envelope.setown(new CEnvelope(NULL, res_body.getLink()));
-    Owned<CMimeMultiPart> resp_multipart;
-    resp_multipart.setown(new CMimeMultiPart("1.0", "", "MIME_boundary", "text/xml", "soaproot"));
-    res_envelope->marshall(resp_multipart);
-        
-    StringBuffer contenttype;
-    StringBuffer responsestr;
-    resp_multipart->serialize(contenttype, responsestr);
-
-    response.set_content_type(contenttype.str());
-    response.set_text(responsestr.str());
+
+    if (!response.getHttpResp() || !response.getHttpResp()->getRespSent())
+    {
+        response.set_status(rpc_response->get_status());
+        response.set_err(rpc_response->get_err());
+
+        //JSON content would've been sent, except certain errors, which don't need the following
+        if (!(ctx && ctx->getResponseFormat()==ESPSerializationJSON))
+        {
+            Owned<CBody> res_body = new CBody;
+            res_body->add_rpcmessage(rpc_response.get());
+
+            Owned<CEnvelope> res_envelope;
+            res_envelope.setown(new CEnvelope(NULL, res_body.getLink()));
+            Owned<CMimeMultiPart> resp_multipart;
+            resp_multipart.setown(new CMimeMultiPart("1.0", "", "MIME_boundary", "text/xml", "soaproot"));
+            res_envelope->marshall(resp_multipart);
+
+            StringBuffer contenttype;
+            StringBuffer responsestr;
+            resp_multipart->serialize(contenttype, responsestr);
+
+            response.set_content_type(contenttype.str());
+            response.set_text(responsestr.str());
+        }
+    }
 
     return 0;
 }

+ 35 - 0
esp/bindings/SOAP/xpp/xjx.hpp

@@ -0,0 +1,35 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _XJX_HPP__
+#define _XJX_HPP__
+#include "jlib.hpp"
+namespace xpp
+{
+class EndTag;
+class StartTag;
+interface XJXPullParser {
+public:
+    virtual ~XJXPullParser() {}
+    virtual void setInput(const char* buf, int bufSize) = 0;
+    virtual int next() = 0;
+    virtual const char* readContent()  = 0;
+    virtual void readEndTag(EndTag& etag) = 0;
+    virtual void readStartTag(StartTag& stag) = 0;
+};
+}
+#endif

+ 188 - 0
esp/bindings/SOAP/xpp/xjx/xjxpp.cpp

@@ -0,0 +1,188 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#include "xjxpp.hpp"
+#include <xpp/EndTag.h>
+#include <xpp/StartTag.h>
+#include <xpp/XmlPullParser.h>
+
+namespace xpp
+{
+void CXJXNotifyEvent::beginNode(const char *tag, offset_t startOffset)
+{
+    m_eventType = 1;
+    m_name.set(tag);
+}
+
+void CXJXNotifyEvent::newAttribute(const char *name, const char *value)
+{
+    m_eventType = 2;
+    m_name.set(name);
+    m_value.set(value);
+}
+
+void CXJXNotifyEvent::beginNodeContent(const char *tag)
+{
+    m_eventType = 3;
+    m_name.set(tag);
+}
+
+void CXJXNotifyEvent::endNode(const char *tag, unsigned length, const void *value, bool binary, offset_t endOffset)
+{
+    m_name.set(tag);
+    m_value.clear();
+    if (length == 0)
+        m_eventType = 4;
+    else
+    {
+        m_eventType = 5;
+        m_value.append(length, (const char*)value);
+    }
+}
+
+int CXJXNotifyEvent::getEventType()
+{
+    return m_eventType;
+}
+
+void CXJXNotifyEvent::resetEventType()
+{
+    m_eventType = 0;
+}
+
+const char* CXJXNotifyEvent::queryValue()
+{
+    return m_value.str();
+}
+
+const char* CXJXNotifyEvent::queryName()
+{
+    return m_name.str();
+}
+
+CXJXPullParser::CXJXPullParser(bool supportNameSpaces) : m_isEndTag(false), m_calledNext(false), m_reachedEnd(false)
+{
+    if(supportNameSpaces)
+        m_readerOptions = ptr_ignoreWhiteSpace;
+    else
+        m_readerOptions = static_cast<PTreeReaderOptions>(ptr_ignoreWhiteSpace | ptr_ignoreNameSpaces);
+}
+
+int CXJXPullParser::next()
+{
+    if (m_isEndTag)
+    {
+        m_isEndTag = false;
+        return XmlPullParser::END_TAG;
+    }
+
+    while ((m_calledNext && !m_reachedEnd) || m_reader->next())
+    {
+        if (m_calledNext)
+            m_calledNext = false;
+        int eventType = m_event->getEventType();
+        m_event->resetEventType();
+        if (eventType == 1)
+            return XmlPullParser::START_TAG;
+        else if (eventType == 4)
+            return XmlPullParser::END_TAG;
+        else if (eventType == 5)
+        {
+            m_isEndTag = true;
+            return XmlPullParser::CONTENT;
+        }
+    }
+
+    return XmlPullParser::END_DOCUMENT;
+}
+
+const char* CXJXPullParser::readContent()
+{
+    return m_event->queryValue();
+}
+
+void CXJXPullParser::readEndTag(EndTag& tag)
+{
+    const char* name = m_event->queryName();
+    if (!name || !*name)
+        return;
+    tag.nameBuf = name;
+    tag.qName = tag.nameBuf.c_str();
+    const char* colon = strchr(tag.qName, ':');
+    if (colon)
+        tag.localName = colon+1;
+    else
+        tag.localName = tag.qName;
+}
+
+void CXJXPullParser::readStartTag(StartTag& tag)
+{
+    const char* name = m_event->queryName();
+    if (!name || !*name)
+        return;
+    tag.nameBuf = name;
+    tag.qName = tag.nameBuf.c_str();
+    const char* colon = strchr(tag.qName, ':');
+    if (colon)
+        tag.localName = colon+1;
+    else
+        tag.localName = tag.qName;
+
+    while(true)
+    {
+        bool hasMore = m_reader->next();
+        m_calledNext = true;
+        if (!hasMore)
+        {
+            m_reachedEnd = true;
+            break;
+        }
+        int eventType = m_event->getEventType();
+        m_event->resetEventType();
+        if (eventType != 2)
+            break;
+        tag.ensureCapacity(tag.attEnd+1);
+        const char* name = m_event->queryName();
+        if (name && *name=='@')
+            name++;
+        tag.attArr[tag.attEnd].qName = name;
+        tag.attArr[tag.attEnd].value = m_event->queryValue();
+        tag.attEnd++;
+    }
+}
+
+void CXJXPullParser::beforeSetInput()
+{
+    m_event.setown(new CXJXNotifyEvent());
+    m_isEndTag = false;
+    m_calledNext = false;
+    m_reachedEnd = false;
+}
+
+void CJsonPullParser::setInput(const char* buf, int bufSize)
+{
+    beforeSetInput();
+    m_reader.setown(createPullJSONBufferReader(buf, bufSize, *m_event.get(), m_readerOptions));
+}
+
+void CXmlPullParser::setInput(const char* buf, int bufSize)
+{
+    beforeSetInput();
+    m_reader.setown(createPullXMLBufferReader(buf, bufSize, *m_event.get(), m_readerOptions));
+}
+
+}

+ 96 - 0
esp/bindings/SOAP/xpp/xjx/xjxpp.hpp

@@ -0,0 +1,96 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _XJXPP_HPP__
+#define _XJXPP_HPP__
+#include "jlib.hpp"
+#include "jptree.hpp"
+#include "xjx.hpp"
+
+namespace xpp
+{
+class CXJXNotifyEvent : implements IPTreeNotifyEvent, public CInterface
+{
+private:
+    StringBuffer m_name;
+    StringBuffer m_value;
+    int m_eventType;
+
+public:
+    IMPLEMENT_IINTERFACE;
+
+    CXJXNotifyEvent() : m_eventType(0) {}
+    virtual ~CXJXNotifyEvent() {}
+
+    //IPTreeNotifyEvent
+    virtual void beginNode(const char *tag, offset_t startOffset) override;
+    virtual void newAttribute(const char *name, const char *value) override;
+    virtual void beginNodeContent(const char *tag) override;
+    virtual void endNode(const char *tag, unsigned length, const void *value, bool binary, offset_t endOffset) override;
+
+    int getEventType();
+    void resetEventType();
+    const char* queryValue();
+    const char* queryName();
+};
+
+class CXJXPullParser : implements XJXPullParser, implements IInterface, public CInterface
+{
+private:
+    bool m_isEndTag;
+    bool m_calledNext;
+    bool m_reachedEnd;
+
+protected:
+    PTreeReaderOptions m_readerOptions;
+    Owned<CXJXNotifyEvent> m_event;
+    Owned<IPullPTreeReader> m_reader;
+    void beforeSetInput();
+
+public:
+    IMPLEMENT_IINTERFACE;
+
+    CXJXPullParser(bool supportNameSpaces);
+    virtual ~CXJXPullParser() {}
+
+    //Implement common methods
+    virtual int next() override;
+    virtual const char* readContent() override;
+    virtual void readEndTag(EndTag& etag) override;
+    virtual void readStartTag(StartTag& stag) override;
+};
+
+class CJsonPullParser : public CXJXPullParser
+{
+public:
+    CJsonPullParser(bool supportNameSpaces) : CXJXPullParser(supportNameSpaces) { }
+    virtual ~CJsonPullParser() { }
+
+    virtual void setInput(const char* buf, int bufSize) override;
+};
+
+class CXmlPullParser : public CXJXPullParser
+{
+public:
+    CXmlPullParser(bool supportNameSpaces) : CXJXPullParser(supportNameSpaces) { }
+    virtual ~CXmlPullParser() { }
+
+    virtual void setInput(const char* buf, int bufSize) override;
+};
+
+}
+#endif

+ 3 - 2
esp/bindings/SOAP/xpp/xpp/EndTag.h

@@ -21,7 +21,7 @@
 
 #include <string>
 
-#include <xpp/XmlPullParser.h>
+#include <sxt/XmlTokenizer.h>
 
 using namespace std;
 
@@ -37,6 +37,7 @@ namespace xpp {
   class EndTag {
 
     friend class XmlPullParser;
+    friend class CXJXPullParser;
 
   public:
     EndTag() { init(); }
@@ -84,7 +85,7 @@ namespace xpp {
     const SXT_CHAR* uri;
     const SXT_CHAR* localName;
     const SXT_CHAR* qName;
-   
+    SXT_STRING nameBuf;
   };
   
 inline ostream& operator<<(ostream& output, 

+ 4 - 2
esp/bindings/SOAP/xpp/xpp/StartTag.h

@@ -20,8 +20,7 @@
 #define XPP_START_TAG_H_
 
 #include <string>
-#include <xpp/XmlPullParser.h>
-
+#include <sxt/XmlTokenizer.h>
 /**
  * Encapsulate XML STag and EmptyElement
  * 
@@ -35,6 +34,7 @@ namespace xpp {
 
   class StartTag {
     friend class XmlPullParser;
+    friend class CXJXPullParser;
 
   public:
     StartTag() { init(); }
@@ -213,9 +213,11 @@ namespace xpp {
     const SXT_CHAR *uri;
     const SXT_CHAR *localName;
     const SXT_CHAR *qName;
+    SXT_STRING nameBuf;
 
     class Attribute { 
       friend class XmlPullParser;
+      friend class CXJXPullParser;
       friend class StartTag;
     public:
       Attribute() {

+ 4 - 4
esp/bindings/SOAP/xpp/xpp/XmlPullParser.h

@@ -28,7 +28,7 @@
 #include <xpp/EndTag.h>
 #include <xpp/StartTag.h>
 #include <xpp/XmlPullParserException.h>
-
+#include "xjx.hpp"
 #include "jliball.hpp"
 
 /**
@@ -76,7 +76,7 @@ using namespace sxt;
 #define XPP_DEBUG false
 
 namespace xpp {
-  class XmlPullParser {
+  class XmlPullParser : implements XJXPullParser {
     
       
   // public 
@@ -87,7 +87,7 @@ namespace xpp {
       END_TAG = 3,
       CONTENT = 4
     };
- 
+
     XmlPullParser() 
     {
       init();
@@ -373,7 +373,7 @@ namespace xpp {
     }
   
   
-    void readEndTag(EndTag& etag) const   {
+    void readEndTag(EndTag& etag)  {
       if(eventType != END_TAG)
         throw XmlPullParserException("no end tag available to read");
       etag.qName = elStack[elStackDepth].qName;

+ 75 - 1
esp/bindings/http/platform/httpbinding.cpp

@@ -41,6 +41,7 @@
 #include  "seclib.hpp"
 #include "../../../system/security/shared/secloader.hpp"
 #include  "../../SOAP/Platform/soapmessage.hpp"
+#include  "../../SOAP/Platform/soapbind.hpp"
 #include "xmlvalidator.hpp"
 #include "xsdparser.hpp"
 #include "espsecurecontext.hpp"
@@ -105,6 +106,7 @@ EspHttpBinding::EspHttpBinding(IPropertyTree* tree, const char *bindname, const
     m_viewConfig = proc_cfg ? proc_cfg->getPropBool("@httpConfigAccess") : false;   
     m_formOptions = proc_cfg ? proc_cfg->getPropBool("@formOptionsAccess") : false;
     m_includeSoapTest = true;
+    m_includeJsonTest = true;
     m_configFile.set(tree ? tree->queryProp("@config") : "esp.xml");
     Owned<IPropertyTree> bnd_cfg = getBindingConfig(tree, bindname, procname);
     m_wsdlVer=0.0;
@@ -871,9 +873,11 @@ void EspHttpBinding::handleHttpPost(CHttpRequest *request, CHttpResponse *respon
             return;
     }
 
-    if(request->isSoapMessage()) 
+    if (request->isSoapMessage() || request->isJsonMessage())
     {
         request->queryParameters()->setProp("__wsdl_address", m_wsdlAddress.str());
+        if(request->isJsonMessage())
+            context.setResponseFormat(ESPSerializationJSON);
         onSoapRequest(request, response);
     }
     else if(request->isFormSubmission())
@@ -968,6 +972,8 @@ int EspHttpBinding::onGet(CHttpRequest* request, CHttpResponse* response)
             return onGetInstantQuery(context, request, response, serviceName.str(), methodName.str());
         case sub_serv_soap_builder:
             return onGetSoapBuilder(context, request, response, serviceName.str(), methodName.str());
+        case sub_serv_json_builder:
+            return onGetJsonBuilder(context, request, response, serviceName.str(), methodName.str());
         case sub_serv_reqsamplexml:
             return onGetReqSampleXml(context, request, response, serviceName.str(), methodName.str());
         case sub_serv_respsamplexml:
@@ -1166,6 +1172,20 @@ void EspHttpBinding::getSoapMessage(StringBuffer& soapmsg, IEspContext& ctx, CHt
         );
 }
 
+void EspHttpBinding::getJsonMessage(StringBuffer& jsonmsg, IEspContext& ctx, CHttpRequest* request, const char *serv, const char *method)
+{
+    ESPSerializationFormat orig_format = ctx.getResponseFormat();
+    ctx.setResponseFormat(ESPSerializationJSON);
+    Owned<IRpcRequestBinding> rpcreq = createReqBinding(ctx, request, serv, method);
+    CSoapRequestBinding* reqbind = dynamic_cast<CSoapRequestBinding*>(rpcreq.get());
+    if (reqbind)
+    {
+        StringBuffer mime;
+        reqbind->appendContent(&ctx, jsonmsg, mime);
+    }
+    ctx.setResponseFormat(orig_format);
+}
+
 #else
 
 //=========================================================
@@ -1338,6 +1358,59 @@ int EspHttpBinding::onGetSoapBuilder(IEspContext &context, CHttpRequest* request
     return 0;
 }
 
+int EspHttpBinding::onGetJsonBuilder(IEspContext &context, CHttpRequest* request, CHttpResponse* response,  const char *serv, const char *method)
+{
+    StringBuffer jsonmsg, serviceQName, methodQName;
+
+    if (!qualifyServiceName(context, serv, method, serviceQName, &methodQName)
+        || methodQName.length()==0)
+        throw createEspHttpException(HTTP_STATUS_BAD_REQUEST_CODE, "Bad Request", HTTP_STATUS_BAD_REQUEST);
+
+    getJsonMessage(jsonmsg,context,request,serviceQName,methodQName);
+
+    //put all URL parameters into dest
+
+    StringBuffer params;
+    const char* excludes[] = {"json_builder_",NULL};
+    getEspUrlParams(context,params,excludes);
+
+    StringBuffer header("Content-Type: application/json; charset=UTF-8");
+
+    Owned<IXslProcessor> xslp = getXslProcessor();
+    Owned<IXslTransform> xform = xslp->createXslTransform();
+    xform->loadXslFromFile(StringBuffer(getCFD()).append("./xslt/wsecl3_jsontest.xsl").str());
+
+    StringBuffer encodedMsg;
+    StringBuffer srcxml;
+    srcxml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><srcxml><jsonreq><![CDATA[");
+    srcxml.append(encodeJSON(encodedMsg, jsonmsg.str())); //encode the whole thing for javascript embedding
+    srcxml.append("]]></jsonreq></srcxml>");
+    xform->setXmlSource(srcxml.str(), srcxml.length());
+    xform->setStringParameter("showhttp", "true()");
+
+    // params
+    xform->setStringParameter("pageName", "JSON Test");
+    xform->setStringParameter("serviceName", serviceQName.str());
+    xform->setStringParameter("methodName", methodQName.str());
+    xform->setStringParameter("header", header.str());
+    xform->setStringParameter("jsonbody", encodedMsg.str());
+
+    ISecUser* user = context.queryUser();
+    bool inhouse = user && (user->getStatus()==SecUserStatus_Inhouse);
+    xform->setParameter("inhouseUser", inhouse ? "true()" : "false()");
+    xform->setStringParameter("destination", methodQName.str());
+
+    StringBuffer page;
+    xform->transform(page);
+
+    response->setContent(page);
+    response->setContentType("text/html; charset=UTF-8");
+    response->setStatus(HTTP_STATUS_OK);
+    response->send();
+
+    return 0;
+}
+
 int EspHttpBinding::onFeaturesAuthorize(IEspContext &context, MapStringTo<SecAccessFlags> & pmap, const char *serviceName, const char *methodName)
 {
     if (!context.validateFeaturesAccess(pmap, false))
@@ -2279,6 +2352,7 @@ int EspHttpBinding::onGetXForm(IEspContext &context, CHttpRequest* request, CHtt
 
         xform->setParameter("formOptionsAccess", m_formOptions?"1":"0");
         xform->setParameter("includeSoapTest", m_includeSoapTest?"1":"0");
+        xform->setParameter("includeJsonTest", m_includeJsonTest?"1":"0");
 
         // set the prop noDefaultValue param
         IProperties* props = context.queryRequestParameters();

+ 4 - 0
esp/bindings/http/platform/httpbinding.hpp

@@ -89,6 +89,7 @@ interface IEspHttpBinding
     virtual int onGetWsdl(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serviceName, const char *methodName)=0;
     virtual int onGetXsd(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serviceName, const char *methodName)=0;
     virtual int onGetSoapBuilder(IEspContext &context, CHttpRequest* request, CHttpResponse* response,  const char *serv, const char *method)=0;
+    virtual int onGetJsonBuilder(IEspContext &context, CHttpRequest* request, CHttpResponse* response,  const char *serv, const char *method)=0;
     virtual int onGetReqSampleXml(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method)=0;
     virtual int onGetRespSampleXml(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method)=0;
     virtual int onGetRespSampleJson(IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method)=0;
@@ -177,6 +178,7 @@ private:
 protected:
     MethodInfoArray m_methods;
     bool                    m_includeSoapTest;
+    bool                    m_includeJsonTest;
     StringBuffer            m_challenge_realm;
     StringAttr              m_defaultSvcVersion;
 
@@ -283,6 +285,7 @@ public:
     virtual int onGetIframe(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *path);
     virtual int onGetContent(IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method);
     virtual int onGetSoapBuilder(IEspContext &context, CHttpRequest* request, CHttpResponse* response,  const char *serv, const char *method);
+    virtual int onGetJsonBuilder(IEspContext &context, CHttpRequest* request, CHttpResponse* response,  const char *serv, const char *method);
 
     virtual int onSoapRequest(CHttpRequest* request, CHttpResponse* response){return 0;}
 
@@ -400,6 +403,7 @@ protected:
     void generateSampleJson(bool isRequest, IEspContext &context, CHttpRequest* request, CHttpResponse* response,    const char *serv, const char *method);
     void generateSampleXmlFromSchema(bool isRequest, IEspContext &context, CHttpRequest* request, CHttpResponse* response, const char *serv, const char *method, const char * schemaxml);
     virtual void getSoapMessage(StringBuffer& soapmsg, IEspContext &context, CHttpRequest* request, const char *serv, const char *method);
+    virtual void getJsonMessage(StringBuffer& jsonmsg, IEspContext &context, CHttpRequest* request, const char *serv, const char *method);
     void onBeforeSendResponse(IEspContext& context, CHttpRequest* request,MemoryBuffer& contentconst,
                             const char *serviceName, const char* methodName);
     void validateResponse(IEspContext& context, CHttpRequest* request,MemoryBuffer& contentconst,

+ 28 - 1
esp/bindings/http/platform/httptransport.cpp

@@ -1053,6 +1053,14 @@ bool isSoapContentType(const char* contenttype)
             Utils::strncasecmp(contenttype, HTTP_TYPE_SOAP, strlen(HTTP_TYPE_SOAP)) == 0;
 }
 
+bool isJsonContentType(const char* contenttype)
+{
+    if (contenttype == NULL)
+        return false;
+    else
+        return Utils::strncasecmp(contenttype, HTTP_TYPE_JSON, strlen(HTTP_TYPE_JSON)) == 0;
+}
+
 bool CHttpMessage::isSoapMessage()
 {
     if(m_content_type.get() == NULL)
@@ -1072,6 +1080,25 @@ bool CHttpMessage::isSoapMessage()
         return isSoapContentType(m_content_type.get());
 }
 
+bool CHttpMessage::isJsonMessage()
+{
+    if (m_content_type.get() == NULL)
+        return false;
+    else if (Utils::strncasecmp(m_content_type.get(), HTTP_TYPE_MULTIPART_RELATED, strlen(HTTP_TYPE_MULTIPART_RELATED)) == 0)
+    {
+        CMimeMultiPart* mpart = queryMultiPart();
+        if (mpart == NULL)
+            return false;
+        CMimeBodyPart* bpart = mpart->queryRootPart();
+        if (bpart != NULL && isJsonContentType(bpart->getContentType()))
+            return true;
+        else
+            return false;
+    }
+    else
+        return isJsonContentType(m_content_type.get());
+}
+
 bool CHttpMessage::isFormSubmission()
 {
     return ((hasContentType(NULL) && (m_paramCount + m_attachCount) > 0) ||
@@ -2063,7 +2090,7 @@ int CHttpRequest::readContentToFiles(StringBuffer netAddress, StringBuffer path,
               CHttpResponse Implementation
 *******************************************************************************/
 
-CHttpResponse::CHttpResponse(ISocket& socket) : CHttpMessage(socket), m_timeout(BSOCKET_CLIENT_READ_TIMEOUT)
+CHttpResponse::CHttpResponse(ISocket& socket) : CHttpMessage(socket), m_timeout(BSOCKET_CLIENT_READ_TIMEOUT), m_respSent(false)
 {
 }
 

+ 1 - 0
esp/bindings/http/platform/httptransport.hpp

@@ -49,6 +49,7 @@
 #define HTTP_TYPE_IMAGE_JPEG                    "image/jpeg"
 #define HTTP_TYPE_OCTET_STREAM                  "application/octet-stream"
 #define HTTP_TYPE_SOAP                          "application/soap"
+#define HTTP_TYPE_JSON                          "application/json"
 #define HTTP_TYPE_MULTIPART_RELATED             "Multipart/Related"
 #define HTTP_TYPE_MULTIPART_FORMDATA            "multipart/form-data"
 #define HTTP_TYPE_FORM_ENCODED                  "application/x-www-form-urlencoded"

+ 4 - 0
esp/bindings/http/platform/httptransport.ipp

@@ -185,6 +185,7 @@ public:
 
     virtual bool isSoapMessage();
     virtual bool isFormSubmission();
+    virtual bool isJsonMessage();
     virtual IArrayOf<CEspCookie>& queryCookies()
     {
         return m_cookies;
@@ -359,6 +360,7 @@ class esp_http_decl CHttpResponse : public CHttpMessage
 private:
     StringAttr  m_status;
     unsigned int m_timeout;
+    bool m_respSent;
 
     virtual int parseFirstLine(char* oneline);
     virtual StringBuffer& constructHeaderBuffer(StringBuffer& headerbuf, bool inclLen);
@@ -400,6 +402,8 @@ public:
     void setTimeOut(unsigned int timeout);
     void setETagCacheControl(const char *etag, const char *contenttype);
     void CheckModifiedHTTPContent(bool modified, const char *lastModified, const char *etag, const char *contenttype, MemoryBuffer &content);
+    bool getRespSent() { return m_respSent; }
+    void setRespSent(bool sent) { m_respSent = sent; }
 };
 
 inline bool canRedirect(CHttpRequest &req)

+ 23 - 0
esp/files/gen_form.js

@@ -349,6 +349,29 @@ function onSubmit(reqType)  // reqType: 0: regular form, 1: soap, 2: form param
         }
     }
 
+    // remove "json_builder_" (somehow FF (not IE) remembers this changed form.action )
+    if (reqType != 4)
+    {
+        var action = form.action;
+        var idx = action.indexOf('json_builder_');
+        if (idx>0)
+        {
+            if (action.length <= idx + 13) // no more char after 'json_builder_'
+            {
+                var ch = action.charAt(idx-1);
+                if (ch == '&' || ch == '?')
+                    action = action.substring(0,idx-1);
+            } else {
+                var ch = action.charAt(idx+13) // the char after 'json_builder_';
+                if (ch == '&')
+                   action = action.substring(0,idx) + action.substring(idx+13);
+            }
+
+            // alert("Old action: " + form.action + "\nNew action: " + action);
+            form.action = action;
+        }
+    }
+
     // --  change action if user wants to
     var dest = document.getElementById('esp_dest');
     if (dest && dest.checked)

+ 1 - 0
esp/protocols/http/CMakeLists.txt

@@ -40,6 +40,7 @@ set(SRCS
     ../../bindings/SOAP/Platform/soapmessage.cpp
     ../../bindings/SOAP/Platform/soapservice.cpp
     ../../bindings/SOAP/Platform/soapparam.cpp
+    ../../bindings/SOAP/xpp/xjx/xjxpp.cpp
     ../../platform/espcontext.cpp
     ../../platform/espprotocol.cpp
     ../../platform/espthread.cpp

+ 1 - 1
esp/scm/soapesp.ecm

@@ -103,7 +103,7 @@ SCMinterface IRpcMessage (IInterface)
     void add_value(const char* path, const char* name, const char* value, IProperties& attrs);
     void add_attr(const char* path, const char* name, const char* value, IProperties& attrs);
 
-   void unmarshall(XmlPullParser* xpp);
+    void unmarshall(XJXPullParser* xpp);
     //void marshall(StringBuffer& outbuf);
     void marshall(StringBuffer& outbuf, CMimeMultiPart* multipart);
     void simple_marshall(StringBuffer& outbuf);

+ 9 - 3
esp/tools/soapplus/http.cpp

@@ -1364,15 +1364,21 @@ StringBuffer& HttpClient::insertSoapHeaders(StringBuffer& request)
         return request;
 
     const char* ptr = request.str();
-    while(*ptr != '\0' && *ptr == ' ')
+    while(*ptr == ' ' || *ptr == '\t' || *ptr == '\r' || *ptr == '\n')
         ptr++;
-    if(*ptr != '<')
+
+    StringBuffer contenttype;
+    if(*ptr == '<')
+        contenttype.set("text/xml");
+    else if(*ptr == '{')
+        contenttype.set("application/json");
+    else
         return request;
 
     StringBuffer headers;
 
     headers.appendf("POST %s HTTP/1.1\r\n", m_path.str());
-    headers.append("Content-Type: text/xml\r\n");
+    headers.appendf("Content-Type: %s\r\n", contenttype.str());
     headers.append("User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n");
     headers.appendf("Content-Length: %d\r\n", request.length());
     headers.append("Host: ").append(m_host.str());

+ 3 - 2
esp/xslt/wsecl3_jsontest.xsl

@@ -43,7 +43,7 @@
         <link rel="stylesheet" type="text/css" href="/esp/files/css/espdefault.css" />
 
         <script>dojoConfig = {async:true, parseOnLoad:false}</script>
-        <script src='/esp/files/dojo/dojo.js'></script>
+        <script src="//ajax.googleapis.com/ajax/libs/dojo/1.13.0/dojo/dojo.js"></script>
         <script type="text/javascript">
 <xsl:text disable-output-escaping="yes">
 <![CDATA[
@@ -141,7 +141,8 @@
               }).then(function(data){
                 dom.byId("resp_body").value = jsonPretty(data);
               }, function(err){
-                dom.byId("resp_body").value = err.toString() + ": \n\n" + err.response.text;
+                dom.byId("resp_body").value = jsonPretty(err.response.text);
+                alert(err.toString());
               });
             });
           });

+ 69 - 1
system/jlib/jexcept.cpp

@@ -401,7 +401,75 @@ public:
             buffer.append("</Exceptions>");
         return buffer;
     }
-    
+
+    StringBuffer& serializeJSON(StringBuffer& buffer, unsigned indent = 0, bool simplified=false, bool root=true, bool enclose=false) const
+    {
+        synchronized block(m_mutex);
+        if (enclose)
+        {
+            buffer.append("{");
+            if (indent) buffer.append("\n");
+        }
+
+        if (root)
+            buffer.append("\"Exceptions\": {");
+
+        if (!simplified)
+        {
+            if (indent) buffer.append("\n\t");
+            buffer.appendf("\"Source\": \"%s\"", source_.str());
+        }
+        ForEachItemIn(i, array_)
+        {
+            if (i == 0 && !simplified)
+                buffer.appendf(", ");
+            else if (i > 0)
+                buffer.append(", ");
+            IException& exception = array_.item(i);
+
+            if (indent) buffer.append("\n\t");
+            if (i == 0)
+                buffer.append("\"Exception\": [");
+            if (indent) buffer.append("\n\t");
+            buffer.append("{");
+
+            if (indent) buffer.append("\n\t\t");
+            buffer.appendf("\"Code\": %d,", exception.errorCode());
+
+            if (indent) buffer.append("\n\t\t");
+            buffer.appendf("\"Audience\": \"%s\",", serializeMessageAudience( exception.errorAudience() ));
+
+            if (!simplified)
+            {
+                if (indent) buffer.append("\n\t\t");
+                buffer.appendf("\"Source\": \"%s\",", source_.str());
+            }
+
+            if (indent) buffer.append("\n\t\t");
+            StringBuffer msg;
+            StringBuffer encoded;
+            encodeJSON(encoded, exception.errorMessage(msg).str());
+            buffer.appendf("\"Message\": \"%s\"", encoded.str());
+
+            if (indent) buffer.append("\n\t");
+            buffer.append("}");
+        }
+
+        if (indent) buffer.append("\n\t");
+        buffer.append("]");
+        if (root)
+        {
+            if (indent) buffer.append("\n");
+            buffer.append("}");
+        }
+        if (enclose)
+        {
+            if (indent) buffer.append("\n");
+            buffer.append("}");
+        }
+        return buffer;
+    }
+
     virtual void deserialize(const char* xml)
     {
         synchronized block(m_mutex);

+ 1 - 0
system/jlib/jexcept.hpp

@@ -50,6 +50,7 @@ interface jlib_thrown_decl IMultiException : extends IException
    virtual void append(IMultiException& e) = 0;
 
    virtual StringBuffer& serialize(StringBuffer& ret, unsigned indent = 0, bool simplified=false, bool root=true) const = 0;
+   virtual StringBuffer& serializeJSON(StringBuffer& ret, unsigned indent = 0, bool simplified=false, bool root=true, bool enclose=false) const = 0;
    virtual void deserialize(const char* xml) = 0; //throws IException on failure!
 
    //the following methods override those in IIException

+ 25 - 4
tools/hidl/hidlcomp.cpp

@@ -4342,7 +4342,12 @@ void EspMessageInfo::write_esp()
         outs(
             "\tconst IMultiException& exceptions = getExceptions();\n"
             "\tif (exceptions.ordinality() > 0)\n"
-            "\t\texceptions.serialize(buffer, 0, true);\n"
+            "\t{\n"
+            "\t\tif(ctx && ctx->getResponseFormat()==ESPSerializationJSON)\n"
+            "\t\t\texceptions.serializeJSON(buffer, 0, true);\n"
+            "\t\telse\n"
+            "\t\t\texceptions.serialize(buffer, 0, true);\n"
+            "\t}\n"
             "\telse\n"
             "\t{\n");
         if (parent)
@@ -5705,7 +5710,9 @@ void EspServInfo::write_esp_binding()
     outs(1, "double clientVer=(ctx) ? ctx->getClientVersion() : 0.0;\n");
     outs(1, "qualifyServiceName(*ctx, ctx->queryServiceName(NULL), NULL, serviceName, NULL);\n");
     outs(1, "CRpcCall* thecall = static_cast<CRpcCall *>(rpc_call);\n"); //interface must be from a class derived from CRpcCall
-    outs(1, "CRpcResponse* response = static_cast<CRpcResponse*>(rpc_response);\n\n");  //interface must be from a class derived from CRpcResponse
+    outs(1, "CRpcResponse* response = static_cast<CRpcResponse*>(rpc_response);\n");  //interface must be from a class derived from CRpcResponse
+    outs(1, "CHttpRequest* httprequest = thecall->getHttpReq();\n");
+    outs(1, "CHttpResponse* httpresponse = response->getHttpResp();\n\n");
     
     outf("\tOwned<IEsp%s> iserv = (IEsp%s*)getService();\n", name_, name_);
     outs("\tif(iserv == NULL)\n");
@@ -5806,10 +5813,24 @@ void EspServInfo::write_esp_binding()
         }
 
         outf("\t\tresponse->set_name(\"%s\");\n", mthi->getResp());
-        outs("\t\tesp_response->serialize(*response);\n");
+        outs("\t\tif(!httprequest || !httpresponse)\n");
+        outs("\t\t{\n");
+        outs("\t\t\tesp_response->serialize(*response);\n");
+        outs("\t\t}\n");
+        outs("\t\telse\n");
+        outs("\t\t{\n");
+        outs("\t\t\tMemoryBuffer content;\n");
+        outs("\t\t\tStringBuffer mimetype;\n");
+        outs("\t\t\tesp_response->appendContent(&context,content, mimetype);\n");
+        outs("\t\t\tonBeforeSendResponse(context,httprequest,content,serviceName.str(),thecall->get_name());\n");
+        outs("\t\t\thttpresponse->setContent(content.length(), content.toByteArray());\n");
+        outs("\t\t\thttpresponse->setContentType(mimetype.str());\n");
+        outs("\t\t\thttpresponse->send();\n");
+        outs("\t\t\thttpresponse->setRespSent(true);\n");
+        outs("\t\t}\n");
         outs("\t\treturn 0;\n\t}\n\n");
     }
-    
+
     outs("\tresponse->set_status(SOAP_CLIENT_ERROR);\n");
     outs("\tStringBuffer msg, svcName;\n");
     outs("\tmsg.appendf(\"Method %s not available in service %s\",thecall->get_name(),getServiceName(svcName).str());\n");