/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* $Rev$ $Date$ */ /** * HTTPD module used to eval component implementations. */ #include #include #include #include #include #include "list.hpp" #include "slist.hpp" #include "value.hpp" #include "element.hpp" #include "monad.hpp" #include "../atom/atom.hpp" #include "../json/json.hpp" #include "../eval/driver.hpp" #include "../scdl/scdl.hpp" #include "../cache/cache.hpp" #include "curl.hpp" #include "httpd.hpp" extern "C" { extern module AP_MODULE_DECLARE_DATA mod_tuscany_eval; } namespace tuscany { namespace httpd { namespace modeval { /** * Server configuration. */ class ServerConf { public: ServerConf() : home("") { } std::string home; }; const ServerConf& serverConf(const request_rec* r) { return *(ServerConf*)ap_get_module_config(r->server->module_config, &mod_tuscany_eval); } /** * Port number used for wiring requests. Set it to zero to use the current * server port number, set it to a port number to direct wiring requests * to that port, for debugging or tracing for example. */ int debugWiringPort = 0; /** * Directory configuration. */ class DirConf { public: DirConf() : contributionPath(""), compositeName(""), componentName(""), implementationPath("") { } std::string contributionPath; std::string compositeName; std::string componentName; std::string implementationPath; cache::cached > component; cache::cached > implementation; }; DirConf& dirConf(const request_rec* r) { return *(DirConf*)ap_get_module_config(r->per_dir_config, &mod_tuscany_eval); } /** * Evaluate an expression against a component implementation. */ const failable evalExpr(const value& expr, const value& impl) { gc_pool pool; eval::Env globalEnv = eval::setupEnvironment(pool); if (logContent) { std::cout<< "expr: " << expr << std::endl; std::cout.flush(); } const value val = eval::evalScript(expr, impl, globalEnv, pool); if (logContent) { std::cout<< "val: " << val << std::endl; std::cout.flush(); } if (isNil(val)) return mkfailure("Could not evaluate expression"); return val; } /** * Returns a list of param values other than the id and method args from a list * of key value pairs. */ const list queryParams(const list >& a) { if (isNil(a)) return list(); const list p = car(a); if (car(p) == value("id") || car(p) == value("method")) return queryParams(cdr(a)); return cons(cadr(p), queryParams(cdr(a))); } /** * Write an HTTP result. */ const failable writeResult(const failable, std::string>& ls, const std::string& ct, request_rec* r) { if (!hasValue(ls)) return mkfailure(reason(ls)); std::ostringstream os; write(ls, os); if (logContent) { std::cout<< "content: " << std::endl << os.str() << std::endl; std::cout.flush(); } const std::string etag(ap_md5(r->pool, (const unsigned char*)std::string(os.str()).c_str())); const char* match = apr_table_get(r->headers_in, "If-None-Match"); apr_table_setn(r->headers_out, "ETag", apr_pstrdup(r->pool, etag.c_str())); if (match != NULL && etag == match) { r->status = HTTP_NOT_MODIFIED; return OK; } ap_set_content_type(r, ct.c_str()); ap_rputs(std::string(os.str()).c_str(), r); return OK; } /** * Handle an HTTP GET. */ const failable get(request_rec* r, const value& impl, const list& px) { // Inspect the query string const list > args = 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 = std::string(cadr(ma)).c_str(); const list params = queryParams(args); // Evaluate the request expression const failable val = evalExpr(cons(func, eval::quotedParameters(append(params, px))), impl); if (!hasValue(val)) return mkfailure(reason(val)); // Return JSON result json::JSONContext cx; return writeResult(json::jsonResult(id, val, cx), "application/json-rpc", r); } // Evaluate an ATOM GET request and return an ATOM feed const list id(path(r->path_info)); if (isNil(id)) { const failable val = evalExpr(cons("getall", eval::quotedParameters(px)), impl); if (!hasValue(val)) return mkfailure(reason(val)); const value feed = val; return writeResult(atom::writeATOMFeed(atom::feedValuesToElements(feed)), "application/atom+xml;type=feed", r); } // Evaluate an ATOM GET and return an ATOM entry const failable val = evalExpr(cons("get", eval::quotedParameters(cons(car(id), px))), impl); if (!hasValue(val)) return mkfailure(reason(val)); const value entry = val; return writeResult(atom::writeATOMEntry(atom::entryValuesToElements(entry)), "application/atom+xml;type=entry", r); } /** * Read the content of a POST. */ const list read(request_rec* r) { char b[2048]; const int n = ap_get_client_block(r, b, 2048); if (n <= 0) return list(); return cons(std::string(b, n), read(r)); } /** * Convert a URI value to an absolute URL. */ const char* url(const value& v, request_rec* r) { std::string u = r->uri; u.append("/"); u.append(v); return ap_construct_url(r->pool, u.c_str(), r); } /** * Convert an ATOM entry to a value. */ const value feedEntry(const list& e) { const list v = elementsToValues(mklist(caddr(e))); return cons(car(e), mklist(cadr(e), cdr(car(v)))); } /** * Handle an HTTP POST. */ const failable post(request_rec* r, const value& impl, const list& px) { const list ls = read(r); if (logContent) { std::cout<< "content: " << std::endl; write(ls, std::cout); std::cout<< std::endl; std::cout.flush(); } // Evaluate a JSON-RPC request and return a JSON result const std::string ct = contentType(r); if (ct.find("application/json-rpc") != std::string::npos || ct.find("text/plain") != std::string::npos) { json::JSONContext cx; const list json = elementsToValues(json::readJSON(ls, cx)); const list > args = postArgs(json); // Extract the request id, method and params const value id = cadr(assoc(value("id"), args)); const value func = std::string(cadr(assoc(value("method"), args))).c_str(); const list params = (list)cadr(assoc(value("params"), args)); // Evaluate the request expression const failable val = evalExpr(cons(func, eval::quotedParameters(append(params, px))), impl); if (!hasValue(val)) return mkfailure(reason(val)); // Return JSON result return writeResult(json::jsonResult(id, val, cx), "application/json-rpc", r); } // Evaluate an ATOM POST request and return the created resource location if (ct.find("application/atom+xml") != std::string::npos) { // Evaluate the request expression const value entry = feedEntry(atom::readEntry(ls)); const failable val = evalExpr(cons("post", eval::quotedParameters(cons(entry, px))), impl); if (!hasValue(val)) return mkfailure(reason(val)); // Return the created resource location apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, url(val, r))); r->status = HTTP_CREATED; return OK; } return HTTP_NOT_IMPLEMENTED; } /** * Handle an HTTP PUT. */ const failable put(request_rec* r, const value& impl, const list& px) { const list ls = read(r); if (logContent) { std::cout<< "content: " << std::endl; write(ls, std::cout); std::cout<< std::endl; std::cout.flush(); } // Evaluate an ATOM PUT request const list id(path(r->path_info)); const value entry = feedEntry(atom::readEntry(ls)); const failable val = evalExpr(cons("put", eval::quotedParameters(append(mklist(entry, car(id)), px))), impl); if (!hasValue(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 value& impl, const list& px) { // Evaluate an ATOM delete request const list id(path(r->path_info)); const failable val = evalExpr(cons("delete", eval::quotedParameters(cons(car(id), px))), impl); if (!hasValue(val)) return mkfailure(reason(val)); if (val == value(false)) return HTTP_NOT_FOUND; return OK; } /** * Report request execution status. */ const int reportStatus(const failable& rc) { if (!hasValue(rc)) return HTTP_INTERNAL_SERVER_ERROR; return rc; } /** * Read the SCDL configuration of a component. */ const failable readComponent(const std::string& path, const std::string& name) { // Read composite std::ifstream is(path); if (is.fail() || is.bad()) return mkfailure("Could not read composite: " + path); // Return the component const list c = scdl::components(readXML(streamList(is))); const value comp = scdl::named(name, c); if (isNil(comp)) return mkfailure("Could not find component: " + name); return comp; } const cache::cached > component(DirConf* conf) { const std::string path(conf->contributionPath + conf->compositeName); const lambda(std::string, std::string)> rc(readComponent); const lambda ft(cache::latestFileTime); return cache::cached >(curry(rc, path, conf->componentName), curry(ft, path)); } /** * Read a component implementation. */ const failable readImplementation(const std::string path) { std::ifstream is(path.c_str(), std::ios_base::in); if (is.fail() || is.bad()) return mkfailure("Could not read implementation: " + path); const value impl = eval::readScript(is); if (isNil(impl)) return mkfailure("Could not read implementation: " + path); return impl; } const cache::cached > implementation(const std::string& path) { const lambda(std::string)> ri(readImplementation); const lambda ft(cache::latestFileTime); return cache::cached >(curry(ri, path), curry(ft, path)); } /** * Convert a list of component references to a list of HTTP proxy lambdas. */ const value mkproxy(const value& ref, const std::string& base, const http::CURLHandle& ch) { return eval::primitiveProcedure(http::proxy(base + std::string(scdl::name(ref)), ch)); } const list proxies(const list& refs, const std::string& base, const http::CURLHandle& ch) { if (isNil(refs)) return refs; return cons(mkproxy(car(refs), base, ch), proxies(cdr(refs), base, ch)); } /** * HTTP request handler. */ int handler(request_rec *r) { if(strcmp(r->handler, "mod_tuscany_eval")) return DECLINED; // Log the request if(logRequests) logRequest(r, "mod_tuscany_eval::handler"); // Set up the read policy const int rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); if(rc != OK) return rc; ap_should_client_block(r); if(r->read_chunked == true && r->remaining == 0) r->chunked = true; //apr_table_setn(r->headers_out, "Connection", "close"); // Retrieve the latest component configuration DirConf& conf = dirConf(r); conf.component = cache::latest(conf.component); const failable comp(conf.component); if (!hasValue(comp)) return HTTP_NOT_FOUND; // Retrieve the latest implementation const std::string path = conf.contributionPath + std::string(scdl::uri(scdl::implementation(comp))); if (path != conf.implementationPath) { conf.implementationPath = path; conf.implementation = cache::latest(implementation(path)); } else conf.implementation = cache::latest(conf.implementation); const failable impl(conf.implementation); if (!hasValue(impl)) return HTTP_NOT_FOUND; // Convert component references to configured proxy lambdas std::ostringstream base; base << "http://localhost:" << (debugWiringPort == 0? ap_get_server_port(r) : debugWiringPort) << "/references/" << std::string(scdl::name(comp)) << "/"; http::CURLHandle ch; const list px(proxies(scdl::references(comp), base.str(), ch)); // Handle HTTP method if (r->header_only) return OK; if(r->method_number == M_GET) return reportStatus(get(r, impl, px)); if(r->method_number == M_POST) return reportStatus(post(r, impl, px)); if(r->method_number == M_PUT) return reportStatus(put(r, impl, px)); if(r->method_number == M_DELETE) return reportStatus(del(r, impl, px)); return HTTP_NOT_IMPLEMENTED; } /** * Configuration commands. */ const char *confHome(cmd_parms *cmd, void *dummy, const char *arg) { ServerConf *c = (ServerConf*)ap_get_module_config(cmd->server->module_config, &mod_tuscany_eval); c->home = arg; return NULL; } const char *confContribution(cmd_parms *cmd, void *c, const char *arg) { DirConf* conf = (DirConf*)c; conf->contributionPath = arg; conf->component = component(conf); return NULL; } const char *confComposite(cmd_parms *cmd, void *c, const char *arg) { DirConf* conf = (DirConf*)c; conf->compositeName = arg; conf->component = component(conf); return NULL; } const char *confComponent(cmd_parms *cmd, void *c, const char *arg) { DirConf* conf = (DirConf*)c; conf->componentName = arg; conf->component = component(conf); return NULL; } void *makeDirConf(apr_pool_t *p, char *dirspec) { DirConf* c = new (apr_palloc(p, sizeof(DirConf))) DirConf(); apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback, apr_pool_cleanup_null) ; return c; } void* makeServerConf(apr_pool_t *p, server_rec *s) { ServerConf* c = new (apr_palloc(p, sizeof(ServerConf))) ServerConf(); apr_pool_cleanup_register(p, c, gc_pool_cleanupCallback, apr_pool_cleanup_null) ; return c; } /** * HTTP server module declaration. */ const command_rec commands[] = { AP_INIT_TAKE1("TuscanyHome", (const char*(*)())confHome, NULL, RSRC_CONF, "Tuscany home directory"), AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, ACCESS_CONF, "SCA contribution location"), AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, ACCESS_CONF, "SCA composite location"), AP_INIT_TAKE1("SCAComponent", (const char*(*)())confComponent, NULL, ACCESS_CONF, "SCA component name"), {NULL} }; int postConfig(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { return OK; } void childInit(apr_pool_t* p, server_rec* svr_rec) { ServerConf *c = (ServerConf*)ap_get_module_config(svr_rec->module_config, &mod_tuscany_eval); if(c == NULL) { std::cerr << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << std::endl; exit(APEXIT_CHILDFATAL); } } void registerHooks(apr_pool_t *p) { ap_hook_post_config(postConfig, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(childInit, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_handler(handler, NULL, NULL, APR_HOOK_MIDDLE); } } } } extern "C" { module AP_MODULE_DECLARE_DATA mod_tuscany_eval = { STANDARD20_MODULE_STUFF, // dir config tuscany::httpd::modeval::makeDirConf, // dir merger, default is to override NULL, // server config tuscany::httpd::modeval::makeServerConf, // merge server config NULL, // command table tuscany::httpd::modeval::commands, // register hooks tuscany::httpd::modeval::registerHooks }; }