diff options
Diffstat (limited to '')
24 files changed, 2995 insertions, 0 deletions
diff --git a/sca-cpp/branches/lightweight-sca/modules/server/Makefile.am b/sca-cpp/branches/lightweight-sca/modules/server/Makefile.am new file mode 100644 index 0000000000..e2fd67d9b8 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/Makefile.am @@ -0,0 +1,50 @@ +# 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. + +INCLUDES = -I${HTTPD_INCLUDE} + +incl_HEADERS = *.hpp +incldir = $(prefix)/include/modules/server + +dist_mod_SCRIPTS = cpp-conf scheme-conf server-conf +moddir = $(prefix)/modules/server + +EXTRA_DIST = domain-test.composite client-test.scm server-test.scm htdocs/*.html htdocs/test/*.xml htdocs/test/*.txt + +mod_LTLIBRARIES = libmod_tuscany_eval.la +noinst_DATA = libmod_tuscany_eval${libsuffix} + +libmod_tuscany_eval_la_SOURCES = mod-eval.cpp +libmod_tuscany_eval_la_LDFLAGS = -lxml2 -lcurl -lmozjs +libmod_tuscany_eval${libsuffix}: + ln -s .libs/libmod_tuscany_eval${libsuffix} + +noinst_test_LTLIBRARIES = libimpl-test.la +noinst_testdir = `pwd`/tmp +noinst_DATA += libimpl-test${libsuffix} + +libimpl_test_la_SOURCES = impl-test.cpp +libimpl-test${libsuffix}: + ln -s .libs/libimpl-test${libsuffix} + +client_test_SOURCES = client-test.cpp +client_test_LDFLAGS = -lxml2 -lcurl -lmozjs + +dist_noinst_SCRIPTS = httpd-test server-test wiring-test +noinst_PROGRAMS = client-test +TESTS = httpd-test server-test wiring-test + diff --git a/sca-cpp/branches/lightweight-sca/modules/server/client-test.cpp b/sca-cpp/branches/lightweight-sca/modules/server/client-test.cpp new file mode 100644 index 0000000000..3f5ff20c56 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/client-test.cpp @@ -0,0 +1,39 @@ +/* + * 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$ */ + +/** + * Test HTTP client functions. + */ + +#include "stream.hpp" +#include "string.hpp" +#include "client-test.hpp" + +int main() { + tuscany::cout << "Testing..." << tuscany::endl; + tuscany::server::testURI = "http://localhost:8090/scheme"; + + tuscany::server::testServer(); + + tuscany::cout << "OK" << tuscany::endl; + + return 0; +} diff --git a/sca-cpp/branches/lightweight-sca/modules/server/client-test.hpp b/sca-cpp/branches/lightweight-sca/modules/server/client-test.hpp new file mode 100644 index 0000000000..1c7b26da39 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/client-test.hpp @@ -0,0 +1,372 @@ +/* + * 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$ */ + +/** + * Test HTTP client functions. + */ + +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <assert.h> +#include "stream.hpp" +#include "string.hpp" +#include "parallel.hpp" +#include "perf.hpp" +#include "../http/http.hpp" + +namespace tuscany { +namespace server { + +string testURI = "http://localhost:8090/scheme"; +bool testBlobs = true; + +ostream* curlWriter(const string& s, ostream* os) { + (*os) << s; + return os; +} + +const bool testGet() { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + { + ostringstream os; + const failable<list<ostream*> > r = http::get<ostream*>(curlWriter, &os, "http://localhost:8090/index.html", ch); + assert(hasContent(r)); + assert(contains(str(os), "HTTP/1.1 200") || contains(str(os), "HTTP/1.0 200")); + assert(contains(str(os), "It works")); + } + { + const failable<value> r = http::getcontent("http://localhost:8090/index.html", ch); + assert(hasContent(r)); + assert(contains(car(reverse(list<value>(content(r)))), "It works")); + } + return true; +} + +struct getLoop { + http::CURLSession& ch; + getLoop(http::CURLSession& ch) : ch(ch) { + } + const bool operator()() const { + const failable<value> r = http::getcontent("http://localhost:8090/index.html", ch); + assert(hasContent(r)); + assert(contains(car(reverse(list<value>(content(r)))), "It works")); + return true; + } +}; + +const bool testGetPerf() { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + const lambda<bool()> gl = getLoop(ch); + cout << "Static GET test " << time(gl, 5, 200) << " ms" << endl; + return true; +} + +const bool testEval() { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + const failable<value> r = http::evalExpr(mklist<value>(string("echo"), string("Hello")), testURI, ch); + assert(hasContent(r)); + assert(content(r) == string("Hello")); + return true; +} + +struct evalLoop { + const string uri; + http::CURLSession& ch; + evalLoop(const string& uri, http::CURLSession& ch) : uri(uri), ch(ch) { + } + const bool operator()() const { + const failable<value> r = http::evalExpr(mklist<value>(string("echo"), string("Hello")), uri, ch); + assert(hasContent(r)); + assert(content(r) == string("Hello")); + return true; + } +}; + +const value blob(string(2048, 'A')); +const list<value> blobs = mklist(blob, blob); + +struct blobEvalLoop { + const string uri; + http::CURLSession& ch; + blobEvalLoop(const string& uri, http::CURLSession& ch) : uri(uri), ch(ch) { + } + const bool operator()() const { + const failable<value> r = content(http::evalExpr(mklist<value>(string("echo"), blobs), uri, ch)); + assert(hasContent(r)); + assert(content(r) == blobs); + return true; + } +}; + +const bool testEvalPerf() { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + const lambda<bool()> el = evalLoop(testURI, ch); + cout << "JSON-RPC eval echo test " << time(el, 5, 200) << " ms" << endl; + + if (testBlobs) { + const lambda<bool()> bel = blobEvalLoop(testURI, ch); + cout << "JSON-RPC eval blob test " << time(bel, 5, 200) << " ms" << endl; + } + return true; +} + +bool testPost() { + gc_scoped_pool pool; + const list<value> i = list<value>() + "content" + (list<value>() + "item" + + (list<value>() + "name" + string("Apple")) + + (list<value>() + "price" + string("$2.99"))); + const list<value> a = list<value>() + (list<value>() + "entry" + + (list<value>() + "title" + string("item")) + + (list<value>() + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")) + + i); + http::CURLSession ch("", "", "", "", 0); + const failable<value> id = http::post(a, testURI, ch); + assert(hasContent(id)); + return true; +} + +struct postLoop { + const string uri; + const value val; + http::CURLSession& ch; + postLoop(const string& uri, const value& val, http::CURLSession& ch) : uri(uri), val(val), ch(ch) { + } + const bool operator()() const { + const failable<value> id = http::post(val, uri, ch); + assert(hasContent(id)); + return true; + } +}; + +struct postBlobLoop { + const string uri; + const value val; + http::CURLSession& ch; + postBlobLoop(const string& uri, const value& val, http::CURLSession& ch) : uri(uri), val(val), ch(ch) { + } + const bool operator()() const { + gc_scoped_pool pool; + const failable<value> id = http::post(val, uri, ch); + assert(hasContent(id)); + return true; + } +}; + +const bool testPostPerf() { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + { + const list<value> i = list<value>() + "content" + (list<value>() + "item" + + (list<value>() + "name" + string("Apple")) + + (list<value>() + "price" + string("$2.99"))); + const list<value> val = list<value>() + (list<value>() + "entry" + + (list<value>() + "title" + string("item")) + + (list<value>() + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")) + + i); + const lambda<bool()> pl = postLoop(testURI, val, ch); + cout << "ATOMPub POST small test " << time(pl, 5, 200) << " ms" << endl; + } + if (testBlobs) { + const list<value> i = list<value>() + "content" + (list<value>() + "item" + + (list<value>() + "name" + string("Apple")) + + (list<value>() + "blob1" + blob) + + (list<value>() + "blob2" + blob) + + (list<value>() + "price" + string("$2.99"))); + const list<value> val = list<value>() + (list<value>() + "entry" + + (list<value>() + "title" + string("item")) + + (list<value>() + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")) + + i); + const lambda<bool()> pl = postBlobLoop(testURI, val, ch); + cout << "ATOMPub POST blob test " << time(pl, 5, 200) << " ms" << endl; + } + return true; +} + +#ifdef WANT_THREADS + +const bool postThread(const string& uri, const int count, const value& val) { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + const lambda<bool()> pl = postLoop(uri, val, ch); + time(pl, 0, count); + return true; +} + +const list<future<bool> > startPost(worker& w, const int threads, const lambda<bool()>& l) { + if (threads == 0) + return list<future<bool> >(); + return cons(submit(w, l), startPost(w, threads - 1, l)); +} + +const bool checkPost(const list<future<bool> >& r) { + if (isNil(r)) + return true; + assert(car(r) == true); + return checkPost(cdr(r)); +} + +struct postThreadLoop { + const lambda<bool()> l; + worker& w; + const int threads; + postThreadLoop(const lambda<bool()>& l, worker& w, const int threads) : l(l), w(w), threads(threads) { + } + const bool operator()() const { + list<future<bool> > r = startPost(w, threads, l); + checkPost(r); + return true; + } +}; + +const bool testPostThreadPerf() { + gc_scoped_pool pool; + const int count = 50; + const int threads = 10; + worker w(threads); + + const list<value> i = list<value>() + "content" + (list<value>() + "item" + + (list<value>() + "name" + string("Apple")) + + (list<value>() + "price" + string("$2.99"))); + const value val = list<value>() + (list<value>() + "entry" + + (list<value>() + "title" + string("item")) + + (list<value>() + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")) + + i); + + const lambda<bool()> pl= curry(lambda<bool(const string, const int, const value)>(postThread), testURI, count, val); + const lambda<bool()> ptl = postThreadLoop(pl, w, threads); + double t = time(ptl, 0, 1) / (threads * count); + cout << "ATOMPub POST thread test " << t << " ms" << endl; + + return true; +} + +#else + +const bool postProc(const string& uri, const int count, const value& val) { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + const lambda<bool()> pl = postLoop(uri, val, ch); + time(pl, 0, count); + return true; +} + +const list<pid_t> startPost(const int procs, const lambda<bool()>& l) { + if (procs == 0) + return list<pid_t>(); + pid_t pid = fork(); + if (pid == 0) { + assert(l() == true); + exit(0); + } + return cons(pid, startPost(procs - 1, l)); +} + +const bool checkPost(const list<pid_t>& r) { + if (isNil(r)) + return true; + int status; + waitpid(car(r), &status, 0); + assert(status == 0); + return checkPost(cdr(r)); +} + +struct postForkLoop { + const lambda<bool()> l; + const int procs; + postForkLoop(const lambda<bool()>& l, const int procs) : l(l), procs(procs) { + } + const bool operator()() const { + list<pid_t> r = startPost(procs, l); + checkPost(r); + return true; + } +}; + +const bool testPostForkPerf() { + gc_scoped_pool pool; + const int count = 50; + const int procs = 10; + + const list<value> i = list<value>() + "content" + (list<value>() + "item" + + (list<value>() + "name" + string("Apple")) + + (list<value>() + "price" + string("$2.99"))); + const value val = list<value>() + (list<value>() + "entry" + + (list<value>() + "title" + string("item")) + + (list<value>() + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")) + + i); + + const lambda<bool()> pl= curry(lambda<bool(const string, const int, const value)>(postProc), testURI, count, val); + const lambda<bool()> ptl = postForkLoop(pl, procs); + double t = time(ptl, 0, 1) / (procs * count); + cout << "ATOMPub POST fork test " << t << " ms" << endl; + + return true; +} + +#endif + +const bool testPut() { + gc_scoped_pool pool; + const list<value> i = list<value>() + "content" + (list<value>() + "item" + + (list<value>() + "name" + string("Apple")) + + (list<value>() + "price" + string("$2.99"))); + const list<value> a = list<value>() + (list<value>() + "entry" + + (list<value>() + "title" + string("item")) + + (list<value>() + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")) + + i); + http::CURLSession ch("", "", "", "", 0); + value rc = content(http::put(a, testURI + "/111", ch)); + assert(rc == value(true)); + return true; +} + +const bool testDel() { + gc_scoped_pool pool; + http::CURLSession ch("", "", "", "", 0); + value rc = content(http::del(testURI + "/111", ch)); + assert(rc == value(true)); + return true; +} + +const bool testServer() { + tuscany::server::testGet(); + tuscany::server::testPost(); + tuscany::server::testPut(); + tuscany::server::testDel(); + tuscany::server::testEval(); + tuscany::server::testGetPerf(); + tuscany::server::testPostPerf(); +#ifdef WANT_THREADS + tuscany::server::testPostThreadPerf(); +#else + tuscany::server::testPostForkPerf(); +#endif + tuscany::server::testEvalPerf(); + return true; +} + +} +} diff --git a/sca-cpp/branches/lightweight-sca/modules/server/client-test.scm b/sca-cpp/branches/lightweight-sca/modules/server/client-test.scm new file mode 100644 index 0000000000..47b799d390 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/client-test.scm @@ -0,0 +1,38 @@ +; 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. + +; JSON-RPC test case + +(define (echo x ref) (ref "echo" x)) + +; ATOMPub test case + +(define (get id ref) + (ref "get" id) +) + +(define (post coll entry ref) + (ref "post" coll entry) +) + +(define (put id entry ref) + (ref "put" id entry) +) + +(define (delete id ref) + (ref "delete" id) +) diff --git a/sca-cpp/branches/lightweight-sca/modules/server/cpp-conf b/sca-cpp/branches/lightweight-sca/modules/server/cpp-conf new file mode 100755 index 0000000000..6b74f60ec5 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/cpp-conf @@ -0,0 +1,37 @@ +#!/bin/sh + +# 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. + +# Generate a C++ server conf +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +mkdir -p $1 +root=`echo "import os; print os.path.realpath('$1')" | python` + +uname=`uname -s` +if [ $uname = "Darwin" ]; then + libsuffix=".dylib" +else + libsuffix=".so" +fi + +cat >>$root/conf/modules.conf <<EOF +# Generated by: cpp-conf $* +# Support for C++ SCA components +LoadModule mod_tuscany_eval $here/libmod_tuscany_eval$libsuffix + +EOF diff --git a/sca-cpp/branches/lightweight-sca/modules/server/domain-test.composite b/sca-cpp/branches/lightweight-sca/modules/server/domain-test.composite new file mode 100644 index 0000000000..1819b3bf4c --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/domain-test.composite @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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. +--> +<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912" + targetNamespace="http://domain/test" + name="domain-test"> + + <component name="scheme-test"> + <implementation.scheme script="server-test.scm"/> + <service name="scheme"> + <binding.http uri="scheme"/> + </service> + </component> + + <component name="property-test"> + <implementation.scheme script="property-test.scm"/> + <service name="properties"> + <binding.http uri="properties"/> + </service> + <property name="host"></property> + <property name="path"></property> + <property name="query"></property> + </component> + + <component name="cpp-test"> + <implementation.cpp path="." library="libimpl-test"/> + <service name="cpp"> + <binding.http uri="cpp"/> + </service> + </component> + + <component name="client-test"> + <implementation.scheme script="client-test.scm"/> + <service name="client"> + <binding.http uri="client"/> + </service> + <reference name="ref" target="scheme-test"> + <binding.http/> + </reference> + </component> + +</composite> diff --git a/sca-cpp/branches/lightweight-sca/modules/server/htdocs/index.html b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/index.html new file mode 100644 index 0000000000..236864edfb --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/index.html @@ -0,0 +1,32 @@ +<!-- + 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. +--> + +<html> +<head> +<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"/> +<meta name="apple-mobile-web-app-capable" content="yes"/> +<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/> +<link rel="stylesheet" type="text/css" href="/ui-min.css"/> +<title>It works</title> +</head> +<body> +<h1>It works!</h1> +</body> +</html> + diff --git a/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/entry.xml b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/entry.xml new file mode 100644 index 0000000000..46053c3138 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/entry.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<entry xmlns="http://www.w3.org/2005/Atom"> + <title type="text">Item</title> + <id>111</id> + <content type="application/xml"> + <item> + <name>Apple</name> + <currencyCode>USD</currencyCode> + <currencySymbol>$</currencySymbol> + <price>2.99</price> + </item> + </content> + <link href="111"/> +</entry> diff --git a/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/feed.xml b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/feed.xml new file mode 100644 index 0000000000..337320e4c5 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/feed.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + <title type="text">Sample Feed</title> + <id>123456789</id> + <entry xmlns="http://www.w3.org/2005/Atom"> + <title type="text">Item</title> + <id>111</id> + <content type="application/xml"> + <item> + <name>Apple</name> + <currencyCode>USD</currencyCode> + <currencySymbol>$</currencySymbol> + <price>2.99</price> + </item> + </content> + <link href="111"/> + </entry> + <entry xmlns="http://www.w3.org/2005/Atom"> + <title type="text">Item</title> + <id>222</id> + <content type="application/xml"> + <item> + <name>Orange</name> + <currencyCode>USD</currencyCode> + <currencySymbol>$</currencySymbol> + <price>3.55</price> + </item> + </content> + <link href="222"/> + </entry> + <entry xmlns="http://www.w3.org/2005/Atom"> + <title type="text">Item</title> + <id>333</id> + <content type="application/xml"> + <item> + <name>Pear</name> + <currencyCode>USD</currencyCode> + <currencySymbol>$</currencySymbol> + <price>1.55</price> + </item> + </content> + <link href="333"/> + </entry> +</feed> diff --git a/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-properties.txt b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-properties.txt new file mode 100644 index 0000000000..2cff8b7339 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-properties.txt @@ -0,0 +1,14 @@ +{ + "id": "1", + "result": { + "host": "localhost", + "path": [ + "c", + "property-test" + ], + "query": { + "id": "1", + "method": "print" + } + } +}
\ No newline at end of file diff --git a/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-request.txt b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-request.txt new file mode 100644 index 0000000000..b5c2457309 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-request.txt @@ -0,0 +1,7 @@ +{ + "id": 1, + "method": "echo", + "params": [ + "Hello" + ] +} diff --git a/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-result.txt b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-result.txt new file mode 100644 index 0000000000..72b27b67db --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/htdocs/test/json-result.txt @@ -0,0 +1,4 @@ +{ + "id": 1, + "result": "Hello" +}
\ No newline at end of file diff --git a/sca-cpp/branches/lightweight-sca/modules/server/httpd-test b/sca-cpp/branches/lightweight-sca/modules/server/httpd-test new file mode 100755 index 0000000000..195caa4562 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/httpd-test @@ -0,0 +1,87 @@ +#!/bin/sh + +# 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. + +echo "Testing..." +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +curl_prefix=`cat $here/../http/curl.prefix` + +# Setup +rm -rf tmp +../http/httpd-conf tmp localhost 8090 htdocs +../http/httpd-event-conf tmp +./server-conf tmp +./scheme-conf tmp +cat >>tmp/conf/httpd.conf <<EOF +SCAContribution `pwd`/ +SCAComposite domain-test.composite +EOF + +../http/httpd-start tmp +sleep 2 + +# Test HTTP GET +$curl_prefix/bin/curl http://localhost:8090/index.html 2>/dev/null >tmp/index.html +diff tmp/index.html htdocs/index.html +rc=$? + +# Test ATOMPub +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/scheme/ >tmp/feed.xml 2>/dev/null + diff tmp/feed.xml htdocs/test/feed.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/scheme/111 >tmp/entry.xml 2>/dev/null + diff tmp/entry.xml htdocs/test/entry.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/scheme/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/scheme/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/scheme/111 -X DELETE 2>/dev/null + rc=$? +fi + +# Test JSON-RPC +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/scheme/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/test/json-request.txt >tmp/json-result.txt 2>/dev/null + diff tmp/json-result.txt htdocs/test/json-result.txt + rc=$? +fi + +# Test built-in properties +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl 'http://localhost:8090/properties?id=1&method=print' >tmp/json-properties.txt 2>/dev/null + diff tmp/json-properties.txt htdocs/test/json-properties.txt + rc=$? +fi + +# Cleanup +../http/httpd-stop tmp +sleep 2 +if [ "$rc" = "0" ]; then + echo "OK" +fi +exit $rc diff --git a/sca-cpp/branches/lightweight-sca/modules/server/impl-test.cpp b/sca-cpp/branches/lightweight-sca/modules/server/impl-test.cpp new file mode 100644 index 0000000000..1bd0843745 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/impl-test.cpp @@ -0,0 +1,76 @@ +/* + * 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$ */ + +/** + * Test component implementation. + */ + +#include "string.hpp" + +#include "function.hpp" +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" + +namespace tuscany { +namespace server { + +const failable<value> get(unused const list<value>& params) { + return value(mklist<value>("text/html", mklist<value>("Hey"))); +} + +const failable<value> post(unused const list<value>& params) { + return value(mklist<value>(string("123456789"))); +} + +const failable<value> put(unused const list<value>& params) { + return value(true); +} + +const failable<value> del(unused const list<value>& params) { + return value(true); +} + +const failable<value> echo(const list<value>& params) { + return value(car(params)); +} + +} +} + +extern "C" { + +const tuscany::value apply(const tuscany::list<tuscany::value>& params) { + const tuscany::value func(car(params)); + if (func == "get") + return tuscany::server::get(cdr(params)); + if (func == "post") + return tuscany::server::post(cdr(params)); + if (func == "put") + return tuscany::server::put(cdr(params)); + if (func == "delete") + return tuscany::server::del(cdr(params)); + if (func == "echo") + return tuscany::server::echo(cdr(params)); + return tuscany::mkfailure<tuscany::value>(); +} + +} diff --git a/sca-cpp/branches/lightweight-sca/modules/server/mod-cpp.hpp b/sca-cpp/branches/lightweight-sca/modules/server/mod-cpp.hpp new file mode 100644 index 0000000000..8cae35e493 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/mod-cpp.hpp @@ -0,0 +1,104 @@ +/* + * 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_modcpp_hpp +#define tuscany_modcpp_hpp + +/** + * Evaluation functions used by mod-eval to evaluate C++ + * component implementations. + */ + +#include "string.hpp" +#include "stream.hpp" + +#include "function.hpp" +#include "list.hpp" +#include "element.hpp" +#include "value.hpp" +#include "monad.hpp" +#include "dynlib.hpp" +#include "../scheme/driver.hpp" + +namespace tuscany { +namespace server { +namespace modcpp { + +/** + * Apply a C++ component implementation function. + */ +const list<value> failableResult(const value& func, const list<value>& v) { + if (isNil(cdr(v))) + return v; + + // Report a failure with an empty reason as 'function not supported' + // Except for the start, and stop functions, which are optional + const value reason = cadr(v); + if (length(reason) == 0) { + if (func == "start" || func == "stop") + return mklist<value>(lambda<value(const list<value>&)>()); + return mklist<value>(value(), string("Function not supported: ") + func); + } + return v; +} + +struct applyImplementation { + const lib ilib; + const lambda<value(const list<value>&)> impl; + const list<value> px; + + applyImplementation(const lib& ilib, const lambda<value(const list<value>&)>& impl, const list<value>& px) : ilib(ilib), impl(impl), px(px) { + } + + const value operator()(const list<value>& params) const { + debug(params, "modeval::cpp::applyImplementation::input"); + + // Apply the component implementation function + const value val = failableResult(car(params), impl(append(params, px))); + + debug(val, "modeval::cpp::applyImplementation::result"); + return val; + } +}; + +/** + * Evaluate a C++ component implementation and convert it to + * an applicable lambda function. + */ +const failable<lambda<value(const list<value>&)> > evalImplementation(const string& path, const value& impl, const list<value>& px) { + + // Configure the implementation's lambda function + const value ipath(attributeValue("path", impl)); + const value iname(attributeValue("library", impl)); + const string fpath(isNil(ipath)? path + iname : path + ipath + "/" + iname); + const lib ilib(*(new (gc_new<lib>()) lib(fpath + dynlibExt))); + const failable<lambda<value(const list<value>&)> > evalf(dynlambda<value(const list<value>&)>("apply", ilib)); + if (!hasContent(evalf)) + return evalf; + const lambda<value(const list<value>&)> l(applyImplementation(ilib, content(evalf), px)); + return l; +} + +} +} +} + +#endif /* tuscany_modcpp_hpp */ diff --git a/sca-cpp/branches/lightweight-sca/modules/server/mod-eval.cpp b/sca-cpp/branches/lightweight-sca/modules/server/mod-eval.cpp new file mode 100644 index 0000000000..3fd69c1fea --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/mod-eval.cpp @@ -0,0 +1,65 @@ +/* + * 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 C++ and Scheme component implementations. + */ + +#define WANT_HTTPD_LOG 1 +#include "string.hpp" +#include "function.hpp" +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" +#include "mod-eval.hpp" +#include "mod-scheme.hpp" +#include "mod-cpp.hpp" + +namespace tuscany { +namespace server { +namespace modeval { + +/** + * Apply a lifecycle start or restart event. + */ +const value applyLifecycle(unused const list<value>& params) { + // Return a nil function as we don't need to handle any subsequent events + return failable<value>(lambda<value(const list<value>&)>()); +} + +/** + * Evaluate a Scheme or C++ component implementation and convert it to an + * applicable lambda function. + */ +const failable<lambda<value(const list<value>&)> > evalImplementation(const string& path, const value& impl, const list<value>& px, unused const lambda<value(const list<value>&)>& lifecycle) { + const string itype(elementName(impl)); + if (contains(itype, ".scheme")) + return modscheme::evalImplementation(path, impl, px); + if (contains(itype, ".cpp")) + return modcpp::evalImplementation(path, impl, px); + if (contains(itype, ".widget")) + return mkfailure<lambda<value(const list<value>&)> >(string("Unsupported implementation type: ") + itype, -1, false); + return mkfailure<lambda<value(const list<value>&)> >(string("Unsupported implementation type: ") + itype); +} + +} +} +} diff --git a/sca-cpp/branches/lightweight-sca/modules/server/mod-eval.hpp b/sca-cpp/branches/lightweight-sca/modules/server/mod-eval.hpp new file mode 100644 index 0000000000..ee99baa039 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/mod-eval.hpp @@ -0,0 +1,1600 @@ +/* + * 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 <sys/stat.h> + +#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" + +#include "apr_md5.h" +#include "ap_provider.h" +#include "mod_auth.h" + +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 contributorName; + value contributor; + string authenticatorName; + value authenticator; +}; + +/** + * Contribution configuration. + */ +class ContribConf { +public: + ContribConf() { + } + + string contribPath; + string composName; +}; + +/** + * Composite assocs. + */ +class Composite { +public: + Composite() { + } + + Composite(const list<value>& refs, const list<value>& svcs, const list<value>& impls) : refs(refs), svcs(svcs), impls(impls) { + } + + list<value> refs; + list<value> svcs; + list<value> impls; +}; + +/** + * Server configuration. + */ +class ServerConf { +public: + ServerConf() { + } + + ServerConf(apr_pool_t* p, const server_rec* s) : p(p), server(s), timeout(0) { + } + + const gc_pool p; + const server_rec* server; + lambda<value(const list<value>&)> lifecycle; + ContribConf contribc; + SSLConf sslc; + int timeout; + 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<value> rpath; + list<value> vpath; + list<value> impls; +}; + +/** + * Authentication cache store function. + */ +static APR_OPTIONAL_FN_TYPE(ap_authn_cache_store) *authnCacheStore = NULL; + +/** + * Convert a result represented as a (content reason? code?) tuple 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)), isNil(cddr(v))? -1 : (int)caddr(v), false); +} + +/** + * 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; + } +}; + +/** + * Make an HTTP proxy lambda to an absolute URI + */ +const value mkhttpProxy(const string& uri, const int timeout, const gc_pool& p) { + debug(uri, "modeval::mkhttpProxy::uri"); + return lambda<value(const list<value>&)>(http::proxy(uri, "", "", "", "", timeout, p)); +} + +/** + * Return a component implementation proxy lambda. + */ +class implProxy { +public: + implProxy(const value& name, const list<value>& impls, const SSLConf& sslc, const int timeout) : name(name), impls(impls), sslc(sslc), timeout(timeout) { + } + + const value callImpl(const value& cname, const list<value>& aparams) const { + //debug(impls, "modeval::implProxy::callImpl::impls"); + + // Lookup the component implementation + const list<value> impl(assoctree<value>(cname, impls)); + 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), sslc.ca, sslc.cert, sslc.key, httpd::cookie(currentRequest), timeout, 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<value(const list<value>&)> px = lambda<value(const list<value>&)>(http::proxy(uri, sslc.ca, sslc.cert, sslc.key, httpd::cookie(currentRequest), timeout, p)); + return px(aparams); + } + + // No SSL 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, "", "", "", "", timeout, 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<value>& impls; + const SSLConf& sslc; + const int timeout; +}; + +const value mkimplProxy(const value& name, const list<value>& impls, const SSLConf& sslc, const int timeout) { + debug(name, "modeval::implProxy::impl"); + return lambda<value(const list<value>&)>(implProxy(name, impls, sslc, timeout)); +} + +/** + * 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 value& ref, const list<value>& impls, const SSLConf& sslc, const int timeout, 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, timeout); + if (isNil(target)) + return mkunwiredProxy(scdl::name(ref)); + if (http::isAbsolute(target)) + return mkhttpProxy(target, timeout, p); + return mkimplProxy(car(pathValues(target)), impls, sslc, timeout); +} + +const list<value> refProxies(const list<value>& refs, const list<value>& impls, const SSLConf& sslc, const int timeout, const gc_pool& p) { + if (isNil(refs)) + return refs; + return cons(mkrefProxy(car(refs), impls, sslc, timeout, p), refProxies(cdr(refs), impls, sslc, timeout, 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<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 { + if (currentRequest == NULL) + return http::hostName(); + const value h = httpd::hostName(currentRequest, v); + debug(h, "modeval::hostPropProxy::value"); + return h; + } +}; + +struct appPropProxy { + const value v; + appPropProxy(const value& v) : v(v) { + } + const value operator()(unused const list<value>& params) const { + if (currentRequest == NULL) + return v; + const RequestConf& reqc = httpd::requestConf<RequestConf>(currentRequest, &mod_tuscany_eval); + const value a = isNil(reqc.vpath)? v : car(reqc.vpath); + debug(a, "modeval::appPropProxy::value"); + return a; + } +}; + +struct pathPropProxy { + const value v; + pathPropProxy(const value& v) : v(v) { + } + const value operator()(unused const list<value>& params) const { + if (currentRequest == NULL) + return v; + const RequestConf& reqc = httpd::requestConf<RequestConf>(currentRequest, &mod_tuscany_eval); + const value p = reqc.rpath; + debug(p, "modeval::pathPropProxy::value"); + return p; + } +}; + +struct queryPropProxy { + const value v; + queryPropProxy(const value& v) : v(v) { + } + const value operator()(unused const list<value>& params) const { + if (currentRequest == NULL) + return v; + const value q = httpd::unescapeArgs(httpd::queryArgs(currentRequest)); + debug(q, "modeval::queryPropProxy::value"); + return q; + } +}; + +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 { + if (currentRequest == NULL) + return v; + const char* env = apr_table_get(currentRequest->subprocess_env, c_str(name)); + if (env == NULL || *env == '\0') + return v; + debug(name, "modeval::envPropProxy::name"); + const value e = string(env); + debug(e, "modeval::envPropProxy::value"); + return e; + } +}; + +struct realmPropProxy { + const value v; + realmPropProxy(const value& v) : v(v) { + } + const value operator()(unused const list<value>& params) const { + if (currentRequest == NULL) + return v; + const char* env = apr_table_get(currentRequest->subprocess_env, "REALM"); + if (env == NULL) + return v; + const string realm = httpd::realm(string(env)); + if (length(realm) == 0) + return v; + const value r = realm; + debug(r, "modeval::realmPropProxy::value"); + return r; + } +}; + +struct timeoutPropProxy { + const value v; + timeoutPropProxy(const value& v) : v(atoi(c_str((string)v))) { + } + const value operator()(unused const list<value>& params) const { + if (currentRequest == NULL) + return v; + const ServerConf& sc = httpd::serverConf<ServerConf>(currentRequest, &mod_tuscany_eval); + const value r = sc.timeout; + debug(r, "modeval::timeoutPropProxy::value"); + return r; + } +}; + +struct userPropProxy { + const value v; + userPropProxy(const value& v) : v(v) { + } + const value operator()(unused const list<value>& params) const { + if (currentRequest == NULL) + return v; + if (currentRequest->user == NULL) + return v; + const value u = string(currentRequest->user); + debug(u, "modeval::userPropProxy::value"); + return u; + } +}; + +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<value(const list<value>&)>(appPropProxy(v)); + 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>&)>(realmPropProxy(v)); + if (n == "timeout") + return lambda<value(const list<value>&)>(timeoutPropProxy(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(const string& contribPath, const value& comp, const list<value>& impls, const lambda<value(const list<value>&)> lifecycle, const SSLConf& sslc, const int timeout, const gc_pool& p) { + 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 + const list<value> rpx(refProxies(scdl::references(comp), impls, sslc, timeout, p)); + + // 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(contribPath, impl, append(rpx, ppx), 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(const list<value>& c, const string& contribPath, const list<value>& impls, const lambda<value(const list<value>&)> lifecycle, const SSLConf& sslc, const int timeout, const gc_pool& p) { + if (isNil(c)) + return c; + return cons<value>(mklist<value>(scdl::name(car(c)), + evalComponent(contribPath, car(c), impls, lifecycle, sslc, timeout, p)), + componentToImplementationAssoc(cdr(c), contribPath, impls, lifecycle, sslc, timeout, p)); +} + +/** + * 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))); +} + +/** + * Get the components returned by a contributor. + */ +const failable<list<value> > getComponents(const lambda<value(const list<value>&)>& contributor, const string& name) { + const failable<value> val = failableResult(contributor(cons<value>("get", mklist<value>(mklist<value>(name))))); + if (!hasContent(val)) + return mkfailure<list<value> >(val); + const list<value> c = assoc<value>(value("composite"), assoc<value>(value("content"), (list<list<value> >)cdr<value>(car<value>(content(val))))); + debug(c, "modeval::getComponents::comp"); + if (isNil(c)) + return mkfailure<list<value> >(string("Could not get composite: ") + name); + const failable<list<string> > x = writeXML(car<value>(valuesToElements(mklist<value>(mklist<value>(c))))); + if (!hasContent(x)) + return mkfailure<list<value> >(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<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> >(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)); +} + +/** + * Return a list of component-name + references pairs. The references are + * arranged in trees of reference-name + reference-target pairs. + */ +const list<value> componentReferenceToTargetTree(const value& c) { + return mklist<value>(scdl::name(c), mkbtree(sort(scdl::referenceToTargetAssoc(scdl::references(c))))); +} + +const list<value> componentReferenceToTargetAssoc(const list<value>& c) { + if (isNil(c)) + return c; + return cons<value>(componentReferenceToTargetTree(car(c)), componentReferenceToTargetAssoc(cdr(c))); +} + +/** + * Return a list of service-URI-path + component-name pairs. Service-URI-paths are + * represented as lists of URI path fragments. + */ +const list<value> defaultBindingURI(const string& cn, const string& sn) { + return mklist<value>(cn, sn); +} + +const list<value> bindingToComponentAssoc(const string& cn, const string& sn, const list<value>& b) { + if (isNil(b)) + return b; + const value uri(scdl::uri(car(b))); + if (isNil(uri)) + return cons<value>(mklist<value>(defaultBindingURI(cn, sn), cn), bindingToComponentAssoc(cn, sn, cdr(b))); + return cons<value>(mklist<value>(pathValues(c_str(string(uri))), cn), bindingToComponentAssoc(cn, sn, cdr(b))); +} + +const list<value> serviceToComponentAssoc(const string& cn, const list<value>& s) { + if (isNil(s)) + return s; + const string sn(scdl::name(car(s))); + const list<value> btoc(bindingToComponentAssoc(cn, sn, scdl::bindings(car(s)))); + if (isNil(btoc)) + return cons<value>(mklist<value>(defaultBindingURI(cn, sn), cn), serviceToComponentAssoc(cn, cdr(s))); + return append<value>(btoc, serviceToComponentAssoc(cn, cdr(s))); +} + +const list<value> uriToComponentAssoc(const list<value>& c) { + if (isNil(c)) + return c; + return append<value>(serviceToComponentAssoc(scdl::name(car(c)), scdl::services(car(c))), uriToComponentAssoc(cdr(c))); +} + +/** + * Configure the components declared in the deployed composite. + */ +const failable<Composite> confComponents(const string& contribPath, const string& composName, const value& contributor, const string& vhost, const list<value>& impls, const lambda<value(const list<value>&)> lifecycle, const SSLConf& sslc, const int timeout, const gc_pool& p) { + debug(contribPath, "modeval::confComponents::contribPath"); + debug(composName, "modeval::confComponents::composName"); + debug(contributor, "modeval::confComponents::contributor"); + debug(vhost, "modeval::confComponents::vhost"); + debug(impls, "modeval::confComponents::impls"); + + const failable<list<value> > fcomps = isNil(contributor)? + readComponents(scdl::resourcePath(length(vhost) != 0? contribPath + vhost + "/" : contribPath, composName)) : + getComponents(contributor, vhost); + if (!hasContent(fcomps)) + return mkfailure<Composite>(fcomps); + + const list<value> comps = content(fcomps); + debug(comps, "modeval::confComponents::comps"); + + const list<value> refs = mkbtree(sort(componentReferenceToTargetAssoc(comps))); + debug(flatten(refs), "modeval::confComponents::refs"); + + const list<value> svcs = mkbtree(sort(uriToComponentAssoc(comps))); + debug(flatten(svcs), "modeval::confComponents::svcs"); + + const list<value> cimpls = mkbtree(sort(componentToImplementationAssoc(comps, + isNil(contributor)? length(vhost) != 0? contribPath + vhost + "/" : contribPath : contribPath, + impls, lifecycle, sslc, timeout, p))); + debug(flatten(cimpls), "modeval::confComponents::impls"); + + return Composite(refs, svcs, cimpls); +} + +/** + * Start the components declared in a composite. + */ +const failable<list<value> > startComponents(const list<value>& impls) { + debug(flatten(impls), "modeval::startComponents::impls"); + const failable<list<value> > fsimpls = applyLifecycleExpr(flatten(impls), mklist<value>("start")); + if (!hasContent(fsimpls)) + return mkfailure<list<value> >(fsimpls); + + const list<value> simpls = content(fsimpls); + debug(impls, "modeval::startComponents::simpls"); + return mkbtree(sort(simpls)); +} + +/** + * Stop the components declared in a composite. + */ +const failable<bool> stopComponents(const list<value>& simpls) { + debug(flatten(simpls), "modeval::stopComponents::simpls"); + applyLifecycleExpr(flatten(simpls), mklist<value>("stop")); + return true; +} + +/** + * Handle an HTTP GET. + */ +const failable<int> get(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::get::uri"); + + // Inspect the query string + const list<list<value> > args = httpd::queryArgs(r); + const list<value> ia = assoc(value("id"), args); + const list<value> ma = assoc(value("method"), args); + + // Evaluate a JSON-RPC request and return a JSON result + if (!isNil(ia) && !isNil(ma)) { + + // Extract the request id, method and params + const value id = cadr(ia); + const value func = c_str(json::funcName(string(cadr(ma)))); + + // Apply the requested function + const failable<value> val = failableResult(impl(cons(func, json::queryParams(args)))); + if (!hasContent(val)) + return mkfailure<int>(val); + + // Return JSON result + js::JSContext cx; + return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc; charset=utf-8", r); + } + + // Evaluate the GET expression + const list<value> params(cddr(rpath)); + const failable<value> val = failableResult(impl(cons<value>("get", mklist<value>(params)))); + if (!hasContent(val)) + return mkfailure<int>(val); + const value c = content(val); + debug(c, "modeval::get::content"); + + // Return a nil value as a not found status + if (!isList(c) && isNil(c)) + return HTTP_NOT_FOUND; + + // Check if the client requested a specific format + const list<value> fmt = assoc<value>("format", args); + + // Write as a scheme value if requested by the client + if (!isNil(fmt) && cadr(fmt) == "scheme") + return httpd::writeResult(mklist<string>(scheme::writeValue(c)), "text/plain; charset=utf-8", r); + + // Write a simple value as a JSON value + if (!isList(c)) { + js::JSContext cx; + if (isSymbol(c)) { + const list<value> lc = mklist<value>(mklist<value>("name", value(string(c)))); + debug(lc, "modeval::get::symbol"); + return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); + } + + const list<value> lc = mklist<value>(mklist<value>("value", c)); + debug(lc, "modeval::get::value"); + return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); + } + + // Write an empty list as a JSON empty value + if (isNil((list<value>)c)) { + js::JSContext cx; + debug(list<value>(), "modeval::get::empty"); + return httpd::writeResult(json::writeJSON(list<value>(), cx), "application/json; charset=utf-8", r); + } + + // Write content-type / content-list pair + if (isString(car<value>(c)) && !isNil(cdr<value>(c)) && isList(cadr<value>(c))) + return httpd::writeResult(convertValues<string>(cadr<value>(c)), car<value>(c), r); + + // Write an assoc value as a JSON result + if (isSymbol(car<value>(c)) && !isNil(cdr<value>(c))) { + js::JSContext cx; + const list<value> lc = mklist<value>(c); + debug(lc, "modeval::get::assoc"); + debug(valuesToElements(lc), "modeval::get::assoc::element"); + return httpd::writeResult(json::writeJSON(valuesToElements(lc), cx), "application/json; charset=utf-8", r); + } + + // Write value as JSON if requested by the client + if (!isNil(fmt) && cadr(fmt) == "json") { + js::JSContext cx; + return httpd::writeResult(json::writeJSON(valuesToElements(c), cx), "application/json; charset=utf-8", r); + } + + // Convert list of values to element values + const list<value> e = valuesToElements(c); + debug(e, "modeval::get::elements"); + + // Write an ATOM feed or entry + if (isList(car<value>(e)) && !isNil(car<value>(e))) { + const list<value> el = car<value>(e); + if (isSymbol(car<value>(el)) && car<value>(el) == element && !isNil(cdr<value>(el)) && isSymbol(cadr<value>(el)) && elementHasChildren(el) && !elementHasValue(el)) { + if (cadr<value>(el) == atom::feed) + return httpd::writeResult(atom::writeATOMFeed(e), "application/atom+xml; charset=utf-8", r); + if (cadr<value>(el) == atom::entry) + return httpd::writeResult(atom::writeATOMEntry(e), "application/atom+xml; charset=utf-8", r); + } + } + + // Write any other compound value as a JSON value + js::JSContext cx; + return httpd::writeResult(json::writeJSON(e, cx), "application/json; charset=utf-8", r); +} + +/** + * Handle an HTTP POST. + */ +const failable<int> post(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::post::uri"); + + // Evaluate a JSON-RPC request and return a JSON result + const string ct = httpd::contentType(r); + if (contains(ct, "application/json-rpc") || contains(ct, "text/plain") || contains(ct, "application/x-www-form-urlencoded")) { + + // Read the JSON request + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + const list<string> ls = httpd::read(r); + debug(ls, "modeval::post::input"); + js::JSContext cx; + const list<value> json = elementsToValues(content(json::readJSON(ls, cx))); + const list<list<value> > args = httpd::postArgs(json); + + // Extract the request id, method and params + const value id = cadr(assoc(value("id"), args)); + const value func = c_str(json::funcName(cadr(assoc(value("method"), args)))); + const list<value> params = (list<value>)cadr(assoc(value("params"), args)); + + // Evaluate the request expression + const failable<value> val = failableResult(impl(cons<value>(func, params))); + if (!hasContent(val)) + return mkfailure<int>(val); + + // Return JSON result + return httpd::writeResult(json::jsonResult(id, content(val), cx), "application/json-rpc; charset=utf-8", r); + } + + // Evaluate an ATOM POST request and return the location of the corresponding created resource + if (contains(ct, "application/atom+xml")) { + + // Read the ATOM entry + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + const list<string> ls = httpd::read(r); + debug(ls, "modeval::post::input"); + const value aval = elementsToValues(content(atom::isATOMEntry(ls)? atom::readATOMEntry(ls) : atom::readATOMFeed(ls))); + + // Evaluate the POST expression + const failable<value> val = failableResult(impl(cons<value>("post", mklist<value>(cddr(rpath), aval)))); + if (!hasContent(val)) + return mkfailure<int>(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>(val); + return (int)content(val); +} + +/** + * Handle an HTTP PUT. + */ +const failable<int> put(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::put::uri"); + + // Read the ATOM entry + const int rc = httpd::setupReadPolicy(r); + if(rc != OK) + return rc; + const list<string> ls = httpd::read(r); + debug(ls, "modeval::put::input"); + const value aval = elementsToValues(content(atom::isATOMEntry(ls)? atom::readATOMEntry(ls) : atom::readATOMFeed(ls))); + + // Evaluate the PUT expression and update the corresponding resource + const failable<value> val = failableResult(impl(cons<value>("put", mklist<value>(cddr(rpath), aval)))); + if (!hasContent(val)) + return mkfailure<int>(val); + if (val == value(false)) + return HTTP_NOT_FOUND; + return OK; +} + +/** + * Handle an HTTP DELETE. + */ +const failable<int> del(const list<value>& rpath, request_rec* r, const lambda<value(const list<value>&)>& impl) { + debug(r->uri, "modeval::delete::uri"); + + // Evaluate an ATOM delete request + const failable<value> val = failableResult(impl(cons<value>("delete", mklist<value>(cddr(rpath))))); + if (!hasContent(val)) + return mkfailure<int>(val); + if (val == value(false)) + return HTTP_NOT_FOUND; + return OK; +} + +/** + * Proceed to handle a service component request. + */ +int proceedToHandler(request_rec* r, const int rc) { + r->handler = "mod_tuscany_eval"; + return rc; +} + +int proceedToHandler(request_rec* r, const int rc, const bool valias, const list<value>& rpath, const list<value>& vpath, const list<value>& 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<RequestConf>(r, &mod_tuscany_eval); + reqc.valias = valias; + reqc.rpath = rpath; + reqc.vpath = vpath; + reqc.impls = impls; + return rc; +} + +/** + * Route a component request to the specified component. + */ +int translateComponent(request_rec *r, const list<value>& rpath, const list<value>& vpath, const list<value>& 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<value> impl(assoctree(cadr(rpath), impls)); + if (isNil(impl)) + return HTTP_NOT_FOUND; + debug(impl, "modeval::translateComponent::impl"); + + return proceedToHandler(r, OK, 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<value>& rpath, const list<value>& vpath, const list<value>& refs, const list<value>& 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<value> 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<value> ref(assoctree<value>(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<value> 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<value> redir = cons<value>(string("c"), cons(tname, pathInfo)); + debug(redir, "modeval::translateReference::redirect"); + return proceedToHandler(r, OK, false, redir, vpath, impls);; +} + +/** + * Find a leaf matching a request path in a tree of URI paths. + */ +const int matchPath(const list<value>& k, const list<value>& p) { + if (isNil(p)) + return true; + if (isNil(k)) + return false; + if (car(k) != car(p)) + return false; + return matchPath(cdr(k), cdr(p)); +} + +const list<value> assocPath(const value& k, const list<value>& tree) { + if (isNil(tree)) + return tree; + if (matchPath(k, car<value>(car(tree)))) + return car(tree); + if (k < car<value>(car(tree))) + return assocPath(k, cadr(tree)); + return assocPath(k, caddr(tree)); +} + +/** + * Route a service request to the component providing the requested service. + */ +int translateService(request_rec *r, const list<value>& rpath, const list<value>& vpath, const list<value>& svcs, const list<value>& 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<value> 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<value> redir = cons<value>(string("c"), cons<value>(cadr(svc), httpd::pathInfo(rpath, car(svc)))); + debug(redir, "modeval::translateService::redirect"); + return proceedToHandler(r, OK, false, redir, vpath, impls); +} + +/** + * Translate a request to the target app and component. + */ +const int translateRequest(request_rec* r, const list<value>& rpath, const list<value>& vpath, const list<value>& refs, const list<value>& svcs, const list<value>& 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 proceedToHandler(r, OK); + + // Translate a component reference request + if ((prefix == string("references") || prefix == string("r")) && translateReference(r, rpath, vpath, refs, impls) == OK) + return proceedToHandler(r, OK); + + // Attempt to translate the request to a service request + if (translateService(r, rpath, vpath, svcs, impls) == OK) + return proceedToHandler(r, OK); + + // Attempt to map a request targeting the main host to an actual file + if (isNil(vpath)) { + const failable<request_rec*> fnr = httpd::internalSubRequest(r->uri, r); + if (!hasContent(fnr)) + return rcode(fnr); + 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"); + return proceedToHandler(r, 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, OK, 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; + + gc_scoped_pool pool(r->pool); + + debug_httpdRequest(r, "modeval::translate::input"); + + // Get the server configuration + const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval); + + // Parse the request path + const list<value> 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<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.contributor)) && httpd::isVhostRequest(sc.server, sc.vhostc.domain, r)) { + const string vname = http::subDomain(httpd::hostName(r)); + const failable<Composite> fvcompos = confComponents(sc.vhostc.contribPath, sc.vhostc.composName, sc.vhostc.contributor, vname, reqc.impls, sc.lifecycle, sc.sslc, sc.timeout, 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<value>(vname), vcompos.refs, vcompos.svcs, reqc.impls); + } + + // Translate a request targeting the main host + const int rc = translateRequest(r, rpath, list<value>(), 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.contributor))) { + const string vname = prefix; + const failable<Composite> fvcompos = confComponents(sc.vhostc.contribPath, sc.vhostc.composName, sc.vhostc.contributor, vname, reqc.impls, sc.lifecycle, sc.sslc, sc.timeout, 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<value>(vname), vcompos.refs, vcompos.svcs, reqc.impls); + } + return DECLINED; +} + +/** + * Handle a component request. + */ +const int handleRequest(const list<value>& rpath, request_rec *r, const list<value>& impls) { + debug(rpath, "modeval::handleRequest::path"); + + // Get the component implementation lambda + const list<value> impl(assoctree<value>(cadr(rpath), impls)); + if (isNil(impl)) { + mkfailure<int>(string("Couldn't find component implementation: ") + cadr(rpath)); + return HTTP_NOT_FOUND; + } + const lambda<value(const list<value>&)> l(cadr<value>(impl)); + + // Handle HTTP method + 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->handler != NULL && r->handler[0] != '\0') + return DECLINED; + + // Attempt to translate the request + const int trc = translate(r); + + // Pass if we couldn't translate the request + if(trc != OK) + return trc; + if(strcmp(r->handler, "mod_tuscany_eval")) + return DECLINED; + + // Create a scope for the current request + gc_scoped_pool pool(r->pool); + ScopedRequest sr(r); + + debug_httpdRequest(r, "modeval::handler::input"); + + // Get the request configuration + RequestConf& reqc = httpd::requestConf<RequestConf>(r, &mod_tuscany_eval); + + // Handle an internal redirect as directed by the translate hook + if (reqc.valias) { + const string redir = path(cons<value>(string("v"), reqc.vhost? reqc.vpath : list<value>())) + 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<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<list<value> > fsimpls = startComponents(reqc.impls); + if (!hasContent(fsimpls)) + return HTTP_INTERNAL_SERVER_ERROR; + const list<value> 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); +} + +/** + * Call an authenticator component to check a user's password. + */ +authn_status checkPassword(request_rec* r, const char* u, const char* p) { + gc_scoped_pool pool(r->pool); + + // Prevent FakeBasicAuth spoofing + const string user = u; + const string password = p; + debug(user, "modeval::checkPassword::user"); + if (substr(user, 0, 1) != "/" && find(user, "/") != length(user) && password == "password") { + mkfailure<int>(string("Encountered FakeBasicAuth spoof: ") + user, HTTP_UNAUTHORIZED); + return AUTH_DENIED; + } + + // Get the server configuration + const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval); + if (isNil(sc.vhostc.authenticator)) { + mkfailure<int>("SCA authenticator not configured"); + return AUTH_GENERAL_ERROR; + } + + // Retrieve the user's password hash + const list<value> uid = pathValues(user); + const failable<value> val = failableResult(sc.vhostc.authenticator(cons<value>("get", mklist<value>(uid)))); + if (!hasContent(val)) { + mkfailure<int>(string("SCA authentication check user failed, user not found: ") + user); + return AUTH_USER_NOT_FOUND; + } + const value hval = content(val); + const list<value> hcontent = isList(hval) && !isNil(hval) && isList(car<value>(hval)) && !isNil(car<value>(hval))? assoc<value>(value("content"), cdr<value>(car<value>(hval))) : list<value>(); + const list<value> hassoc = isNil(hcontent)? list<value>() : assoc<value>(value("hash"), cdr<value>(hcontent)); + if (isNil(hassoc)) { + mkfailure<int>(string("SCA authentication check user failed, hash not found: ") + user); + return AUTH_USER_NOT_FOUND; + } + const string hash = cadr<value>(hassoc); + if (length(hash) == 0) { + mkfailure<int>(string("SCA authentication check user failed: ") + user); + return AUTH_USER_NOT_FOUND; + } + + // Cache the hash in the auth cache provider, if available + if (authnCacheStore != NULL) + authnCacheStore(r, "component", u, NULL, c_str(hash)); + + // Validate the presented password against the hash + const apr_status_t rv = apr_password_validate(p, c_str(hash)); + if (rv != APR_SUCCESS) { + mkfailure<int>(string("SCA authentication user password check failed: ") + user); + return AUTH_DENIED; + } + return AUTH_GRANTED; +} + +/** + * 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<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"); + 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.timeout = mainsc.timeout; + 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<value>&); + + gc_scoped_pool pool(p); + + // Get the server configuration and determine the server name + ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_eval); + debug(httpd::serverName(s), "modeval::postConfig::serverName"); + 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<bool>(string("Couldn't chdir to the deployed contribution: ") + sc.contribc.contribPath); + 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<Composite> compos = confComponents(sc.contribc.contribPath, sc.contribc.composName, value(), "", sc.compos.impls, sc.lifecycle, sc.sslc, sc.timeout, 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); +} + +/** + * Exit after a failure. + */ +void failureExitChild() { + cfailure << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl; + exit(APEXIT_CHILDFATAL); +} + +/** + * 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) + failureExitChild(); + ServerConf& sc = *psc; + + // Start the components in the child process + const failable<list<value> > fsimpls = startComponents(sc.compos.impls); + if (!hasContent(fsimpls)) + failureExitChild(); + sc.compos.impls = content(fsimpls); + + // Get the vhost contributor component implementation lambda + if (length(sc.vhostc.contributorName) != 0) { + const list<value> impl(assoctree<value>(sc.vhostc.contributorName, sc.compos.impls)); + if (isNil(impl)) { + mkfailure<int>(string("Couldn't find contributor component implementation: ") + sc.vhostc.contributorName); + failureExitChild(); + } + sc.vhostc.contributor = cadr<value>(impl); + } + + // Get the vhost authenticator component implementation lambda + if (length(sc.vhostc.authenticatorName) != 0) { + const list<value> impl(assoctree<value>(sc.vhostc.authenticatorName, sc.compos.impls)); + if (isNil(impl)) { + mkfailure<int>(string("Couldn't find authenticator component implementation: ") + sc.vhostc.authenticatorName); + failureExitChild(); + } + sc.vhostc.authenticator = cadr<value>(impl); + } + + // 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<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<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<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<ServerConf>(cmd, &mod_tuscany_eval); + sc.vhostc.contribPath = arg; + return NULL; +} +const char* confVirtualContributor(cmd_parms *cmd, unused void *c, const char *arg) { + gc_scoped_pool pool(cmd->pool); + ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval); + sc.vhostc.contributorName = 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.vhostc.composName = arg; + return NULL; +} +const char* confAuthenticator(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.vhostc.authenticatorName = 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.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<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<ServerConf>(cmd, &mod_tuscany_eval); + sc.sslc.key = arg; + return NULL; +} +const char* confTimeout(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.timeout = atoi(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*(*)())confVirtualContributor, NULL, RSRC_CONF, "SCA virtual host contributor component"), + AP_INIT_TAKE1("SCAVirtualComposite", (const char*(*)())confVirtualComposite, NULL, RSRC_CONF, "SCA virtual composite location"), + AP_INIT_TAKE1("SCAAuthenticator", (const char*(*)())confAuthenticator, NULL, RSRC_CONF, "SCA authenticator component"), + 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"), + AP_INIT_TAKE1("SCAWiringTimeout", (const char*(*)())confTimeout, NULL, RSRC_CONF, "SCA wiring timeout"), + {NULL, NULL, NULL, 0, NO_ARGS, NULL} +}; + + +const authn_provider AuthnProvider = { + &checkPassword, + NULL +}; + +void retrieveAuthnCacheStore() { + authnCacheStore = APR_RETRIEVE_OPTIONAL_FN(ap_authn_cache_store); +} + +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_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "component", AUTHN_PROVIDER_VERSION, &AuthnProvider, AP_AUTH_INTERNAL_PER_CONF); + ap_hook_optional_fn_retrieve(retrieveAuthnCacheStore, NULL, NULL, APR_HOOK_MIDDLE); +} + +} +} +} + +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 diff --git a/sca-cpp/branches/lightweight-sca/modules/server/mod-scheme.hpp b/sca-cpp/branches/lightweight-sca/modules/server/mod-scheme.hpp new file mode 100644 index 0000000000..43d7bf4041 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/mod-scheme.hpp @@ -0,0 +1,91 @@ +/* + * 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_modscheme_hpp +#define tuscany_modscheme_hpp + +/** + * Evaluation functions used by mod-eval to evaluate Scheme + * component implementations. + */ + +#include "string.hpp" +#include "stream.hpp" +#include "function.hpp" +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" +#include "../scheme/eval.hpp" + +namespace tuscany { +namespace server { +namespace modscheme { + +/** + * Convert proxy lambdas to evaluator primitive procedures. + */ +const list<value> primitiveProcedures(const list<value>& l) { + if (isNil(l)) + return l; + return cons<value>(mklist<value>(scheme::primitiveSymbol, car(l)), primitiveProcedures(cdr(l))); +} + +/** + * Apply a Scheme component implementation function. + */ +struct applyImplementation { + const value impl; + const list<value> px; + + applyImplementation(const value& impl, const list<value>& px) : impl(impl), px(scheme::quotedParameters(primitiveProcedures(px))) { + } + + const value operator()(const list<value>& params) const { + const value expr = cons<value>(car(params), append(scheme::quotedParameters(cdr(params)), px)); + debug(expr, "modeval::scheme::applyImplementation::input"); + scheme::Env env = scheme::setupEnvironment(); + const value res = scheme::evalScript(expr, impl, env); + const value val = isNil(res)? mklist<value>(value(), string("Could not evaluate expression")) : mklist<value>(res); + debug(val, "modeval::scheme::applyImplementation::result"); + return val; + } +}; + +/** + * Evaluate a Scheme component implementation and convert it to an + * applicable lambda function. + */ +const failable<lambda<value(const list<value>&)> > evalImplementation(const string& path, const value& impl, const list<value>& px) { + const string fpath(path + attributeValue("script", impl)); + ifstream is(fpath); + if (fail(is)) + return mkfailure<lambda<value(const list<value>&)> >(string("Could not read implementation: ") + fpath); + const value script = scheme::readScript(is); + if (isNil(script)) + return mkfailure<lambda<value(const list<value>&)> >(string("Could not read implementation: ") + fpath); + return lambda<value(const list<value>&)>(applyImplementation(script, px)); +} + +} +} +} + +#endif /* tuscany_modscheme_hpp */ diff --git a/sca-cpp/branches/lightweight-sca/modules/server/property-test.scm b/sca-cpp/branches/lightweight-sca/modules/server/property-test.scm new file mode 100644 index 0000000000..f5ba76f1f3 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/property-test.scm @@ -0,0 +1,21 @@ +; 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. + +; Built-in property test case + +(define (print host path query) (list (list 'host (host)) (list 'path (path)) (list 'query (query)))) + diff --git a/sca-cpp/branches/lightweight-sca/modules/server/scheme-conf b/sca-cpp/branches/lightweight-sca/modules/server/scheme-conf new file mode 100755 index 0000000000..bc4074c8be --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/scheme-conf @@ -0,0 +1,37 @@ +#!/bin/sh + +# 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. + +# Generate a Scheme server conf +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +mkdir -p $1 +root=`echo "import os; print os.path.realpath('$1')" | python` + +uname=`uname -s` +if [ $uname = "Darwin" ]; then + libsuffix=".dylib" +else + libsuffix=".so" +fi + +cat >>$root/conf/modules.conf <<EOF +# Generated by: scheme-conf $* +# Support for Scheme SCA components +LoadModule mod_tuscany_eval $here/libmod_tuscany_eval$libsuffix + +EOF diff --git a/sca-cpp/branches/lightweight-sca/modules/server/server-conf b/sca-cpp/branches/lightweight-sca/modules/server/server-conf new file mode 100755 index 0000000000..bfa5ac8473 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/server-conf @@ -0,0 +1,40 @@ +#!/bin/sh + +# 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. + +# Generate a server conf +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +mkdir -p $1 +root=`echo "import os; print os.path.realpath('$1')" | python` + +# Serve Javascript scripts and CSS +$here/../js/js-conf $1 + +# Configure SSL cert used for wiring +ssl=`cat $root/conf/httpd.conf | grep "# Generated by: httpd-ssl-conf"` +if [ "$ssl" != "" ]; then + cat >>$root/conf/httpd.conf <<EOF +# Configure SSL certificates +SCAWiringSSLCACertificateFile "$root/cert/ca.crt" +SCAWiringSSLCertificateFile "$root/cert/server.crt" +SCAWiringSSLCertificateKeyFile "$root/cert/server.key" + +EOF + +fi + diff --git a/sca-cpp/branches/lightweight-sca/modules/server/server-test b/sca-cpp/branches/lightweight-sca/modules/server/server-test new file mode 100755 index 0000000000..6b48d13e83 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/server-test @@ -0,0 +1,41 @@ +#!/bin/sh + +# 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. + +# Setup +rm -rf tmp +../http/httpd-conf tmp localhost 8090 htdocs +../http/httpd-event-conf tmp +./server-conf tmp +./scheme-conf tmp +cat >>tmp/conf/httpd.conf <<EOF +SCAContribution `pwd`/ +SCAComposite domain-test.composite +EOF + +../http/httpd-start tmp +sleep 2 + +# Test +./client-test 2>/dev/null +rc=$? + +# Cleanup +../http/httpd-stop tmp +sleep 2 +exit $rc diff --git a/sca-cpp/branches/lightweight-sca/modules/server/server-test.scm b/sca-cpp/branches/lightweight-sca/modules/server/server-test.scm new file mode 100644 index 0000000000..4bbff6e5c2 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/server-test.scm @@ -0,0 +1,44 @@ +; 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. + +; JSON-RPC test case + +(define (echo x) x) + +; ATOMPub test case + +(define (get id) + (if (nul id) + '((feed (title "Sample Feed") (id "123456789") (entry + (((title "Item") (id "111") (content (item (name "Apple") (currencyCode "USD") (currencySymbol "$") (price 2.99)))) + ((title "Item") (id "222") (content (item (name "Orange") (currencyCode "USD") (currencySymbol "$") (price 3.55)))) + ((title "Item") (id "333") (content (item (name "Pear") (currencyCode "USD") (currencySymbol "$") (price 1.55)))))))) + + (list (list 'entry '(title "Item") (list 'id (car id)) '(content (item (name "Apple") (currencyCode "USD") (currencySymbol "$") (price 2.99)))))) +) + +(define (post collection item) + '("123456789") +) + +(define (put id item) + true +) + +(define (delete id) + true +) diff --git a/sca-cpp/branches/lightweight-sca/modules/server/wiring-test b/sca-cpp/branches/lightweight-sca/modules/server/wiring-test new file mode 100755 index 0000000000..7e1aea22b1 --- /dev/null +++ b/sca-cpp/branches/lightweight-sca/modules/server/wiring-test @@ -0,0 +1,80 @@ +#!/bin/sh + +# 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. + +echo "Testing..." +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +curl_prefix=`cat $here/../http/curl.prefix` + +# Setup +rm -rf tmp +../http/httpd-conf tmp localhost 8090 htdocs +../http/httpd-event-conf tmp +./server-conf tmp +./scheme-conf tmp +cat >>tmp/conf/httpd.conf <<EOF +SCAContribution `pwd`/ +SCAComposite domain-test.composite +EOF + +../http/httpd-start tmp +sleep 2 + +# Test HTTP GET +$curl_prefix/bin/curl http://localhost:8090/index.html 2>/dev/null >tmp/index.html +diff tmp/index.html htdocs/index.html +rc=$? + +# Test ATOMPub +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/client/ >tmp/feed.xml 2>/dev/null + diff tmp/feed.xml htdocs/test/feed.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/client/111 >tmp/entry.xml 2>/dev/null + diff tmp/entry.xml htdocs/test/entry.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/client/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/client/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/client/111 -X DELETE 2>/dev/null + rc=$? +fi + +# Test JSON-RPC +if [ "$rc" = "0" ]; then + $curl_prefix/bin/curl http://localhost:8090/client/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/test/json-request.txt >tmp/json-result.txt 2>/dev/null + diff tmp/json-result.txt htdocs/test/json-result.txt + rc=$? +fi + +# Cleanup +../http/httpd-stop tmp +sleep 2 +if [ "$rc" = "0" ]; then + echo "OK" +fi +exit $rc |