/* * 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 /** * HTTPD module used to eval component implementations. */ #include "string.hpp" #include "stream.hpp" #include "function.hpp" #include "list.hpp" #include "tree.hpp" #include "value.hpp" #include "element.hpp" #include "monad.hpp" #include "../scheme/io.hpp" #include "../atom/atom.hpp" #include "../json/json.hpp" #include "../scdl/scdl.hpp" #include "../http/http.hpp" #include "../http/httpd.hpp" extern "C" { extern module AP_MODULE_DECLARE_DATA mod_tuscany_eval; } namespace tuscany { namespace server { namespace modeval { /** * Server configuration. */ class ServerConf { public: ServerConf(apr_pool_t* p, server_rec* s) : p(p), server(s), wiringServerName(""), contributionPath(""), compositeName(""), virtualHostContributionPath(""), virtualHostCompositeName(""), ca(""), cert(""), key("") { } const gc_pool p; server_rec* server; lambda&)> lifecycle; string wiringServerName; string contributionPath; string compositeName; string virtualHostContributionPath; string virtualHostCompositeName; string ca; string cert; string key; list implementations; list implTree; }; /** * 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 composite configuration. */ const bool hasVirtualCompositeConf(const ServerConf& sc) { return sc.virtualHostContributionPath != "" && sc.virtualHostCompositeName != ""; } /** * Convert a result represented as a content + failure pair to a * failable monad. */ const failable failableResult(const list& v) { if (isNil(cdr(v))) return car(v); return mkfailure(string(cadr(v))); } /** * Store current HTTP request for access from property and proxy lambda functions. */ #ifdef WANT_THREADS perthread_ptr currentRequest; #else request_rec* currentRequest = NULL; #endif class ScopedRequest { public: ScopedRequest(request_rec* r) { currentRequest = r; } ~ScopedRequest() { currentRequest = NULL; } }; /** * Handle an HTTP GET. */ const failable get(request_rec* r, const lambda&)>& impl) { debug(r->uri, "modeval::get::uri"); // Inspect the query string const list > args = httpd::queryArgs(r); const list ia = assoc(value("id"), args); const list 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 val = failableResult(impl(cons(func, json::queryParams(args)))); if (!hasContent(val)) return mkfailure(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 path(pathValues(r->uri)); const list params(append(cddr(path), mkvalues(args))); const failable val = failableResult(impl(cons("get", mklist(params)))); if (!hasContent(val)) return mkfailure(reason(val)); const value c = content(val); debug(c, "modeval::get::content"); // Check if the client requested a specific format const list fmt = assoc("format", args); // Write as a scheme value if requested by the client if (!isNil(fmt) && cadr(fmt) == "scheme") return httpd::writeResult(mklist(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 lc = mklist(mklist("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 lc = mklist(mklist("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)c)) { js::JSContext cx; debug(list(), "modeval::get::empty"); return httpd::writeResult(json::writeJSON(list(), cx), "application/json; charset=utf-8", r); } // Write content-type / content-list pair if (isString(car(c)) && !isNil(cdr(c)) && isList(cadr(c))) return httpd::writeResult(convertValues(cadr(c)), car(c), r); // Write an assoc value as a JSON result if (isSymbol(car(c)) && !isNil(cdr(c))) { js::JSContext cx; const list lc = mklist(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 e = valuesToElements(c); debug(e, "modeval::get::elements"); // Write an ATOM feed or entry if (isList(car(e)) && !isNil(car(e))) { const list el = car(e); if (isSymbol(car(el)) && car(el) == element && !isNil(cdr(el)) && isSymbol(cadr(el)) && elementHasChildren(el) && !elementHasValue(el)) { if (cadr(el) == atom::feed) return httpd::writeResult(atom::writeATOMFeed(e), "application/atom+xml; charset=utf-8", r); if (cadr(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 post(request_rec* r, const lambda&)>& 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 ls = httpd::read(r); debug(ls, "modeval::post::input"); js::JSContext cx; const list json = elementsToValues(content(json::readJSON(ls, cx))); const list > 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 params = (list)cadr(assoc(value("params"), args)); // Evaluate the request expression const failable val = failableResult(impl(cons(func, params))); if (!hasContent(val)) return mkfailure(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 list path(pathValues(r->uri)); const int rc = httpd::setupReadPolicy(r); if(rc != OK) return rc; const list ls = httpd::read(r); debug(ls, "modeval::post::input"); const value entry = elementsToValues(content(atom::readATOMEntry(ls))); // Evaluate the POST expression const failable val = failableResult(impl(cons("post", mklist(cddr(path), entry)))); if (!hasContent(val)) return mkfailure(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 val = failableResult(impl(cons("handle", mklist(httpd::requestValue(r))))); if (!hasContent(val)) return mkfailure(reason(val)); return (int)content(val); } /** * Handle an HTTP PUT. */ const failable put(request_rec* r, const lambda&)>& impl) { debug(r->uri, "modeval::put::url"); // Read the ATOM entry const list path(pathValues(r->uri)); const int rc = httpd::setupReadPolicy(r); if(rc != OK) return rc; const list 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 val = failableResult(impl(cons("put", mklist(cddr(path), entry)))); if (!hasContent(val)) return mkfailure(reason(val)); if (val == value(false)) return HTTP_NOT_FOUND; return OK; } /** * Handle an HTTP DELETE. */ const failable del(request_rec* r, const lambda&)>& impl) { debug(r->uri, "modeval::delete::url"); // Evaluate an ATOM delete request const list path(pathValues(r->uri)); const failable val = failableResult(impl(cons("delete", mklist(cddr(path))))); if (!hasContent(val)) return mkfailure(reason(val)); if (val == value(false)) return HTTP_NOT_FOUND; return OK; } /** * 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; if (strncmp(r->uri, "/components/", 12) != 0 && strncmp(r->uri, "/c/", 3) != 0) return DECLINED; r->handler = "mod_tuscany_eval"; return OK; } /** * Make an HTTP proxy lambda to a component reference. */ const value mkhttpProxy(const ServerConf& sc, const string& ref, const string& base) { const string uri = base + ref; debug(uri, "modeval::mkhttpProxy::uri"); return lambda&)>(http::proxy(uri, sc.ca, sc.cert, sc.key, "", sc.p)); } /** * Make an HTTP proxy lambda to an absolute URI */ const value mkhttpProxy(const ServerConf& sc, const string& uri) { debug(uri, "modeval::mkhttpProxy::uri"); return lambda&)>(http::proxy(uri, "", "", "", "", sc.p)); } /** * Return a component implementation proxy lambda. */ class implProxy { public: implProxy(const ServerConf& sc, const value& name) : sc(sc), name(name) { } const value callImpl(const value& cname, const list& aparams) const { // Lookup the component implementation const list impl(assoctree(cname, sc.implTree)); if (isNil(impl)) return mkfailure(string("Couldn't find component implementation: ") + cname); // Call its lambda function const lambda&)> l(cadr(impl)); const value func = c_str(car(aparams)); const failable val = failableResult(l(cons(func, cdr(aparams)))); debug(val, "modeval::implProxy::result"); if (!hasContent(val)) return value(); return content(val); } const value operator()(const list& params) const { debug(name, "modeval::implProxy::name"); debug(params, "modeval::implProxy::input"); // If the reference was 'wiredByImpl' use the first param as target if (isNil(name)) { const value uri = cadr(params); const list aparams = cons(car(params), cddr(params)); debug(uri, "modeval::implProxy::wiredByImpl::uri"); debug(aparams, "modeval::implProxy::wiredByImpl::input"); // Use an HTTP proxy if the target is an absolute :// target if (http::isAbsolute(uri)) { gc_pool p(currentRequest->pool); // Interpret a uri in the form app://appname, convert it using the scheme, // top level domain and port number from the current request if (http::scheme(uri, p) == "app") { ostringstream appuri; appuri << httpd::scheme(currentRequest) << "://" << substr(uri, 6) << "." << http::topDomain(httpd::hostName(currentRequest)) << ":" << httpd::port(currentRequest) << "/"; debug(str(appuri), "modeval::implProxy::httpproxy::appuri"); const lambda&)> px = lambda&)>(http::proxy(str(appuri), sc.ca, sc.cert, sc.key, httpd::cookie(currentRequest), p)); return px(aparams); } // Pass our certificate and the cookie from the current request // only if the target is in the same top level domain if (http::topDomain(http::hostName(uri, p)) == http::topDomain(httpd::hostName(currentRequest))) { debug(uri, "modeval::implProxy::httpproxy::samedomain"); const lambda&)> px = lambda&)>(http::proxy(uri, sc.ca, sc.cert, sc.key, httpd::cookie(currentRequest), p)); return px(aparams); } // No certificate or cookie on a cross domain call debug(uri, "modeval::implProxy::httpproxy::crossdomain"); const lambda&)> px = lambda&)>(http::proxy(uri, "", "", "", "", p)); return px(aparams); } // Call the component implementation return callImpl(uri, aparams); } // Call the component implementation return callImpl(name, params); } private: const ServerConf& sc; const value name; }; const value mkimplProxy(const ServerConf& sc, const value& name) { debug(name, "modeval::implProxy::impl"); return lambda&)>(implProxy(sc, name)); } /** * Return a proxy lambda for an unwired reference. */ class unwiredProxy { public: unwiredProxy(const value& name) : name(name) { } const value operator()(const list& params) const { debug(name, "modeval::unwiredProxy::name"); debug(params, "modeval::unwiredProxy::input"); // Get function returns a default empty value if (car(params) == "get") { debug(value(), "modeval::unwiredProxy::result"); return value(); } // All other functions return a failure return mkfailure(string("Reference is not wired: ") + name); } private: const value name; }; /** * Make a proxy lambda for an unwired reference. */ const value mkunwiredProxy(const string& ref) { debug(ref, "modeval::mkunwiredProxy::ref"); return lambda&)>(unwiredProxy(ref)); } /** * Convert a list of component references to a list of proxy lambdas. */ const value mkrefProxy(const ServerConf& sc, const value& ref, unused const string& base) { const value target = scdl::target(ref); const bool wbyimpl = scdl::wiredByImpl(ref); debug(ref, "modeval::mkrefProxy::ref"); debug(target, "modeval::mkrefProxy::target"); debug(wbyimpl, "modeval::mkrefProxy::wiredByImpl"); // Use an HTTP proxy or an internal proxy to the component implementation if (wbyimpl) return mkimplProxy(sc, value()); if (isNil(target)) return mkunwiredProxy(scdl::name(ref)); if (http::isAbsolute(target)) return mkhttpProxy(sc, target); return mkimplProxy(sc, car(pathValues(target))); } const list refProxies(const ServerConf& sc, const list& refs, const string& base) { if (isNil(refs)) return refs; return cons(mkrefProxy(sc, car(refs), base), refProxies(sc, cdr(refs), base)); } /** * Convert a list of component properties to a list of lambda functions that just return * the property value. The host, user and email properties are configured with the values * from the HTTP request, if any. */ struct propProxy { const value v; propProxy(const value& v) : v(v) { } const value operator()(unused const list& params) const { return v; } }; struct hostPropProxy { const value v; hostPropProxy(const value& v) : v(v) { } const value operator()(unused const list& params) const { const value r = httpd::hostName(currentRequest, v); debug(r, "modeval::hostPropProxy::value"); return r; } }; struct pathPropProxy { pathPropProxy(unused const value& v) { } const value operator()(unused const list& params) const { const value v = pathValues(currentRequest->uri); debug(v, "modeval::pathPropProxy::value"); return v; } }; struct queryPropProxy { queryPropProxy(unused const value& v) { } const value operator()(unused const list& params) const { const value v = httpd::unescapeArgs(httpd::queryArgs(currentRequest)); debug(v, "modeval::queryPropProxy::value"); return v; } }; struct envPropProxy { const string name; const value v; envPropProxy(const string& name, const value& v) : name(name), v(v) { } const value operator()(unused const list& params) const { const char* env = apr_table_get(currentRequest->subprocess_env, c_str(name)); if (env == NULL || *env == '\0') return v; return string(env); } }; struct userPropProxy { const value v; userPropProxy(const value& v) : v(v) { } const value operator()(unused const list& params) const { if (currentRequest->user == NULL) return v; return string(currentRequest->user); } }; const value mkpropProxy(const value& prop) { const value n = scdl::name(prop); const value v = elementHasValue(prop)? elementValue(prop):value(string("")); if (n == "host") return lambda&)>(hostPropProxy(v)); if (n == "path") return lambda&)>(pathPropProxy(v)); if (n == "query") return lambda&)>(queryPropProxy(v)); if (n == "user") return lambda&)>(userPropProxy(v)); if (n == "realm") return lambda&)>(envPropProxy("REALM", v)); if (n == "email") return lambda&)>(envPropProxy("EMAIL", v)); if (n == "nickname") return lambda&)>(envPropProxy("NICKNAME", v)); if (n == "fullname") return lambda&)>(envPropProxy("FULLNAME", v)); if (n == "firstname") return lambda&)>(envPropProxy("FIRSTNAME", v)); if (n == "lastname") return lambda&)>(envPropProxy("LASTNAME", v)); return lambda&)>(propProxy(v)); } const list propProxies(const list& props) { if (isNil(props)) return props; return cons(mkpropProxy(car(props)), propProxies(cdr(props))); } /** * Evaluate a component and convert it to an applicable lambda function. */ struct implementationFailure { const value reason; implementationFailure(const value& r) : reason(r) { } // Default implementation representing an implementation that // couldn't be evaluated. Report the evaluation error on all // subsequent calls expect start/stop. const value operator()(unused const list& params) const { const value func = car(params); if (func == "start" || func == "stop") return mklist(lambda&)>()); return mklist(value(), reason); } }; const value evalComponent(ServerConf& sc, const value& comp) { extern const failable&)> > evalImplementation(const string& cpath, const value& impl, const list& px, const lambda&)>& lifecycle); const value impl = scdl::implementation(comp); debug(comp, "modeval::evalComponent::comp"); debug(impl, "modeval::evalComponent::impl"); // Convert component references to configured proxy lambdas ostringstream base; base << sc.wiringServerName << "/references/" << string(scdl::name(comp)) << "/"; const list rpx(refProxies(sc, scdl::references(comp), str(base))); // Convert component properties to configured proxy lambdas const list ppx(propProxies(scdl::properties(comp))); // Evaluate the component implementation and convert it to an applicable lambda function const failable&)> > cimpl(evalImplementation(sc.contributionPath, impl, append(rpx, ppx), sc.lifecycle)); if (!hasContent(cimpl)) return lambda&)>(implementationFailure(reason(cimpl))); return content(cimpl); } /** * Return a list of component-name + configured-implementation pairs. */ const list componentToImplementationAssoc(ServerConf& sc, const list& c) { if (isNil(c)) return c; return cons(mklist(scdl::name(car(c)), evalComponent(sc, car(c))), componentToImplementationAssoc(sc, cdr(c))); } /** * Read the components declared in a composite. */ const failable > readComponents(const string& path) { ifstream is(path); if (fail(is)) return mkfailure >(string("Could not read composite: ") + path); return scdl::components(readXML(streamList(is))); } /** * Apply a list of component implementations to a start or restart lifecycle expression. * Return the functions returned by the component implementations. */ const failable > applyLifecycleExpr(const list& impls, const list& expr) { if (isNil(impls)) return list(); // Evaluate lifecycle expression against a component implementation lambda const lambda&)> l = cadr(car(impls)); const failable r = failableResult(l(expr)); if (!hasContent(r)) return mkfailure >(reason(r)); const lambda&)> rl = content(r); // Use the returned lambda function, if any, from now on const lambda&)> al = isNil(rl)? l : rl; // Continue with the rest of the list const failable > nr = applyLifecycleExpr(cdr(impls), expr); if (!hasContent(nr)) return nr; return cons(mklist(car(car(impls)), value(al)), content(nr)); } /** * Configure the components declared in the deployed composite. */ const failable confComponents(ServerConf& sc) { if (!hasCompositeConf(sc)) return false; debug(sc.contributionPath, "modeval::confComponents::contributionPath"); 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 const failable > comps = readComponents(sc.contributionPath + sc.compositeName); if (!hasContent(comps)) return mkfailure(reason(comps)); sc.implementations = componentToImplementationAssoc(sc, content(comps)); debug(sc.implementations, "modeval::confComponents::implementations"); return true; } /** * Start the components declared in the deployed composite. */ const failable startComponents(ServerConf& sc) { // Start the components and record the returned implementation lambda functions debug(sc.implementations, "modeval::startComponents::start"); const failable > impls = applyLifecycleExpr(sc.implementations, mklist("start")); if (!hasContent(impls)) return mkfailure(reason(impls)); sc.implementations = content(impls); debug(sc.implementations, "modeval::startComponents::implementations"); return true; } /** * Virtual host scoped server configuration. */ class VirtualHostConf { public: VirtualHostConf(const gc_pool& p, const ServerConf& sc) : sc(sc), vsc(pool(p), sc.server) { vsc.lifecycle = sc.lifecycle; vsc.virtualHostContributionPath = sc.virtualHostContributionPath; vsc.virtualHostCompositeName = sc.virtualHostCompositeName; vsc.ca = sc.ca; vsc.cert = sc.cert; vsc.key = sc.key; } ~VirtualHostConf() { extern const failable virtualHostCleanup(const ServerConf& vsc, const ServerConf& sc); virtualHostCleanup(vsc, sc); } const ServerConf& sc; ServerConf vsc; }; /** * Configure and start the components deployed in a virtual host. */ const failable virtualHostConfig(ServerConf& vsc, const ServerConf& sc, request_rec* r) { // Determine the server name and wiring server name debug(httpd::serverName(vsc.server), "modeval::virtualHostConfig::serverName"); debug(httpd::serverName(r), "modeval::virtualHostConfig::virtualHostName"); vsc.wiringServerName = httpd::serverName(r); debug(vsc.wiringServerName, "modeval::virtualHostConfig::wiringServerName"); debug(vsc.virtualHostContributionPath, "modwiring::virtualHostConfig::virtualHostContributionPath"); // Resolve the configured virtual contribution under // the virtual host's SCA contribution root vsc.contributionPath = vsc.virtualHostContributionPath + http::subDomain(httpd::hostName(r)) + "/"; vsc.compositeName = vsc.virtualHostCompositeName; // Chdir to the virtual host's contribution if (chdir(c_str(sc.contributionPath)) != 0) return mkfailure(string("Couldn't chdir to the deployed contribution: ") + sc.contributionPath); // Configure the deployed components const failable cr = confComponents(vsc); if (!hasContent(cr)) return cr; // Start the configured components const failable sr = startComponents(vsc); if (!hasContent(sr)) return sr; // Store the implementation lambda functions (from both the virtual host and the // main server) in a tree for fast retrieval vsc.implTree = mkbtree(sort(append(vsc.implementations, sc.implementations))); return true; } /** * Cleanup a virtual host. */ const failable virtualHostCleanup(const ServerConf& vsc, const ServerConf& sc) { if (!hasCompositeConf(vsc)) return true; debug("modeval::virtualHostCleanup"); // Stop the component implementations applyLifecycleExpr(vsc.implementations, mklist("stop")); // Chdir back to the main server's contribution if (chdir(c_str(sc.contributionPath)) != 0) return mkfailure(string("Couldn't chdir to the deployed contribution: ") + sc.contributionPath); return true; } /** * HTTP request handler. */ 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_eval")) return DECLINED; // Create a scoped memory pool gc_scoped_pool pool(r->pool); ScopedRequest sr(r); httpdDebugRequest(r, "modeval::handler::input"); // Get the server configuration const ServerConf& sc = httpd::serverConf(r, &mod_tuscany_eval); // Process dynamic virtual host configuration, if any VirtualHostConf vhc(gc_pool(r->pool), sc); const bool usevh = hasVirtualCompositeConf(vhc.vsc) && httpd::isVirtualHostRequest(sc.server, r); if (usevh) { const failable cr = virtualHostConfig(vhc.vsc, sc, r); if (!hasContent(cr)) return httpd::reportStatus(mkfailure(reason(cr))); } // Get the component implementation lambda const list path(pathValues(r->uri)); if (isNil(cdr(path))) return HTTP_NOT_FOUND; const list impl(assoctree(cadr(path), usevh? vhc.vsc.implTree : sc.implTree)); if (isNil(impl)) { mkfailure(string("Couldn't find component implementation: ") + cadr(path)); return HTTP_NOT_FOUND; } // Handle HTTP method const lambda&)> l(cadr(impl)); if (r->header_only) return OK; if(r->method_number == M_GET) return httpd::reportStatus(get(r, l)); if(r->method_number == M_POST) return httpd::reportStatus(post(r, l)); if(r->method_number == M_PUT) return httpd::reportStatus(put(r, l)); if(r->method_number == M_DELETE) return httpd::reportStatus(del(r, l)); return HTTP_NOT_IMPLEMENTED; } /** * Cleanup callback, called when the server is stopped or restarted. */ apr_status_t serverCleanup(void* v) { gc_pool pool; ServerConf& sc = *(ServerConf*)v; debug("modeval::serverCleanup"); // Stop the component implementations applyLifecycleExpr(sc.implementations, mklist("stop")); // Call the module lifecycle function if (isNil(sc.lifecycle)) return APR_SUCCESS; debug("modeval::serverCleanup::stop"); sc.lifecycle(mklist("stop")); return APR_SUCCESS; } /** * Called after all the configuration commands have been run. * Process the server configuration and configure the deployed components. */ const int postConfigMerge(const ServerConf& mainsc, server_rec* s) { if (s == NULL) return OK; ServerConf& sc = httpd::serverConf(s, &mod_tuscany_eval); debug(httpd::serverName(s), "modeval::postConfigMerge::serverName"); if (sc.wiringServerName == "") sc.wiringServerName = mainsc.wiringServerName != ""? mainsc.wiringServerName : httpd::serverName(s); debug(sc.wiringServerName, "modeval::postConfigMerge::wiringServerName"); sc.lifecycle = mainsc.lifecycle; sc.contributionPath = mainsc.contributionPath; sc.compositeName = mainsc.compositeName; sc.virtualHostContributionPath = mainsc.virtualHostContributionPath; sc.virtualHostCompositeName = mainsc.virtualHostCompositeName; if (sc.ca == "") sc.ca = mainsc.ca; if (sc.cert == "") sc.cert = mainsc.cert; if (sc.key == "") sc.key = mainsc.key; sc.implementations = mainsc.implementations; sc.implTree = mainsc.implTree; return postConfigMerge(mainsc, s->next); } int postConfig(apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp, server_rec *s) { extern const value applyLifecycle(const list&); gc_scoped_pool pool(p); // Get the server configuration and determine the wiring server name ServerConf& sc = httpd::serverConf(s, &mod_tuscany_eval); debug(httpd::serverName(s), "modeval::postConfig::serverName"); if (sc.wiringServerName == "") sc.wiringServerName = httpd::serverName(s); debug(sc.wiringServerName, "modeval::postConfig::wiringServerName"); // Count the calls to post config const string k("tuscany::modeval::postConfig"); const long int count = (long int)httpd::userData(k, s); httpd::putUserData(k, (void*)(count + 1), s); // Count == 0, do nothing as post config is always called twice, // count == 1 is the first start, count > 1 is a restart if (count == 0) return OK; if (count == 1) { // Chdir to the deployed contribution if (chdir(c_str(sc.contributionPath)) != 0) { mkfailure(string("Couldn't chdir to the deployed contribution: ") + sc.contributionPath); return -1; } debug("modeval::postConfig::start"); const failable r = failableResult(applyLifecycle(mklist("start"))); if (!hasContent(r)) return -1; debug("modeval::postConfig::setlifecycle"); sc.lifecycle = content(r); } if (count > 1) { debug("modeval::postConfig::restart"); const failable r = failableResult(applyLifecycle(mklist("restart"))); if (!hasContent(r)) return -1; debug("modeval::postConfig::setlifecycle"); sc.lifecycle = content(r); } // Configure the deployed components const failable res = confComponents(sc); if (!hasContent(res)) { cfailure << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl; return -1; } // Register a cleanup callback, called when the server is stopped or restarted apr_pool_pre_cleanup_register(p, (void*)&sc, serverCleanup); // Merge the configuration into the virtual hosts return postConfigMerge(sc, s->next); } /** * Child process initialization. */ void childInit(apr_pool_t* p, server_rec* s) { gc_scoped_pool pool(p); ServerConf* psc = (ServerConf*)ap_get_module_config(s->module_config, &mod_tuscany_eval); if(psc == NULL) { cfailure << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl; exit(APEXIT_CHILDFATAL); } ServerConf& sc = *psc; // Start the components in the child process const failable res = startComponents(sc); if (!hasContent(res)) { cfailure << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl; exit(APEXIT_CHILDFATAL); } // Store the implementation lambda functions in a tree for fast retrieval sc.implTree = mkbtree(sort(sc.implementations)); // Merge the updated configuration into the virtual hosts postConfigMerge(sc, s->next); // Register a cleanup callback, called when the child is stopped or restarted apr_pool_pre_cleanup_register(p, (void*)psc, serverCleanup); } /** * Configuration commands. */ const char* confWiringServerName(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_eval); sc.wiringServerName = arg; return NULL; } const char* confContribution(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_eval); 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(cmd, &mod_tuscany_eval); sc.compositeName = 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(cmd, &mod_tuscany_eval); 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(cmd, &mod_tuscany_eval); sc.virtualHostCompositeName = arg; return NULL; } const char* confCAFile(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_eval); sc.ca = arg; return NULL; } const char* confCertFile(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_eval); sc.cert = arg; return NULL; } const char* confCertKeyFile(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_eval); sc.key = arg; return NULL; } const char* confEnv(unused cmd_parms *cmd, unused void *c, const char *name, const char *value) { gc_scoped_pool pool(cmd->pool); setenv(name, value != NULL? value : "", 1); return NULL; } /** * HTTP server module declaration. */ const command_rec commands[] = { AP_INIT_TAKE1("SCAWiringServerName", (const char*(*)())confWiringServerName, NULL, RSRC_CONF, "SCA wiring server name"), 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("SCAVirtualContribution", (const char*(*)())confVirtualContribution, NULL, RSRC_CONF, "SCA virtual host contribution location"), 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"), AP_INIT_TAKE1("SCAWiringSSLCertificateFile", (const char*(*)())confCertFile, NULL, RSRC_CONF, "SCA wiring SSL certificate file"), AP_INIT_TAKE1("SCAWiringSSLCertificateKeyFile", (const char*(*)())confCertKeyFile, NULL, RSRC_CONF, "SCA wiring SSL certificate key file"), {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_eval = { STANDARD20_MODULE_STUFF, // dir config and merger NULL, NULL, // server config and merger tuscany::httpd::makeServerConf, NULL, // commands and hooks tuscany::server::modeval::commands, tuscany::server::modeval::registerHooks }; } #endif