/* * Licensed to the Apache Software Foundation (ASF) under one * or more provider 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 #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 { /** * SSL certificate configuration. */ class SSLConf { public: SSLConf() { } string ca; string cert; string key; }; /** * Virtual host configuration. */ class VhostConf { public: VhostConf() { } string domain; string contribPath; string composName; string providerName; value provider; }; /** * Contribution configuration. */ class ContribConf { public: ContribConf() { } string contribPath; string composName; }; /** * Composite assocs. */ class Composite { public: Composite() { } Composite(const list& refs, const list& svcs, const list& impls) : refs(refs), svcs(svcs), impls(impls) { } list refs; list svcs; list impls; }; /** * Server configuration. */ class ServerConf { public: ServerConf() { } ServerConf(apr_pool_t* p, const server_rec* s) : p(p), server(s) { } const gc_pool p; const server_rec* server; lambda&)> lifecycle; ContribConf contribc; SSLConf sslc; VhostConf vhostc; Composite compos; }; /** * Request configuration. */ class RequestConf { public: RequestConf(apr_pool_t* p, const request_rec* r) : p(p), request(r), vhost(false), valias(false) { } const gc_pool p; const request_rec* request; bool vhost; bool valias; list rpath; list vpath; list impls; }; /** * 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)), false); } /** * 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; } }; /** * Make an HTTP proxy lambda to an absolute URI */ const value mkhttpProxy(const string& uri, const gc_pool& p) { debug(uri, "modeval::mkhttpProxy::uri"); return lambda&)>(http::proxy(uri, "", "", "", "", p)); } /** * Return a component implementation proxy lambda. */ class implProxy { public: implProxy(const value& name, const list& impls, const SSLConf& sslc) : name(name), impls(impls), sslc(sslc) { } const value callImpl(const value& cname, const list& aparams) const { debug(impls, "modeval::implProxy::callImpl::impls"); // Lookup the component implementation const list impl(assoctree(cname, impls)); 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), sslc.ca, sslc.cert, sslc.key, httpd::cookie(currentRequest), p)); return px(aparams); } // Pass our SSL 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, sslc.ca, sslc.cert, sslc.key, httpd::cookie(currentRequest), p)); return px(aparams); } // No SSL 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 value name; const list& impls; const SSLConf& sslc; }; const value mkimplProxy(const value& name, const list& impls, const SSLConf& sslc) { debug(name, "modeval::implProxy::impl"); return lambda&)>(implProxy(name, impls, sslc)); } /** * 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 value& ref, const list& impls, const SSLConf& sslc, const gc_pool& p) { 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(value(), impls, sslc); if (isNil(target)) return mkunwiredProxy(scdl::name(ref)); if (http::isAbsolute(target)) return mkhttpProxy(target, p); return mkimplProxy(car(pathValues(target)), impls, sslc); } const list refProxies(const list& refs, const list& impls, const SSLConf& sslc, const gc_pool& p) { if (isNil(refs)) return refs; return cons(mkrefProxy(car(refs), impls, sslc, p), refProxies(cdr(refs), impls, sslc, p)); } /** * 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 appPropProxy { const value v; appPropProxy(const value& v) : v(v) { } const value operator()(unused const list& params) const { const RequestConf& reqc = httpd::requestConf(currentRequest, &mod_tuscany_eval); const value a = isNil(reqc.vpath)? v : car(reqc.vpath); debug(a, "modeval::appPropProxy::value"); return a; } }; struct pathPropProxy { pathPropProxy(unused const value& v) { } const value operator()(unused const list& params) const { const RequestConf& reqc = httpd::requestConf(currentRequest, &mod_tuscany_eval); const value v = reqc.rpath; 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 == "app") return lambda&)>(appPropProxy(v)); 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(const string& contribPath, const value& comp, const list& impls, const lambda&)> lifecycle, const SSLConf& sslc, const gc_pool& p) { 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 const list rpx(refProxies(scdl::references(comp), impls, sslc, p)); // 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(contribPath, impl, append(rpx, ppx), lifecycle)); if (!hasContent(cimpl)) return lambda&)>(implementationFailure(reason(cimpl))); return content(cimpl); } /** * Return a list of component-name + configured-implementation pairs. */ const list componentToImplementationAssoc(const list& c, const string& contribPath, const list& impls, const lambda&)> lifecycle, const SSLConf& sslc, const gc_pool& p) { if (isNil(c)) return c; return cons(mklist(scdl::name(car(c)), evalComponent(contribPath, car(c), impls, lifecycle, sslc, p)), componentToImplementationAssoc(cdr(c), contribPath, impls, lifecycle, sslc, p)); } /** * 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))); } /** * Get the components returned by a provider. */ const failable > getComponents(const lambda&)>& provider, const string& name) { const failable val = failableResult(provider(cons("get", mklist(mklist(name))))); if (!hasContent(val)) return mkfailure >(reason(val)); const list c = assoc(value("content"), (list >)cdr(content(val))); if (isNil(c)) return mkfailure >(string("Could not get composite: ") + name); const failable > x = writeXML(car(valuesToElements(mklist(mklist(cadr(c)))))); if (!hasContent(x)) return mkfailure >(reason(x)); return scdl::components(readXML(content(x))); } /** * 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)); } /** * Return a list of component-name + references pairs. The references are * arranged in trees of reference-name + reference-target pairs. */ const list componentReferenceToTargetTree(const value& c) { return mklist(scdl::name(c), mkbtree(sort(scdl::referenceToTargetAssoc(scdl::references(c))))); } const list componentReferenceToTargetAssoc(const list& c) { if (isNil(c)) return c; return cons(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 defaultBindingURI(const string& cn, const string& sn) { return mklist(cn, sn); } const list bindingToComponentAssoc(const string& cn, const string& sn, const list& b) { if (isNil(b)) return b; const value uri(scdl::uri(car(b))); if (isNil(uri)) return cons(mklist(defaultBindingURI(cn, sn), cn), bindingToComponentAssoc(cn, sn, cdr(b))); return cons(mklist(pathValues(c_str(string(uri))), cn), bindingToComponentAssoc(cn, sn, cdr(b))); } const list serviceToComponentAssoc(const string& cn, const list& s) { if (isNil(s)) return s; const string sn(scdl::name(car(s))); const list btoc(bindingToComponentAssoc(cn, sn, scdl::bindings(car(s)))); if (isNil(btoc)) return cons(mklist(defaultBindingURI(cn, sn), cn), serviceToComponentAssoc(cn, cdr(s))); return append(btoc, serviceToComponentAssoc(cn, cdr(s))); } const list uriToComponentAssoc(const list& c) { if (isNil(c)) return c; return append(serviceToComponentAssoc(scdl::name(car(c)), scdl::services(car(c))), uriToComponentAssoc(cdr(c))); } /** * Configure the components declared in the deployed composite. */ const failable confComponents(const string& contribPath, const string& composName, const value& provider, const string& vhost, const list& impls, const lambda&)> lifecycle, const SSLConf& sslc, const gc_pool& p) { debug(contribPath, "modeval::confComponents::contribPath"); debug(composName, "modeval::confComponents::composName"); debug(provider, "modeval::confComponents::provider"); debug(vhost, "modeval::confComponents::vhost"); debug(impls, "modeval::confComponents::impls"); const failable > fcomps = isNil(provider)? readComponents(scdl::resourcePath(length(vhost) != 0? contribPath + vhost + "/" : contribPath, composName)) : getComponents(provider, vhost); if (!hasContent(fcomps)) return mkfailure(reason(fcomps)); const list comps = content(fcomps); debug(comps, "modeval::confComponents::comps"); const list refs = mkbtree(sort(componentReferenceToTargetAssoc(comps))); debug(flatten(refs), "modeval::confComponents::refs"); const list svcs = mkbtree(sort(uriToComponentAssoc(comps))); debug(flatten(svcs), "modeval::confComponents::svcs"); const list cimpls = mkbtree(sort(componentToImplementationAssoc(comps, isNil(provider)? length(vhost) != 0? contribPath + vhost + "/" : contribPath : contribPath, impls, lifecycle, sslc, p))); debug(flatten(cimpls), "modeval::confComponents::impls"); return Composite(refs, svcs, cimpls); } /** * Start the components declared in a composite. */ const failable > startComponents(const list& impls) { debug(flatten(impls), "modeval::startComponents::impls"); const failable > fsimpls = applyLifecycleExpr(flatten(impls), mklist("start")); if (!hasContent(fsimpls)) return mkfailure >(reason(fsimpls)); const list simpls = content(fsimpls); debug(impls, "modeval::startComponents::simpls"); return mkbtree(sort(simpls)); } /** * Stop the components declared in a composite. */ const failable stopComponents(const list& simpls) { debug(flatten(simpls), "modeval::stopComponents::simpls"); applyLifecycleExpr(flatten(simpls), mklist("stop")); return true; } /** * Handle an HTTP GET. */ const failable get(const list& rpath, 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 params(cddr(rpath)); 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(const list& rpath, request_rec* r, const lambda&)>& 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 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 int rc = httpd::setupReadPolicy(r); if(rc != OK) return rc; const list ls = httpd::read(r); debug(ls, "modeval::post::input"); const value aval = elementsToValues(content(atom::isATOMEntry(ls)? atom::readATOMEntry(ls) : atom::readATOMFeed(ls))); // Evaluate the POST expression const failable val = failableResult(impl(cons("post", mklist(cddr(rpath), aval)))); 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(const list& rpath, request_rec* r, const lambda&)>& impl) { debug(r->uri, "modeval::put::uri"); // Read the ATOM entry const int rc = httpd::setupReadPolicy(r); if(rc != OK) return rc; const list ls = httpd::read(r); debug(ls, "modeval::put::input"); const value aval = elementsToValues(content(atom::isATOMEntry(ls)? atom::readATOMEntry(ls) : atom::readATOMFeed(ls))); // Evaluate the PUT expression and update the corresponding resource const failable val = failableResult(impl(cons("put", mklist(cddr(rpath), aval)))); if (!hasContent(val)) return mkfailure(reason(val)); if (val == value(false)) return HTTP_NOT_FOUND; return OK; } /** * Handle an HTTP DELETE. */ const failable del(const list& rpath, request_rec* r, const lambda&)>& impl) { debug(r->uri, "modeval::delete::uri"); // Evaluate an ATOM delete request const failable val = failableResult(impl(cons("delete", mklist(cddr(rpath))))); if (!hasContent(val)) return mkfailure(reason(val)); if (val == value(false)) return HTTP_NOT_FOUND; return OK; } /** * Proceed to handle a service component request. */ int proceedToHandler(request_rec* r, const bool valias, const list& rpath, const list& vpath, const list& impls) { r->handler = "mod_tuscany_eval"; r->filename = apr_pstrdup(r->pool, c_str(string("/redirect:") + r->uri)); // Store the selected vhost, path and composite in the request RequestConf& reqc = httpd::requestConf(r, &mod_tuscany_eval); reqc.valias = valias; reqc.rpath = rpath; reqc.vpath = vpath; reqc.impls = impls; return OK; } /** * Route a component request to the specified component. */ int translateComponent(request_rec *r, const list& rpath, const list& vpath, const list& impls) { debug(rpath, "modeval::translateComponent::rpath"); debug(flatten(impls), "modeval::translateComponent::impls"); // Find the requested component if (isNil(cdr(rpath))) return HTTP_NOT_FOUND; const list impl(assoctree(cadr(rpath), impls)); if (isNil(impl)) return HTTP_NOT_FOUND; debug(impl, "modeval::translateComponent::impl"); return proceedToHandler(r, false, rpath, vpath, impls);; } /** * Route a /references/component-name/reference-name request, * to the target of the component reference. */ int translateReference(request_rec *r, const list& rpath, const list& vpath, const list& refs, const list& impls) { debug(rpath, "modeval::translateReference::rpath"); debug(flatten(refs), "modeval::translateReference::refs"); // Find the requested component if (isNil(cdr(rpath))) return HTTP_NOT_FOUND; const list comp(assoctree(cadr(rpath), refs)); if (isNil(comp)) return HTTP_NOT_FOUND; debug(comp, "modeval::translateReference::comp"); // Find the requested reference and target configuration const list ref(assoctree(caddr(rpath), cadr(comp))); if (isNil(ref)) return HTTP_NOT_FOUND; debug(ref, "modeval::translateReference::ref"); 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 pathInfo = cdddr(rpath); if (http::isAbsolute(target)) { string turi = target + path(pathInfo) + (r->args != NULL? string("?") + 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; } // Route to a relative target URI using a local internal redirect // / c / target component name / request path info const value tname = substr(target, 0, find(target, '/')); const list redir = cons(string("c"), cons(tname, pathInfo)); debug(redir, "modeval::translateReference::redirect"); return proceedToHandler(r, false, redir, vpath, impls);; } /** * Find a leaf matching a request path in a tree of URI paths. */ const int matchPath(const list& k, const list& 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 assocPath(const value& k, const list& tree) { if (isNil(tree)) return tree; if (matchPath(k, car(car(tree)))) return car(tree); if (k < car(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(request_rec *r, const list& rpath, const list& vpath, const list& svcs, const list& impls) { debug(rpath, "modeval::translateService::rpath"); debug(flatten(svcs), "modeval::translateService::svcs"); // Find the requested component if (isNil(rpath)) return HTTP_NOT_FOUND; const list svc(assocPath(rpath, svcs)); if (isNil(svc)) return DECLINED; debug(svc, "modeval::translateService::svc"); // Dispatch to the target component using a local internal redirect // / c / target component name / request path info const list redir = cons(string("c"), cons(cadr(svc), httpd::pathInfo(rpath, car(svc)))); debug(redir, "modeval::translateService::redirect"); return proceedToHandler(r, false, redir, vpath, impls); } /** * Translate a request to the target app and component. */ const int translateRequest(request_rec* r, const list& rpath, const list& vpath, const list& refs, const list& svcs, const list& impls) { debug(vpath, "modeval::translateRequest::vpath"); debug(rpath, "modeval::translateRequest::rpath"); const string prefix = isNil(rpath)? "" : car(rpath); // Translate a component request if ((prefix == string("components") || prefix == string("c")) && translateComponent(r, rpath, vpath, impls) == OK) return OK; // Translate a component reference request if ((prefix == string("references") || prefix == string("r")) && translateReference(r, rpath, vpath, refs, impls) == OK) return OK; // Attempt to translate the request to a service request if (translateService(r, rpath, vpath, svcs, impls) == OK) return OK; // Attempt to map a request targeting the main host to an actual file if (isNil(vpath)) { const failable 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) { // Found the target file, let the default handler serve it debug(nr->filename, "modeval::translateRequest::file"); return DECLINED; } } else { // Make sure a document root request ends with a '/' using // an external redirect if (isNil(rpath) && r->uri[strlen(r->uri) - 1] != '/') { const string target = string(r->uri) + string("/") + (r->args != NULL? string("?") + string(r->args) : string("")); debug(target, "modeval::translateRequest::location"); r->handler = "mod_tuscany_eval"; return httpd::externalRedirect(target, r); } // If the request didn't match a service, reference or component, // redirect it to / v / app / path. This will allow mapping to // the actual app resource using HTTPD aliases. debug(true, "modeval::translateRequest::valias"); return proceedToHandler(r, true, rpath, vpath, impls); } return HTTP_NOT_FOUND; } /** * 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(r, &mod_tuscany_eval); // Parse the request path const list rpath = pathValues(r->uri); // Let default handler handle a resource request const string prefix = isNil(rpath)? "" : car(rpath); if (prefix == string("vhosts") || prefix == string("v")) return DECLINED; // Get the request configuration RequestConf& reqc = httpd::requestConf(r, &mod_tuscany_eval); // If the request is targeting a virtual host, configure the components // in that virtual host if (length(sc.vhostc.domain) != 0 && (length(sc.vhostc.contribPath) != 0 || !isNil(sc.vhostc.provider)) && httpd::isVhostRequest(sc.server, sc.vhostc.domain, r)) { const string vname = http::subDomain(httpd::hostName(r)); const failable fvcompos = confComponents(sc.vhostc.contribPath, sc.vhostc.composName, sc.vhostc.provider, vname, reqc.impls, sc.lifecycle, sc.sslc, sc.p); if (!hasContent(fvcompos)) return DECLINED; const Composite vcompos = content(fvcompos); // Flag the request as virtual host based reqc.vhost = true; // Translate the request reqc.impls = vcompos.impls; return translateRequest(r, rpath, mklist(vname), vcompos.refs, vcompos.svcs, reqc.impls); } // Translate a request targeting the main host const int rc = translateRequest(r, rpath, list(), sc.compos.refs, sc.compos.svcs, sc.compos.impls); if (rc != HTTP_NOT_FOUND) return rc; // Attempt to map the first segment of the request path to a virtual host if (length(prefix) != 0 && (length(sc.vhostc.contribPath) != 0 || !isNil(sc.vhostc.provider))) { const string vname = prefix; const failable fvcompos = confComponents(sc.vhostc.contribPath, sc.vhostc.composName, sc.vhostc.provider, vname, reqc.impls, sc.lifecycle, sc.sslc, sc.p); if (!hasContent(fvcompos)) return DECLINED; const Composite vcompos = content(fvcompos); // Translate the request reqc.impls = vcompos.impls; return translateRequest(r, cdr(rpath), mklist(vname), vcompos.refs, vcompos.svcs, reqc.impls); } return DECLINED; } /** * Handle a component request. */ const int handleRequest(const list& rpath, request_rec *r, const list& impls) { debug(rpath, "modeval::handleRequest::path"); // Get the component implementation lambda const list impl(assoctree(cadr(rpath), impls)); if (isNil(impl)) { mkfailure(string("Couldn't find component implementation: ") + cadr(rpath)); 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(rpath, r, l)); if(r->method_number == M_POST) return httpd::reportStatus(post(rpath, r, l)); if(r->method_number == M_PUT) return httpd::reportStatus(put(rpath, r, l)); if(r->method_number == M_DELETE) return httpd::reportStatus(del(rpath, r, l)); return HTTP_NOT_IMPLEMENTED; } /** * 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; // Nothing to do for an external redirect if (r->status == HTTP_MOVED_TEMPORARILY) return OK; // Create a scoped memory pool and a scope for the current request gc_scoped_pool pool(r->pool); ScopedRequest sr(r); httpdDebugRequest(r, "modeval::handler::input"); // Get the request configuration RequestConf& reqc = httpd::requestConf(r, &mod_tuscany_eval); // Handle an internal redirect as directed by the translate hook if (reqc.valias) { const string redir = path(cons(string("v"), reqc.vhost? reqc.vpath : list())) + string(r->uri) + (r->args != NULL? string("?") + string(r->args) : string("")); debug(redir, "modeval::handler::internalredirect"); return httpd::internalRedirect(redir, r); } if (isNil(reqc.rpath)) return HTTP_NOT_FOUND; // Get the server configuration const ServerConf& sc = httpd::serverConf(r, &mod_tuscany_eval); // Handle a request targeting a component in a virtual host if (!isNil(reqc.vpath)) { // Start the components in the virtual host const failable > fsimpls = startComponents(reqc.impls); if (!hasContent(fsimpls)) return HTTP_INTERNAL_SERVER_ERROR; const list simpls = content(fsimpls); // Merge the components in the virtual host with the components in the main host reqc.impls = mkbtree(sort(append(flatten(sc.compos.impls), flatten(simpls)))); // Handle the request against the running components const int rc = handleRequest(reqc.rpath, r, reqc.impls); // Stop the components in the virtual host stopComponents(simpls); return rc; } // Handle a request targeting a component in the main host return handleRequest(reqc.rpath, r, sc.compos.impls); } /** * 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 stopComponents(sc.compos.impls); // 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"); sc.lifecycle = mainsc.lifecycle; sc.contribc = mainsc.contribc; sc.vhostc = mainsc.vhostc; if (sc.sslc.ca == "") sc.sslc.ca = mainsc.sslc.ca; if (sc.sslc.cert == "") sc.sslc.cert = mainsc.sslc.cert; if (sc.sslc.key == "") sc.sslc.key = mainsc.sslc.key; sc.compos = mainsc.compos; 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 server name ServerConf& sc = httpd::serverConf(s, &mod_tuscany_eval); debug(httpd::serverName(s), "modeval::postConfig::serverName"); debug(sc.contribc.contribPath, "modeval::postConfig::contribPath"); debug(sc.contribc.composName, "modeval::postConfig::composName"); // 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.contribc.contribPath)) != 0) { mkfailure(string("Couldn't chdir to the deployed contribution: ") + sc.contribc.contribPath); 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 compos = confComponents(sc.contribc.contribPath, sc.contribc.composName, value(), "", sc.compos.impls, sc.lifecycle, sc.sslc, sc.p); if (!hasContent(compos)) { cfailure << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl; return -1; } sc.compos = content(compos); // 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 > fsimpls = startComponents(sc.compos.impls); if (!hasContent(fsimpls)) { cfailure << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl; exit(APEXIT_CHILDFATAL); } sc.compos.impls = content(fsimpls); // Create a proxy for the vhost provider if needed if (length(sc.vhostc.providerName) != 0) sc.vhostc.provider = mkimplProxy(sc.vhostc.providerName, sc.compos.impls, sc.sslc); // 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* 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.contribc.contribPath = 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.contribc.composName = 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(cmd, &mod_tuscany_eval); sc.vhostc.domain = 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.vhostc.contribPath = arg; return NULL; } const char* confVirtualprovider(cmd_parms *cmd, unused void *c, const char *arg) { gc_scoped_pool pool(cmd->pool); ServerConf& sc = httpd::serverConf(cmd, &mod_tuscany_eval); sc.vhostc.providerName = 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.vhostc.composName = 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.sslc.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.sslc.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.sslc.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("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 path"), AP_INIT_TAKE1("SCAVirtualContributor", (const char*(*)())confVirtualprovider, NULL, RSRC_CONF, "SCA virtual host provider 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"), 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_LAST); } } } } 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