diff options
author | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2012-01-30 15:56:11 +0000 |
---|---|---|
committer | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2012-01-30 15:56:11 +0000 |
commit | f4c4803ce0c14585e3c2af3641d94b651877e04c (patch) | |
tree | 9f10df32f2d34a3c697aaee897bad0b180922095 /sca-cpp/trunk/modules/server | |
parent | 1b10f18fce6baeb721a725661ded630614831304 (diff) |
Refactor server hosting module, rename it and move it under a hosting dir, and refactor its datastore components into a single component.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1237740 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'sca-cpp/trunk/modules/server')
-rw-r--r-- | sca-cpp/trunk/modules/server/Makefile.am | 9 | ||||
-rw-r--r-- | sca-cpp/trunk/modules/server/client-test.cpp | 2 | ||||
-rw-r--r-- | sca-cpp/trunk/modules/server/client-test.hpp | 2 | ||||
-rw-r--r-- | sca-cpp/trunk/modules/server/domain-test.composite | 4 | ||||
-rwxr-xr-x | sca-cpp/trunk/modules/server/httpd-test | 12 | ||||
-rw-r--r-- | sca-cpp/trunk/modules/server/mod-eval.hpp | 918 | ||||
-rw-r--r-- | sca-cpp/trunk/modules/server/mod-wiring.cpp | 530 | ||||
-rwxr-xr-x | sca-cpp/trunk/modules/server/server-conf | 7 |
8 files changed, 606 insertions, 878 deletions
diff --git a/sca-cpp/trunk/modules/server/Makefile.am b/sca-cpp/trunk/modules/server/Makefile.am index edc0dfb251..e2fd67d9b8 100644 --- a/sca-cpp/trunk/modules/server/Makefile.am +++ b/sca-cpp/trunk/modules/server/Makefile.am @@ -25,19 +25,14 @@ moddir = $(prefix)/modules/server EXTRA_DIST = domain-test.composite client-test.scm server-test.scm htdocs/*.html htdocs/test/*.xml htdocs/test/*.txt -mod_LTLIBRARIES = libmod_tuscany_eval.la libmod_tuscany_wiring.la -noinst_DATA = libmod_tuscany_eval${libsuffix} libmod_tuscany_wiring${libsuffix} +mod_LTLIBRARIES = libmod_tuscany_eval.la +noinst_DATA = libmod_tuscany_eval${libsuffix} libmod_tuscany_eval_la_SOURCES = mod-eval.cpp libmod_tuscany_eval_la_LDFLAGS = -lxml2 -lcurl -lmozjs libmod_tuscany_eval${libsuffix}: ln -s .libs/libmod_tuscany_eval${libsuffix} -libmod_tuscany_wiring_la_SOURCES = mod-wiring.cpp -libmod_tuscany_wiring_la_LDFLAGS = -lxml2 -lcurl -lmozjs -libmod_tuscany_wiring${libsuffix}: - ln -s .libs/libmod_tuscany_wiring${libsuffix} - noinst_test_LTLIBRARIES = libimpl-test.la noinst_testdir = `pwd`/tmp noinst_DATA += libimpl-test${libsuffix} diff --git a/sca-cpp/trunk/modules/server/client-test.cpp b/sca-cpp/trunk/modules/server/client-test.cpp index 5de7ab6e7b..3f5ff20c56 100644 --- a/sca-cpp/trunk/modules/server/client-test.cpp +++ b/sca-cpp/trunk/modules/server/client-test.cpp @@ -29,7 +29,7 @@ int main() { tuscany::cout << "Testing..." << tuscany::endl; - tuscany::server::testURI = "http://localhost:8090/test"; + tuscany::server::testURI = "http://localhost:8090/scheme"; tuscany::server::testServer(); diff --git a/sca-cpp/trunk/modules/server/client-test.hpp b/sca-cpp/trunk/modules/server/client-test.hpp index 50b246a073..2dab7b6dfd 100644 --- a/sca-cpp/trunk/modules/server/client-test.hpp +++ b/sca-cpp/trunk/modules/server/client-test.hpp @@ -36,7 +36,7 @@ namespace tuscany { namespace server { -string testURI = "http://localhost:8090/test"; +string testURI = "http://localhost:8090/scheme"; bool testBlobs = true; ostream* curlWriter(const string& s, ostream* os) { diff --git a/sca-cpp/trunk/modules/server/domain-test.composite b/sca-cpp/trunk/modules/server/domain-test.composite index b003dd15cd..1819b3bf4c 100644 --- a/sca-cpp/trunk/modules/server/domain-test.composite +++ b/sca-cpp/trunk/modules/server/domain-test.composite @@ -23,8 +23,8 @@ <component name="scheme-test"> <implementation.scheme script="server-test.scm"/> - <service name="test"> - <binding.http uri="test"/> + <service name="scheme"> + <binding.http uri="scheme"/> </service> </component> diff --git a/sca-cpp/trunk/modules/server/httpd-test b/sca-cpp/trunk/modules/server/httpd-test index d541c145e9..63b268d7b3 100755 --- a/sca-cpp/trunk/modules/server/httpd-test +++ b/sca-cpp/trunk/modules/server/httpd-test @@ -41,31 +41,31 @@ rc=$? # Test ATOMPub if [ "$rc" = "0" ]; then - $curl_prefix/bin/curl http://localhost:8090/test/ >tmp/feed.xml 2>/dev/null + $curl_prefix/bin/curl http://localhost:8090/scheme/ >tmp/feed.xml 2>/dev/null diff tmp/feed.xml htdocs/test/feed.xml rc=$? fi if [ "$rc" = "0" ]; then - $curl_prefix/bin/curl http://localhost:8090/test/111 >tmp/entry.xml 2>/dev/null + $curl_prefix/bin/curl http://localhost:8090/scheme/111 >tmp/entry.xml 2>/dev/null diff tmp/entry.xml htdocs/test/entry.xml rc=$? fi if [ "$rc" = "0" ]; then - $curl_prefix/bin/curl http://localhost:8090/test/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null + $curl_prefix/bin/curl http://localhost:8090/scheme/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null rc=$? fi if [ "$rc" = "0" ]; then - $curl_prefix/bin/curl http://localhost:8090/test/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null + $curl_prefix/bin/curl http://localhost:8090/scheme/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null rc=$? fi if [ "$rc" = "0" ]; then - $curl_prefix/bin/curl http://localhost:8090/test/111 -X DELETE 2>/dev/null + $curl_prefix/bin/curl http://localhost:8090/scheme/111 -X DELETE 2>/dev/null rc=$? fi # Test JSON-RPC if [ "$rc" = "0" ]; then - $curl_prefix/bin/curl http://localhost:8090/test/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/test/json-request.txt >tmp/json-result.txt 2>/dev/null + $curl_prefix/bin/curl http://localhost:8090/scheme/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/test/json-request.txt >tmp/json-result.txt 2>/dev/null diff tmp/json-result.txt htdocs/test/json-result.txt rc=$? fi diff --git a/sca-cpp/trunk/modules/server/mod-eval.hpp b/sca-cpp/trunk/modules/server/mod-eval.hpp index 62d91c6236..999a5793ad 100644 --- a/sca-cpp/trunk/modules/server/mod-eval.hpp +++ b/sca-cpp/trunk/modules/server/mod-eval.hpp @@ -52,50 +52,61 @@ namespace server { namespace modeval { /** + * Set to true to wire using mod_proxy, false to wire using HTTP client redirects. + */ +const bool useModProxy = true; + +/** * Server configuration. */ class ServerConf { public: - ServerConf(apr_pool_t* p, server_rec* s) : p(p), server(s), contributionPath(""), compositeName(""), virtualHostDomain(""), virtualHostContributionPath(""), virtualHostCompositeName(""), ca(""), cert(""), key("") { + ServerConf(apr_pool_t* p, server_rec* s) : p(p), server(s) { } - ServerConf(apr_pool_t* p, const ServerConf& ssc, const string& name) : p(p), server(ssc.server), lifecycle(ssc.lifecycle), contributionPath(ssc.virtualHostContributionPath + name + "/"), compositeName(ssc.virtualHostCompositeName), virtualHostDomain(""), virtualHostContributionPath(""), virtualHostCompositeName(""), ca(ssc.ca), cert(ssc.cert), key(ssc.key) { + ServerConf(apr_pool_t* p, const ServerConf& ssc, const string& name) : p(p), server(ssc.server), lifecycle(ssc.lifecycle), vhostName(name), compositeName(ssc.vhostCompositeName), ca(ssc.ca), cert(ssc.cert), key(ssc.key), vhostContributor(ssc.vhostContributor) { + contributionPath = length(ssc.vhostContributionPath) != 0? ssc.vhostContributionPath + name + "/" : ssc.contributionPath; } const gc_pool p; server_rec* server; lambda<value(const list<value>&)> lifecycle; + string vhostName; string contributionPath; string compositeName; - string virtualHostDomain; - string virtualHostContributionPath; - string virtualHostCompositeName; + string vhostDomain; + string vhostContributionPath; + string vhostCompositeName; + string vhostContributorName; string ca; string cert; string key; + list<value> references; + list<value> services; list<value> implementations; list<value> implTree; + value vhostContributor; }; /** - * Return true if a server contains a composite configuration. + * Return true if a server contains a contribution configuration. */ -const bool hasCompositeConf(const ServerConf& sc) { - return sc.contributionPath != "" && sc.compositeName != ""; +const bool hasContributionConf(const ServerConf& sc) { + return length(sc.contributionPath) != 0; } /** * Return true if a server contains a virtual host domain configuration. */ -const bool hasVirtualDomainConf(const ServerConf& sc) { - return sc.virtualHostDomain != ""; +const bool hasVhostDomainConf(const ServerConf& sc) { + return length(sc.vhostDomain) != 0; } /** - * Return true if a server contains a virtual host composite configuration. + * Return true if a server contains a virtual host contribution configuration. */ -const bool hasVirtualCompositeConf(const ServerConf& sc) { - return sc.virtualHostContributionPath != "" && sc.virtualHostCompositeName != ""; +const bool hasVhostContributionConf(const ServerConf& sc) { + return length(sc.vhostContributionPath) != 0 || length(sc.vhostContributorName) != 0; } /** @@ -129,262 +140,6 @@ public: }; /** - * Handle an HTTP GET. - */ -const failable<int> get(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { - debug(r->uri, "modeval::get::uri"); - - // 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 = c_str(json::funcName(string(cadr(ma)))); - - // Apply the requested function - const failable<value> val = failableResult(impl(cons(func, json::queryParams(args)))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - - // Return JSON result - js::JSContext cx; - return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc; charset=utf-8", r); - } - - // Evaluate the GET expression - const list<value> params(append<value>(cddr(rpath), mkvalues(args))); - const failable<value> val = failableResult(impl(cons<value>("get", mklist<value>(params)))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - const value c = content(val); - debug(c, "modeval::get::content"); - - // Check if the client requested a specific format - const list<value> fmt = assoc<value>("format", args); - - // Write as a scheme value if requested by the client - if (!isNil(fmt) && cadr(fmt) == "scheme") - return httpd::writeResult(mklist<string>(scheme::writeValue(c)), "text/plain; charset=utf-8", r); - - // Write a simple value as a JSON value - if (!isList(c)) { - js::JSContext cx; - if (isSymbol(c)) { - const list<value> lc = mklist<value>(mklist<value>("name", value(string(c)))); - debug(lc, "modeval::get::symbol"); - return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); - } - const list<value> lc = mklist<value>(mklist<value>("value", c)); - debug(lc, "modeval::get::value"); - return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); - } - - // Write an empty list as a JSON empty value - if (isNil((list<value>)c)) { - js::JSContext cx; - debug(list<value>(), "modeval::get::empty"); - return httpd::writeResult(json::writeJSON(list<value>(), cx), "application/json; charset=utf-8", r); - } - - // Write content-type / content-list pair - if (isString(car<value>(c)) && !isNil(cdr<value>(c)) && isList(cadr<value>(c))) - return httpd::writeResult(convertValues<string>(cadr<value>(c)), car<value>(c), r); - - // Write an assoc value as a JSON result - if (isSymbol(car<value>(c)) && !isNil(cdr<value>(c))) { - js::JSContext cx; - const list<value> lc = mklist<value>(c); - debug(lc, "modeval::get::assoc"); - debug(valuesToElements(lc), "modeval::get::assoc::element"); - return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); - } - - // Write value as JSON if requested by the client - if (!isNil(fmt) && cadr(fmt) == "json") { - js::JSContext cx; - return httpd::writeResult(json::writeJSON(valuesToElements(c), cx), "application/json; charset=utf-8", r); - } - - // Convert list of values to element values - const list<value> e = valuesToElements(c); - debug(e, "modeval::get::elements"); - - // Write an ATOM feed or entry - if (isList(car<value>(e)) && !isNil(car<value>(e))) { - const list<value> el = car<value>(e); - if (isSymbol(car<value>(el)) && car<value>(el) == element && !isNil(cdr<value>(el)) && isSymbol(cadr<value>(el)) && elementHasChildren(el) && !elementHasValue(el)) { - if (cadr<value>(el) == atom::feed) - return httpd::writeResult(atom::writeATOMFeed(e), "application/atom+xml; charset=utf-8", r); - if (cadr<value>(el) == atom::entry) - return httpd::writeResult(atom::writeATOMEntry(e), "application/atom+xml; charset=utf-8", r); - } - } - - // Write any other compound value as a JSON value - js::JSContext cx; - return httpd::writeResult(json::writeJSON(e, cx), "application/json; charset=utf-8", r); -} - -/** - * Handle an HTTP POST. - */ -const failable<int> post(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { - debug(r->uri, "modeval::post::url"); - - // Evaluate a JSON-RPC request and return a JSON result - const string ct = httpd::contentType(r); - if (contains(ct, "application/json-rpc") || contains(ct, "text/plain") || contains(ct, "application/x-www-form-urlencoded")) { - - // Read the JSON request - const int rc = httpd::setupReadPolicy(r); - if(rc != OK) - return rc; - const list<string> ls = httpd::read(r); - debug(ls, "modeval::post::input"); - js::JSContext 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 = c_str(json::funcName(cadr(assoc(value("method"), args)))); - const list<value> params = (list<value>)cadr(assoc(value("params"), args)); - - // Evaluate the request expression - const failable<value> val = failableResult(impl(cons<value>(func, params))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - - // Return JSON result - return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc; charset=utf-8", r); - } - - // Evaluate an ATOM POST request and return the location of the corresponding created resource - if (contains(ct, "application/atom+xml")) { - - // Read the ATOM entry - const int rc = httpd::setupReadPolicy(r); - if(rc != OK) - return rc; - const list<string> ls = httpd::read(r); - debug(ls, "modeval::post::input"); - const value entry = elementsToValues(content(atom::readATOMEntry(ls))); - - // Evaluate the POST expression - const failable<value> val = failableResult(impl(cons<value>("post", mklist<value>(cddr(rpath), entry)))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - - // Return the created resource location - debug(content(val), "modeval::post::location"); - apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, c_str(httpd::url(r->uri, content(val), r)))); - r->status = HTTP_CREATED; - return OK; - } - - // Unknown content type, wrap the HTTP request struct in a value and pass it to - // the component implementation function - const failable<value> val = failableResult(impl(cons<value>("handle", mklist<value>(httpd::requestValue(r))))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - return (int)content(val); -} - -/** - * Handle an HTTP PUT. - */ -const failable<int> put(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { - debug(r->uri, "modeval::put::url"); - - // Read the ATOM entry - const int rc = httpd::setupReadPolicy(r); - if(rc != OK) - return rc; - const list<string> ls = httpd::read(r); - debug(ls, "modeval::put::input"); - const value entry = elementsToValues(content(atom::readATOMEntry(ls))); - - // Evaluate the PUT expression and update the corresponding resource - const failable<value> val = failableResult(impl(cons<value>("put", mklist<value>(cddr(rpath), entry)))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - if (val == value(false)) - return HTTP_NOT_FOUND; - return OK; -} - -/** - * Handle an HTTP DELETE. - */ -const failable<int> del(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { - debug(r->uri, "modeval::delete::url"); - - // Evaluate an ATOM delete request - const failable<value> val = failableResult(impl(cons<value>("delete", mklist<value>(cddr(rpath))))); - if (!hasContent(val)) - return mkfailure<int>(reason(val)); - if (val == value(false)) - return HTTP_NOT_FOUND; - return OK; -} - -/** - * Translate a component request. - */ -const int translateRequest(const ServerConf& sc, const list<value>& rpath, request_rec *r) { - debug(rpath, "modeval::translateRequest::path"); - if (isNil(rpath)) - return DECLINED; - - // Translate a component request - const value c = car(rpath); - if (c == string("components") || c == string("c")) { - r->handler = "mod_tuscany_eval"; - return OK; - } - - // Translate a request targeting a virtual host or virtual app - if (hasVirtualCompositeConf(sc) && !isNil(cdr(rpath))) { - const string cp = sc.virtualHostContributionPath + string(c) + "/" + sc.virtualHostCompositeName; - struct stat st; - const int s = stat(c_str(cp), &st); - if (s != -1) { - const value d = cadr(rpath); - if (d == string("components") || d == string("c")) { - r->handler = "mod_tuscany_eval"; - return OK; - } - } - } - - return DECLINED; -} - -/** - * Translate a component request. - */ -int translate(request_rec *r) { - if(r->method_number != M_GET && r->method_number != M_POST && r->method_number != M_PUT && r->method_number != M_DELETE) - return DECLINED; - - // Create a scoped memory pool - gc_scoped_pool pool(r->pool); - - httpdDebugRequest(r, "modeval::translate::input"); - - // Get the server configuration - const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval); - - // Translate the request - return translateRequest(sc, pathValues(r->uri), r); -} - -/** * Make an HTTP proxy lambda to a component reference. */ const value mkhttpProxy(const ServerConf& sc, const string& ref, const string& base) { @@ -573,7 +328,7 @@ struct appPropProxy { appPropProxy(const value& v) : v(v) { } const value operator()(unused const list<value>& params) const { - const char* n = apr_table_get(currentRequest->headers_in, "X-Request-AppName"); + const char* n = apr_table_get(currentRequest->notes, "X-Request-AppName"); const value a = n != NULL? value(string(n)) : v; debug(a, "modeval::appPropProxy::value"); return a; @@ -584,7 +339,7 @@ struct pathPropProxy { pathPropProxy(unused const value& v) { } const value operator()(unused const list<value>& params) const { - const char* u = apr_table_get(currentRequest->headers_in, "X-Request-URI"); + const char* u = apr_table_get(currentRequest->notes, "X-Request-URI"); const value v = u != NULL? pathValues(string(u)) : list<value>(); debug(v, "modeval::pathPropProxy::value"); return v; @@ -743,21 +498,79 @@ const failable<list<value> > applyLifecycleExpr(const list<value>& impls, const } /** + * Return a list of component-name + references pairs. The references are + * arranged in trees of reference-name + reference-target pairs. + */ +const list<value> componentReferenceToTargetTree(const value& c) { + return mklist<value>(scdl::name(c), mkbtree(sort(scdl::referenceToTargetAssoc(scdl::references(c))))); +} + +const list<value> componentReferenceToTargetAssoc(const list<value>& c) { + if (isNil(c)) + return c; + return cons<value>(componentReferenceToTargetTree(car(c)), componentReferenceToTargetAssoc(cdr(c))); +} + +/** + * Return a list of service-URI-path + component-name pairs. Service-URI-paths are + * represented as lists of URI path fragments. + */ +const list<value> defaultBindingURI(const string& cn, const string& sn) { + return mklist<value>(cn, sn); +} + +const list<value> bindingToComponentAssoc(const string& cn, const string& sn, const list<value>& b) { + if (isNil(b)) + return b; + const value uri(scdl::uri(car(b))); + if (isNil(uri)) + return cons<value>(mklist<value>(defaultBindingURI(cn, sn), cn), bindingToComponentAssoc(cn, sn, cdr(b))); + return cons<value>(mklist<value>(pathValues(c_str(string(uri))), cn), bindingToComponentAssoc(cn, sn, cdr(b))); +} + +const list<value> serviceToComponentAssoc(const string& cn, const list<value>& s) { + if (isNil(s)) + return s; + const string sn(scdl::name(car(s))); + const list<value> btoc(bindingToComponentAssoc(cn, sn, scdl::bindings(car(s)))); + if (isNil(btoc)) + return cons<value>(mklist<value>(defaultBindingURI(cn, sn), cn), serviceToComponentAssoc(cn, cdr(s))); + return append<value>(btoc, serviceToComponentAssoc(cn, cdr(s))); +} + +const list<value> uriToComponentAssoc(const list<value>& c) { + if (isNil(c)) + return c; + return append<value>(serviceToComponentAssoc(scdl::name(car(c)), scdl::services(car(c))), uriToComponentAssoc(cdr(c))); +} + +/** * Configure the components declared in the deployed composite. */ const failable<bool> confComponents(ServerConf& sc) { - if (!hasCompositeConf(sc)) + if (!hasContributionConf(sc)) return false; debug(sc.contributionPath, "modeval::confComponents::contributionPath"); + debug(sc.contributionPath, "modeval::confComponents::contributorName"); debug(sc.compositeName, "modeval::confComponents::compositeName"); if (sc.ca != "") debug(sc.ca, "modeval::confComponents::sslCA"); if (sc.cert != "") debug(sc.cert, "modeval::confComponents::sslCert"); if (sc.key != "") debug(sc.key, "modeval::confComponents::sslKey"); - // Read the components and get their implementation lambda functions + // Read the components and get their services, references and implementation + // lambda functions const failable<list<value> > comps = readComponents(scdl::resourcePath(sc.contributionPath, sc.compositeName)); if (!hasContent(comps)) return mkfailure<bool>(reason(comps)); + + const list<value> refs = componentReferenceToTargetAssoc(content(comps)); + debug(refs, "modeval::confComponents::references"); + sc.references = mkbtree(sort(refs)); + + const list<value> svcs = uriToComponentAssoc(content(comps)); + debug(svcs, "modeval::confComponents::services"); + sc.services = mkbtree(sort(svcs)); + sc.implementations = componentToImplementationAssoc(sc, content(comps)); debug(sc.implementations, "modeval::confComponents::implementations"); return true; @@ -781,21 +594,36 @@ const failable<bool> startComponents(ServerConf& sc) { /** * Configure and start the components deployed in a virtual host. */ -const failable<bool> virtualHostConfig(ServerConf& sc, const ServerConf& ssc, request_rec* r) { - debug(httpd::serverName(ssc.server), "modeval::virtualHostConfig::serverName"); - debug(httpd::serverName(r), "modeval::virtualHostConfig::virtualHostName"); - debug(ssc.virtualHostContributionPath, "modwiring::virtualHostConfig::virtualHostContributionPath"); - debug(sc.contributionPath, "modeval::virtualHostConfig::contributionPath"); - - // Chdir to the virtual host's contribution - if (chdir(c_str(sc.contributionPath)) != 0) - return mkfailure<bool>(string("Couldn't chdir to the deployed contribution: ") + sc.contributionPath); +const failable<bool> vhostConfig(ServerConf& sc, const ServerConf& ssc, request_rec* r) { + debug(httpd::serverName(ssc.server), "modeval::vhostConfig::serverName"); + debug(httpd::serverName(r), "modeval::vhostConfig::vhostName"); + debug(ssc.vhostContributionPath, "modeval::vhostConfig::vhostContributionPath"); + debug(sc.contributionPath, "modeval::vhostConfig::contributionPath"); // Configure the deployed components const failable<bool> cr = confComponents(sc); if (!hasContent(cr)) return cr; + // Store the virtual host configuration in the request config + + return true; +} + +/** + * Start the components deployed in a virtual host. + */ +const failable<bool> vhostStart(ServerConf& sc, const ServerConf& ssc, request_rec* r) { + debug(httpd::serverName(ssc.server), "modeval::vhostStart::serverName"); + debug(httpd::serverName(r), "modeval::vhostStart::vhostName"); + debug(ssc.vhostContributionPath, "modeval::vhostStart::vhostContributionPath"); + debug(sc.contributionPath, "modeval::vhostStart::contributionPath"); + + // Configure the components deployed in a virtual host + const failable<bool> cr = vhostConfig(sc, ssc, r); + if (!hasContent(cr)) + return cr; + // Start the configured components const failable<bool> sr = startComponents(sc); if (!hasContent(sr)) @@ -808,23 +636,441 @@ const failable<bool> virtualHostConfig(ServerConf& sc, const ServerConf& ssc, re } /** - * Cleanup a virtual host. + * Stop a virtual host. */ -const failable<bool> virtualHostCleanup(const ServerConf& sc, const ServerConf& ssc) { - if (!hasCompositeConf(sc)) +const failable<bool> vhostStop(const ServerConf& sc, unused const ServerConf& ssc) { + if (!hasContributionConf(sc)) return true; - debug("modeval::virtualHostCleanup"); + debug("modeval::vhostStop"); // Stop the component implementations applyLifecycleExpr(sc.implementations, mklist<value>("stop")); - // Chdir back to the main server's contribution - if (chdir(c_str(ssc.contributionPath)) != 0) - return mkfailure<bool>(string("Couldn't chdir to the deployed contribution: ") + ssc.contributionPath); return true; } /** + * Handle an HTTP GET. + */ +const failable<int> get(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::get::uri"); + + // 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 = c_str(json::funcName(string(cadr(ma)))); + + // Apply the requested function + const failable<value> val = failableResult(impl(cons(func, json::queryParams(args)))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + + // Return JSON result + js::JSContext cx; + return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc; charset=utf-8", r); + } + + // Evaluate the GET expression + const list<value> params(append<value>(cddr(rpath), mkvalues(args))); + const failable<value> val = failableResult(impl(cons<value>("get", mklist<value>(params)))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + const value c = content(val); + debug(c, "modeval::get::content"); + + // Check if the client requested a specific format + const list<value> fmt = assoc<value>("format", args); + + // Write as a scheme value if requested by the client + if (!isNil(fmt) && cadr(fmt) == "scheme") + return httpd::writeResult(mklist<string>(scheme::writeValue(c)), "text/plain; charset=utf-8", r); + + // Write a simple value as a JSON value + if (!isList(c)) { + js::JSContext cx; + if (isSymbol(c)) { + const list<value> lc = mklist<value>(mklist<value>("name", value(string(c)))); + debug(lc, "modeval::get::symbol"); + return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); + } + const list<value> lc = mklist<value>(mklist<value>("value", c)); + debug(lc, "modeval::get::value"); + return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); + } + + // Write an empty list as a JSON empty value + if (isNil((list<value>)c)) { + js::JSContext cx; + debug(list<value>(), "modeval::get::empty"); + return httpd::writeResult(json::writeJSON(list<value>(), cx), "application/json; charset=utf-8", r); + } + + // Write content-type / content-list pair + if (isString(car<value>(c)) && !isNil(cdr<value>(c)) && isList(cadr<value>(c))) + return httpd::writeResult(convertValues<string>(cadr<value>(c)), car<value>(c), r); + + // Write an assoc value as a JSON result + if (isSymbol(car<value>(c)) && !isNil(cdr<value>(c))) { + js::JSContext cx; + const list<value> lc = mklist<value>(c); + debug(lc, "modeval::get::assoc"); + debug(valuesToElements(lc), "modeval::get::assoc::element"); + return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); + } + + // Write value as JSON if requested by the client + if (!isNil(fmt) && cadr(fmt) == "json") { + js::JSContext cx; + return httpd::writeResult(json::writeJSON(valuesToElements(c), cx), "application/json; charset=utf-8", r); + } + + // Convert list of values to element values + const list<value> e = valuesToElements(c); + debug(e, "modeval::get::elements"); + + // Write an ATOM feed or entry + if (isList(car<value>(e)) && !isNil(car<value>(e))) { + const list<value> el = car<value>(e); + if (isSymbol(car<value>(el)) && car<value>(el) == element && !isNil(cdr<value>(el)) && isSymbol(cadr<value>(el)) && elementHasChildren(el) && !elementHasValue(el)) { + if (cadr<value>(el) == atom::feed) + return httpd::writeResult(atom::writeATOMFeed(e), "application/atom+xml; charset=utf-8", r); + if (cadr<value>(el) == atom::entry) + return httpd::writeResult(atom::writeATOMEntry(e), "application/atom+xml; charset=utf-8", r); + } + } + + // Write any other compound value as a JSON value + js::JSContext cx; + return httpd::writeResult(json::writeJSON(e, cx), "application/json; charset=utf-8", r); +} + +/** + * Handle an HTTP POST. + */ +const failable<int> post(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::post::uri"); + + // Evaluate a JSON-RPC request and return a JSON result + const string ct = httpd::contentType(r); + if (contains(ct, "application/json-rpc") || contains(ct, "text/plain") || contains(ct, "application/x-www-form-urlencoded")) { + + // Read the JSON request + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + const list<string> ls = httpd::read(r); + debug(ls, "modeval::post::input"); + js::JSContext 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 = c_str(json::funcName(cadr(assoc(value("method"), args)))); + const list<value> params = (list<value>)cadr(assoc(value("params"), args)); + + // Evaluate the request expression + const failable<value> val = failableResult(impl(cons<value>(func, params))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + + // Return JSON result + return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc; charset=utf-8", r); + } + + // Evaluate an ATOM POST request and return the location of the corresponding created resource + if (contains(ct, "application/atom+xml")) { + + // Read the ATOM entry + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + const list<string> ls = httpd::read(r); + debug(ls, "modeval::post::input"); + const value entry = elementsToValues(content(atom::readATOMEntry(ls))); + + // Evaluate the POST expression + const failable<value> val = failableResult(impl(cons<value>("post", mklist<value>(cddr(rpath), entry)))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + + // Return the created resource location + debug(content(val), "modeval::post::location"); + apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, c_str(httpd::url(r->uri, content(val), r)))); + r->status = HTTP_CREATED; + return OK; + } + + // Unknown content type, wrap the HTTP request struct in a value and pass it to + // the component implementation function + const failable<value> val = failableResult(impl(cons<value>("handle", mklist<value>(httpd::requestValue(r))))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + return (int)content(val); +} + +/** + * Handle an HTTP PUT. + */ +const failable<int> put(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::put::uri"); + + // Read the ATOM entry + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + const list<string> ls = httpd::read(r); + debug(ls, "modeval::put::input"); + const value entry = elementsToValues(content(atom::readATOMEntry(ls))); + + // Evaluate the PUT expression and update the corresponding resource + const failable<value> val = failableResult(impl(cons<value>("put", mklist<value>(cddr(rpath), entry)))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + if (val == value(false)) + return HTTP_NOT_FOUND; + return OK; +} + +/** + * Handle an HTTP DELETE. + */ +const failable<int> del(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::delete::uri"); + + // Evaluate an ATOM delete request + const failable<value> val = failableResult(impl(cons<value>("delete", mklist<value>(cddr(rpath))))); + if (!hasContent(val)) + return mkfailure<int>(reason(val)); + if (val == value(false)) + return HTTP_NOT_FOUND; + return OK; +} + +/** + * Route a /references/component-name/reference-name request, + * to the target of the component reference. + */ +int translateReference(const ServerConf& sc, request_rec *r, const list<value>& rpath, const list<value>& apath) { + httpdDebugRequest(r, "modeval::translateReference::input"); + debug(r->uri, "modeval::translateReference::uri"); + debug(apath, "modeval::translateReference::apath"); + debug(rpath, "modeval::translateReference::rpath"); + + // Find the requested component + if (isNil(cdr(rpath))) + return HTTP_NOT_FOUND; + const list<value> comp(assoctree(cadr(rpath), sc.references)); + if (isNil(comp)) + return HTTP_NOT_FOUND; + + // Find the requested reference and target configuration + const list<value> ref(assoctree<value>(caddr(rpath), cadr(comp))); + if (isNil(ref)) + return HTTP_NOT_FOUND; + const string target(cadr(ref)); + debug(target, "modeval::translateReference::target"); + + // Route to an absolute target URI using mod_proxy or an HTTP client redirect + const list<value> pathInfo = cdddr(rpath); + if (http::isAbsolute(target)) { + if (useModProxy) { + // Build proxy URI + string turi = target + path(pathInfo) + (r->args != NULL? string("?") + r->args : string("")); + const string proxy(string("proxy:") + turi); + debug(proxy, "modeval::translateReference::proxy"); + r->filename = apr_pstrdup(r->pool, c_str(proxy)); + r->proxyreq = PROXYREQ_REVERSE; + r->handler = "proxy-server"; + apr_table_setn(r->notes, "proxy-nocanon", "1"); + return OK; + } + + debug(target, "modeval::translateReference::location"); + r->handler = "mod_tuscany_eval"; + return httpd::externalRedirect(target, r); + } + + // Route to a relative target URI using a local internal redirect + // /components/, target component name and request path info + const value tname = substr(target, 0, find(target, '/')); + const string redir = path(append(apath, cons<value>(string("c"), cons(tname, pathInfo)))); + debug(redir, "modeval::translateReference::redirect"); + r->uri = apr_pstrdup(r->pool, c_str(redir)); + r->handler = "mod_tuscany_eval"; + return OK; +} + +/** + * Find a leaf matching a request path in a tree of URI paths. + */ +const int matchPath(const list<value>& k, const list<value>& p) { + if (isNil(p)) + return true; + if (isNil(k)) + return false; + if (car(k) != car(p)) + return false; + return matchPath(cdr(k), cdr(p)); +} + +const list<value> assocPath(const value& k, const list<value>& tree) { + if (isNil(tree)) + return tree; + if (matchPath(k, car<value>(car(tree)))) + return car(tree); + if (k < car<value>(car(tree))) + return assocPath(k, cadr(tree)); + return assocPath(k, caddr(tree)); +} + +/** + * Route a service request to the component providing the requested service. + */ +int translateService(const ServerConf& sc, request_rec *r, const list<value>& rpath, const list<value>& apath) { + httpdDebugRequest(r, "modeval::translateService::input"); + debug(r->uri, "modeval::translateService::uri"); + + // Find the requested component + debug(sc.services, "modeval::translateService::services"); + const list<value> svc(assocPath(rpath, sc.services)); + if (isNil(svc)) + return DECLINED; + debug(svc, "modeval::translateService::service"); + + // Build a component-name + path-info URI + const list<value> target(append(apath, cons<value>(string("c"), cons<value>(cadr(svc), httpd::pathInfo(rpath, car(svc)))))); + debug(target, "modeval::translateService::target"); + + // Dispatch to the target component using a local internal redirect + const string redir(path(target)); + debug(redir, "modeval::translateService::redirect"); + r->uri = apr_pstrdup(r->pool, c_str(redir)); + r->handler = "mod_tuscany_eval"; + return OK; +} + +/** + * Translate a request to the target app and component. + */ +const int translateRequest(const ServerConf& sc, request_rec* r, const list<value>& rpath, const list<value>& apath) { + debug(apath, "modeval::translateRequest::apath"); + debug(rpath, "modeval::translateRequest::rpath"); + if (isNil(apath) && isNil(rpath)) + return DECLINED; + + if (!isNil(rpath)) { + + // If the request is targeting a virtual host, use the corresponding + // virtual host configuration + if (isNil(apath)) { + if (hasVhostDomainConf(sc) && hasVhostContributionConf(sc) && httpd::isVhostRequest(sc.server, sc.vhostDomain, r)) { + const string aname = httpd::hostName(r); + ServerConf vsc(r->pool, sc, aname); + if (!hasContent(vhostConfig(vsc, sc, r))) + return DECLINED; + return translateRequest(vsc, r, rpath, mklist<value>(aname)); + } + } + + // Let default handler handle a resource request + const value prefix = car(rpath); + if (prefix == string("vhosts") || prefix == string("v")) + return DECLINED; + + // Let our handler handle a component request + if (prefix == string("components") || prefix == string("c")) { + r->handler = "mod_tuscany_eval"; + return OK; + } + + // Translate a component reference request + if (prefix == string("references") || prefix == string("r")) + return translateReference(sc, r, rpath, apath); + + // Attempt to translate the request to a service request + if (translateService(sc, r, rpath, apath) == OK) + return OK; + + // Attempt to map the request to an actual file + if (isNil(apath)) { + const failable<request_rec*, int> fnr = httpd::internalSubRequest(r->uri, r); + if (!hasContent(fnr)) + return HTTP_INTERNAL_SERVER_ERROR; + request_rec* nr = content(fnr); + nr->uri = r->filename; + const int tr = ap_core_translate(nr); + if (tr != OK) + return tr; + if (ap_directory_walk(nr) == OK && ap_file_walk(nr) == OK && nr->finfo.filetype != APR_NOFILE) { + debug(nr->filename, "modeval::translateRequest::file"); + return DECLINED; + } + + // If the request is targeting a virtual app, use the corresponding + // virtual host configuration + if (hasVhostContributionConf(sc)) { + const string cp = sc.vhostContributionPath + string(prefix) + "/" + sc.vhostCompositeName; + ServerConf vsc(r->pool, sc, string(prefix)); + if (!hasContent(vhostConfig(vsc, sc, r))) + return DECLINED; + return translateRequest(vsc, r, cdr(rpath), mklist<value>(car(rpath))); + } + } + } + + // If we're in a virtual app and the request didn't match a service, + // reference or component, redirect it to /v/<uri>. This will allow + // mapping to the actual app resources using HTTPD aliases. + if (!isNil(apath)) { + if (isNil(rpath) && r->uri[strlen(r->uri) -1] != '/') { + + // Make sure a document root request ends with a '/', using + // an external redirect + const string target = string(r->uri) + string("/") + (r->args != NULL? string("?") + r->args : string(""));; + debug(target, "modeval::translateRequest::location"); + r->handler = "mod_tuscany_eval"; + return httpd::externalRedirect(target, r); + } + + // Do an internal redirect to /v/<uri> + const string redir = string("/redirect:") + string("/v") + string(r->uri); + debug(redir, "modeval::translateRequest::redirect"); + r->filename = apr_pstrdup(r->pool, c_str(redir)); + r->handler = "mod_tuscany_eval"; + return OK; + } + + return DECLINED; +} + +/** + * Translate a request. + */ +int translate(request_rec *r) { + if(r->method_number != M_GET && r->method_number != M_POST && r->method_number != M_PUT && r->method_number != M_DELETE) + return DECLINED; + + // Create a scoped memory pool + gc_scoped_pool pool(r->pool); + + httpdDebugRequest(r, "modeval::translate::input"); + + // Get the server configuration + const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval); + + // Translate the request + return translateRequest(sc, r, pathValues(r->uri), list<value>()); +} + +/** * Handle a component request. */ const int handleRequest(const ServerConf& sc, const list<value>& rpath, request_rec *r) { @@ -832,45 +1078,44 @@ const int handleRequest(const ServerConf& sc, const list<value>& rpath, request_ if (isNil(cdr(rpath))) return HTTP_NOT_FOUND; - // Handle a request targeting a virtual host or virtual app - if (hasVirtualCompositeConf(sc)) { - if (hasVirtualDomainConf(sc) && httpd::isVirtualHostRequest(sc.server, sc.virtualHostDomain, r)) { + if (hasVhostContributionConf(sc)) { + + // Handle a request targeting a component in a virtual host + if (hasVhostDomainConf(sc) && httpd::isVhostRequest(sc.server, sc.vhostDomain, r)) { // Determine the app name from the host sub-domain name, and - // store it in a header + // store it in a request note const string app = http::subDomain(httpd::hostName(r)); - apr_table_setn(r->headers_in, "X-Request-AppName", apr_pstrdup(r->pool, c_str(app))); + apr_table_setn(r->notes, "X-Request-AppName", apr_pstrdup(r->pool, c_str(app))); ServerConf vsc(r->pool, sc, app); - if (!hasContent(virtualHostConfig(vsc, sc, r))) + if (!hasContent(vhostStart(vsc, sc, r))) return HTTP_INTERNAL_SERVER_ERROR; const int rc = handleRequest(vsc, rpath, r); - virtualHostCleanup(vsc, sc); + vhostStop(vsc, sc); return rc; } + // Handle a request targeting a component in a virtual app const value c = car(rpath); if (c != string("components") && c != string("c")) { - // Determine the app name from the request URI path + // Determine the app name from the request URI path and + // store it in a request note const string app = string(c); - const string cp = sc.virtualHostContributionPath + app + "/" + sc.virtualHostCompositeName; - struct stat st; - const int s = stat(c_str(cp), &st); - if (s != -1) { - // Store the app name in a header - apr_table_setn(r->headers_in, "X-Request-AppName", apr_pstrdup(r->pool, c_str(app))); - ServerConf vsc(r->pool, sc, app); - if (!hasContent(virtualHostConfig(vsc, sc, r))) - return HTTP_INTERNAL_SERVER_ERROR; - const int rc = handleRequest(vsc, cdr(rpath), r); - virtualHostCleanup(vsc, sc); - return rc; - } + const string cp = sc.vhostContributionPath + app + "/" + sc.vhostCompositeName; + apr_table_setn(r->notes, "X-Request-AppName", apr_pstrdup(r->pool, c_str(app))); + + ServerConf vsc(r->pool, sc, app); + if (!hasContent(vhostStart(vsc, sc, r))) + return HTTP_INTERNAL_SERVER_ERROR; + const int rc = handleRequest(vsc, cdr(rpath), r); + vhostStop(vsc, sc); + return rc; } } - // Store the request uri path in a header - apr_table_setn(r->headers_in, "X-Request-URI", apr_pstrdup(r->pool, c_str(path(rpath)))); + // Store the request uri path in a request note + apr_table_setn(r->notes, "X-Request-URI", apr_pstrdup(r->pool, c_str(path(rpath)))); // Get the component implementation lambda const list<value> impl(assoctree<value>(cadr(rpath), sc.implTree)); @@ -903,11 +1148,20 @@ int handler(request_rec *r) { if(strcmp(r->handler, "mod_tuscany_eval")) return DECLINED; - // Create a scoped memory pool - gc_scoped_pool pool(r->pool); + // Nothing to do for an external redirect + if (r->status == HTTP_MOVED_TEMPORARILY) + return OK; + // Handle an internal redirect as directed by the translate hook + if (r->filename != NULL && !strncmp(r->filename, "/redirect:", 10)) { + if (r->args == NULL) + return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info)), r); + return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info), string(r->args)), r); + } + + + // Create a scope for the current request ScopedRequest sr(r); - httpdDebugRequest(r, "modeval::handler::input"); // Get the server configuration const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval); @@ -946,16 +1200,21 @@ const int postConfigMerge(const ServerConf& mainsc, server_rec* s) { ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_eval); debug(httpd::serverName(s), "modeval::postConfigMerge::serverName"); sc.lifecycle = mainsc.lifecycle; + sc.vhostName = mainsc.vhostName; sc.contributionPath = mainsc.contributionPath; sc.compositeName = mainsc.compositeName; - sc.virtualHostDomain = mainsc.virtualHostDomain; - sc.virtualHostContributionPath = mainsc.virtualHostContributionPath; - sc.virtualHostCompositeName = mainsc.virtualHostCompositeName; + sc.vhostDomain = mainsc.vhostDomain; + sc.vhostContributionPath = mainsc.vhostContributionPath; + sc.vhostCompositeName = mainsc.vhostCompositeName; + sc.vhostContributorName = mainsc.vhostContributorName; if (sc.ca == "") sc.ca = mainsc.ca; if (sc.cert == "") sc.cert = mainsc.cert; if (sc.key == "") sc.key = mainsc.key; + sc.references = mainsc.references; + sc.services = mainsc.services; sc.implementations = mainsc.implementations; sc.implTree = mainsc.implTree; + sc.vhostContributor = mainsc.vhostContributor; return postConfigMerge(mainsc, s->next); } @@ -964,7 +1223,7 @@ int postConfig(apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp, gc_scoped_pool pool(p); - // Get the server configuration and determine the wiring server name + // Get the server configuration and determine the server name ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_eval); debug(httpd::serverName(s), "modeval::postConfig::serverName"); @@ -1037,6 +1296,10 @@ void childInit(apr_pool_t* p, server_rec* s) { // Store the implementation lambda functions in a tree for fast retrieval sc.implTree = mkbtree(sort(sc.implementations)); + // Create a proxy for the vhost contributor if needed + if (length(sc.vhostContributorName) != 0) + sc.vhostContributor = mkimplProxy(sc, sc.vhostContributorName); + // Merge the updated configuration into the virtual hosts postConfigMerge(sc, s->next); @@ -1062,19 +1325,25 @@ const char* confComposite(cmd_parms *cmd, unused void *c, const char *arg) { const char* confVirtualDomain(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval); - sc.virtualHostDomain = arg; + sc.vhostDomain = arg; return NULL; } const char* confVirtualContribution(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval); - sc.virtualHostContributionPath = arg; + sc.vhostContributionPath = arg; + return NULL; +} +const char* confVirtualContributor(cmd_parms *cmd, unused void *c, const char *arg) { + gc_scoped_pool pool(cmd->pool); + ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval); + sc.vhostContributorName = arg; return NULL; } const char* confVirtualComposite(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval); - sc.virtualHostCompositeName = arg; + sc.vhostCompositeName = arg; return NULL; } const char* confCAFile(cmd_parms *cmd, unused void *c, const char *arg) { @@ -1108,7 +1377,8 @@ const command_rec commands[] = { AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, RSRC_CONF, "SCA contribution location"), AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, RSRC_CONF, "SCA composite location"), AP_INIT_TAKE1("SCAVirtualDomain", (const char*(*)())confVirtualDomain, NULL, RSRC_CONF, "SCA virtual host domain"), - AP_INIT_TAKE1("SCAVirtualContribution", (const char*(*)())confVirtualContribution, NULL, RSRC_CONF, "SCA virtual host contribution location"), + AP_INIT_TAKE1("SCAVirtualContribution", (const char*(*)())confVirtualContribution, NULL, RSRC_CONF, "SCA virtual host contribution path"), + AP_INIT_TAKE1("SCAVirtualContributor", (const char*(*)())confVirtualContributor, NULL, RSRC_CONF, "SCA virtual host contributor component"), AP_INIT_TAKE1("SCAVirtualComposite", (const char*(*)())confVirtualComposite, NULL, RSRC_CONF, "SCA virtual composite location"), AP_INIT_TAKE12("SCASetEnv", (const char*(*)())confEnv, NULL, OR_FILEINFO, "Environment variable name and optional value"), AP_INIT_TAKE1("SCAWiringSSLCACertificateFile", (const char*(*)())confCAFile, NULL, RSRC_CONF, "SCA wiring SSL CA certificate file"), @@ -1122,7 +1392,7 @@ void registerHooks(unused 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); - ap_hook_translate_name(translate, NULL, NULL, APR_HOOK_FIRST); + ap_hook_translate_name(translate, NULL, NULL, APR_HOOK_LAST); } } diff --git a/sca-cpp/trunk/modules/server/mod-wiring.cpp b/sca-cpp/trunk/modules/server/mod-wiring.cpp deleted file mode 100644 index 0dd0529a42..0000000000 --- a/sca-cpp/trunk/modules/server/mod-wiring.cpp +++ /dev/null @@ -1,530 +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 wire component references and route requests to - * target service components. - */ - -#include <sys/stat.h> - -#define WANT_HTTPD_LOG 1 -#include "string.hpp" -#include "stream.hpp" -#include "list.hpp" -#include "tree.hpp" -#include "value.hpp" -#include "monad.hpp" -#include "../scdl/scdl.hpp" -#include "../http/http.hpp" -#include "../http/httpd.hpp" - -extern "C" { -extern module AP_MODULE_DECLARE_DATA mod_tuscany_wiring; -} - -namespace tuscany { -namespace server { -namespace modwiring { - -/** - * Set to true to wire using mod_proxy, false to wire using HTTP client redirects. - */ -const bool useModProxy = true; - -/** - * Server configuration. - */ -class ServerConf { -public: - ServerConf(apr_pool_t* p, server_rec* s) : p(p), server(s), contributionPath(""), compositeName(""), virtualHostDomain(""), virtualHostContributionPath(""), virtualHostCompositeName("") { - } - - ServerConf(apr_pool_t* p, const ServerConf& ssc, const string& name) : p(p), server(ssc.server), contributionPath(ssc.virtualHostContributionPath + name + "/"), compositeName(ssc.virtualHostCompositeName), virtualHostDomain(""), virtualHostContributionPath(""), virtualHostCompositeName("") { - } - - const gc_pool p; - server_rec* server; - string contributionPath; - string compositeName; - string virtualHostDomain; - string virtualHostContributionPath; - string virtualHostCompositeName; - list<value> references; - list<value> services; -}; - -/** - * Return true if a server contains a composite configuration. - */ -const bool hasCompositeConf(const ServerConf& sc) { - return sc.contributionPath != "" && sc.compositeName != ""; -} - -/** - * Return true if a server contains a virtual host domain configuration. - */ -const bool hasVirtualDomainConf(const ServerConf& sc) { - return sc.virtualHostDomain != ""; -} - -/** - * Return true if a server contains a virtual host composite configuration. - */ -const bool hasVirtualCompositeConf(const ServerConf& sc) { - return sc.virtualHostContributionPath != "" && sc.virtualHostCompositeName != ""; -} - -/** - * Route a /references/component-name/reference-name request, - * to the target of the component reference. - */ -int translateReference(const ServerConf& sc, request_rec *r, const list<value>& rpath, const list<value>& apath) { - httpdDebugRequest(r, "modwiring::translateReference::input"); - debug(r->uri, "modwiring::translateReference::uri"); - debug(apath, "modwiring::translateReference::apath"); - debug(rpath, "modwiring::translateReference::rpath"); - - // Find the requested component - if (isNil(cdr(rpath))) - return HTTP_NOT_FOUND; - const list<value> comp(assoctree(cadr(rpath), sc.references)); - if (isNil(comp)) - return HTTP_NOT_FOUND; - - // Find the requested reference and target configuration - const list<value> ref(assoctree<value>(caddr(rpath), cadr(comp))); - if (isNil(ref)) - return HTTP_NOT_FOUND; - const string target(cadr(ref)); - debug(target, "modwiring::translateReference::target"); - - // Route to an absolute target URI using mod_proxy or an HTTP client redirect - const list<value> pathInfo = cdddr(rpath); - if (http::isAbsolute(target)) { - if (useModProxy) { - // Build proxy URI - string turi = target + path(pathInfo) + (r->args != NULL? string("?") + r->args : string("")); - const string proxy(string("proxy:") + turi); - debug(proxy, "modwiring::translateReference::proxy"); - r->filename = apr_pstrdup(r->pool, c_str(proxy)); - r->proxyreq = PROXYREQ_REVERSE; - r->handler = "proxy-server"; - apr_table_setn(r->notes, "proxy-nocanon", "1"); - return OK; - } - - debug(target, "modwiring::translateReference::location"); - r->handler = "mod_tuscany_wiring"; - return httpd::externalRedirect(target, r); - } - - // Route to a relative target URI using a local internal redirect - // /components/, target component name and request path info - const value tname = substr(target, 0, find(target, '/')); - const string tp = path(append(apath, cons<value>(string("c"), cons(tname, pathInfo)))) + (r->args != NULL? string("?") + r->args : string("")); - const string redir(string("/redirect:") + tp); - debug(redir, "modwiring::translateReference::redirect"); - r->filename = apr_pstrdup(r->pool, c_str(redir)); - r->handler = "mod_tuscany_wiring"; - return OK; -} - -/** - * Find a leaf matching a request path in a tree of URI paths. - */ -const int matchPath(const list<value>& k, const list<value>& p) { - if (isNil(p)) - return true; - if (isNil(k)) - return false; - if (car(k) != car(p)) - return false; - return matchPath(cdr(k), cdr(p)); -} - -const list<value> assocPath(const value& k, const list<value>& tree) { - if (isNil(tree)) - return tree; - if (matchPath(k, car<value>(car(tree)))) - return car(tree); - if (k < car<value>(car(tree))) - return assocPath(k, cadr(tree)); - return assocPath(k, caddr(tree)); -} - -/** - * Route a service request to the component providing the requested service. - */ -int translateService(const ServerConf& sc, request_rec *r, const list<value>& rpath, const list<value>& apath) { - httpdDebugRequest(r, "modwiring::translateService::input"); - debug(r->uri, "modwiring::translateService::uri"); - - // Find the requested component - debug(sc.services, "modwiring::translateService::services"); - const list<value> svc(assocPath(rpath, sc.services)); - if (isNil(svc)) - return DECLINED; - debug(svc, "modwiring::translateService::service"); - - // Build a component-name + path-info URI - const list<value> target(append(apath, cons<value>(string("c"), cons<value>(cadr(svc), httpd::pathInfo(rpath, car(svc)))))); - debug(target, "modwiring::translateService::target"); - - // Dispatch to the target component using a local internal redirect - const string tp(path(target) + (r->args != NULL? string("?") + r->args : string(""))); - const string redir(string("/redirect:") + tp); - debug(redir, "modwiring::translateService::redirect"); - r->filename = apr_pstrdup(r->pool, c_str(redir)); - r->handler = "mod_tuscany_wiring"; - return OK; -} - -/** - * Read the components declared in a composite. - */ -const failable<list<value> > readComponents(const string& path) { - ifstream is(path); - if (fail(is)) - return mkfailure<list<value> >(string("Could not read composite: ") + path); - return scdl::components(readXML(streamList(is))); -} - -/** - * Return a list of component-name + references pairs. The references are - * arranged in trees of reference-name + reference-target pairs. - */ -const list<value> componentReferenceToTargetTree(const value& c) { - return mklist<value>(scdl::name(c), mkbtree(sort(scdl::referenceToTargetAssoc(scdl::references(c))))); -} - -const list<value> componentReferenceToTargetAssoc(const list<value>& c) { - if (isNil(c)) - return c; - return cons<value>(componentReferenceToTargetTree(car(c)), componentReferenceToTargetAssoc(cdr(c))); -} - -/** - * Return a list of service-URI-path + component-name pairs. Service-URI-paths are - * represented as lists of URI path fragments. - */ -const list<value> defaultBindingURI(const string& cn, const string& sn) { - return mklist<value>(cn, sn); -} - -const list<value> bindingToComponentAssoc(const string& cn, const string& sn, const list<value>& b) { - if (isNil(b)) - return b; - const value uri(scdl::uri(car(b))); - if (isNil(uri)) - return cons<value>(mklist<value>(defaultBindingURI(cn, sn), cn), bindingToComponentAssoc(cn, sn, cdr(b))); - return cons<value>(mklist<value>(pathValues(c_str(string(uri))), cn), bindingToComponentAssoc(cn, sn, cdr(b))); -} - -const list<value> serviceToComponentAssoc(const string& cn, const list<value>& s) { - if (isNil(s)) - return s; - const string sn(scdl::name(car(s))); - const list<value> btoc(bindingToComponentAssoc(cn, sn, scdl::bindings(car(s)))); - if (isNil(btoc)) - return cons<value>(mklist<value>(defaultBindingURI(cn, sn), cn), serviceToComponentAssoc(cn, cdr(s))); - return append<value>(btoc, serviceToComponentAssoc(cn, cdr(s))); -} - -const list<value> uriToComponentAssoc(const list<value>& c) { - if (isNil(c)) - return c; - return append<value>(serviceToComponentAssoc(scdl::name(car(c)), scdl::services(car(c))), uriToComponentAssoc(cdr(c))); -} - -/** - * Configure the components declared in the server's deployment composite. - */ -const bool confComponents(ServerConf& sc) { - if (!hasCompositeConf(sc)) - return true; - debug(sc.contributionPath, "modwiring::confComponents::contributionPath"); - debug(sc.compositeName, "modwiring::confComponents::compositeName"); - - // Read the component configuration and store the references and service URIs - // in trees for fast retrieval later - const failable<list<value> > comps = readComponents(scdl::resourcePath(sc.contributionPath, sc.compositeName)); - if (!hasContent(comps)) - return true; - const list<value> refs = componentReferenceToTargetAssoc(content(comps)); - debug(refs, "modwiring::confComponents::references"); - sc.references = mkbtree(sort(refs)); - - const list<value> svcs = uriToComponentAssoc(content(comps)); - debug(svcs, "modwiring::confComponents::services"); - sc.services = mkbtree(sort(svcs)); - return true; -} - -/** - * Configure and start the components deployed in a virtual host. - */ -const failable<ServerConf> virtualHostConfig(ServerConf& sc, const ServerConf& ssc, request_rec* r) { - debug(httpd::serverName(ssc.server), "modwiring::virtualHostConfig::serverName"); - debug(httpd::serverName(r), "modwiring::virtualHostConfig::virtualHostName"); - debug(ssc.virtualHostContributionPath, "modwiring::virtualHostConfig::virtualHostContributionPath"); - debug(sc.contributionPath, "modwiring::virtualHostConfig::contributionPath"); - - // Configure the wiring for the deployed components - confComponents(sc); - - return sc; -} - -/** - * Translate an HTTP service or reference request and route it - * to the target component. - */ -const int translateRequest(const ServerConf& sc, request_rec *r, const list<value>& rpath, const list<value>& apath) { - debug(apath, "modwiring::translateRequest::apath"); - debug(rpath, "modwiring::translateRequest::rpath"); - if (isNil(apath) && isNil(rpath)) - return DECLINED; - - if (!isNil(rpath)) { - - // No translation needed for a component or resource request - const value c = car(rpath); - if (c == string("components") || c == string("c") || c == string("vhosts") || c == string("v")) - return DECLINED; - - // If the request is targeting a virtual host, use the corresponding - // virtual host configuration - const bool vdc = hasVirtualDomainConf(sc); - const bool vcc = hasVirtualCompositeConf(sc); - if (vdc && vcc && httpd::isVirtualHostRequest(sc.server, sc.virtualHostDomain, r)) { - ServerConf vsc(r->pool, sc, http::subDomain(httpd::hostName(r))); - if (!hasContent(virtualHostConfig(vsc, sc, r))) - return HTTP_INTERNAL_SERVER_ERROR; - return translateRequest(vsc, r, rpath, list<value>()); - } - - // Translate a component reference request - if (c == string("references") || c == string("r")) - return translateReference(sc, r, rpath, apath); - - // Attempt to translate the request to a service request - if (translateService(sc, r, rpath, apath) == OK) - return OK; - - // If the request is targeting a virtual app, use the corresponding - // virtual host configuration - if (vcc) { - const string cp = sc.virtualHostContributionPath + string(c) + "/" + sc.virtualHostCompositeName; - struct stat st; - const int s = stat(c_str(cp), &st); - if (s == -1) - return DECLINED; - ServerConf vsc(r->pool, sc, string(c)); - if (!hasContent(virtualHostConfig(vsc, sc, r))) - return HTTP_INTERNAL_SERVER_ERROR; - return translateRequest(vsc, r, cdr(rpath), mklist<value>(car(rpath))); - } - } - - // If we're in a virtual app and the request is targeting a regular - // resource, redirect it to /v/<uri>. This will allow mapping to the - // actual resources using HTTPD aliases. - if (!isNil(apath)) { - const string tp = string("/v") + string(r->uri) + (r->args != NULL? string("?") + r->args : string("")); - const string redir = string("/redirect:") + tp; - debug(redir, "modwiring::translateRequest::redirect"); - r->filename = apr_pstrdup(r->pool, c_str(redir)); - r->handler = "mod_tuscany_wiring"; - return OK; - } - - return DECLINED; -} - -/** - * Translate an HTTP service or reference request and route it - * to the target component. - */ -int translate(request_rec *r) { - if(r->method_number != M_GET && r->method_number != M_POST && r->method_number != M_PUT && r->method_number != M_DELETE) - return DECLINED; - - // Create a scoped memory pool - gc_scoped_pool pool(r->pool); - httpdDebugRequest(r, "modwiring::translate::input"); - - // Get the server configuration - const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_wiring); - - // Translate the request - return translateRequest(sc, r, pathValues(r->uri), list<value>()); -} - -/** - * HTTP request handler, redirect to a target component. - */ -int handler(request_rec *r) { - if(r->method_number != M_GET && r->method_number != M_POST && r->method_number != M_PUT && r->method_number != M_DELETE) - return DECLINED; - if(strcmp(r->handler, "mod_tuscany_wiring")) - return DECLINED; - if (r->filename == NULL || strncmp(r->filename, "/redirect:", 10) != 0) - return DECLINED; - - // Nothing to do for an external redirect - if (r->status == HTTP_MOVED_TEMPORARILY) - return OK; - - // Create a scoped memory pool - gc_scoped_pool pool(r->pool); - - httpdDebugRequest(r, "modwiring::handler::input"); - debug(r->uri, "modwiring::handler::uri"); - debug(r->filename, "modwiring::handler::filename"); - debug(r->path_info, "modwiring::handler::path info"); - - // Do an internal redirect - if (r->args == NULL) - return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info)), r); - return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info), string(r->args)), r); -} - -/** - * Called after all the configuration commands have been run. - * Process the server configuration and configure the wiring for the deployed components. - */ -const int postConfigMerge(const ServerConf& mainsc, server_rec* s) { - if (s == NULL) - return OK; - debug(httpd::serverName(s), "modwiring::postConfigMerge::serverName"); - ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_wiring); - sc.contributionPath = mainsc.contributionPath; - sc.compositeName = mainsc.compositeName; - sc.virtualHostDomain = mainsc.virtualHostDomain; - sc.virtualHostContributionPath = mainsc.virtualHostContributionPath; - sc.virtualHostCompositeName = mainsc.virtualHostCompositeName; - sc.references = mainsc.references; - sc.services = mainsc.services; - return postConfigMerge(mainsc, s->next); -} - -int postConfig(apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp, server_rec *s) { - gc_scoped_pool pool(p); - - // Count the calls to post config, skip the first one as - // postConfig is always called twice - const string k("tuscany::modwiring::postConfig"); - const long int count = (long int)httpd::userData(k, s); - httpd::putUserData(k, (void*)(count + 1), s); - if (count == 0) - return OK; - - // Configure the wiring for the deployed components - debug(httpd::serverName(s), "modwiring::postConfig::serverName"); - ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_wiring); - confComponents(sc); - - // Merge the config into any virtual hosts - return postConfigMerge(sc, s->next); -} - -/** - * Child process initialization. - */ -void childInit(apr_pool_t* p, server_rec* s) { - gc_scoped_pool pool(p); - if(ap_get_module_config(s->module_config, &mod_tuscany_wiring) == NULL) { - cfailure << "[Tuscany] Due to one or more errors mod_tuscany_wiring loading failed. Causing apache to stop loading." << endl; - exit(APEXIT_CHILDFATAL); - } -} - -/** - * Configuration commands. - */ -const char *confContribution(cmd_parms *cmd, unused void *c, const char *arg) { - gc_scoped_pool pool(cmd->pool); - ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring); - sc.contributionPath = arg; - return NULL; -} -const char *confComposite(cmd_parms *cmd, unused void *c, const char *arg) { - gc_scoped_pool pool(cmd->pool); - ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring); - sc.compositeName = arg; - return NULL; -} -const char *confVirtualDomain(cmd_parms *cmd, unused void *c, const char *arg) { - gc_scoped_pool pool(cmd->pool); - ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring); - sc.virtualHostDomain = arg; - return NULL; -} -const char *confVirtualContribution(cmd_parms *cmd, unused void *c, const char *arg) { - gc_scoped_pool pool(cmd->pool); - ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring); - sc.virtualHostContributionPath = arg; - return NULL; -} -const char *confVirtualComposite(cmd_parms *cmd, unused void *c, const char *arg) { - gc_scoped_pool pool(cmd->pool); - ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring); - sc.virtualHostCompositeName = arg; - return NULL; -} - -/** - * HTTP server module declaration. - */ -const command_rec commands[] = { - AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, RSRC_CONF, "SCA contribution location"), - AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, RSRC_CONF, "SCA composite location"), - AP_INIT_TAKE1("SCAVirtualDomain", (const char*(*)())confVirtualDomain, NULL, RSRC_CONF, "SCA virtual host domain"), - AP_INIT_TAKE1("SCAVirtualContribution", (const char*(*)())confVirtualContribution, NULL, RSRC_CONF, "SCA virtual host contribution location"), - AP_INIT_TAKE1("SCAVirtualComposite", (const char*(*)())confVirtualComposite, NULL, RSRC_CONF, "SCA virtual host composite location"), - {NULL, NULL, NULL, 0, NO_ARGS, NULL} -}; - -void registerHooks(unused 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); - ap_hook_translate_name(translate, NULL, NULL, APR_HOOK_FIRST); -} - -} -} -} - -extern "C" { - -module AP_MODULE_DECLARE_DATA mod_tuscany_wiring = { - STANDARD20_MODULE_STUFF, - // dir config and merger - NULL, 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 index 47934f973e..83eea0cc6c 100755 --- a/sca-cpp/trunk/modules/server/server-conf +++ b/sca-cpp/trunk/modules/server/server-conf @@ -31,13 +31,6 @@ else libsuffix=".so" fi -cat >>$root/conf/modules.conf <<EOF -# Generated by: server-conf $* -# Support for SCA component wiring -LoadModule mod_tuscany_wiring $here/libmod_tuscany_wiring$libsuffix - -EOF - cat >>$root/conf/httpd.conf <<EOF # Generated by: server-conf $* # Serve JavaScript client scripts |