diff options
Diffstat (limited to '')
19 files changed, 1119 insertions, 64 deletions
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 |