diff options
author | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2009-11-23 05:25:33 +0000 |
---|---|---|
committer | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2009-11-23 05:25:33 +0000 |
commit | 585f81b9436e137c3ed784d9a91efa9e6e792e03 (patch) | |
tree | f40f0179601320ace0b454698dc76aaeddfc6a5d /sca-cpp/trunk/modules | |
parent | 3c6b7d709c7197078f8261f8e3464b0f217c988e (diff) |
Refactored httpd server integration, split http support and server logic in two modules. Added functions to load component implementations packaged as dynamic libraries. Minor monad code cleanup, converted cast operators to separate functions.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@883249 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'sca-cpp/trunk/modules')
31 files changed, 1315 insertions, 847 deletions
diff --git a/sca-cpp/trunk/modules/atom/atom-test.cpp b/sca-cpp/trunk/modules/atom/atom-test.cpp index 7c14b954a0..ba651e8eb2 100644 --- a/sca-cpp/trunk/modules/atom/atom-test.cpp +++ b/sca-cpp/trunk/modules/atom/atom-test.cpp @@ -82,13 +82,13 @@ bool testEntry() { assert(os.str() == itemEntry); } { - const list<value> a = readEntry(mklist(itemEntry)); + const list<value> a = content(readEntry(mklist(itemEntry))); std::ostringstream os; writeATOMEntry<std::ostringstream*>(writer, &os, a); assert(os.str() == itemEntry); } { - const list<value> a = readEntry(mklist(incompleteEntry)); + const list<value> a = content(readEntry(mklist(incompleteEntry))); std::ostringstream os; writeATOMEntry<std::ostringstream*>(writer, &os, a); assert(os.str() == completedEntry); @@ -135,7 +135,7 @@ bool testFeed() { assert(os.str() == emptyFeed); } { - const list<value> a = readFeed(mklist(emptyFeed)); + const list<value> a = content(readFeed(mklist(emptyFeed))); std::ostringstream os; writeATOMFeed<std::ostringstream*>(writer, &os, a); assert(os.str() == emptyFeed); @@ -171,7 +171,7 @@ bool testFeed() { assert(os.str() == itemFeed); } { - const list<value> a = readFeed(mklist(itemFeed)); + const list<value> a = content(readFeed(mklist(itemFeed))); std::ostringstream os; writeATOMFeed<std::ostringstream*>(writer, &os, a); assert(os.str() == itemFeed); diff --git a/sca-cpp/trunk/modules/atom/atom.hpp b/sca-cpp/trunk/modules/atom/atom.hpp index 5054c635a0..ac19b93e38 100644 --- a/sca-cpp/trunk/modules/atom/atom.hpp +++ b/sca-cpp/trunk/modules/atom/atom.hpp @@ -113,9 +113,9 @@ template<typename R> const failable<R, std::string> writeATOMEntry(const lambda< const failable<list<std::string>, std::string> writeATOMEntry(const list<value>& l) { const failable<list<std::string>, std::string> ls = writeATOMEntry<list<std::string> >(rcons<std::string>, list<std::string>(), l); - if (!hasValue(ls)) + if (!hasContent(ls)) return ls; - return reverse(list<std::string>(ls)); + return reverse(list<std::string>(content(ls))); } /** @@ -139,9 +139,9 @@ template<typename R> const failable<R, std::string> writeATOMFeed(const lambda<R */ const failable<list<std::string>, std::string> writeATOMFeed(const list<value>& l) { const failable<list<std::string>, std::string> ls = writeATOMFeed<list<std::string> >(rcons<std::string>, list<std::string>(), l); - if (!hasValue(ls)) + if (!hasContent(ls)) return ls; - return reverse(list<std::string>(ls)); + return reverse(list<std::string>(content(ls))); } /** diff --git a/sca-cpp/trunk/modules/http/curl-test.cpp b/sca-cpp/trunk/modules/http/curl-test.cpp index 863aa98828..0a6fbcd8a6 100644 --- a/sca-cpp/trunk/modules/http/curl-test.cpp +++ b/sca-cpp/trunk/modules/http/curl-test.cpp @@ -53,16 +53,15 @@ const bool testGet() { CURLHandle ch; { std::ostringstream os; - const failable<list<std::ostringstream*>, std::string> r = get<std::ostringstream*>(curlWriter, &os, "http://localhost:8091", ch); - assert(hasValue(r)); + const failable<list<std::ostringstream*>, std::string> r = get<std::ostringstream*>(curlWriter, &os, "http://localhost:8090", ch); + assert(hasContent(r)); assert(contains(os.str(), "HTTP/1.1 200 OK")); assert(contains(os.str(), "It works")); } { - const failable<value, std::string> r = get("http://localhost:8091", ch); - assert(hasValue(r)); - const value val = r; - assert(contains(val, "It works")); + const failable<value, std::string> r = get("http://localhost:8090", ch); + assert(hasContent(r)); + assert(contains(content(r), "It works")); } return true; } @@ -70,10 +69,9 @@ const bool testGet() { const bool testGetLoop(const int count, CURLHandle& ch) { if (count == 0) return true; - const failable<value, std::string> r = get("http://localhost:8091", ch); - assert(hasValue(r)); - const value val = r; - assert(contains(val, "It works")); + const failable<value, std::string> r = get("http://localhost:8090", ch); + assert(hasContent(r)); + assert(contains(content(r), "It works")); return testGetLoop(count - 1, ch); } @@ -95,142 +93,6 @@ const bool testGetPerf() { return true; } -const bool testEval() { - CURLHandle ch; - const value val = evalExpr(mklist<value>(std::string("echo"), std::string("Hello")), "http://localhost:8091/test", ch); - assert(val == std::string("Hello")); - return true; -} - -const bool testEvalLoop(const int count, CURLHandle& ch) { - if (count == 0) - return true; - const value val = evalExpr(mklist<value>(std::string("echo"), std::string("Hello")), "http://localhost:8091/test", ch); - assert(val == std::string("Hello")); - return testEvalLoop(count - 1, ch); -} - -const value blob(std::string(3000, 'A')); -const list<value> blobs = mklist(blob, blob, blob, blob, blob); - -const bool testBlobEvalLoop(const int count, CURLHandle& ch) { - if (count == 0) - return true; - const value val = evalExpr(mklist<value>(std::string("echo"), blobs), "http://localhost:8091/test", ch); - assert(val == blobs); - return testBlobEvalLoop(count - 1, ch); -} - -const bool testEvalPerf() { - const int count = 50; - CURLHandle ch; - struct timeval start; - struct timeval end; - { - testEvalLoop(5, ch); - - gettimeofday(&start, NULL); - - testEvalLoop(count, ch); - - gettimeofday(&end, NULL); - std::cout << "JSON-RPC eval echo test " << duration(start, end, count) << " ms" << std::endl; - } - { - testBlobEvalLoop(5, ch); - - gettimeofday(&start, NULL); - - testBlobEvalLoop(count, ch); - - gettimeofday(&end, NULL); - std::cout << "JSON-RPC eval blob test " << duration(start, end, count) << " ms" << std::endl; - } - return true; -} - -const bool testFeed() { - return true; -} - -bool testPost() { - const list<value> i = list<value>() - << (list<value>() << "name" << std::string("Apple")) - << (list<value>() << "price" << std::string("$2.99")); - const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); - CURLHandle ch; - value rc = post(a, "http://localhost:8091/test", ch); - assert(rc == value(true)); - return true; -} - -const bool testPostLoop(const int count, const value& val, CURLHandle& ch) { - if (count == 0) - return true; - const value rc = post(val, "http://localhost:8091/test", ch); - assert(rc == value(true)); - return testPostLoop(count - 1, val, ch); -} - -const bool testPostPerf() { - const int count = 50; - CURLHandle ch; - struct timeval start; - struct timeval end; - { - const list<value> i = list<value>() - << (list<value>() << "name" << std::string("Apple")) - << (list<value>() << "price" << std::string("$2.99")); - const list<value> val = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); - testPostLoop(5, val, ch); - - gettimeofday(&start, NULL); - - testPostLoop(count, val, ch); - - gettimeofday(&end, NULL); - std::cout << "ATOMPub POST small test " << duration(start, end, count) << " ms" << std::endl; - } - { - const list<value> i = list<value>() - << (list<value>() << "name" << std::string("Apple")) - << (list<value>() << "blob1" << blob) - << (list<value>() << "blob2" << blob) - << (list<value>() << "blob3" << blob) - << (list<value>() << "blob4" << blob) - << (list<value>() << "blob5" << blob) - << (list<value>() << "price" << std::string("$2.99")); - const list<value> val = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); - testPostLoop(5, val, ch); - - gettimeofday(&start, NULL); - - testPostLoop(count, val, ch); - - gettimeofday(&end, NULL); - std::cout << "ATOMPub POST blob test " << duration(start, end, count) << " ms" << std::endl; - } - return true; -} - -const bool testPut() { - const list<value> i = list<value>() - << (list<value>() << "name" << std::string("Apple")) - << (list<value>() << "price" << std::string("$2.99")); - const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); - CURLHandle ch; - value rc = put(a, "http://localhost:8091/test/111", ch); - assert(rc == value(true)); - return true; -} - -const bool testDel() { - CURLHandle ch; - value rc = del("http://localhost:8091/test/123456789", ch); - assert(rc == value(true)); - return true; -} - } } @@ -239,13 +101,6 @@ int main() { tuscany::http::testGet(); tuscany::http::testGetPerf(); - tuscany::http::testPost(); - tuscany::http::testPostPerf(); - tuscany::http::testEval(); - tuscany::http::testEvalPerf(); - tuscany::http::testFeed(); - tuscany::http::testPut(); - tuscany::http::testDel(); std::cout << "OK" << std::endl; diff --git a/sca-cpp/trunk/modules/http/curl.hpp b/sca-cpp/trunk/modules/http/curl.hpp index 5ee3a090b0..6c3a3a47dc 100644 --- a/sca-cpp/trunk/modules/http/curl.hpp +++ b/sca-cpp/trunk/modules/http/curl.hpp @@ -209,29 +209,29 @@ const failable<value, std::string> evalExpr(const value& expr, const std::string // Convert expression to a JSON-RPC request json::JSONContext cx; const failable<list<std::string>, std::string> jsreq = jsonRequest(1, car<value>(expr), cdr<value>(expr), cx); - if (!hasValue(jsreq)) + if (!hasContent(jsreq)) return mkfailure<value, std::string>(reason(jsreq)); if (logContent) { std::cout<< "content: " << std::endl; - write(jsreq, std::cout); + write(content(jsreq), std::cout); std::cout<< std::endl; std::cout.flush(); } // POST it to the URL const list<std::string> h = mklist<std::string>("Content-Type: application/json-rpc"); - const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(mklist<list<std::string> >(h, jsreq), rcons<std::string>, list<std::string>(), url, "POST", ch); - if (!hasValue(res)) + const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(mklist<list<std::string> >(h, content(jsreq)), rcons<std::string>, list<std::string>(), url, "POST", ch); + if (!hasContent(res)) return mkfailure<value, std::string>(reason(res)); // Return result if (logContent) { std::cout << "content:" << std::endl; - write(cadr<list<std::string> >(res), std::cout); + write(cadr<list<std::string> >(content(res)), std::cout); std::cout << std::endl; } - const list<value> val = elementsToValues(json::readJSON(cadr<list<std::string> >(res), cx)); + const list<value> val = elementsToValues(content(json::readJSON(cadr<list<std::string> >(content(res)), cx))); return cadr<value>(cadr<value>(val)); } @@ -250,9 +250,8 @@ const failable<value, std::string> get(const std::string& url, const CURLHandle& // Get the contents of the resource at the given URL const failable<list<list<std::string> >, std::string> res = get<list<std::string> >(rcons<std::string>, list<std::string>(), url, ch); - if (!hasValue(res)) + if (!hasContent(res)) return mkfailure<value, std::string>(reason(res)); - const list<list<std::string> > ls = res; const std::string ct; if (ct.find("application/atom+xml") != std::string::npos) { @@ -261,7 +260,7 @@ const failable<value, std::string> get(const std::string& url, const CURLHandle& // Return the content as a string value std::ostringstream os; - write(reverse(cadr(ls)), os); + write(reverse(cadr(content(res))), os); return value(os.str()); } @@ -272,19 +271,19 @@ const failable<value, std::string> post(const value& val, const std::string& url // Convert value to an ATOM entry const failable<list<std::string>, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); - if (!hasValue(entry)) + if (!hasContent(entry)) return mkfailure<value, std::string>(reason(entry)); if (logContent) { std::cout << "content:" << std::endl; - write(list<std::string>(entry), std::cout); + write(list<std::string>(content(entry)), std::cout); std::cout << std::endl; } // POST it to the URL const list<std::string> h = mklist<std::string>("Content-Type: application/atom+xml"); - const list<list<std::string> > req = mklist<list<std::string> >(h, entry); + const list<list<std::string> > req = mklist<list<std::string> >(h, content(entry)); const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(req, rcons<std::string>, list<std::string>(), url, "POST", ch); - if (!hasValue(res)) + if (!hasContent(res)) return mkfailure<value, std::string>(reason(res)); return value(true); } @@ -296,19 +295,19 @@ const failable<value, std::string> put(const value& val, const std::string& url, // Convert value to an ATOM entry const failable<list<std::string>, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); - if (!hasValue(entry)) + if (!hasContent(entry)) return mkfailure<value, std::string>(reason(entry)); if (logContent) { std::cout << "content:" << std::endl; - write(list<std::string>(entry), std::cout); + write(list<std::string>(content(entry)), std::cout); std::cout << std::endl; } // PUT it to the URL const list<std::string> h = mklist<std::string>("Content-Type: application/atom+xml"); - const list<list<std::string> > req = mklist<list<std::string> >(h, entry); + const list<list<std::string> > req = mklist<list<std::string> >(h, content(entry)); const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(req, rcons<std::string>, list<std::string>(), url, "PUT", ch); - if (!hasValue(res)) + if (!hasContent(res)) return mkfailure<value, std::string>(reason(res)); return value(true); } @@ -319,7 +318,7 @@ const failable<value, std::string> put(const value& val, const std::string& url, const failable<value, std::string> del(const std::string& url, const CURLHandle& ch) { const list<list<std::string> > req = mklist(list<std::string>(), list<std::string>()); const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(req, rcons<std::string>, list<std::string>(), url, "DELETE", ch); - if (!hasValue(res)) + if (!hasContent(res)) return mkfailure<value, std::string>(reason(res)); return value(true); } @@ -333,9 +332,9 @@ struct proxy { const value operator()(const list<value>& args) const { failable<value, std::string> val = evalExpr(args, url, ch); - if (!hasValue(val)) + if (!hasContent(val)) return value(); - return val; + return content(val); } const std::string url; diff --git a/sca-cpp/trunk/modules/http/http-test b/sca-cpp/trunk/modules/http/http-test index d70db8d469..1ab0da64b9 100755 --- a/sca-cpp/trunk/modules/http/http-test +++ b/sca-cpp/trunk/modules/http/http-test @@ -18,17 +18,7 @@ # under the License. # Setup -./httpd-conf tmp 8091 htdocs -cat >>tmp/conf/httpd.conf <<EOF - -<Location /test> -SetHandler mod_tuscany_eval -SCAContribution `pwd`/ -SCAComposite httpd-test.composite -SCAComponent httpd-test -</Location> -EOF - +./httpd-conf tmp 8090 htdocs apachectl -k start -d `pwd`/tmp sleep 1 diff --git a/sca-cpp/trunk/modules/http/httpd-conf b/sca-cpp/trunk/modules/http/httpd-conf index 10a5b47ac2..b00ee06ed3 100755 --- a/sca-cpp/trunk/modules/http/httpd-conf +++ b/sca-cpp/trunk/modules/http/httpd-conf @@ -31,7 +31,5 @@ ServerName 127.0.0.1 Listen $port DocumentRoot $htdocs TypesConfig $here/conf/mime.types -LoadModule mod_tuscany_eval $here/.libs/libmod_tuscany_eval.so -LoadModule mod_tuscany_wiring $here/.libs/libmod_tuscany_wiring.so EOF diff --git a/sca-cpp/trunk/modules/http/httpd-test b/sca-cpp/trunk/modules/http/httpd-test index 1d9b3cb34d..57c35c5cc9 100755 --- a/sca-cpp/trunk/modules/http/httpd-test +++ b/sca-cpp/trunk/modules/http/httpd-test @@ -21,16 +21,6 @@ echo "Testing..." # Setup ./httpd-conf tmp 8090 htdocs -cat >>tmp/conf/httpd.conf <<EOF - -<Location /test> -SetHandler mod_tuscany_eval -SCAContribution `pwd`/ -SCAComposite httpd-test.composite -SCAComponent httpd-test -</Location> -EOF - apachectl -k start -d `pwd`/tmp sleep 1 @@ -39,37 +29,6 @@ curl http://localhost:8090/index.html 2>/dev/null >tmp/index.html diff tmp/index.html htdocs/index.html rc=$? -# Test ATOMPub -if [ "$rc" = "0" ]; then - curl http://localhost:8090/test/ >tmp/feed.xml 2>/dev/null - diff tmp/feed.xml htdocs/feed.xml - rc=$? -fi -if [ "$rc" = "0" ]; then - curl http://localhost:8090/test/111 >tmp/entry.xml 2>/dev/null - diff tmp/entry.xml htdocs/entry.xml - rc=$? -fi -if [ "$rc" = "0" ]; then - curl http://localhost:8090/test/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null - rc=$? -fi -if [ "$rc" = "0" ]; then - curl http://localhost:8090/test/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null - rc=$? -fi -if [ "$rc" = "0" ]; then - curl http://localhost:8090/test/111 -X DELETE 2>/dev/null - rc=$? -fi - -# Test JSON-RPC -if [ "$rc" = "0" ]; then - curl http://localhost:8090/test/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/json-request.txt >tmp/json-result.txt 2>/dev/null - diff tmp/json-result.txt htdocs/json-result.txt - rc=$? -fi - # Cleanup apachectl -k stop -d `pwd`/tmp sleep 2 diff --git a/sca-cpp/trunk/modules/http/httpd.hpp b/sca-cpp/trunk/modules/http/httpd.hpp index 1271afc03c..a9ced05208 100644 --- a/sca-cpp/trunk/modules/http/httpd.hpp +++ b/sca-cpp/trunk/modules/http/httpd.hpp @@ -23,7 +23,7 @@ #define tuscany_httpd_hpp /** - * HTTPD module utility functions. + * HTTPD module implementation functions. */ #include <string> @@ -62,6 +62,32 @@ bool logRequests = false; bool logContent = false; /** + * Returns a server-scoped module configuration. + */ +template<typename C> void* makeServerConf(apr_pool_t *p, server_rec *s) { + C* c = new (apr_palloc(p, sizeof(C))) C(s); + apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback<C>, apr_pool_cleanup_null) ; + return c; +} + +template<typename C> const C& serverConf(const request_rec* r, const module* mod) { + return *(C*)ap_get_module_config(r->server->module_config, mod); +} + +/** + * Returns a directory-scoped module configuration. + */ +template<typename C> void *makeDirConf(apr_pool_t *p, char *dirspec) { + C* c = new (apr_palloc(p, sizeof(C))) C(dirspec); + apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback<C>, apr_pool_cleanup_null) ; + return c; +} + +template<typename C> C& dirConf(const request_rec* r, const module* mod) { + return *(C*)ap_get_module_config(r->per_dir_config, mod); +} + +/** * Convert a path string to a list of values. */ const list<std::string> pathTokens(const char* p) { @@ -111,6 +137,8 @@ int logHeader(void* r, const char* key, const char* value) { } const bool logRequest(request_rec* r, const std::string& msg) { + if (!logRequests) + return true; std::cout << msg << std::endl; std::cout << "protocol: " << optional(r->protocol) << std::endl; std::cout << "method: " << optional(r->method) << std::endl; @@ -128,6 +156,32 @@ const bool logRequest(request_rec* r, const std::string& msg) { return true; } +const bool logValue(const value& v, const std::string& msg) { + if (!logContent) + return true; + std::cout<< msg << ": " << v << std::endl; + std::cout.flush(); + return true; +} + +const bool logValue(const failable<value, std::string>& v, const std::string& msg) { + if (!logContent) + return true; + std::cout<< msg << ": " << v << std::endl; + std::cout.flush(); + return true; +} + +const bool logStrings(const list<std::string>& ls, const std::string& msg) { + if (!logContent) + return true; + std::cout<< msg << ": " << std::endl; + write(ls, std::cout); + std::cout<< std::endl; + std::cout.flush(); + return true; +} + /** * Returns a list of key value pairs from the args in a query string. */ @@ -144,6 +198,19 @@ const list<list<value> > queryArgs(const request_rec* r) { } /** + * Returns a list of param values other than the id and method args from a list + * of key value pairs. + */ +const list<value> queryParams(const list<list<value> >& a) { + if (isNil(a)) + return list<value>(); + const list<value> p = car(a); + if (car(p) == value("id") || car(p) == value("method")) + return queryParams(cdr(a)); + return cons(cadr(p), queryParams(cdr(a))); +} + +/** * Converts the args received in a POST to a list of key value pairs. */ const list<list<value> > postArgs(const list<value>& a) { @@ -153,6 +220,83 @@ const list<list<value> > postArgs(const list<value>& a) { return cons(l, postArgs(cdr(a))); } +/** + * Setup the HTTP read policy. + */ +const int setupReadPolicy(request_rec* r) { + const int rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); + if(rc != OK) + return rc; + ap_should_client_block(r); + if(r->read_chunked == true && r->remaining == 0) + r->chunked = true; + //apr_table_setn(r->headers_out, "Connection", "close"); + return OK; +} + +/** + * Read the content of a POST or PUT. + */ +const list<std::string> read(request_rec* r) { + char b[2048]; + const int n = ap_get_client_block(r, b, 2048); + if (n <= 0) + return list<std::string>(); + return cons(std::string(b, n), read(r)); +} + +/** + * Convert a URI value to an absolute URL. + */ +const char* url(const value& v, request_rec* r) { + std::string u = r->uri; + u.append("/"); + u.append(v); + return ap_construct_url(r->pool, u.c_str(), r); +} + +/** + * Convert an ATOM entry to a value. + */ +const value feedEntry(const list<value>& e) { + const list<value> v = elementsToValues(mklist<value>(caddr(e))); + return cons(car(e), mklist<value>(cadr(e), cdr<value>(car(v)))); +} + +/** + * Write an HTTP result. + */ +const failable<int, std::string> writeResult(const failable<list<std::string>, std::string>& ls, const std::string& ct, request_rec* r) { + if (!hasContent(ls)) + return mkfailure<int, std::string>(reason(ls)); + std::ostringstream os; + write(content(ls), os); + if (logContent) { + std::cout<< "content: " << std::endl << os.str() << std::endl; + std::cout.flush(); + } + + const std::string etag(ap_md5(r->pool, (const unsigned char*)std::string(os.str()).c_str())); + const char* match = apr_table_get(r->headers_in, "If-None-Match"); + apr_table_setn(r->headers_out, "ETag", apr_pstrdup(r->pool, etag.c_str())); + if (match != NULL && etag == match) { + r->status = HTTP_NOT_MODIFIED; + return OK; + } + ap_set_content_type(r, ct.c_str()); + ap_rputs(std::string(os.str()).c_str(), r); + return OK; +} + +/** + * Report request execution status. + */ +const int reportStatus(const failable<int, std::string>& rc) { + if (!hasContent(rc)) + return HTTP_INTERNAL_SERVER_ERROR; + return content(rc); +} + } } diff --git a/sca-cpp/trunk/modules/http/mod-eval.cpp b/sca-cpp/trunk/modules/http/mod-eval.cpp deleted file mode 100644 index 6fef2be2cb..0000000000 --- a/sca-cpp/trunk/modules/http/mod-eval.cpp +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -/* $Rev$ $Date$ */ - -/** - * HTTPD module used to eval component implementations. - */ - -#include <sys/stat.h> - -#include <string> -#include <iostream> -#include <sstream> -#include <fstream> - -#include "list.hpp" -#include "slist.hpp" -#include "value.hpp" -#include "element.hpp" -#include "monad.hpp" -#include "../atom/atom.hpp" -#include "../json/json.hpp" -#include "../eval/driver.hpp" -#include "../scdl/scdl.hpp" -#include "../cache/cache.hpp" -#include "curl.hpp" -#include "httpd.hpp" - -extern "C" { -extern module AP_MODULE_DECLARE_DATA mod_tuscany_eval; -} - -namespace tuscany { -namespace httpd { -namespace modeval { - -/** - * Server configuration. - */ -class ServerConf { -public: - ServerConf() : home("") { - } - std::string home; -}; - -const ServerConf& serverConf(const request_rec* r) { - return *(ServerConf*)ap_get_module_config(r->server->module_config, &mod_tuscany_eval); -} - -/** - * Port number used for wiring requests. Set it to zero to use the current - * server port number, set it to a port number to direct wiring requests - * to that port, for debugging or tracing for example. - */ -int debugWiringPort = 0; - -/** - * Directory configuration. - */ -class DirConf { -public: - DirConf() : contributionPath(""), compositeName(""), componentName(""), implementationPath("") { - } - std::string contributionPath; - std::string compositeName; - std::string componentName; - std::string implementationPath; - cache::cached<failable<value, std::string> > component; - cache::cached<failable<value, std::string> > implementation; -}; - -DirConf& dirConf(const request_rec* r) { - return *(DirConf*)ap_get_module_config(r->per_dir_config, &mod_tuscany_eval); -} - -/** - * Evaluate an expression against a component implementation. - */ -const failable<value, std::string> evalExpr(const value& expr, const value& impl) { - gc_pool pool; - eval::Env globalEnv = eval::setupEnvironment(pool); - if (logContent) { - std::cout<< "expr: " << expr << std::endl; - std::cout.flush(); - } - const value val = eval::evalScript(expr, impl, globalEnv, pool); - if (logContent) { - std::cout<< "val: " << val << std::endl; - std::cout.flush(); - } - - if (isNil(val)) - return mkfailure<value, std::string>("Could not evaluate expression"); - return val; -} - -/** - * Returns a list of param values other than the id and method args from a list - * of key value pairs. - */ -const list<value> queryParams(const list<list<value> >& a) { - if (isNil(a)) - return list<value>(); - const list<value> p = car(a); - if (car(p) == value("id") || car(p) == value("method")) - return queryParams(cdr(a)); - return cons(cadr(p), queryParams(cdr(a))); -} - -/** - * Write an HTTP result. - */ -const failable<int, std::string> writeResult(const failable<list<std::string>, std::string>& ls, const std::string& ct, request_rec* r) { - if (!hasValue(ls)) - return mkfailure<int, std::string>(reason(ls)); - std::ostringstream os; - write(ls, os); - if (logContent) { - std::cout<< "content: " << std::endl << os.str() << std::endl; - std::cout.flush(); - } - - const std::string etag(ap_md5(r->pool, (const unsigned char*)std::string(os.str()).c_str())); - const char* match = apr_table_get(r->headers_in, "If-None-Match"); - apr_table_setn(r->headers_out, "ETag", apr_pstrdup(r->pool, etag.c_str())); - if (match != NULL && etag == match) { - r->status = HTTP_NOT_MODIFIED; - return OK; - } - ap_set_content_type(r, ct.c_str()); - ap_rputs(std::string(os.str()).c_str(), r); - return OK; -} - -/** - * Handle an HTTP GET. - */ -const failable<int, std::string> get(request_rec* r, const value& impl, const list<value>& px) { - - // Inspect the query string - const list<list<value> > args = queryArgs(r); - const list<value> ia = assoc(value("id"), args); - const list<value> ma = assoc(value("method"), args); - - // Evaluate a JSON-RPC request and return a JSON result - if (!isNil(ia) && !isNil(ma)) { - - // Extract the request id, method and params - const value id = cadr(ia); - const value func = std::string(cadr(ma)).c_str(); - const list<value> params = queryParams(args); - - // Evaluate the request expression - const failable<value, std::string> val = evalExpr(cons<value>(func, eval::quotedParameters(append(params, px))), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - - // Return JSON result - json::JSONContext cx; - return writeResult(json::jsonResult(id, val, cx), "application/json-rpc", r); - } - - // Evaluate an ATOM GET request and return an ATOM feed - const list<value> id(path(r->path_info)); - if (isNil(id)) { - const failable<value, std::string> val = evalExpr(cons<value>("getall", eval::quotedParameters(px)), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - const value feed = val; - return writeResult(atom::writeATOMFeed(atom::feedValuesToElements(feed)), "application/atom+xml;type=feed", r); - } - - // Evaluate an ATOM GET and return an ATOM entry - const failable<value, std::string> val = evalExpr(cons<value>("get", eval::quotedParameters(cons<value>(car(id), px))), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - const value entry = val; - return writeResult(atom::writeATOMEntry(atom::entryValuesToElements(entry)), "application/atom+xml;type=entry", r); - -} - -/** - * Read the content of a POST. - */ -const list<std::string> read(request_rec* r) { - char b[2048]; - const int n = ap_get_client_block(r, b, 2048); - if (n <= 0) - return list<std::string>(); - return cons(std::string(b, n), read(r)); -} - -/** - * Convert a URI value to an absolute URL. - */ -const char* url(const value& v, request_rec* r) { - std::string u = r->uri; - u.append("/"); - u.append(v); - return ap_construct_url(r->pool, u.c_str(), r); -} - -/** - * Convert an ATOM entry to a value. - */ -const value feedEntry(const list<value>& e) { - const list<value> v = elementsToValues(mklist<value>(caddr(e))); - return cons(car(e), mklist<value>(cadr(e), cdr<value>(car(v)))); -} - -/** - * Handle an HTTP POST. - */ -const failable<int, std::string> post(request_rec* r, const value& impl, const list<value>& px) { - const list<std::string> ls = read(r); - if (logContent) { - std::cout<< "content: " << std::endl; - write(ls, std::cout); - std::cout<< std::endl; - std::cout.flush(); - } - - // Evaluate a JSON-RPC request and return a JSON result - const std::string ct = contentType(r); - if (ct.find("application/json-rpc") != std::string::npos || ct.find("text/plain") != std::string::npos) { - json::JSONContext cx; - const list<value> json = elementsToValues(json::readJSON(ls, cx)); - const list<list<value> > args = postArgs(json); - - // Extract the request id, method and params - const value id = cadr(assoc(value("id"), args)); - const value func = std::string(cadr(assoc(value("method"), args))).c_str(); - const list<value> params = (list<value>)cadr(assoc(value("params"), args)); - - // Evaluate the request expression - const failable<value, std::string> val = evalExpr(cons<value>(func, eval::quotedParameters(append(params, px))), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - - // Return JSON result - return writeResult(json::jsonResult(id, val, cx), "application/json-rpc", r); - } - - // Evaluate an ATOM POST request and return the created resource location - if (ct.find("application/atom+xml") != std::string::npos) { - - // Evaluate the request expression - const value entry = feedEntry(atom::readEntry(ls)); - const failable<value, std::string> val = evalExpr(cons<value>("post", eval::quotedParameters(cons<value>(entry, px))), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - - // Return the created resource location - apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, url(val, r))); - r->status = HTTP_CREATED; - return OK; - } - - return HTTP_NOT_IMPLEMENTED; -} - -/** - * Handle an HTTP PUT. - */ -const failable<int, std::string> put(request_rec* r, const value& impl, const list<value>& px) { - const list<std::string> ls = read(r); - if (logContent) { - std::cout<< "content: " << std::endl; - write(ls, std::cout); - std::cout<< std::endl; - std::cout.flush(); - } - - // Evaluate an ATOM PUT request - const list<value> id(path(r->path_info)); - const value entry = feedEntry(atom::readEntry(ls)); - const failable<value, std::string> val = evalExpr(cons<value>("put", eval::quotedParameters(append(mklist<value>(entry, car(id)), px))), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - if (val == value(false)) - return HTTP_NOT_FOUND; - return OK; -} - -/** - * Handle an HTTP DELETE. - */ -const failable<int, std::string> del(request_rec* r, const value& impl, const list<value>& px) { - - // Evaluate an ATOM delete request - const list<value> id(path(r->path_info)); - const failable<value, std::string> val = evalExpr(cons<value>("delete", eval::quotedParameters(cons<value>(car(id), px))), impl); - if (!hasValue(val)) - return mkfailure<int, std::string>(reason(val)); - if (val == value(false)) - return HTTP_NOT_FOUND; - return OK; -} - -/** - * Report request execution status. - */ -const int reportStatus(const failable<int, std::string>& rc) { - if (!hasValue(rc)) - return HTTP_INTERNAL_SERVER_ERROR; - return rc; -} - -/** - * Read the SCDL configuration of a component. - */ -const failable<value, std::string> readComponent(const std::string& path, const std::string& name) { - - // Read composite - std::ifstream is(path); - if (is.fail() || is.bad()) - return mkfailure<value, std::string>("Could not read composite: " + path); - - // Return the component - const list<value> c = scdl::components(readXML(streamList(is))); - const value comp = scdl::named(name, c); - if (isNil(comp)) - return mkfailure<value, std::string>("Could not find component: " + name); - return comp; -} - -const cache::cached<failable<value, std::string> > component(DirConf* conf) { - const std::string path(conf->contributionPath + conf->compositeName); - const lambda<failable<value, std::string>(std::string, std::string)> rc(readComponent); - const lambda<unsigned long(std::string)> ft(cache::latestFileTime); - return cache::cached<failable<value, std::string> >(curry(rc, path, conf->componentName), curry(ft, path)); -} - -/** - * Read a component implementation. - */ -const failable<value, std::string> readImplementation(const std::string path) { - std::ifstream is(path.c_str(), std::ios_base::in); - if (is.fail() || is.bad()) - return mkfailure<value, std::string>("Could not read implementation: " + path); - const value impl = eval::readScript(is); - if (isNil(impl)) - return mkfailure<value, std::string>("Could not read implementation: " + path); - return impl; -} - -const cache::cached<failable<value, std::string> > implementation(const std::string& path) { - const lambda<failable<value, std::string>(std::string)> ri(readImplementation); - const lambda<unsigned long(std::string)> ft(cache::latestFileTime); - return cache::cached<failable<value, std::string> >(curry(ri, path), curry(ft, path)); -} - -/** - * Convert a list of component references to a list of HTTP proxy lambdas. - */ -const value mkproxy(const value& ref, const std::string& base, const http::CURLHandle& ch) { - return eval::primitiveProcedure(http::proxy(base + std::string(scdl::name(ref)), ch)); -} - -const list<value> proxies(const list<value>& refs, const std::string& base, const http::CURLHandle& ch) { - if (isNil(refs)) - return refs; - return cons(mkproxy(car(refs), base, ch), proxies(cdr(refs), base, ch)); -} - -/** - * HTTP request handler. - */ -int handler(request_rec *r) { - if(strcmp(r->handler, "mod_tuscany_eval")) - return DECLINED; - - // Log the request - if(logRequests) - logRequest(r, "mod_tuscany_eval::handler"); - - // Set up the read policy - const int rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); - if(rc != OK) - return rc; - ap_should_client_block(r); - if(r->read_chunked == true && r->remaining == 0) - r->chunked = true; - //apr_table_setn(r->headers_out, "Connection", "close"); - - // Retrieve the latest component configuration - DirConf& conf = dirConf(r); - conf.component = cache::latest(conf.component); - const failable<value, std::string> comp(conf.component); - if (!hasValue(comp)) - return HTTP_NOT_FOUND; - - // Retrieve the latest implementation - const std::string path = conf.contributionPath + std::string(scdl::uri(scdl::implementation(comp))); - if (path != conf.implementationPath) { - conf.implementationPath = path; - conf.implementation = cache::latest(implementation(path)); - } - else - conf.implementation = cache::latest(conf.implementation); - const failable<value, std::string> impl(conf.implementation); - if (!hasValue(impl)) - return HTTP_NOT_FOUND; - - // Convert component references to configured proxy lambdas - std::ostringstream base; - base << "http://localhost:" << (debugWiringPort == 0? ap_get_server_port(r) : debugWiringPort) << "/references/" << std::string(scdl::name(comp)) << "/"; - http::CURLHandle ch; - const list<value> px(proxies(scdl::references(comp), base.str(), ch)); - - // Handle HTTP method - if (r->header_only) - return OK; - if(r->method_number == M_GET) - return reportStatus(get(r, impl, px)); - if(r->method_number == M_POST) - return reportStatus(post(r, impl, px)); - if(r->method_number == M_PUT) - return reportStatus(put(r, impl, px)); - if(r->method_number == M_DELETE) - return reportStatus(del(r, impl, px)); - return HTTP_NOT_IMPLEMENTED; -} - -/** - * Configuration commands. - */ -const char *confHome(cmd_parms *cmd, void *dummy, const char *arg) { - ServerConf *c = (ServerConf*)ap_get_module_config(cmd->server->module_config, &mod_tuscany_eval); - c->home = arg; - return NULL; -} -const char *confContribution(cmd_parms *cmd, void *c, const char *arg) { - DirConf* conf = (DirConf*)c; - conf->contributionPath = arg; - conf->component = component(conf); - return NULL; -} -const char *confComposite(cmd_parms *cmd, void *c, const char *arg) { - DirConf* conf = (DirConf*)c; - conf->compositeName = arg; - conf->component = component(conf); - return NULL; -} -const char *confComponent(cmd_parms *cmd, void *c, const char *arg) { - DirConf* conf = (DirConf*)c; - conf->componentName = arg; - conf->component = component(conf); - return NULL; -} - -void *makeDirConf(apr_pool_t *p, char *dirspec) { - DirConf* c = new (apr_palloc(p, sizeof(DirConf))) DirConf(); - apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback<DirConf>, apr_pool_cleanup_null) ; - return c; -} -void* makeServerConf(apr_pool_t *p, server_rec *s) { - ServerConf* c = new (apr_palloc(p, sizeof(ServerConf))) ServerConf(); - apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback<ServerConf>, apr_pool_cleanup_null) ; - return c; -} - -/** - * HTTP server module declaration. - */ -const command_rec commands[] = { - AP_INIT_TAKE1("TuscanyHome", (const char*(*)())confHome, NULL, RSRC_CONF, "Tuscany home directory"), - AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, ACCESS_CONF, "SCA contribution location"), - AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, ACCESS_CONF, "SCA composite location"), - AP_INIT_TAKE1("SCAComponent", (const char*(*)())confComponent, NULL, ACCESS_CONF, "SCA component name"), - {NULL} -}; - -int postConfig(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { - return OK; -} - -void childInit(apr_pool_t* p, server_rec* svr_rec) { - ServerConf *c = (ServerConf*)ap_get_module_config(svr_rec->module_config, &mod_tuscany_eval); - if(c == NULL) { - std::cerr << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << std::endl; - exit(APEXIT_CHILDFATAL); - } -} - -void registerHooks(apr_pool_t *p) { - ap_hook_post_config(postConfig, NULL, NULL, APR_HOOK_MIDDLE); - ap_hook_child_init(childInit, NULL, NULL, APR_HOOK_MIDDLE); - ap_hook_handler(handler, NULL, NULL, APR_HOOK_MIDDLE); -} - -} -} -} - -extern "C" { - -module AP_MODULE_DECLARE_DATA mod_tuscany_eval = { - STANDARD20_MODULE_STUFF, - // dir config - tuscany::httpd::modeval::makeDirConf, - // dir merger, default is to override - NULL, - // server config - tuscany::httpd::modeval::makeServerConf, - // merge server config - NULL, - // command table - tuscany::httpd::modeval::commands, - // register hooks - tuscany::httpd::modeval::registerHooks -}; - -} diff --git a/sca-cpp/trunk/modules/json/json-test.cpp b/sca-cpp/trunk/modules/json/json-test.cpp index fd506cea65..4d1bbffd5a 100644 --- a/sca-cpp/trunk/modules/json/json-test.cpp +++ b/sca-cpp/trunk/modules/json/json-test.cpp @@ -70,11 +70,11 @@ bool testJSON() { std::istringstream is(os.str()); const list<std::string> il = streamList(is); - const list<value> r = readJSON(il, cx); + const list<value> r = content(readJSON(il, cx)); assert(r == l); std::ostringstream wos; - write(writeJSON(r, cx), wos); + write(content(writeJSON(r, cx)), wos); assert(wos.str() == os.str()); } return true; @@ -84,7 +84,7 @@ bool testJSONRPC() { JSONContext cx; { const std::string lm("{\"id\": 1, \"method\": \"system.listMethods\", \"params\": []}"); - const list<value> e = readJSON(mklist(lm), cx); + const list<value> e = content(readJSON(mklist(lm), cx)); const list<value> v = elementsToValues(e); assert(assoc<value>("id", v) == mklist<value>("id", 1)); assert(assoc<value>("method", v) == mklist<value>("method", std::string("system.listMethods"))); @@ -92,16 +92,16 @@ bool testJSONRPC() { } { const std::string i("{\"id\":3,\"result\":[{\"price\":\"$2.99\",\"name\":\"Apple\"},{\"price\":\"$3.55\",\"name\":\"Orange\"},{\"price\":\"$1.55\",\"name\":\"Pear\"}]}"); - const list<value> e = readJSON(mklist(i), cx); + const list<value> e = content(readJSON(mklist(i), cx)); const std::string i2("{\"id\":3,\"result\":{\"0\":{\"price\":\"$2.99\",\"name\":\"Apple\"},\"1\":{\"price\":\"$3.55\",\"name\":\"Orange\"},\"2\":{\"price\":\"$1.55\",\"name\":\"Pear\"}}}"); - const list<value> e2 = readJSON(mklist(i), cx); + const list<value> e2 = content(readJSON(mklist(i), cx)); assert(e == e2); } { const std::string i("{\"id\":3,\"result\":[{\"price\":\"$2.99\",\"name\":\"Apple\"},{\"price\":\"$3.55\",\"name\":\"Orange\"},{\"price\":\"$1.55\",\"name\":\"Pear\"}]}"); - const list<value> e = readJSON(mklist(i), cx); + const list<value> e = content(readJSON(mklist(i), cx)); std::ostringstream os; - write(writeJSON(e, cx), os); + write(content(writeJSON(e, cx)), os); assert(os.str() == i); const list<value> v = elementsToValues(e); const list<value> r = valuesToElements(v); @@ -111,16 +111,16 @@ bool testJSONRPC() { const list<value> r = mklist<value>(mklist<value>("id", 1), mklist<value>("result", mklist<value>(std::string("Service.get"), std::string("Service.getTotal")))); const list<value> e = valuesToElements(r); std::ostringstream os; - write(writeJSON(e, cx), os); + write(content(writeJSON(e, cx)), os); assert(os.str() == "{\"id\":1,\"result\":[\"Service.get\",\"Service.getTotal\"]}"); } { const std::string f("{\"id\":1,\"result\":[\"Sample Feed\",\"123456789\",[\"Item\",\"111\",{\"javaClass\":\"services.Item\",\"name\":\"Apple\",\"currencyCode\":\"USD\",\"currencySymbol\":\"$\",\"price\":2.99}],[\"Item\",\"222\",{\"javaClass\":\"services.Item\",\"name\":\"Orange\",\"currencyCode\":\"USD\",\"currencySymbol\":\"$\",\"price\":3.55}],[\"Item\",\"333\",{\"javaClass\":\"services.Item\",\"name\":\"Pear\",\"currencyCode\":\"USD\",\"currencySymbol\":\"$\",\"price\":1.55}]]}"); - const list<value> r = readJSON(mklist(f), cx); + const list<value> r = content(readJSON(mklist(f), cx)); const list<value> v = elementsToValues(r); const list<value> e = valuesToElements(v); std::ostringstream os; - write(writeJSON(e, cx), os); + write(content(writeJSON(e, cx)), os); assert(os.str() == f); } return true; diff --git a/sca-cpp/trunk/modules/json/json.hpp b/sca-cpp/trunk/modules/json/json.hpp index f6c8eb5fe8..7b18d237ec 100644 --- a/sca-cpp/trunk/modules/json/json.hpp +++ b/sca-cpp/trunk/modules/json/json.hpp @@ -217,7 +217,7 @@ const failable<list<value>, std::string> readJSON(const list<std::string>& ilist if(!JS_FinishJSONParse(cx, parser, JSVAL_NULL)) return mkfailure<list<value>, std::string>("JS_FinishJSONParse failed"); - if(!hasValue(consumed)) + if(!hasContent(consumed)) return mkfailure<list<value>, std::string>(reason(consumed)); return list<value>(jsValToValue(val, cx)); @@ -318,7 +318,7 @@ const failable<bool, std::string> writeList(const list<value>& l, JSObject* o, c // Write its children const failable<bool, std::string> w = writeList(elementChildren(token), child, cx); - if (!hasValue(w)) + if (!hasContent(w)) return w; } } @@ -356,7 +356,7 @@ template<typename R> const failable<R, std::string> writeJSON(const lambda<R(std JSObject* o = JS_NewObject(cx, NULL, NULL, NULL); jsval val = OBJECT_TO_JSVAL(o); const failable<bool, std::string> w = writeList(l, o, cx); - if (!hasValue(w)) + if (!hasContent(w)) return mkfailure<R, std::string>(reason(w)); WriteContext<R> wcx(reduce, initial, cx); @@ -370,9 +370,9 @@ template<typename R> const failable<R, std::string> writeJSON(const lambda<R(std */ const failable<list<std::string>, std::string> writeJSON(const list<value>& l, const JSONContext& cx) { const failable<list<std::string>, std::string> ls = writeJSON<list<std::string> >(rcons<std::string>, list<std::string>(), l, cx); - if (!hasValue(ls)) + if (!hasContent(ls)) return ls; - return reverse(list<std::string>(ls)); + return reverse(list<std::string>(content(ls))); } /** diff --git a/sca-cpp/trunk/modules/scdl/scdl-test b/sca-cpp/trunk/modules/scdl/scdl-test Binary files differdeleted file mode 100755 index acef45d225..0000000000 --- a/sca-cpp/trunk/modules/scdl/scdl-test +++ /dev/null diff --git a/sca-cpp/trunk/modules/server/Makefile.am b/sca-cpp/trunk/modules/server/Makefile.am new file mode 100644 index 0000000000..204c8e4ae4 --- /dev/null +++ b/sca-cpp/trunk/modules/server/Makefile.am @@ -0,0 +1,36 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +noinst_PROGRAMS = client-test + +libdir=$(prefix)/lib +lib_LTLIBRARIES = libmod_tuscany_eval.la libmod_tuscany_wiring.la + +INCLUDES = -I. -I$(top_builddir)/kernel -I${LIBXML2_INCLUDE} -I${HTTPD_INCLUDE} -I${APR_INCLUDE} -I${JS_INCLUDE} -I${CURL_INCLUDE} + +libmod_tuscany_eval_la_SOURCES = mod-eval.cpp +libmod_tuscany_eval_la_LIBADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${APR_LIB} -lapr-1 -laprutil-1 -L${CURL_LIB} -lcurl -L${JS_LIB} -lmozjs + +libmod_tuscany_wiring_la_SOURCES = mod-wiring.cpp +libmod_tuscany_wiring_la_LIBADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${APR_LIB} -lapr-1 -laprutil-1 -L${CURL_LIB} -lcurl -L${JS_LIB} -lmozjs + +client_test_SOURCES = client-test.cpp +client_test_LDADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${APR_LIB} -lapr-1 -laprutil-1 -L${CURL_LIB} -lcurl -L${JS_LIB} -lmozjs + +TESTS = httpd-test http-test wiring-test + + diff --git a/sca-cpp/trunk/modules/server/client-test.cpp b/sca-cpp/trunk/modules/server/client-test.cpp new file mode 100644 index 0000000000..b43cb92c52 --- /dev/null +++ b/sca-cpp/trunk/modules/server/client-test.cpp @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* $Rev$ $Date$ */ + +/** + * Test HTTP client functions. + */ + +#include <assert.h> +#include <sys/time.h> +#include <time.h> +#include <iostream> +#include <sstream> +#include <string> +#include "slist.hpp" +#include "../http/curl.hpp" + +namespace tuscany { +namespace server { + +const bool contains(const std::string& str, const std::string& pattern) { + return str.find(pattern) != str.npos; +} + +const double duration(struct timeval start, struct timeval end, int count) { + long t = (end.tv_sec * 1000 + end.tv_usec / 1000) - (start.tv_sec * 1000 + start.tv_usec / 1000); + return (double)t / (double)count; +} + +std::ostringstream* curlWriter(const std::string& s, std::ostringstream* os) { + (*os) << s; + return os; +} + +const bool testGet() { + http::CURLHandle ch; + { + std::ostringstream os; + const failable<list<std::ostringstream*>, std::string> r = http::get<std::ostringstream*>(curlWriter, &os, "http://localhost:8090", ch); + assert(hasContent(r)); + assert(contains(os.str(), "HTTP/1.1 200 OK")); + assert(contains(os.str(), "It works")); + } + { + const failable<value, std::string> r = http::get("http://localhost:8090", ch); + assert(hasContent(r)); + assert(contains(content(r), "It works")); + } + return true; +} + +const bool testGetLoop(const int count, http::CURLHandle& ch) { + if (count == 0) + return true; + const failable<value, std::string> r = get("http://localhost:8090", ch); + assert(hasContent(r)); + assert(contains(content(r), "It works")); + return testGetLoop(count - 1, ch); +} + +const bool testGetPerf() { + const int count = 50; + http::CURLHandle ch; + struct timeval start; + struct timeval end; + { + testGetLoop(5, ch); + + gettimeofday(&start, NULL); + + testGetLoop(count, ch); + + gettimeofday(&end, NULL); + std::cout << "Static GET test " << duration(start, end, count) << " ms" << std::endl; + } + return true; +} + +const bool testEval() { + http::CURLHandle ch; + const value val = content(http::evalExpr(mklist<value>(std::string("echo"), std::string("Hello")), "http://localhost:8090/test", ch)); + assert(val == std::string("Hello")); + return true; +} + +const bool testEvalLoop(const int count, http::CURLHandle& ch) { + if (count == 0) + return true; + const value val = content(http::evalExpr(mklist<value>(std::string("echo"), std::string("Hello")), "http://localhost:8090/test", ch)); + assert(val == std::string("Hello")); + return testEvalLoop(count - 1, ch); +} + +const value blob(std::string(3000, 'A')); +const list<value> blobs = mklist(blob, blob, blob, blob, blob); + +const bool testBlobEvalLoop(const int count, http::CURLHandle& ch) { + if (count == 0) + return true; + const value val = content(http::evalExpr(mklist<value>(std::string("echo"), blobs), "http://localhost:8090/test", ch)); + assert(val == blobs); + return testBlobEvalLoop(count - 1, ch); +} + +const bool testEvalPerf() { + const int count = 50; + http::CURLHandle ch; + struct timeval start; + struct timeval end; + { + testEvalLoop(5, ch); + + gettimeofday(&start, NULL); + + testEvalLoop(count, ch); + + gettimeofday(&end, NULL); + std::cout << "JSON-RPC eval echo test " << duration(start, end, count) << " ms" << std::endl; + } + { + testBlobEvalLoop(5, ch); + + gettimeofday(&start, NULL); + + testBlobEvalLoop(count, ch); + + gettimeofday(&end, NULL); + std::cout << "JSON-RPC eval blob test " << duration(start, end, count) << " ms" << std::endl; + } + return true; +} + +const bool testFeed() { + return true; +} + +bool testPost() { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + http::CURLHandle ch; + value rc = content(http::post(a, "http://localhost:8090/test", ch)); + assert(rc == value(true)); + return true; +} + +const bool testPostLoop(const int count, const value& val, http::CURLHandle& ch) { + if (count == 0) + return true; + const value rc = content(http::post(val, "http://localhost:8090/test", ch)); + assert(rc == value(true)); + return testPostLoop(count - 1, val, ch); +} + +const bool testPostPerf() { + const int count = 50; + http::CURLHandle ch; + struct timeval start; + struct timeval end; + { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> val = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + testPostLoop(5, val, ch); + + gettimeofday(&start, NULL); + + testPostLoop(count, val, ch); + + gettimeofday(&end, NULL); + std::cout << "ATOMPub POST small test " << duration(start, end, count) << " ms" << std::endl; + } + { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "blob1" << blob) + << (list<value>() << "blob2" << blob) + << (list<value>() << "blob3" << blob) + << (list<value>() << "blob4" << blob) + << (list<value>() << "blob5" << blob) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> val = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + testPostLoop(5, val, ch); + + gettimeofday(&start, NULL); + + testPostLoop(count, val, ch); + + gettimeofday(&end, NULL); + std::cout << "ATOMPub POST blob test " << duration(start, end, count) << " ms" << std::endl; + } + return true; +} + +const bool testPut() { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + http::CURLHandle ch; + value rc = content(http::put(a, "http://localhost:8090/test/111", ch)); + assert(rc == value(true)); + return true; +} + +const bool testDel() { + http::CURLHandle ch; + value rc = content(http::del("http://localhost:8090/test/123456789", ch)); + assert(rc == value(true)); + return true; +} + +} +} + +int main() { + std::cout << "Testing..." << std::endl; + + tuscany::server::testGet(); + tuscany::server::testGetPerf(); + tuscany::server::testPost(); + tuscany::server::testPostPerf(); + tuscany::server::testEval(); + tuscany::server::testEvalPerf(); + tuscany::server::testFeed(); + tuscany::server::testPut(); + tuscany::server::testDel(); + + std::cout << "OK" << std::endl; + + return 0; +} diff --git a/sca-cpp/trunk/modules/http/htdocs/entry.xml b/sca-cpp/trunk/modules/server/htdocs/entry.xml index 86b8a10547..86b8a10547 100644 --- a/sca-cpp/trunk/modules/http/htdocs/entry.xml +++ b/sca-cpp/trunk/modules/server/htdocs/entry.xml diff --git a/sca-cpp/trunk/modules/http/htdocs/feed.xml b/sca-cpp/trunk/modules/server/htdocs/feed.xml index 5e37de6580..5e37de6580 100644 --- a/sca-cpp/trunk/modules/http/htdocs/feed.xml +++ b/sca-cpp/trunk/modules/server/htdocs/feed.xml diff --git a/sca-cpp/trunk/modules/server/htdocs/index.html b/sca-cpp/trunk/modules/server/htdocs/index.html new file mode 100644 index 0000000000..1bfb3e30c2 --- /dev/null +++ b/sca-cpp/trunk/modules/server/htdocs/index.html @@ -0,0 +1,21 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> + +<html><body><h1>It works!</h1></body></html> + diff --git a/sca-cpp/trunk/modules/http/htdocs/json-request.txt b/sca-cpp/trunk/modules/server/htdocs/json-request.txt index b4bd07fc46..b4bd07fc46 100644 --- a/sca-cpp/trunk/modules/http/htdocs/json-request.txt +++ b/sca-cpp/trunk/modules/server/htdocs/json-request.txt diff --git a/sca-cpp/trunk/modules/http/htdocs/json-result.txt b/sca-cpp/trunk/modules/server/htdocs/json-result.txt index 121bf74902..121bf74902 100644 --- a/sca-cpp/trunk/modules/http/htdocs/json-result.txt +++ b/sca-cpp/trunk/modules/server/htdocs/json-result.txt diff --git a/sca-cpp/trunk/modules/server/http-test b/sca-cpp/trunk/modules/server/http-test new file mode 100755 index 0000000000..6d23911c31 --- /dev/null +++ b/sca-cpp/trunk/modules/server/http-test @@ -0,0 +1,43 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Setup +../http/httpd-conf tmp 8090 htdocs +./server-conf tmp +cat >>tmp/conf/httpd.conf <<EOF + +<Location /test> +SetHandler mod_tuscany_eval +SCAContribution `pwd`/ +SCAComposite httpd-test.composite +SCAComponent httpd-test +</Location> +EOF + +apachectl -k start -d `pwd`/tmp +sleep 1 + +# Test +./client-test +rc=$? + +# Cleanup +apachectl -k stop -d `pwd`/tmp +sleep 2 +return $rc diff --git a/sca-cpp/trunk/modules/http/httpd-client.scm b/sca-cpp/trunk/modules/server/httpd-client.scm index 12275693f4..12275693f4 100644 --- a/sca-cpp/trunk/modules/http/httpd-client.scm +++ b/sca-cpp/trunk/modules/server/httpd-client.scm diff --git a/sca-cpp/trunk/modules/server/httpd-test b/sca-cpp/trunk/modules/server/httpd-test new file mode 100755 index 0000000000..7fa2112f75 --- /dev/null +++ b/sca-cpp/trunk/modules/server/httpd-test @@ -0,0 +1,80 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +echo "Testing..." + +# Setup +../http/httpd-conf tmp 8090 htdocs +./server-conf tmp +cat >>tmp/conf/httpd.conf <<EOF + +<Location /test> +SetHandler mod_tuscany_eval +SCAContribution `pwd`/ +SCAComposite httpd-test.composite +SCAComponent httpd-test +</Location> +EOF + +apachectl -k start -d `pwd`/tmp +sleep 1 + +# Test HTTP GET +curl http://localhost:8090/index.html 2>/dev/null >tmp/index.html +diff tmp/index.html htdocs/index.html +rc=$? + +# Test ATOMPub +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/ >tmp/feed.xml 2>/dev/null + diff tmp/feed.xml htdocs/feed.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/111 >tmp/entry.xml 2>/dev/null + diff tmp/entry.xml htdocs/entry.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/111 -X DELETE 2>/dev/null + rc=$? +fi + +# Test JSON-RPC +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/json-request.txt >tmp/json-result.txt 2>/dev/null + diff tmp/json-result.txt htdocs/json-result.txt + rc=$? +fi + +# Cleanup +apachectl -k stop -d `pwd`/tmp +sleep 2 +if [ "$rc" = "0" ]; then + echo "OK" +fi +return $rc diff --git a/sca-cpp/trunk/modules/http/httpd-test.composite b/sca-cpp/trunk/modules/server/httpd-test.composite index 875d26ae1b..875d26ae1b 100644 --- a/sca-cpp/trunk/modules/http/httpd-test.composite +++ b/sca-cpp/trunk/modules/server/httpd-test.composite diff --git a/sca-cpp/trunk/modules/http/httpd-test.scm b/sca-cpp/trunk/modules/server/httpd-test.scm index 0566eaf36f..0566eaf36f 100644 --- a/sca-cpp/trunk/modules/http/httpd-test.scm +++ b/sca-cpp/trunk/modules/server/httpd-test.scm diff --git a/sca-cpp/trunk/modules/server/mod-cpp.hpp b/sca-cpp/trunk/modules/server/mod-cpp.hpp new file mode 100644 index 0000000000..cb24b76f6c --- /dev/null +++ b/sca-cpp/trunk/modules/server/mod-cpp.hpp @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* $Rev$ $Date$ */ + +#ifndef tuscany_modcpp_hpp +#define tuscany_modcpp_hpp + +/** + * Evaluation functions used by mod-eval to evaluate implementation.cpp + * component implementations. + */ + +#include <string> +#include <iostream> +#include <fstream> + +#include "function.hpp" +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" +#include "dynlib.hpp" +#include "cache.hpp" +#include "../eval/driver.hpp" +#include "../http/httpd.hpp" +#include "mod-eval.hpp" + +namespace tuscany { +namespace server { +namespace modeval { +namespace cpp { + +/** + * Evaluate a C++ component implementation function. + */ +struct evalImplementation { + const lib ilib; + const ilambda impl; + evalImplementation(const lib& ilib, const ilambda& impl) : ilib(ilib), impl(impl) { + } + const failable<value, std::string> operator()(const value& func, const list<value>& params) const { + httpd::logValue(cons<value>(func, params), "expr"); + const failable<value, std::string> val = impl(func, params); + httpd::logValue(content(val), "val"); + return val; + } +}; + +/** + * Read a C++ component implementation. + */ +const failable<ilambda, std::string> readLatestImplementation(const std::string path) { + const failable<lib, std::string> ilib(dynlib(path)); + if (!hasContent(ilib)) + return mkfailure<ilambda, std::string>(reason(ilib)); + + const failable<ilambda, std::string> impl(dynlambda<failable<value, std::string>(value, list<value>)>("eval", content(ilib))); + if (!hasContent(impl)) + return impl; + return ilambda(evalImplementation(content(ilib), content(impl))); +} + +const cached<failable<ilambda, std::string> > readImplementation(const std::string& path) { + const lambda<failable<ilambda, std::string>(std::string)> ri(readLatestImplementation); + const lambda<unsigned long(std::string)> ft(latestFileTime); + const std::string p(path + dynlibExt); + return cached<failable<ilambda, std::string> >(curry(ri, p), curry(ft, p)); +} + +} +} +} +} + +#endif /* tuscany_modcpp_hpp */ diff --git a/sca-cpp/trunk/modules/server/mod-eval.cpp b/sca-cpp/trunk/modules/server/mod-eval.cpp new file mode 100644 index 0000000000..f843b9bdc5 --- /dev/null +++ b/sca-cpp/trunk/modules/server/mod-eval.cpp @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* $Rev$ $Date$ */ + +/** + * HTTPD module used to eval component implementations. + */ + +#include <string> +#include <iostream> +#include <sstream> +#include <fstream> + +#include "function.hpp" +#include "list.hpp" +#include "slist.hpp" +#include "value.hpp" +#include "element.hpp" +#include "monad.hpp" +#include "cache.hpp" +#include "../atom/atom.hpp" +#include "../json/json.hpp" +#include "../scdl/scdl.hpp" +#include "../http/curl.hpp" +#include "../http/httpd.hpp" +#include "mod-eval.hpp" +#include "mod-scm.hpp" +#include "mod-cpp.hpp" + +extern "C" { +extern module AP_MODULE_DECLARE_DATA mod_tuscany_eval; +} + +namespace tuscany { +namespace server { +namespace modeval { + +/** + * Server configuration. + */ +class ServerConf { +public: + ServerConf(server_rec* s) : s(s), home("") { + } + server_rec* s; + std::string home; +}; + +/** + * Port number used for wiring requests. Set it to zero to use the current + * server port number, set it to a port number to direct wiring requests + * to that port, for debugging or tracing for example. + */ +int debugWiringPort = 0; + +/** + * Directory configuration. + */ +class DirConf { +public: + DirConf(char* dirspec) : dirspec(dirspec), contributionPath(""), compositeName(""), componentName(""), implementationPath("") { + } + char* dirspec; + std::string contributionPath; + std::string compositeName; + std::string componentName; + std::string implementationPath; + cached<failable<value, std::string> > component; + cached<failable<ilambda, std::string> > implementation; +}; + +/** + * Handle an HTTP GET. + */ +const failable<int, std::string> get(request_rec* r, const ilambda& impl, const list<value>& px) { + + // Inspect the query string + const list<list<value> > args = httpd::queryArgs(r); + const list<value> ia = assoc(value("id"), args); + const list<value> ma = assoc(value("method"), args); + + // Evaluate a JSON-RPC request and return a JSON result + if (!isNil(ia) && !isNil(ma)) { + + // Extract the request id, method and params + const value id = cadr(ia); + const value func = std::string(cadr(ma)).c_str(); + const list<value> params = httpd::queryParams(args); + + // Apply the requested function + const failable<value, std::string> val = impl(func, append(params, px)); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + + // Return JSON result + json::JSONContext cx; + return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc", r); + } + + // Evaluate an ATOM GET request and return an ATOM feed + const list<value> id(httpd::path(r->path_info)); + if (isNil(id)) { + const failable<value, std::string> val = impl("getall", px); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + return httpd::writeResult(atom::writeATOMFeed(atom::feedValuesToElements(content(val))), "application/atom+xml;type=feed", r); + } + + // Evaluate an ATOM GET and return an ATOM entry + const failable<value, std::string> val = impl("get", cons<value>(car(id), px)); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + return httpd::writeResult(atom::writeATOMEntry(atom::entryValuesToElements(content(val))), "application/atom+xml;type=entry", r); +} + +/** + * Handle an HTTP POST. + */ +const failable<int, std::string> post(request_rec* r, const ilambda& impl, const list<value>& px) { + const list<std::string> ls = httpd::read(r); + httpd::logStrings(ls, "content"); + + // Evaluate a JSON-RPC request and return a JSON result + const std::string ct = httpd::contentType(r); + if (ct.find("application/json-rpc") != std::string::npos || ct.find("text/plain") != std::string::npos) { + json::JSONContext cx; + const list<value> json = elementsToValues(content(json::readJSON(ls, cx))); + const list<list<value> > args = httpd::postArgs(json); + + // Extract the request id, method and params + const value id = cadr(assoc(value("id"), args)); + const value func = std::string(cadr(assoc(value("method"), args))).c_str(); + const list<value> params = (list<value>)cadr(assoc(value("params"), args)); + + // Evaluate the request expression + const failable<value, std::string> val = impl(func, append(params, px)); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + + // Return JSON result + return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc", r); + } + + // Evaluate an ATOM POST request and return the created resource location + if (ct.find("application/atom+xml") != std::string::npos) { + + // Evaluate the request expression + const value entry = httpd::feedEntry(content(atom::readEntry(ls))); + const failable<value, std::string> val = impl("post", cons<value>(entry, px)); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + + // Return the created resource location + apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, httpd::url(content(val), r))); + r->status = HTTP_CREATED; + return OK; + } + + return HTTP_NOT_IMPLEMENTED; +} + +/** + * Handle an HTTP PUT. + */ +const failable<int, std::string> put(request_rec* r, const ilambda& impl, const list<value>& px) { + const list<std::string> ls = httpd::read(r); + httpd::logStrings(ls, "content"); + + // Evaluate an ATOM PUT request + const list<value> id(httpd::path(r->path_info)); + const value entry = httpd::feedEntry(content(atom::readEntry(ls))); + const failable<value, std::string> val = impl("put", append(mklist<value>(entry, car(id)), px)); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + if (val == value(false)) + return HTTP_NOT_FOUND; + return OK; +} + +/** + * Handle an HTTP DELETE. + */ +const failable<int, std::string> del(request_rec* r, const ilambda& impl, const list<value>& px) { + + // Evaluate an ATOM delete request + const list<value> id(httpd::path(r->path_info)); + const failable<value, std::string> val = impl("delete", cons<value>(car(id), px)); + if (!hasContent(val)) + return mkfailure<int, std::string>(reason(val)); + if (val == value(false)) + return HTTP_NOT_FOUND; + return OK; +} + +/** + * Read the SCDL configuration of a component. + */ +const failable<value, std::string> readComponent(const std::string& path, const std::string& name) { + + // Read composite + std::ifstream is(path); + if (is.fail() || is.bad()) + return mkfailure<value, std::string>("Could not read composite: " + path); + + // Return the component + const list<value> c = scdl::components(readXML(streamList(is))); + const value comp = scdl::named(name, c); + if (isNil(comp)) + return mkfailure<value, std::string>("Could not find component: " + name); + return comp; +} + +const cached<failable<value, std::string> > component(DirConf* conf) { + const std::string path(conf->contributionPath + conf->compositeName); + const lambda<failable<value, std::string>(std::string, std::string)> rc(readComponent); + const lambda<unsigned long(std::string)> ft(latestFileTime); + return cached<failable<value, std::string> >(curry(rc, path, conf->componentName), curry(ft, path)); +} + +/** + * Convert a list of component references to a list of HTTP proxy lambdas. + */ +const value mkproxy(const value& ref, const std::string& base, const http::CURLHandle& ch) { + return eval::primitiveProcedure(http::proxy(base + std::string(scdl::name(ref)), ch)); +} + +const list<value> proxies(const list<value>& refs, const std::string& base, const http::CURLHandle& ch) { + if (isNil(refs)) + return refs; + return cons(mkproxy(car(refs), base, ch), proxies(cdr(refs), base, ch)); +} + +/** + * Returns the component implementation with the given implementation type and path. + * For now only Scheme and C++ implementations are supported. + */ +const cached<failable<ilambda, std::string> > implementation(const std::string& itype, const std::string& path) { + if (itype.find(".scheme") != std::string::npos) + return latest(scm::readImplementation(path)); + if (itype.find(".cpp") != std::string::npos) + return latest(cpp::readImplementation(path)); + return cached<failable<ilambda, std::string> >(); +} + +/** + * HTTP request handler. + */ +int handler(request_rec *r) { + if(strcmp(r->handler, "mod_tuscany_eval")) + return DECLINED; + httpd::logRequest(r, "mod_tuscany_eval::handler"); + + // Set up the read policy + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + + // Retrieve the latest component configuration + DirConf& conf = httpd::dirConf<DirConf>(r, &mod_tuscany_eval); + conf.component = latest(conf.component); + const failable<value, std::string> comp(content(conf.component)); + if (!hasContent(comp)) + return HTTP_NOT_FOUND; + + // Retrieve the latest implementation + const value ielement= scdl::implementation(content(comp)); + const std::string path = conf.contributionPath + std::string(scdl::uri(ielement)); + if (path != conf.implementationPath) { + conf.implementationPath = path; + conf.implementation = implementation(elementName(ielement), path); + } + else + conf.implementation = latest(conf.implementation); + const failable<ilambda, std::string> impl(content(conf.implementation)); + if (!hasContent(impl)) + return HTTP_NOT_FOUND; + + // Convert component references to configured proxy lambdas + std::ostringstream base; + base << "http://localhost:" << (debugWiringPort == 0? ap_get_server_port(r) : debugWiringPort) << "/references/" << std::string(scdl::name(content(comp))) << "/"; + http::CURLHandle ch; + const list<value> px(proxies(scdl::references(content(comp)), base.str(), ch)); + + // Handle HTTP method + if (r->header_only) + return OK; + if(r->method_number == M_GET) + return httpd::reportStatus(get(r, content(impl), px)); + if(r->method_number == M_POST) + return httpd::reportStatus(post(r, content(impl), px)); + if(r->method_number == M_PUT) + return httpd::reportStatus(put(r, content(impl), px)); + if(r->method_number == M_DELETE) + return httpd::reportStatus(del(r, content(impl), px)); + return HTTP_NOT_IMPLEMENTED; +} + +/** + * Configuration commands. + */ +const char *confHome(cmd_parms *cmd, void *dummy, const char *arg) { + ServerConf *c = (ServerConf*)ap_get_module_config(cmd->server->module_config, &mod_tuscany_eval); + c->home = arg; + return NULL; +} +const char *confContribution(cmd_parms *cmd, void *c, const char *arg) { + DirConf* conf = (DirConf*)c; + conf->contributionPath = arg; + conf->component = component(conf); + return NULL; +} +const char *confComposite(cmd_parms *cmd, void *c, const char *arg) { + DirConf* conf = (DirConf*)c; + conf->compositeName = arg; + conf->component = component(conf); + return NULL; +} +const char *confComponent(cmd_parms *cmd, void *c, const char *arg) { + DirConf* conf = (DirConf*)c; + conf->componentName = arg; + conf->component = component(conf); + return NULL; +} + +/** + * HTTP server module declaration. + */ +const command_rec commands[] = { + AP_INIT_TAKE1("TuscanyHome", (const char*(*)())confHome, NULL, RSRC_CONF, "Tuscany home directory"), + AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, ACCESS_CONF, "SCA contribution location"), + AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, ACCESS_CONF, "SCA composite location"), + AP_INIT_TAKE1("SCAComponent", (const char*(*)())confComponent, NULL, ACCESS_CONF, "SCA component name"), + {NULL} +}; + +int postConfig(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { + return OK; +} + +void childInit(apr_pool_t* p, server_rec* svr_rec) { + ServerConf *c = (ServerConf*)ap_get_module_config(svr_rec->module_config, &mod_tuscany_eval); + if(c == NULL) { + std::cerr << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << std::endl; + exit(APEXIT_CHILDFATAL); + } +} + +void registerHooks(apr_pool_t *p) { + ap_hook_post_config(postConfig, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(childInit, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(handler, NULL, NULL, APR_HOOK_MIDDLE); +} + +} +} +} + +extern "C" { + +module AP_MODULE_DECLARE_DATA mod_tuscany_eval = { + STANDARD20_MODULE_STUFF, + // dir config and merger + tuscany::httpd::makeDirConf<tuscany::server::modeval::DirConf>, NULL, + // server config and merger + tuscany::httpd::makeServerConf<tuscany::server::modeval::ServerConf>, NULL, + // commands and hooks + tuscany::server::modeval::commands, tuscany::server::modeval::registerHooks +}; + +} diff --git a/sca-cpp/trunk/modules/server/mod-eval.hpp b/sca-cpp/trunk/modules/server/mod-eval.hpp new file mode 100644 index 0000000000..a350538956 --- /dev/null +++ b/sca-cpp/trunk/modules/server/mod-eval.hpp @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* $Rev$ $Date$ */ + +#ifndef tuscany_modeval_hpp +#define tuscany_modeval_hpp + +/** + * Defines the signature of component implementation lambdas + * expected by mod-eval. + */ + +#include <string> + +#include "function.hpp" +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" + +namespace tuscany { +namespace server { +namespace modeval { + +/** + * Represents a component implementation lambda function. + */ +typedef lambda<failable<value, std::string>(value, list<value>)> ilambda; + +} +} +} + +#endif /* tuscany_modeval_hpp */ diff --git a/sca-cpp/trunk/modules/server/mod-scm.hpp b/sca-cpp/trunk/modules/server/mod-scm.hpp new file mode 100644 index 0000000000..386d032695 --- /dev/null +++ b/sca-cpp/trunk/modules/server/mod-scm.hpp @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* $Rev$ $Date$ */ + +#ifndef tuscany_modscm_hpp +#define tuscany_modscm_hpp + +/** + * Evaluation functions used by mod-eval to evaluate implementation.scheme + * component implementations. + */ + +#include <string> +#include <iostream> +#include <fstream> + +#include "function.hpp" +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" +#include "cache.hpp" +#include "../eval/driver.hpp" +#include "../http/httpd.hpp" +#include "mod-eval.hpp" + +namespace tuscany { +namespace server { +namespace modeval { +namespace scm { + +/** + * Evaluate a script component implementation function. + */ +struct evalImplementation { + const value impl; + evalImplementation(const value& impl) : impl(impl) { + } + const failable<value, std::string> operator()(const value& func, const list<value>& params) const { + const value expr = cons<value>(func, eval::quotedParameters(params)); + httpd::logValue(expr, "expr"); + gc_pool pool; + eval::Env globalEnv = eval::setupEnvironment(pool); + const value val = eval::evalScript(expr, impl, globalEnv, pool); + httpd::logValue(val, "val"); + if (isNil(val)) + return mkfailure<value, std::string>("Could not evaluate expression"); + return val; + } +}; + +/** + * Read a script component implementation. + */ +const failable<ilambda, std::string> readLatestImplementation(const std::string path) { + std::ifstream is(path.c_str(), std::ios_base::in); + if (is.fail() || is.bad()) + return mkfailure<ilambda, std::string>("Could not read implementation: " + path); + const value impl = eval::readScript(is); + if (isNil(impl)) + return mkfailure<ilambda, std::string>("Could not read implementation: " + path); + return ilambda(evalImplementation(impl)); +} + +const cached<failable<ilambda, std::string> > readImplementation(const std::string& path) { + const lambda<failable<ilambda, std::string>(std::string)> ri(readLatestImplementation); + const lambda<unsigned long(std::string)> ft(latestFileTime); + return cached<failable<ilambda, std::string> >(curry(ri, path), curry(ft, path)); +} + +} +} +} +} + +#endif /* tuscany_modscm_hpp */ diff --git a/sca-cpp/trunk/modules/http/mod-wiring.cpp b/sca-cpp/trunk/modules/server/mod-wiring.cpp index 965d5a87fb..2d3c8ce045 100644 --- a/sca-cpp/trunk/modules/http/mod-wiring.cpp +++ b/sca-cpp/trunk/modules/server/mod-wiring.cpp @@ -20,7 +20,7 @@ /* $Rev$ $Date$ */ /** - * HTTPD module used to wire components. + * HTTPD module used to wire component references. */ #include <sys/stat.h> @@ -34,16 +34,16 @@ #include "slist.hpp" #include "value.hpp" #include "monad.hpp" +#include "cache.hpp" #include "../scdl/scdl.hpp" -#include "../cache/cache.hpp" -#include "httpd.hpp" +#include "../http/httpd.hpp" extern "C" { extern module AP_MODULE_DECLARE_DATA mod_tuscany_wiring; } namespace tuscany { -namespace httpd { +namespace server { namespace modwiring { /** @@ -51,15 +51,12 @@ namespace modwiring { */ class ServerConf { public: - std::string home; - ServerConf() : home("") { + ServerConf(server_rec* s) : home("") { } + server_rec* s; + std::string home; }; -const ServerConf& serverConf(const request_rec* r) { - return *(ServerConf*)ap_get_module_config(r->server->module_config, &mod_tuscany_wiring); -} - /** * Set to true to wire using mod_proxy, false to wire using HTTP client redirects. */ @@ -70,17 +67,14 @@ const bool useModProxy = true; */ class DirConf { public: - DirConf() : contributionPath(""), compositeName("") { + DirConf(char* dirspec) : contributionPath(""), compositeName("") { } + char* dirspec; std::string contributionPath; std::string compositeName; - cache::cached<failable<list<value>, std::string> > components; + cached<failable<list<value>, std::string> > components; }; -DirConf& dirConf(const request_rec* r) { - return *(DirConf*)ap_get_module_config(r->per_dir_config, &mod_tuscany_wiring); -} - /** * Read the SCDL configuration of the deployed components. */ @@ -91,11 +85,11 @@ const failable<list<value>, std::string> readComponents(const std::string& path) return scdl::components(readXML(streamList(is))); } -const cache::cached<failable<list<value>, std::string> > components(DirConf* conf) { +const cached<failable<list<value>, std::string> > components(DirConf* conf) { const std::string path(conf->contributionPath + conf->compositeName); const lambda<failable<list<value>, std::string>(std::string)> rc(readComponents); - const lambda<unsigned long(std::string)> ft(cache::latestFileTime); - return cache::cached<failable<list<value>, std::string> >(curry(rc, path), curry(ft, path)); + const lambda<unsigned long(std::string)> ft(latestFileTime); + return cached<failable<list<value>, std::string> >(curry(rc, path), curry(ft, path)); } /** @@ -112,19 +106,16 @@ const bool isAbsolute(const std::string& uri) { int translate(request_rec *r) { if (strncmp(r->uri, "/references/", 12) != 0) return DECLINED; - const list<value> rpath(path(r->uri)); - - // Log the request - if(logRequests) - logRequest(r, "mod_tuscany_wiring::translate"); + httpd::logRequest(r, "mod_tuscany_wiring::translate"); // Find the requested component, reference and its target configuration - DirConf& conf = dirConf(r); - conf.components = cache::latest(conf.components); - const failable<list<value>, std::string> comps(conf.components); - if (!hasValue(comps)) + DirConf& conf = httpd::dirConf<DirConf>(r, &mod_tuscany_wiring); + conf.components = latest(conf.components); + const failable<list<value>, std::string> comps(content(conf.components)); + if (!hasContent(comps)) return HTTP_INTERNAL_SERVER_ERROR; - const value comp(scdl::named(cadr(rpath), list<value>(comps))); + const list<value> rpath(httpd::path(r->uri)); + const value comp(scdl::named(cadr(rpath), list<value>(content(comps)))); if (isNil(comp)) return HTTP_NOT_FOUND; const value ref(scdl::named(caddr(rpath), scdl::references(comp))); @@ -174,10 +165,7 @@ const std::string redirect(const std::string& file, const std::string& pi, const int handler(request_rec *r) { if(strcmp(r->handler, "mod_tuscany_wiring")) return DECLINED; - - // Log the request - if(logRequests) - logRequest(r, "mod_tuscany_wiring::handler"); + httpd::logRequest(r, "mod_tuscany_wiring::handler"); // Do an internal redirect if (r->filename == NULL || strncmp(r->filename, "/redirect:", 10) != 0) @@ -211,17 +199,6 @@ const char *confComposite(cmd_parms *cmd, void *c, const char *arg) { return NULL; } -void *makeDirConf(apr_pool_t *p, char *dirspec) { - DirConf* c = new (apr_palloc(p, sizeof(DirConf))) DirConf(); - apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback<DirConf>, apr_pool_cleanup_null) ; - return c; -} -void* makeServerConf(apr_pool_t *p, server_rec *s) { - ServerConf* c = new (apr_palloc(p, sizeof(ServerConf))) ServerConf(); - apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback<ServerConf>, apr_pool_cleanup_null) ; - return c; -} - /** * HTTP server module declaration. */ @@ -259,18 +236,12 @@ extern "C" { module AP_MODULE_DECLARE_DATA mod_tuscany_wiring = { STANDARD20_MODULE_STUFF, - // dir config - tuscany::httpd::modwiring::makeDirConf, - // dir merger, default is to override - NULL, - // server config - tuscany::httpd::modwiring::makeServerConf, - // merge server config - NULL, - // command table - tuscany::httpd::modwiring::commands, - // register hooks - tuscany::httpd::modwiring::registerHooks + // dir config and merger + tuscany::httpd::makeDirConf<tuscany::server::modwiring::DirConf>, NULL, + // server config and merger + tuscany::httpd::makeServerConf<tuscany::server::modwiring::ServerConf>, NULL, + // commands and hooks + tuscany::server::modwiring::commands, tuscany::server::modwiring::registerHooks }; } diff --git a/sca-cpp/trunk/modules/server/server-conf b/sca-cpp/trunk/modules/server/server-conf new file mode 100755 index 0000000000..dfe4265bae --- /dev/null +++ b/sca-cpp/trunk/modules/server/server-conf @@ -0,0 +1,32 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Generate a server conf +here=`readlink -f $0`; here=`dirname $here` +root=`readlink -f $1` + +mkdir -p $root +mkdir -p $root/logs +mkdir -p $root/conf +cat >>$root/conf/httpd.conf <<EOF +LoadModule mod_tuscany_eval $here/.libs/libmod_tuscany_eval.so +LoadModule mod_tuscany_wiring $here/.libs/libmod_tuscany_wiring.so +LoadModule mod_tuscany_cache $here/.libs/libmod_tuscany_cache.so +EOF + diff --git a/sca-cpp/trunk/modules/http/wiring-test b/sca-cpp/trunk/modules/server/wiring-test index 0c3f36b513..c50732ddb2 100755 --- a/sca-cpp/trunk/modules/http/wiring-test +++ b/sca-cpp/trunk/modules/server/wiring-test @@ -20,7 +20,8 @@ echo "Testing..." # Setup -./httpd-conf tmp 8092 htdocs +../http/httpd-conf tmp 8090 htdocs +./server-conf tmp cat >>tmp/conf/httpd.conf <<EOF <Location /test> @@ -48,37 +49,37 @@ apachectl -k start -d `pwd`/tmp sleep 1 # Test HTTP GET -curl http://localhost:8092/index.html 2>/dev/null >tmp/index.html +curl http://localhost:8090/index.html 2>/dev/null >tmp/index.html diff tmp/index.html htdocs/index.html rc=$? # Test ATOMPub if [ "$rc" = "0" ]; then - curl http://localhost:8092/client/ >tmp/feed.xml 2>/dev/null + curl http://localhost:8090/client/ >tmp/feed.xml 2>/dev/null diff tmp/feed.xml htdocs/feed.xml rc=$? fi if [ "$rc" = "0" ]; then - curl http://localhost:8092/client/111 >tmp/entry.xml 2>/dev/null + curl http://localhost:8090/client/111 >tmp/entry.xml 2>/dev/null diff tmp/entry.xml htdocs/entry.xml rc=$? fi if [ "$rc" = "0" ]; then - curl http://localhost:8092/client/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null + curl http://localhost:8090/client/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null rc=$? fi if [ "$rc" = "0" ]; then - curl http://localhost:8092/client/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null + curl http://localhost:8090/client/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null rc=$? fi if [ "$rc" = "0" ]; then - curl http://localhost:8092/client/111 -X DELETE 2>/dev/null + curl http://localhost:8090/client/111 -X DELETE 2>/dev/null rc=$? fi # Test JSON-RPC if [ "$rc" = "0" ]; then - curl http://localhost:8092/client/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/json-request.txt >tmp/json-result.txt 2>/dev/null + curl http://localhost:8090/client/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/json-request.txt >tmp/json-result.txt 2>/dev/null diff tmp/json-result.txt htdocs/json-result.txt rc=$? fi |