blob: 00f59b9dda28baead400ac06ee0ed2a8f2346766 [file] [log] [blame]
/*
* 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<value(const list<value>&)> lifecycle;
string wiringServerName;
string contributionPath;
string compositeName;
string virtualHostContributionPath;
string virtualHostCompositeName;
string ca;
string cert;
string key;
list<value> implementations;
list<value> 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<value> failableResult(const list<value>& v) {
if (isNil(cdr(v)))
return car(v);
return mkfailure<value>(string(cadr(v)));
}
/**
* Store current HTTP request for access from property and proxy lambda functions.
*/
#ifdef WANT_THREADS
perthread_ptr<request_rec> 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<int> get(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> path(pathValues(r->uri));
const list<value> params(append<value>(cddr(path), 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(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 list<value> path(pathValues(r->uri));
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(path), 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(request_rec* r, const lambda<value(const list<value>&)>& impl) {
debug(r->uri, "modeval::put::url");
// Read the ATOM entry
const list<value> path(pathValues(r->uri));
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(path), 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(request_rec* r, const lambda<value(const list<value>&)>& impl) {
debug(r->uri, "modeval::delete::url");
// Evaluate an ATOM delete request
const list<value> path(pathValues(r->uri));
const failable<value> val = failableResult(impl(cons<value>("delete", mklist<value>(cddr(path)))));
if (!hasContent(val))
return mkfailure<int>(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<value(const list<value>&)>(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<value(const list<value>&)>(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<value>& aparams) const {
// Lookup the component implementation
const list<value> impl(assoctree<value>(cname, sc.implTree));
if (isNil(impl))
return mkfailure<value>(string("Couldn't find component implementation: ") + cname);
// Call its lambda function
const lambda<value(const list<value>&)> l(cadr<value>(impl));
const value func = c_str(car(aparams));
const failable<value> 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<value>& 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<value> aparams = cons<value>(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<value(const list<value>&)> px = lambda<value(const list<value>&)>(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<value(const list<value>&)> px = lambda<value(const list<value>&)>(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<value(const list<value>&)> px = lambda<value(const list<value>&)>(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<value(const list<value>&)>(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<value>& 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<value>(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<value(const list<value>&)>(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<value> refProxies(const ServerConf& sc, const list<value>& 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<value>& params) const {
return v;
}
};
struct hostPropProxy {
const value v;
hostPropProxy(const value& v) : v(v) {
}
const value operator()(unused const list<value>& 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<value>& 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<value>& 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<value>& 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<value>& 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<value(const list<value>&)>(hostPropProxy(v));
if (n == "path")
return lambda<value(const list<value>&)>(pathPropProxy(v));
if (n == "query")
return lambda<value(const list<value>&)>(queryPropProxy(v));
if (n == "user")
return lambda<value(const list<value>&)>(userPropProxy(v));
if (n == "realm")
return lambda<value(const list<value>&)>(envPropProxy("REALM", v));
if (n == "email")
return lambda<value(const list<value>&)>(envPropProxy("EMAIL", v));
if (n == "nickname")
return lambda<value(const list<value>&)>(envPropProxy("NICKNAME", v));
if (n == "fullname")
return lambda<value(const list<value>&)>(envPropProxy("FULLNAME", v));
if (n == "firstname")
return lambda<value(const list<value>&)>(envPropProxy("FIRSTNAME", v));
if (n == "lastname")
return lambda<value(const list<value>&)>(envPropProxy("LASTNAME", v));
return lambda<value(const list<value>&)>(propProxy(v));
}
const list<value> propProxies(const list<value>& 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<value>& params) const {
const value func = car(params);
if (func == "start" || func == "stop")
return mklist<value>(lambda<value(const list<value>&)>());
return mklist<value>(value(), reason);
}
};
const value evalComponent(ServerConf& sc, const value& comp) {
extern const failable<lambda<value(const list<value>&)> > evalImplementation(const string& cpath, const value& impl, const list<value>& px, const lambda<value(const list<value>&)>& 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<value> rpx(refProxies(sc, scdl::references(comp), str(base)));
// Convert component properties to configured proxy lambdas
const list<value> ppx(propProxies(scdl::properties(comp)));
// Evaluate the component implementation and convert it to an applicable lambda function
const failable<lambda<value(const list<value>&)> > cimpl(evalImplementation(sc.contributionPath, impl, append(rpx, ppx), sc.lifecycle));
if (!hasContent(cimpl))
return lambda<value(const list<value>&)>(implementationFailure(reason(cimpl)));
return content(cimpl);
}
/**
* Return a list of component-name + configured-implementation pairs.
*/
const list<value> componentToImplementationAssoc(ServerConf& sc, const list<value>& c) {
if (isNil(c))
return c;
return cons<value>(mklist<value>(scdl::name(car(c)), evalComponent(sc, car(c))), componentToImplementationAssoc(sc, cdr(c)));
}
/**
* 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)));
}
/**
* Apply a list of component implementations to a start or restart lifecycle expression.
* Return the functions returned by the component implementations.
*/
const failable<list<value> > applyLifecycleExpr(const list<value>& impls, const list<value>& expr) {
if (isNil(impls))
return list<value>();
// Evaluate lifecycle expression against a component implementation lambda
const lambda<value(const list<value>&)> l = cadr<value>(car(impls));
const failable<value> r = failableResult(l(expr));
if (!hasContent(r))
return mkfailure<list<value> >(reason(r));
const lambda<value(const list<value>&)> rl = content(r);
// Use the returned lambda function, if any, from now on
const lambda<value(const list<value>&)> al = isNil(rl)? l : rl;
// Continue with the rest of the list
const failable<list<value> > nr = applyLifecycleExpr(cdr(impls), expr);
if (!hasContent(nr))
return nr;
return cons<value>(mklist<value>(car<value>(car(impls)), value(al)), content(nr));
}
/**
* Configure the components declared in the deployed composite.
*/
const failable<bool> 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<list<value> > comps = readComponents(sc.contributionPath + sc.compositeName);
if (!hasContent(comps))
return mkfailure<bool>(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<bool> startComponents(ServerConf& sc) {
// Start the components and record the returned implementation lambda functions
debug(sc.implementations, "modeval::startComponents::start");
const failable<list<value> > impls = applyLifecycleExpr(sc.implementations, mklist<value>("start"));
if (!hasContent(impls))
return mkfailure<bool>(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<bool> 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<bool> 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<bool>(string("Couldn't chdir to the deployed contribution: ") + sc.contributionPath);
// Configure the deployed components
const failable<bool> cr = confComponents(vsc);
if (!hasContent(cr))
return cr;
// Start the configured components
const failable<bool> 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<bool> virtualHostCleanup(const ServerConf& vsc, const ServerConf& sc) {
if (!hasCompositeConf(vsc))
return true;
debug("modeval::virtualHostCleanup");
// Stop the component implementations
applyLifecycleExpr(vsc.implementations, mklist<value>("stop"));
// Chdir back to the main server's contribution
if (chdir(c_str(sc.contributionPath)) != 0)
return mkfailure<bool>(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<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<bool> cr = virtualHostConfig(vhc.vsc, sc, r);
if (!hasContent(cr))
return httpd::reportStatus(mkfailure<int>(reason(cr)));
}
// Get the component implementation lambda
const list<value> path(pathValues(r->uri));
if (isNil(cdr(path)))
return HTTP_NOT_FOUND;
const list<value> impl(assoctree<value>(cadr(path), usevh? vhc.vsc.implTree : sc.implTree));
if (isNil(impl)) {
mkfailure<int>(string("Couldn't find component implementation: ") + cadr(path));
return HTTP_NOT_FOUND;
}
// Handle HTTP method
const lambda<value(const list<value>&)> l(cadr<value>(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<value>("stop"));
// Call the module lifecycle function
if (isNil(sc.lifecycle))
return APR_SUCCESS;
debug("modeval::serverCleanup::stop");
sc.lifecycle(mklist<value>("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<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<value>&);
gc_scoped_pool pool(p);
// Get the server configuration and determine the wiring server name
ServerConf& sc = httpd::serverConf<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<bool>(string("Couldn't chdir to the deployed contribution: ") + sc.contributionPath);
return -1;
}
debug("modeval::postConfig::start");
const failable<value> r = failableResult(applyLifecycle(mklist<value>("start")));
if (!hasContent(r))
return -1;
debug("modeval::postConfig::setlifecycle");
sc.lifecycle = content(r);
}
if (count > 1) {
debug("modeval::postConfig::restart");
const failable<value> r = failableResult(applyLifecycle(mklist<value>("restart")));
if (!hasContent(r))
return -1;
debug("modeval::postConfig::setlifecycle");
sc.lifecycle = content(r);
}
// Configure the deployed components
const failable<bool> 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<bool> 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<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<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<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<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<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<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<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<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<tuscany::server::modeval::ServerConf>, NULL,
// commands and hooks
tuscany::server::modeval::commands, tuscany::server::modeval::registerHooks
};
}
#endif