diff options
Diffstat (limited to '')
36 files changed, 1135 insertions, 337 deletions
diff --git a/cpp/sca/configure.ac b/cpp/sca/configure.ac index a799eb4764..30f263a73a 100644 --- a/cpp/sca/configure.ac +++ b/cpp/sca/configure.ac @@ -267,6 +267,7 @@ AC_CONFIG_FILES([Makefile kernel/Makefile modules/Makefile modules/atom/Makefile + modules/cache/Makefile modules/eval/Makefile modules/http/Makefile modules/json/Makefile diff --git a/cpp/sca/etc/git-exclude b/cpp/sca/etc/git-exclude index 450e879202..fd55cecff2 100644 --- a/cpp/sca/etc/git-exclude +++ b/cpp/sca/etc/git-exclude @@ -55,7 +55,6 @@ Doxyfile *_Wrapper.h gmon.out *~ -valgrind.txt # Specific ignores sca/runtime/extensions/cpp/tools/scagen/docs/ @@ -67,6 +66,7 @@ atom-test eval-test eval-shell json-test +cache-test curl-test store-function-test store-object-test diff --git a/cpp/sca/etc/httpd-ipcrm b/cpp/sca/etc/httpd-ipcrm new file mode 100755 index 0000000000..b457e7385f --- /dev/null +++ b/cpp/sca/etc/httpd-ipcrm @@ -0,0 +1,23 @@ +#!/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. + +# Remove ipcs created by httpd + +ipcs -s | grep 0x | awk '{ print $2 }' | xargs -i -t ipcrm -s {} + diff --git a/cpp/sca/modules/Makefile.am b/cpp/sca/modules/Makefile.am index fef28b5120..dd085903f0 100644 --- a/cpp/sca/modules/Makefile.am +++ b/cpp/sca/modules/Makefile.am @@ -15,5 +15,5 @@ # specific language governing permissions and limitations # under the License. -SUBDIRS = atom eval http json +SUBDIRS = atom cache eval http json diff --git a/cpp/sca/modules/atom/atom-test.cpp b/cpp/sca/modules/atom/atom-test.cpp index 618f534001..7c14b954a0 100644 --- a/cpp/sca/modules/atom/atom-test.cpp +++ b/cpp/sca/modules/atom/atom-test.cpp @@ -33,7 +33,7 @@ namespace tuscany { namespace atom { -std::ostringstream* writer(std::ostringstream* os, const std::string& s) { +std::ostringstream* writer(const std::string& s, std::ostringstream* os) { (*os) << s; return os; } @@ -78,19 +78,19 @@ bool testEntry() { << (list<value>() << element << "price" << std::string("$2.99")); const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); std::ostringstream os; - writeEntry<std::ostringstream*>(writer, &os, a); + writeATOMEntry<std::ostringstream*>(writer, &os, a); assert(os.str() == itemEntry); } { const list<value> a = readEntry(mklist(itemEntry)); std::ostringstream os; - writeEntry<std::ostringstream*>(writer, &os, a); + writeATOMEntry<std::ostringstream*>(writer, &os, a); assert(os.str() == itemEntry); } { const list<value> a = readEntry(mklist(incompleteEntry)); std::ostringstream os; - writeEntry<std::ostringstream*>(writer, &os, a); + writeATOMEntry<std::ostringstream*>(writer, &os, a); assert(os.str() == completedEntry); } return true; @@ -131,13 +131,13 @@ std::string itemFeed("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" bool testFeed() { { std::ostringstream os; - writeFeed<std::ostringstream*>(writer, &os, mklist<value>("Feed", "1234")); + writeATOMFeed<std::ostringstream*>(writer, &os, mklist<value>("Feed", "1234")); assert(os.str() == emptyFeed); } { const list<value> a = readFeed(mklist(emptyFeed)); std::ostringstream os; - writeFeed<std::ostringstream*>(writer, &os, a); + writeATOMFeed<std::ostringstream*>(writer, &os, a); assert(os.str() == emptyFeed); } { @@ -152,7 +152,7 @@ bool testFeed() { << (list<value>() << element << "price" << "$3.55"))); const list<value> a = cons<value>("Feed", cons<value>("1234", i)); std::ostringstream os; - writeFeed<std::ostringstream*>(writer, &os, a); + writeATOMFeed<std::ostringstream*>(writer, &os, a); assert(os.str() == itemFeed); } { @@ -167,13 +167,13 @@ bool testFeed() { << (list<value>() << "price" << "$3.55"))); const list<value> a = cons<value>("Feed", cons<value>("1234", i)); std::ostringstream os; - writeFeed<std::ostringstream*>(writer, &os, a); + writeATOMFeed<std::ostringstream*>(writer, &os, a); assert(os.str() == itemFeed); } { const list<value> a = readFeed(mklist(itemFeed)); std::ostringstream os; - writeFeed<std::ostringstream*>(writer, &os, a); + writeATOMFeed<std::ostringstream*>(writer, &os, a); assert(os.str() == itemFeed); } return true; diff --git a/cpp/sca/modules/atom/atom.hpp b/cpp/sca/modules/atom/atom.hpp index 78b60da70f..2077081320 100644 --- a/cpp/sca/modules/atom/atom.hpp +++ b/cpp/sca/modules/atom/atom.hpp @@ -38,7 +38,7 @@ namespace atom { /** * Convert a list of elements to a list of values representing an ATOM entry. */ -const list<value> entry(const list<value>& e) { +const list<value> entryValue(const list<value>& e) { const list<value> lt = filter<value>(selector(mklist<value>(element, "title")), e); const value t = isNil(lt)? value(std::string("")) : elementValue(car(lt)); const list<value> li = filter<value>(selector(mklist<value>(element, "id")), e); @@ -50,10 +50,10 @@ const list<value> entry(const list<value>& e) { /** * Convert a list of elements to a list of values representing ATOM entries. */ -const list<value> entries(const list<value>& e) { +const list<value> entriesValues(const list<value>& e) { if (isNil(e)) return list<value>(); - return cons<value>(entry(car(e)), entries(cdr(e))); + return cons<value>(entryValue(car(e)), entriesValues(cdr(e))); } /** @@ -63,7 +63,7 @@ const failable<list<value>, std::string> readEntry(const list<std::string>& ilis const list<value> e = readXML(ilist); if (isNil(e)) return std::string("Empty entry"); - return entry(car(e)); + return entryValue(car(e)); } /** @@ -78,7 +78,7 @@ const failable<list<value>, std::string> readFeed(const list<std::string>& ilist const list<value> e = filter<value>(selector(mklist<value>(element, "entry")), car(f)); if (isNil(e)) return mklist<value>(elementValue(car(t)), elementValue(car(i))); - return cons<value>(elementValue(car(t)), cons(elementValue(car(i)), entries(e))); + return cons<value>(elementValue(car(t)), cons(elementValue(car(i)), entriesValues(e))); } /** @@ -107,20 +107,12 @@ const list<value> entriesElements(const list<value>& l) { * Convert a list of values representing an ATOM entry to an ATOM entry. * The first two values in the list are the entry id and title. */ -template<typename R> const failable<R, std::string> writeEntry(const lambda<R(R, std::string)>& reduce, const R& initial, const list<value>& l) { +template<typename R> const failable<R, std::string> writeATOMEntry(const lambda<R(std::string, R)>& reduce, const R& initial, const list<value>& l) { return writeXML<R>(reduce, initial, mklist<value>(entryElement(l))); } -/** - * Convert a list of values representing an ATOM entry to a list of strings. - * The first two values in the list are the entry id and title. - */ -const list<std::string> writeStrings(const list<std::string>& listSoFar, const std::string& s) { - return cons(s, listSoFar); -} - -const failable<list<std::string>, std::string> writeEntry(const list<value>& l) { - const failable<list<std::string>, std::string> ls = writeEntry<list<std::string> >(writeStrings, list<std::string>(), l); +const failable<list<std::string>, std::string> writeATOMEntry(const list<value>& l) { + const failable<list<std::string>, std::string> ls = writeATOMEntry<list<std::string> >(rcons<std::string>, list<std::string>(), l); if (!hasValue(ls)) return ls; return reverse(list<std::string>(ls)); @@ -130,7 +122,7 @@ const failable<list<std::string>, std::string> writeEntry(const list<value>& l) * Convert a list of values representing an ATOM feed to an ATOM feed. * The first two values in the list are the feed id and title. */ -template<typename R> const failable<R, std::string> writeFeed(const lambda<R(R, std::string)>& reduce, const R& initial, const list<value>& l) { +template<typename R> const failable<R, std::string> writeATOMFeed(const lambda<R(std::string, R)>& reduce, const R& initial, const list<value>& l) { const list<value> f = list<value>() << element << "feed" << (list<value>() << attribute << "xmlns" << "http://www.w3.org/2005/Atom") << (list<value>() << element << "title" << (list<value>() << attribute << "type" << "text") << car(l)) @@ -145,13 +137,33 @@ template<typename R> const failable<R, std::string> writeFeed(const lambda<R(R, * Convert a list of values representing an ATOM feed to a list of strings. * The first two values in the list are the feed id and title. */ -const failable<list<std::string>, std::string> writeFeed(const list<value>& l) { - const failable<list<std::string>, std::string> ls = writeFeed<list<std::string> >(writeStrings, list<std::string>(), l); +const failable<list<std::string>, std::string> writeATOMFeed(const list<value>& l) { + const failable<list<std::string>, std::string> ls = writeATOMFeed<list<std::string> >(rcons<std::string>, list<std::string>(), l); if (!hasValue(ls)) return ls; return reverse(list<std::string>(ls)); } +/** + * Convert an ATOM entry containing a value to an ATOM entry containing an item element. + */ +const list<value> entryValuesToElements(const list<value> val) { + return cons(car(val), cons(cadr(val), valuesToElements(mklist<value>(cons<value>("item", (list<value>)caddr(val)))))); +} + +/** + * Convert an ATOM feed containing values to an ATOM feed containing elements. + */ +const list<value> feedValuesToElementsLoop(const list<value> val) { + if (isNil(val)) + return list<value>(); + return cons<value>(entryValuesToElements(car(val)), feedValuesToElementsLoop(cdr(val))); +} + +const list<value> feedValuesToElements(const list<value>& val) { + return cons(car<value>(val), cons<value>(cadr<value>(val), feedValuesToElementsLoop(cddr<value>(val)))); +} + } } diff --git a/cpp/sca/modules/cache/Makefile.am b/cpp/sca/modules/cache/Makefile.am new file mode 100644 index 0000000000..581b8b6682 --- /dev/null +++ b/cpp/sca/modules/cache/Makefile.am @@ -0,0 +1,26 @@ +# 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. + +noinst_PROGRAMS = cache-test + +INCLUDES = -I. -I$(top_builddir)/kernel -I${LIBXML2_INCLUDE} -I${APR_INCLUDE} + +cache_test_SOURCES = cache-test.cpp +cache_test_LDADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${APR_LIB} -lapr-1 -laprutil-1 + +TESTS = memcached-test + diff --git a/cpp/sca/modules/cache/cache-test.cpp b/cpp/sca/modules/cache/cache-test.cpp new file mode 100644 index 0000000000..776ac72363 --- /dev/null +++ b/cpp/sca/modules/cache/cache-test.cpp @@ -0,0 +1,59 @@ +/* + * 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 Memcached access functions. + */ + +#include <assert.h> +#include <iostream> +#include <string> +#include "cache.hpp" + +namespace tuscany { +namespace cache { + +bool testCache() { + Cache cache; + addServer("localhost", 11311, cache); + + assert(hasValue(post("a", "AAA", cache))); + assert(get("a", cache) == value(std::string("AAA"))); + assert(hasValue(put("a", "aaa", cache))); + assert(get("a", cache) == value(std::string("aaa"))); + assert(hasValue(del("a", cache))); + assert(!hasValue(get("a", cache))); + + return true; +} + +} +} + +int main() { + std::cout << "Testing..." << std::endl; + + tuscany::cache::testCache(); + + std::cout << "OK" << std::endl; + + return 0; +} diff --git a/cpp/sca/modules/cache/cache.hpp b/cpp/sca/modules/cache/cache.hpp new file mode 100644 index 0000000000..d4a24ca3d9 --- /dev/null +++ b/cpp/sca/modules/cache/cache.hpp @@ -0,0 +1,125 @@ +/* + * 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_cache_hpp +#define tuscany_cache_hpp + +/** + * Memcached access functions. + */ + +#include "apr.h" +#include "apu.h" +#include "apr_general.h" +#include "apr_strings.h" +#include "apr_hash.h" +#include "apr_memcache.h" +#include "apr_network_io.h" + +#include <string> +#include "list.hpp" +#include "value.hpp" +#include "monad.hpp" + +namespace tuscany { +namespace cache { + +class Cache { +public: + Cache() { + apr_pool_create(&pool, NULL); + apr_memcache_create(pool, 1, 0, &mc); + } + ~Cache() { + apr_pool_destroy(pool); + } + + operator apr_memcache_t*() const { + return mc; + } + + operator apr_pool_t*() const { + return pool; + } + +private: + apr_pool_t* pool; + apr_memcache_t* mc; + +}; + +const failable<bool, std::string> addServer(const std::string& host, const int port, Cache& cache) { + apr_memcache_server_t *server; + const apr_status_t sc = apr_memcache_server_create(cache, host.c_str(), port, 0, 1, 1, 60, &server); + if (sc != APR_SUCCESS) + return "Could not create server"; + const apr_status_t as = apr_memcache_add_server(cache, server); + if (as != APR_SUCCESS) + return "Could not add server"; + return true; +} + +const failable<bool, std::string> post(const value& key, const value& val, Cache& cache) { + const std::string v(val); + const apr_status_t rc = apr_memcache_add(cache, std::string(key).c_str(), const_cast<char*>(v.c_str()), v.size(), 0, 27); + if (rc != APR_SUCCESS) + return "Could not add entry"; + return true; +} + +const failable<bool, std::string> put(const value& key, const value& val, Cache& cache) { + const std::string v(val); + const apr_status_t rc = apr_memcache_replace(cache, std::string(key).c_str(), const_cast<char*>(v.c_str()), v.size(), 0, 27); + if (rc != APR_SUCCESS) + return "Could not add entry"; + return true; +} + +const failable<value, std::string> get(const value& key, Cache& cache) { + apr_pool_t* vpool; + const apr_status_t pc = apr_pool_create(&vpool, cache); + if (pc != APR_SUCCESS) + return std::string("Could not allocate memory"); + + char *data; + apr_size_t size; + const apr_status_t rc = apr_memcache_getp(cache, cache, std::string(key).c_str(), &data, &size, NULL); + if (rc != APR_SUCCESS) { + apr_pool_destroy(vpool); + return std::string("Could not get entry"); + } + + const value val(std::string(data, size)); + apr_pool_destroy(vpool); + return val; +} + +const failable<bool, std::string> del(const value& key, Cache& cache) { + const apr_status_t rc = apr_memcache_delete(cache, std::string(key).c_str(), 0); + if (rc != APR_SUCCESS) + return "Could not add entry"; + return true; +} + +} +} + +#endif /* tuscany_cache_hpp */ diff --git a/cpp/sca/modules/cache/memcached-test b/cpp/sca/modules/cache/memcached-test new file mode 100755 index 0000000000..2f9870a49b --- /dev/null +++ b/cpp/sca/modules/cache/memcached-test @@ -0,0 +1,31 @@ +#!/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 +cmd="memcached -l 127.0.0.1 -m 4 -p 11311" +$cmd & +sleep 1 + +# Test +./cache-test +rc=$? + +# Cleanup +ps -f | grep -v grep | grep "$cmd" | awk '{ print $2 }' | xargs kill +return $rc diff --git a/cpp/sca/modules/eval/driver.hpp b/cpp/sca/modules/eval/driver.hpp index f777973ebf..4c69ecb0a1 100644 --- a/cpp/sca/modules/eval/driver.hpp +++ b/cpp/sca/modules/eval/driver.hpp @@ -36,40 +36,39 @@ namespace eval { const std::string evalOutputPrompt("; "); const std::string evalInputPrompt("=> "); -const bool promptForInput(std::ostream& out, const std::string str) { +const bool promptForInput(const std::string str, std::ostream& out) { out << "\n\n" << str; return true; } -const bool announceOutput(std::ostream& out, const std::string str) { +const bool announceOutput(const std::string str, std::ostream& out) { out << "\n" << str; return true; } -const bool userPrint(std::ostream& out, const value object) { - if(isCompoundProcedure(object)) - out << mklist<value>(compoundProcedureSymbol, procedureParameters(object), procedureBody(object), "<procedure-env>"); - out << object; +const bool userPrint(const value val, std::ostream& out) { + if(isCompoundProcedure(val)) + writeValue(mklist<value>(compoundProcedureSymbol, procedureParameters(val), procedureBody(val), "<procedure-env>"), out); + writeValue(val, out); return true; } const value evalDriverLoop(std::istream& in, std::ostream& out, Env& globalEnv, const gc_pool& pool) { - promptForInput(out, evalInputPrompt); - value input = read(in); + promptForInput(evalInputPrompt, out); + value input = readValue(in); if (isNil(input)) return input; const value output = evalExpr(input, globalEnv, pool); - announceOutput(out, evalOutputPrompt); - userPrint(out, output); + announceOutput(evalOutputPrompt, out); + userPrint(output, out); return evalDriverLoop(in, out, globalEnv, pool); } const bool evalDriverRun(std::istream& in, std::ostream& out) { gc_pool pool; - setupEvalOut(out); + setupDisplay(out); Env globalEnv = setupEnvironment(pool); evalDriverLoop(in, out, globalEnv, pool); - cleanupEnvironment(globalEnv); return true; } diff --git a/cpp/sca/modules/eval/environment.hpp b/cpp/sca/modules/eval/environment.hpp index 90a1d88854..fa9667b1ba 100644 --- a/cpp/sca/modules/eval/environment.hpp +++ b/cpp/sca/modules/eval/environment.hpp @@ -151,14 +151,6 @@ const Env setupEnvironment(const gc_pool& pool) { return env; } -const bool cleanupEnvironment(Env& env) { - if (isNil(env)) - return true; - *firstFrame(env) = list<value>(); - Env enclosing = enclosingEnvironment(env); - return cleanupEnvironment(enclosing); -} - const value lookupEnvLoop(const value& var, const Env& env); const value lookupEnvScan(const value& var, const list<value>& vars, const list<value>& vals, const Env& env) { diff --git a/cpp/sca/modules/eval/eval-test.cpp b/cpp/sca/modules/eval/eval-test.cpp index 584c8470b1..7d92c79eba 100644 --- a/cpp/sca/modules/eval/eval-test.cpp +++ b/cpp/sca/modules/eval/eval-test.cpp @@ -39,7 +39,6 @@ bool testEnv() { defineVariable("x", env, env); assert(lookupVariableValue(value("x"), env) == env); assert(lookupVariableValue("a", env) == value(1)); - cleanupEnvironment(env); return true; } @@ -59,23 +58,41 @@ bool testEnvGC() { bool testRead() { std::istringstream is("abcd"); - assert(read(is) == "abcd"); + assert(readValue(is) == "abcd"); std::istringstream is2("123"); - assert(read(is2) == value(123)); + assert(readValue(is2) == value(123)); std::istringstream is3("(abcd)"); - assert(read(is3) == mklist(value("abcd"))); + assert(readValue(is3) == mklist(value("abcd"))); std::istringstream is4("(abcd xyz)"); - assert(read(is4) == mklist<value>("abcd", "xyz")); + assert(readValue(is4) == mklist<value>("abcd", "xyz")); std::istringstream is5("(abcd (xyz tuv))"); - assert(read(is5) == mklist<value>("abcd", mklist<value>("xyz", "tuv"))); + assert(readValue(is5) == mklist<value>("abcd", mklist<value>("xyz", "tuv"))); return true; } +bool testWrite() { + const list<value> i = list<value>() + << (list<value>() << "item" << "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b" + << (list<value>() << "item" + << (list<value>() << "name" << "Apple") + << (list<value>() << "price" << "$2.99"))) + << (list<value>() << "item" << "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c" + << (list<value>() << "item" + << (list<value>() << "name" << "Orange") + << (list<value>() << "price" << "$3.55"))); + const list<value> a = cons<value>("Feed", cons<value>("feed-1234", i)); + std::ostringstream os; + writeValue(a, os); + std::istringstream is(os.str()); + assert(readValue(is) == a); + return true; +} + const std::string testSchemeNumber( "(define (testNumber) (if (= 1 1) (display \"testNumber ok\") (error \"testNumber\"))) " "(testNumber)"); @@ -178,6 +195,7 @@ int main() { tuscany::eval::testEnv(); tuscany::eval::testEnvGC(); tuscany::eval::testRead(); + tuscany::eval::testWrite(); tuscany::eval::testEval(); tuscany::eval::testEvalExpr(); tuscany::eval::testEvalGC(); diff --git a/cpp/sca/modules/eval/eval.hpp b/cpp/sca/modules/eval/eval.hpp index 78051c5a2b..01d242f07b 100644 --- a/cpp/sca/modules/eval/eval.hpp +++ b/cpp/sca/modules/eval/eval.hpp @@ -30,7 +30,7 @@ #include "list.hpp" #include "value.hpp" #include "primitive.hpp" -#include "read.hpp" +#include "io.hpp" #include "environment.hpp" namespace tuscany { diff --git a/cpp/sca/modules/eval/read.hpp b/cpp/sca/modules/eval/io.hpp index 994462f145..2a55e67bbf 100644 --- a/cpp/sca/modules/eval/read.hpp +++ b/cpp/sca/modules/eval/io.hpp @@ -19,11 +19,11 @@ /* $Rev$ $Date$ */ -#ifndef tuscany_eval_read_hpp -#define tuscany_eval_read_hpp +#ifndef tuscany_eval_io_hpp +#define tuscany_eval_io_hpp /** - * Script evaluator read functions. + * Script evaluator IO functions. */ #include <iostream> @@ -96,7 +96,7 @@ const value readQuoted(std::istream& in); const value readIdentifier(const char chr, std::istream& in); const value readString(const char chr, std::istream& in); const value readNumber(const char chr, std::istream& in); -const value read(std::istream& in); +const value readValue(std::istream& in); const value readToken(std::istream& in) { const char firstChar = readChar(in); @@ -121,7 +121,7 @@ const value readToken(std::istream& in) { } const value readQuoted(std::istream& in) { - return mklist(quoteSymbol, read(in)); + return mklist(quoteSymbol, readValue(in)); } const list<value> readList(const list<value>& listSoFar, std::istream& in) { @@ -172,13 +172,17 @@ const value readNumber(const char chr, std::istream& in) { return stringToNumber(listToString(readNumberHelper(mklist(chr), in))); } -const value read(std::istream& in) { +const value readValue(std::istream& in) { const value nextToken = readToken(in); if(isLeftParenthesis(nextToken)) return readList(list<value> (), in); return nextToken; } +const bool writeValue(const value& val, std::ostream& out) { + out << val; +} + } } -#endif /* tuscany_eval_read_hpp */ +#endif /* tuscany_eval_io_hpp */ diff --git a/cpp/sca/modules/eval/primitive.hpp b/cpp/sca/modules/eval/primitive.hpp index 423e5af07b..9d62d6b1a6 100644 --- a/cpp/sca/modules/eval/primitive.hpp +++ b/cpp/sca/modules/eval/primitive.hpp @@ -40,73 +40,67 @@ const value primitiveSymbol("primitive"); const value quoteSymbol("'"); const value lambdaSymbol("lambda"); -std::ostream* evalOut = &std::cout; +std::ostream* displayOut = &std::cout; -const bool setupEvalOut(std::ostream& out) { - evalOut = &out; +const bool setupDisplay(std::ostream& out) { + displayOut = &out; return true; } -const value valueCar(list<value>& args) { +const value carProc(list<value>& args) { return car((list<value> )car(args)); } -const value valueCdr(list<value>& args) { +const value cdrProc(list<value>& args) { return cdr((list<value> )car(args)); } -const value valueCons(list<value>& args) { +const value consProc(list<value>& args) { return cons(car(args), (list<value> )cadr(args)); } -const value valueList(list<value>& args) { +const value listProc(list<value>& args) { return args; } -const value valueNul(list<value>& args) { +const value nulProc(list<value>& args) { return (bool)isNil(car(args)); } -const value valueEqual(list<value>& args) { +const value equalProc(list<value>& args) { return (bool)(car(args) == cadr(args)); } -const value valueAdd(list<value>& args) { +const value addProc(list<value>& args) { if (isNil(cdr(args))) return (double)car(args); return (double)car(args) + (double)cadr(args); } -const value valueSub(list<value>& args) { +const value subProc(list<value>& args) { if (isNil(cdr(args))) return (double)0 - (double)car(args); return (double)car(args) - (double)cadr(args); } -const value valueMul(list<value>& args) { +const value mulProc(list<value>& args) { return (double)car(args) * (double)cadr(args); } -const value valueDiv(list<value>& args) { +const value divProc(list<value>& args) { return (double)car(args) / (double)cadr(args); } -const value valueDisplay(list<value>& args) { - *evalOut << car(args); +const value displayProc(list<value>& args) { + *displayOut << car(args); return true; } -const value valueComment(list<value>& args) { - *evalOut << "; " << car(args); +const value commentProc(list<value>& args) { return true; } -const value valueError(list<value>& args) { - std::cerr << (std::string)car(args); - return true; -} - -const value valueUuid(list<value>& args) { +const value uuidProc(list<value>& args) { apr_uuid_t uuid; apr_uuid_get(&uuid); char buf[APR_UUID_FORMATTED_LENGTH]; @@ -164,20 +158,20 @@ const list<value> primitiveProcedureNames() { } const list<value> primitiveProcedureObjects() { - list<value> l = mklist(primitiveProcedure(valueCar)); - l = cons(primitiveProcedure(valueCdr), l); - l = cons(primitiveProcedure(valueCons), l); - l = cons(primitiveProcedure(valueList), l); - l = cons(primitiveProcedure(valueNul), l); - l = cons(primitiveProcedure(valueEqual), l); - l = cons(primitiveProcedure(valueAdd), l); - l = cons(primitiveProcedure(valueSub), l); - l = cons(primitiveProcedure(valueMul), l); - l = cons(primitiveProcedure(valueDiv), l); - l = cons(primitiveProcedure(valueEqual), l); - l = cons(primitiveProcedure(valueDisplay), l); - l = cons(primitiveProcedure(valueUuid), l); - l = cons(primitiveProcedure(valueComment), l); + list<value> l = mklist(primitiveProcedure(carProc)); + l = cons(primitiveProcedure(cdrProc), l); + l = cons(primitiveProcedure(consProc), l); + l = cons(primitiveProcedure(listProc), l); + l = cons(primitiveProcedure(nulProc), l); + l = cons(primitiveProcedure(equalProc), l); + l = cons(primitiveProcedure(addProc), l); + l = cons(primitiveProcedure(subProc), l); + l = cons(primitiveProcedure(mulProc), l); + l = cons(primitiveProcedure(divProc), l); + l = cons(primitiveProcedure(equalProc), l); + l = cons(primitiveProcedure(displayProc), l); + l = cons(primitiveProcedure(uuidProc), l); + l = cons(primitiveProcedure(commentProc), l); return l; } diff --git a/cpp/sca/modules/http/test-conf/mime.types b/cpp/sca/modules/http/conf/mime.types index 4279f51bca..4279f51bca 100644 --- a/cpp/sca/modules/http/test-conf/mime.types +++ b/cpp/sca/modules/http/conf/mime.types diff --git a/cpp/sca/modules/http/curl-test.cpp b/cpp/sca/modules/http/curl-test.cpp index 4e23a036da..b02a2fac4d 100644 --- a/cpp/sca/modules/http/curl-test.cpp +++ b/cpp/sca/modules/http/curl-test.cpp @@ -24,6 +24,8 @@ */ #include <assert.h> +#include <sys/time.h> +#include <time.h> #include <iostream> #include <sstream> #include <string> @@ -37,25 +39,164 @@ bool contains(const std::string& str, const std::string& pattern) { return str.find(pattern) != str.npos; } -std::ostringstream* curlWriter(std::ostringstream* os, const std::string& s) { +std::ostringstream* curlWriter(const std::string& s, std::ostringstream* os) { (*os) << s; return os; } -const bool testCURL() { +const bool testEval() { + CURLHandle ch; + const value val = evalExpr(mklist<value>(std::string("echo"), std::string("Hello")), "http://localhost:8091/test", ch); + assert(val == std::string("Hello")); + return true; +} + +const bool testEvalLoop(const int count, CURLHandle& ch) { + if (count == 0) + return true; + const value val = evalExpr(mklist<value>(std::string("echo"), std::string("Hello")), "http://localhost:8091/test", ch); + assert(val == std::string("Hello")); + return testEvalLoop(count - 1, ch); +} + +const value blob(std::string(3000, 'A')); +const list<value> blobs = mklist(blob, blob, blob, blob, blob); + +const bool testBlobEvalLoop(const int count, CURLHandle& ch) { + if (count == 0) + return true; + const value val = evalExpr(mklist<value>(std::string("echo"), blobs), "http://localhost:8091/test", ch); + assert(val == blobs); + return testBlobEvalLoop(count - 1, ch); +} + +const bool testEvalPerf() { + const int count = 50; + CURLHandle ch; + struct timeval start; + struct timeval end; + { + testEvalLoop(5, ch); + + gettimeofday(&start, NULL); + + testEvalLoop(count, ch); + + gettimeofday(&end, NULL); + long t = (end.tv_sec * 1000 + end.tv_usec / 1000) - (start.tv_sec * 1000 + start.tv_usec / 1000); + std::cout << "JSON-RPC eval echo test " << (t / count) << " ms" << std::endl; + } + { + testBlobEvalLoop(5, ch); + + gettimeofday(&start, NULL); + + testBlobEvalLoop(count, ch); + + gettimeofday(&end, NULL); + long t = (end.tv_sec * 1000 + end.tv_usec / 1000) - (start.tv_sec * 1000 + start.tv_usec / 1000); + std::cout << "JSON-RPC eval blob test " << (t / count) << " ms" << std::endl; + } +} + +const bool testGet() { + CURLHandle ch; { std::ostringstream os; - const failable<std::ostringstream*, int> r = get<std::ostringstream*>(curlWriter, &os, "http://localhost:9091"); + const failable<list<std::ostringstream*>, std::string> r = get<std::ostringstream*>(curlWriter, &os, "http://localhost:8091", ch); assert(hasValue(r)); + assert(contains(os.str(), "HTTP/1.1 200 OK")); assert(contains(os.str(), "It works")); } { - std::ostringstream os; - const failable<list<std::string>, int> r = get("http://localhost:9091"); + const failable<value, std::string> r = get("http://localhost:8091", ch); assert(hasValue(r)); - write(r, os); - assert(contains(os.str(), "It works")); + const value val = r; + assert(contains(val, "It works")); + } + return true; +} + +const bool testFeed() { + return true; +} + +bool testPost() { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + CURLHandle ch; + value rc = post(a, "http://localhost:8091/test", ch); + assert(rc == value(true)); + return true; +} + +const bool testPostLoop(const int count, const value& val, CURLHandle& ch) { + if (count == 0) + return true; + const value rc = post(val, "http://localhost:8091/test", ch); + assert(rc == value(true)); + return testPostLoop(count - 1, val, ch); +} + +const bool testPostPerf() { + const int count = 50; + CURLHandle ch; + struct timeval start; + struct timeval end; + { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> val = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + testPostLoop(5, val, ch); + + gettimeofday(&start, NULL); + + testPostLoop(count, val, ch); + + gettimeofday(&end, NULL); + long t = (end.tv_sec * 1000 + end.tv_usec / 1000) - (start.tv_sec * 1000 + start.tv_usec / 1000); + std::cout << "ATOMPub POST small test " << (t / count) << " ms" << std::endl; + } + { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "blob1" << blob) + << (list<value>() << "blob2" << blob) + << (list<value>() << "blob3" << blob) + << (list<value>() << "blob4" << blob) + << (list<value>() << "blob5" << blob) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> val = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + testPostLoop(5, val, ch); + + gettimeofday(&start, NULL); + + testPostLoop(count, val, ch); + + gettimeofday(&end, NULL); + long t = (end.tv_sec * 1000 + end.tv_usec / 1000) - (start.tv_sec * 1000 + start.tv_usec / 1000); + std::cout << "ATOMPub POST blob test " << (t / count) << " ms" << std::endl; } +} + +const bool testPut() { + const list<value> i = list<value>() + << (list<value>() << "name" << std::string("Apple")) + << (list<value>() << "price" << std::string("$2.99")); + const list<value> a = mklist<value>(std::string("item"), std::string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i); + CURLHandle ch; + value rc = put(a, "http://localhost:8091/test", ch); + assert(rc == value(true)); + return true; +} + +const bool testDel() { + CURLHandle ch; + value rc = del("http://localhost:8091/test/123456789", ch); + assert(rc == value(true)); return true; } @@ -65,7 +206,14 @@ const bool testCURL() { int main() { std::cout << "Testing..." << std::endl; - tuscany::http::testCURL(); + tuscany::http::testGet(); + tuscany::http::testPost(); + //tuscany::http::testPostPerf(); + tuscany::http::testEval(); + //tuscany::http::testEvalPerf(); + tuscany::http::testFeed(); + tuscany::http::testPut(); + tuscany::http::testDel(); std::cout << "OK" << std::endl; diff --git a/cpp/sca/modules/http/curl.hpp b/cpp/sca/modules/http/curl.hpp index 2ea23010cb..c0d79cef13 100644 --- a/cpp/sca/modules/http/curl.hpp +++ b/cpp/sca/modules/http/curl.hpp @@ -34,11 +34,18 @@ #include "value.hpp" #include "element.hpp" #include "monad.hpp" +#include "../atom/atom.hpp" +#include "../json/json.hpp" namespace tuscany { namespace http { /** + * Set to true to log HTTP content. + */ +bool logContent = false; + +/** * CURL library context, one per process. */ class CURLContext { @@ -72,13 +79,37 @@ private: }; /** + * Context passed to the read callback function. + */ +class CURLReadContext { +public: + CURLReadContext(const list<std::string>& ilist) : ilist(ilist) { + } + list<std::string> ilist; +}; + +/** + * Called by CURL to read data to send. + */ +size_t readCallback(void *ptr, size_t size, size_t nmemb, void *data) { + CURLReadContext& rcx = *static_cast<CURLReadContext*>(data); + if (isNil(rcx.ilist)) + return 0; + rcx.ilist = fragment(rcx.ilist, size * nmemb); + const std::string s = car(rcx.ilist); + rcx.ilist = cdr(rcx.ilist); + s.copy((char*)ptr, s.length()); + return s.length(); +} + +/** * Context passed to CURL write callback function. */ template<typename R> class CURLWriteContext { public: - CURLWriteContext(const lambda<R(R, std::string)>& reduce, const R& accum) : reduce(reduce), accum(accum) { + CURLWriteContext(const lambda<R(std::string, R)>& reduce, const R& accum) : reduce(reduce), accum(accum) { } - const lambda<R(R, std::string)> reduce; + const lambda<R(std::string, R)> reduce; R accum; }; @@ -88,7 +119,7 @@ public: template<typename R> size_t writeCallback(void *ptr, size_t size, size_t nmemb, void *data) { CURLWriteContext<R>& wcx = *(static_cast<CURLWriteContext<R>*> (data)); const size_t realsize = size * nmemb; - wcx.accum = wcx.reduce(wcx.accum, std::string((const char*)ptr, realsize)); + wcx.accum = wcx.reduce(std::string((const char*)ptr, realsize), wcx.accum); return realsize; } @@ -98,50 +129,199 @@ template<typename R> size_t writeCallback(void *ptr, size_t size, size_t nmemb, template<typename R> size_t headerCallback(void *ptr, size_t size, size_t nmemb, void *data) { CURLWriteContext<R>& wcx = *(static_cast<CURLWriteContext<R>*> (data)); const size_t realsize = size * nmemb; - wcx.accum = wcx.reduce(wcx.accum, std::string((const char*)ptr, realsize)); + wcx.accum = wcx.reduce(std::string((const char*)ptr, realsize), wcx.accum); return realsize; } /** - * HTTP GET, get a resource from a URL. + * Apply an HTTP verb to a list containing a list of headers and a list of content, and + * a reduce function used to process the response. */ -template<typename R> const failable<R, int> get(const lambda<R(R, std::string)>& reduce, const R& initial, const std::string& url) { - CURLWriteContext<R> wcx(reduce, initial); +curl_slist* headers(curl_slist* cl, const list<std::string> h) { + if (isNil(h)) + return cl; + return headers(curl_slist_append(cl, std::string(car(h)).c_str()), cdr(h)); +} + +template<typename R> const failable<list<R>, std::string> apply(const list<list<std::string> > req, const lambda<R(std::string, R)>& reduce, const R& initial, const std::string& url, const std::string& verb, CURLHandle& ch) { // Init the curl session - CURLHandle ch; - curl_easy_setopt(ch, CURLOPT_USERAGENT, "libcurl-agent/1.0"); - curl_easy_setopt(ch, CURLOPT_URL, url.c_str()); + curl_easy_reset(ch); + curl_easy_setopt(ch, CURLOPT_USERAGENT, "libcurl/1.0"); + + //TODO use HTTP chunking, for now just convert request to a single string + std::ostringstream os; + write(cadr(req), os); + const std::string s = os.str(); + const int sz = s.length(); + if (sz < 1400) + curl_easy_setopt(ch, CURLOPT_TCP_NODELAY, true); + + // Setup the read, header and write callbacks + CURLReadContext rcx(mklist(s)); + curl_easy_setopt(ch, CURLOPT_READFUNCTION, (size_t (*)(void*, size_t, size_t, void*))readCallback); + curl_easy_setopt(ch, CURLOPT_READDATA, &rcx); + CURLWriteContext<R> hcx(reduce, initial); + curl_easy_setopt(ch, CURLOPT_HEADERFUNCTION, (size_t (*)(void*, size_t, size_t, void*))headerCallback<R>); + curl_easy_setopt(ch, CURLOPT_HEADERDATA, &hcx); + CURLWriteContext<R> wcx(reduce, initial); curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, (size_t (*)(void*, size_t, size_t, void*))writeCallback<R>); curl_easy_setopt(ch, CURLOPT_WRITEDATA, &wcx); - curl_easy_setopt(ch, CURLOPT_HEADERFUNCTION, (size_t (*)(void*, size_t, size_t, void*))headerCallback<R>); - curl_easy_setopt(ch, CURLOPT_HEADERDATA, &wcx); - - // Perform the HTTP GET + + // Set the request headers + curl_slist* hl = headers(NULL, car(req)); + if (hl != NULL) + curl_easy_setopt(ch, CURLOPT_HTTPHEADER, hl); + + // Apply the HTTP verb + curl_easy_setopt(ch, CURLOPT_URL, url.c_str()); + if (verb == "POST") { + curl_easy_setopt(ch, CURLOPT_POST, true); + curl_easy_setopt(ch, CURLOPT_POSTFIELDSIZE, sz); + } else if (verb == "PUT") { + curl_easy_setopt(ch, CURLOPT_UPLOAD, true); + curl_easy_setopt(ch, CURLOPT_INFILESIZE, sz); + } else if (verb == "DELETE") + curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, "DELETE"); const CURLcode rc = curl_easy_perform(ch); - if (rc) - return rc; + + if (hl != NULL) + curl_slist_free_all(hl); // Return the HTTP return code or content + if (rc) + return std::string(curl_easy_strerror(rc)); long httprc; curl_easy_getinfo (ch, CURLINFO_RESPONSE_CODE, &httprc); - if (httprc != 200) - return httprc; - return wcx.accum; + if (httprc != 200 && httprc != 201) { + std::ostringstream es; + es << "HTTP code " << httprc; + return es.str(); + } + return mklist<R>(hcx.accum, wcx.accum); } /** - * HTTP GET, get a list of values representing a resource from a URL. + * Evaluate an expression remotely, at the given URL. */ -const list<std::string> writeStringList(const list<std::string>& listSoFar, const std::string& s) { - return cons(s, listSoFar); +const failable<value, std::string> evalExpr(const value expr, const std::string& url, CURLHandle& ch) { + + // Convert expression to a JSON-RPC request + json::JSONContext cx; + const failable<list<std::string>, std::string> jsreq = jsonRequest(1, car<value>(expr), cdr<value>(expr), cx); + if (!hasValue(jsreq)) + return std::string(jsreq); + + if (logContent) { + std::cout<< "content: " << std::endl; + write(jsreq, std::cout); + std::cout<< std::endl; + std::cout.flush(); + } + + // POST it to the URL + const list<std::string> h = mklist<std::string>("Content-Type: application/json-rpc"); + const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(mklist<list<std::string> >(h, jsreq), rcons<std::string>, list<std::string>(), url, "POST", ch); + if (!hasValue(res)) + return std::string(res); + + // Return result + if (logContent) { + std::cout << "content:" << std::endl; + write(cadr<list<std::string> >(res), std::cout); + std::cout << std::endl; + } + const list<value> val = elementsToValues(json::readJSON(cadr<list<std::string> >(res), cx)); + return cadr<value>(cadr<value>(val)); +} + +/** + * HTTP GET, return the resource at the given URL. + */ +template<typename R> const failable<list<R>, std::string> get(const lambda<R(std::string, R)>& reduce, const R& initial, const std::string& url, CURLHandle& ch) { + const list<list<std::string> > req = mklist(list<std::string>(), list<std::string>()); + return apply(req, reduce, initial, url, "GET", ch); +} + +/** + * HTTP GET, return a list of values representing the resource at the given URL. + */ +const failable<value, std::string> get(const std::string& url, CURLHandle& ch) { + + // Get the contents of the resource at the given URL + const failable<list<list<std::string> >, std::string> res = get<list<std::string> >(rcons<std::string>, list<std::string>(), url, ch); + if (!hasValue(res)) + return std::string(res); + const list<list<std::string> > ls = res; + + // TODO Return an ATOM feed + const std::string ct; + if (ct.find("application/atom+xml") != std::string::npos) { + } + + // Return the content as a string value + std::ostringstream os; + write(reverse(cadr(ls)), os); + return value(os.str()); } -const failable<list<std::string>, int> get(const std::string& url) { - const failable<list<std::string>, int> r = get<list<std::string> >(writeStringList, list<std::string>(), url); - if (!hasValue(r)) - return r; - return reverse(list<std::string>(r)); +/** + * HTTP POST. + */ +const failable<value, std::string> post(const value& val, const std::string& url, CURLHandle& ch) { + + // Convert value to an ATOM entry + const failable<list<std::string>, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); + if (!hasValue(entry)) + return std::string(entry); + if (logContent) { + std::cout << "content:" << std::endl; + write(list<std::string>(entry), std::cout); + std::cout << std::endl; + } + + // POST it to the URL + const list<std::string> h = mklist<std::string>("Content-Type: application/atom+xml"); + const list<list<std::string> > req = mklist<list<std::string> >(h, entry); + const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(req, rcons<std::string>, list<std::string>(), url, "POST", ch); + if (!hasValue(res)) + return std::string(res); + return value(true); +} + +/** + * HTTP PUT. + */ +const failable<value, std::string> put(const value& val, const std::string& url, CURLHandle& ch) { + + // Convert value to an ATOM entry + const failable<list<std::string>, std::string> entry = atom::writeATOMEntry(atom::entryValuesToElements(val)); + if (!hasValue(entry)) + return std::string(entry); + if (logContent) { + std::cout << "content:" << std::endl; + write(list<std::string>(entry), std::cout); + std::cout << std::endl; + } + + // POST it to the URL + const list<std::string> h = mklist<std::string>("Content-Type: application/atom+xml"); + const list<list<std::string> > req = mklist<list<std::string> >(h, entry); + const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(req, rcons<std::string>, list<std::string>(), url, "PUT", ch); + if (!hasValue(res)) + return std::string(res); + return value(true); +} + +/** + * HTTP DELETE. + */ +const failable<value, std::string> del(const std::string& url, CURLHandle& ch) { + const list<list<std::string> > req = mklist(list<std::string>(), list<std::string>()); + const failable<list<list<std::string> >, std::string> res = apply<list<std::string> >(req, rcons<std::string>, list<std::string>(), url, "DELETE", ch); + if (!hasValue(res)) + return std::string(res); + return value(true); } } diff --git a/cpp/sca/modules/http/htdocs/entry.xml b/cpp/sca/modules/http/htdocs/entry.xml new file mode 100644 index 0000000000..4906fbedc8 --- /dev/null +++ b/cpp/sca/modules/http/htdocs/entry.xml @@ -0,0 +1,2 @@ +<?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><javaClass>services.Item</javaClass><name>Apple</name><currency>USD</currency><symbol>$</symbol><price>2.99</price></item></content><link href="111"/></entry> diff --git a/cpp/sca/modules/http/htdocs/feed.xml b/cpp/sca/modules/http/htdocs/feed.xml new file mode 100644 index 0000000000..4ca3183739 --- /dev/null +++ b/cpp/sca/modules/http/htdocs/feed.xml @@ -0,0 +1,2 @@ +<?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>javaClass</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>javaClass</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>javaClass</item></content><link href="333"/></entry></feed> diff --git a/cpp/sca/modules/http/test-htdocs/index.html b/cpp/sca/modules/http/htdocs/index.html index 1bfb3e30c2..1bfb3e30c2 100644 --- a/cpp/sca/modules/http/test-htdocs/index.html +++ b/cpp/sca/modules/http/htdocs/index.html diff --git a/cpp/sca/modules/http/htdocs/json-request.txt b/cpp/sca/modules/http/htdocs/json-request.txt new file mode 100644 index 0000000000..b4bd07fc46 --- /dev/null +++ b/cpp/sca/modules/http/htdocs/json-request.txt @@ -0,0 +1 @@ +{"id":1,"method":"echo","params":["Hello"]} diff --git a/cpp/sca/modules/http/htdocs/json-result.txt b/cpp/sca/modules/http/htdocs/json-result.txt new file mode 100644 index 0000000000..121bf74902 --- /dev/null +++ b/cpp/sca/modules/http/htdocs/json-result.txt @@ -0,0 +1 @@ +{"id":1,"result":"Hello"}
\ No newline at end of file diff --git a/cpp/sca/modules/http/http-test b/cpp/sca/modules/http/http-test index b0dd21cbd1..e369cc34f7 100755 --- a/cpp/sca/modules/http/http-test +++ b/cpp/sca/modules/http/http-test @@ -17,18 +17,24 @@ # specific language governing permissions and limitations # under the License. -mkdir -p tmp -mkdir -p tmp/conf -cp test-conf/* tmp/conf -cat >tmp/conf/httpd.conf <<EOF -ServerName 127.0.0.1 -Listen 9091 -DocumentRoot `pwd`/test-htdocs +# Setup +./httpd-conf tmp 8091 htdocs +cat >>tmp/conf/httpd.conf <<EOF +<Location /test> +SetHandler mod_tuscany +SCAContribution `pwd`/ +SCAComponent httpd-test +SCAImplementation httpd-test.scm +</Location> EOF -mkdir -p tmp/logs apachectl -k start -d `pwd`/tmp sleep 1 + +# Test ./curl-test rc=$? + +# Cleanup apachectl -k stop -d `pwd`/tmp +sleep 1 return $rc diff --git a/cpp/sca/modules/http/httpd-conf b/cpp/sca/modules/http/httpd-conf new file mode 100755 index 0000000000..e076e22bb1 --- /dev/null +++ b/cpp/sca/modules/http/httpd-conf @@ -0,0 +1,36 @@ +#!/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 minimal httpd.conf +here=`readlink -f $0`; here=`dirname $here` +root=`readlink -f $1` +port=$2 +htdocs=`readlink -f $3` + +mkdir -p $root +mkdir -p $root/logs +mkdir -p $root/conf +cat >$root/conf/httpd.conf <<EOF +ServerName 127.0.0.1 +Listen $port +DocumentRoot $htdocs +TypesConfig $here/conf/mime.types +LoadModule mod_tuscany $here/.libs/libmod_tuscany.so +EOF + diff --git a/cpp/sca/modules/http/httpd-test b/cpp/sca/modules/http/httpd-test index 3e4c5f82de..04c584496c 100755 --- a/cpp/sca/modules/http/httpd-test +++ b/cpp/sca/modules/http/httpd-test @@ -18,20 +18,59 @@ # under the License. echo "Testing..." -mkdir -p tmp -mkdir -p tmp/conf -cp test-conf/* tmp/conf -cat >tmp/conf/httpd.conf <<EOF -ServerName 127.0.0.1 -Listen 9090 -DocumentRoot `pwd`/test-htdocs + +# Setup +./httpd-conf tmp 8090 htdocs +cat >>tmp/conf/httpd.conf <<EOF +<Location /test> +SetHandler mod_tuscany +SCAContribution `pwd`/ +SCAComponent httpd-test +SCAImplementation httpd-test.scm +</Location> EOF -mkdir -p tmp/logs apachectl -k start -d `pwd`/tmp sleep 1 -curl http://localhost:9090/index.html 2>&1 | grep "It works" >/dev/null + +# Test HTTP GET +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 http://localhost:8090/test/ >tmp/feed.xml 2>/dev/null + diff tmp/feed.xml htdocs/feed.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/111 >tmp/entry.xml 2>/dev/null + diff tmp/entry.xml htdocs/entry.xml + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/ -X POST -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/111 -X PUT -H "Content-type: application/atom+xml" --data @htdocs/entry.xml 2>/dev/null + rc=$? +fi +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/111 -X DELETE 2>/dev/null + rc=$? +fi + +# Test JSON-RPC +if [ "$rc" = "0" ]; then + curl http://localhost:8090/test/ -X POST -H "Content-type: application/json-rpc" --data @htdocs/json-request.txt >tmp/json-result.txt 2>/dev/null + diff tmp/json-result.txt htdocs/json-result.txt + rc=$? +fi + +# Cleanup apachectl -k stop -d `pwd`/tmp +sleep 1 if [ "$rc" = "0" ]; then echo "OK" fi diff --git a/cpp/sca/modules/http/httpd-test.scm b/cpp/sca/modules/http/httpd-test.scm new file mode 100644 index 0000000000..a3ddf8dda8 --- /dev/null +++ b/cpp/sca/modules/http/httpd-test.scm @@ -0,0 +1,33 @@ +(; "JSON-RPC test case") + +(define (echo x) x) + +(; "ATOMPub test case") + +(define (getall) + '("Sample Feed" "123456789" + ("Item" "111" (javaClass "services.Item") (name "Apple") (currency "USD") (symbol "$") (price 2.99)) + ("Item" "222" (javaClass "services.Item") (name "Orange") (currency "USD") (symbol "$") (price 3.55)) + ("Item" "333" (javaClass "services.Item") (name "Pear") (currency "USD") (symbol "$") (price 1.55))) +) + +(define (get id) + (define entry '((javaClass "services.Item") (name "Apple") (currency "USD") (symbol "$") (price 2.99))) + (cons "Item" (list id entry)) +) + +(define (post entry) + (display entry) + "123456789" +) + +(define (put entry) + (display entry) + true +) + +(define (delete . args) + (display args) + true +) + diff --git a/cpp/sca/modules/http/mod.cpp b/cpp/sca/modules/http/mod.cpp index 88891a668c..811ee0c633 100644 --- a/cpp/sca/modules/http/mod.cpp +++ b/cpp/sca/modules/http/mod.cpp @@ -64,6 +64,12 @@ namespace tuscany { namespace httpd { /** + * Set to true to log requests and content. + */ +bool logRequests = false; +bool logContent = false; + +/** * Server configuration. */ struct ServerConf { @@ -139,10 +145,8 @@ const std::string contentType(const request_rec* r) { } /** - * Log HTTP request info to standard out for now, for debugging purposes. + * Log HTTP request info. */ -bool logRequests = true; - int logHeader(void* r, const char* key, const char* value) { std::cout << "header key: " << key << ", value: " << value << std::endl; return 1; @@ -161,6 +165,7 @@ const bool logRequest(request_rec* r) { apr_table_do(logHeader, r, r->headers_in, NULL); std::cout << "uri: " << optional(r->uri) << std::endl; std::cout << "path info: " << optional(r->path_info) << std::endl; + std::cout << "filename: " << optional(r->filename) << std::endl; std::cout << "path: " << pathTokens(r) << std::endl; std::cout << "args: " << optional(r->args) << std::endl; std::cout.flush(); @@ -170,34 +175,32 @@ const bool logRequest(request_rec* r) { /** * Evaluate an expression against a component implementation. */ -const value evalExprLoop(std::istream& is, const value& req, eval::Env& globalEnv) { - value in = eval::read(is); +const value evalExprLoop(std::istream& is, const value& expr, eval::Env& globalEnv, const gc_pool& pool) { + value in = eval::readValue(is); if (isNil(in)) - return eval::evalApply(req, globalEnv); - eval::evalApply(in, globalEnv); - return evalExprLoop(is, req, globalEnv); + return eval::evalExpr(expr, globalEnv, pool); + eval::evalExpr(in, globalEnv, pool); + return evalExprLoop(is, expr, globalEnv, pool); } -const failable<value, int> evalExpr(const value& expr, const std::string& contrib, const std::string& impl) { - // Setup the evaluator - eval::Env globalEnv = eval::setupEnvironment(); - std::ostringstream nullos; - eval::setupEvalOut(nullos); - +const failable<value, std::string> evalExpr(const value& expr, const std::string& contrib, const std::string& impl) { // Retrieve the component implementation const std::string path = contrib + impl; std::ifstream is(path.c_str(), std::ios_base::in); if (is.fail() || is.bad()) - return HTTP_NOT_FOUND; + return std::string("HTTP_NOT_FOUND"); // Evaluate the expr + gc_pool pool; + eval::Env globalEnv = eval::setupEnvironment(pool); std::cout<< "expr: " << expr << std::endl; std::cout.flush(); - const value val = evalExprLoop(is, expr, globalEnv); + const value val = evalExprLoop(is, expr, globalEnv, pool); std::cout<< "val: " << val << std::endl; std::cout.flush(); + if (isNil(val)) - return HTTP_INTERNAL_SERVER_ERROR; + return std::string("Could not evaluate expression"); return val; } @@ -230,74 +233,17 @@ const list<value> queryParams(list<list<value> > a) { } /** - * Convert a value to a JSON result. - */ -const failable<list<std::string>, int> jsonResult(json::JSONContext& cx, const value& id, const failable<value, int>& val) { - if (!hasValue(val)) - return int(val); - const list<value> r = mklist<value>(mklist<value>("id", id), mklist<value>("result", val)); - failable<list<std::string>, std::string> ls = json::write(cx, valuesToElements(r)); - if (!hasValue(ls)) - return HTTP_INTERNAL_SERVER_ERROR; - std::cout<< "content: " << std::endl; - write(ls, std::cout); - std::cout<< std::endl; - std::cout.flush(); - return list<std::string>(ls); -} - -/** - * Convert a value to an ATOM entry. - */ -const list<value> feedEntryResult(const list<value> e) { - return cons(car(e), cons(cadr(e), valuesToElements(mklist<value>(cons<value>("item", (list<value>)caddr(e)))))); -} - -/** - * Convert a value to an ATOM feed. - */ -const list<value> feedEntriesResults(const list<value> e) { - if (isNil(e)) - return list<value>(); - return cons<value>(feedEntryResult(car(e)), feedEntriesResults(cdr(e))); -} - -const failable<list<std::string>, int> feedResult(const failable<value, int>& val) { - if (!hasValue(val)) - return int(val); - const value v = val; - list<value> f = cons(car<value>(v), cons<value>(cadr<value>(v), feedEntriesResults(cddr<value>(v)))); - failable<list<std::string>, std::string> ls = atom::writeFeed(f); - if (!hasValue(ls)) - return HTTP_INTERNAL_SERVER_ERROR; - return list<std::string>(ls); -} - -/** - * Convert a value to an ATOM entry result. - */ -const failable<list<std::string>, int> entryResult(const failable<value, int>& val) { - if (!hasValue(val)) - return int(val); - const value v = val; - list<value> e = feedEntryResult(v); - std::cout<< "entry: " << e << std::endl; - failable<list<std::string>, std::string> ls = atom::writeEntry(e); - if (!hasValue(ls)) - return HTTP_INTERNAL_SERVER_ERROR; - return list<std::string>(ls); -} - -/** * Write an HTTP result. */ -const int writeResult(const failable<list<std::string>, int> ls, const std::string& ct, request_rec* r) { +const failable<int, std::string> writeResult(const failable<list<std::string>, std::string> ls, const std::string& ct, request_rec* r) { if (!hasValue(ls)) - return ls; + return std::string(ls); std::ostringstream os; write(ls, os); - std::cout<< "content: " << os.str() << std::endl; - std::cout.flush(); + if (logContent) { + std::cout<< "content: " << std::endl << os.str() << std::endl; + std::cout.flush(); + } std::string etag(ap_md5(r->pool, (const unsigned char*)std::string(os.str()).c_str())); const char* match = apr_table_get(r->headers_in, "If-None-Match"); @@ -314,7 +260,7 @@ const int writeResult(const failable<list<std::string>, int> ls, const std::stri /** * Handle an HTTP GET. */ -const int get(request_rec* r) { +const failable<int, std::string> get(request_rec* r) { // Inspect the query string const list<list<value> > args = queryArgs(r); @@ -326,26 +272,34 @@ const int get(request_rec* r) { // Extract the request id, method and params const value id = cadr(ia); - const value method = std::string(cadr(ma)).c_str(); + const value func = std::string(cadr(ma)).c_str(); const list<value> params = queryParams(args); // Evaluate the request expression - const failable<value, int> val = evalExpr(cons(method, params), contribution(r), implementation(r)); + const failable<value, std::string> val = evalExpr(cons(func, eval::quotedParameters(params)), contribution(r), implementation(r)); + if (!hasValue(val)) + return std::string(val); // Return JSON result json::JSONContext cx; - return writeResult(jsonResult(cx, id, val), "application/json-rpc", r); + return writeResult(json::jsonResult(id, val, cx), "application/json-rpc", r); } // Evaluate an ATOM GET request and return an ATOM feed - if (length(path(r)) < 2) { - const failable<value, int> val = evalExpr(cons<value>("getall"), contribution(r), implementation(r)); - return writeResult(feedResult(val), "application/atom+xml", r); + if (isNil(path(r))) { + const failable<value, std::string> val = evalExpr(mklist<value>("getall"), contribution(r), implementation(r)); + if (!hasValue(val)) + return std::string(val); + const value feed = val; + return writeResult(atom::writeATOMFeed(atom::feedValuesToElements(feed)), "application/atom+xml;type=feed", r); } // Evaluate an ATOM GET and return an ATOM entry - const failable<value, int> val = evalExpr(cons<value>("get", cdr(path(r))), contribution(r), implementation(r)); - return writeResult(entryResult(val), "application/atom+xml", r); + const failable<value, std::string> val = evalExpr(cons<value>("get", path(r)), contribution(r), implementation(r)); + if (!hasValue(val)) + return std::string(val); + const value entry = val; + return writeResult(atom::writeATOMEntry(atom::entryValuesToElements(entry)), "application/atom+xml;type=entry", r); } @@ -370,10 +324,10 @@ const list<list<value> > postArgs(list<value> a) { return cons(l, postArgs(cdr(a))); } -const char* url(const std::string& loc, request_rec* r) { +const char* url(const value& v, request_rec* r) { std::string u = r->uri; u.append("/"); - u.append(loc); + u.append(v); return ap_construct_url(r->pool, u.c_str(), r); } @@ -388,43 +342,49 @@ const value feedEntry(const list<value> e) { /** * Handle an HTTP POST. */ -const int post(request_rec* r) { - const std::string ct = contentType(r); +const failable<int, std::string> post(request_rec* r) { + const list<std::string> ls = read(r); + if (logContent) { + std::cout<< "content: " << std::endl; + write(ls, std::cout); + std::cout<< std::endl; + std::cout.flush(); + } // Evaluate a JSON-RPC request and return a JSON result + const std::string ct = contentType(r); if (ct.find("application/json-rpc") != std::string::npos || ct.find("text/plain") != std::string::npos) { json::JSONContext cx; - const list<value> json = elementsToValues(json::read(cx, read(r))); + const list<value> json = elementsToValues(json::readJSON(ls, cx)); const list<list<value> > args = postArgs(json); // Extract the request id, method and params const value id = cadr(assoc(value("id"), args)); - const value method = std::string(cadr(assoc(value("method"), args))).c_str(); + const value func = std::string(cadr(assoc(value("method"), args))).c_str(); const list<value> params = (list<value>)cadr(assoc(value("params"), args)); // Evaluate the request expression - const failable<value, int> val = evalExpr(cons(method, params), contribution(r), implementation(r)); + const failable<value, std::string> val = evalExpr(cons(func, eval::quotedParameters(params)), contribution(r), implementation(r)); + if (!hasValue(val)) + return std::string(val); // Return JSON result - return writeResult(jsonResult(cx, id, val), "application/json-rpc", r); + return writeResult(json::jsonResult(id, val, cx), "application/json-rpc", r); } // Evaluate an ATOM POST request and return the created resource location if (ct.find("application/atom+xml") != std::string::npos) { - const list<std::string> c = read(r); - std::cout << "POST content: " << c << std::endl; - const list<value> e = atom::readEntry(c); - std::cout << "POST entry: " << e << std::endl; - const value v = feedEntry(e); - std::cout << "POST param: " << v << std::endl; // Evaluate the request expression - const failable<value, int> val = evalExpr(mklist<value>("post", mklist(v)), contribution(r), implementation(r)); - - const char* u = url("abcd", r); - apr_table_setn(r->headers_out, "Location", u); - apr_table_setn(r->headers_out, "Content-Location", u); - return HTTP_CREATED; + const value entry = feedEntry(atom::readEntry(ls)); + const failable<value, std::string> val = evalExpr(cons<value>("post", eval::quotedParameters(mklist<value>(entry))), contribution(r), implementation(r)); + if (!hasValue(val)) + return std::string(val); + + // Return the created resource location + apr_table_setn(r->headers_out, "Location", url(val, r)); + r->status = HTTP_CREATED; + return OK; } return HTTP_NOT_IMPLEMENTED; @@ -433,25 +393,48 @@ const int post(request_rec* r) { /** * Handle an HTTP PUT. */ -const int put(request_rec* r) { - // TODO later +const failable<int, std::string> put(request_rec* r) { + const list<std::string> ls = read(r); + std::cout<< "content: " << std::endl; + write(ls, std::cout); + std::cout<< std::endl; + std::cout.flush(); + + // Evaluate an ATOM PUT request + const value entry = feedEntry(atom::readEntry(ls)); + const failable<value, std::string> val = evalExpr(cons<value>("put", eval::quotedParameters(mklist<value>(entry))), contribution(r), implementation(r)); + if (!hasValue(val)) + return std::string(val); + if (val == value(false)) + return HTTP_NOT_FOUND; return OK; } /** * Handle an HTTP DELETE. */ -const int del(request_rec* r) { +const failable<int, std::string> del(request_rec* r) { // Evaluate an ATOM delete request - const failable<value, int> val = evalExpr(cons<value>("delete", cdr(path(r))), contribution(r), implementation(r)); + const failable<value, std::string> val = evalExpr(cons<value>("delete", path(r)), contribution(r), implementation(r)); if (!hasValue(val)) - return val; + return std::string(val); + if (val == value(false)) + return HTTP_NOT_FOUND; return OK; } /** - * HTTP handler entry point. + * Report request execution status. + */ +const int reportStatus(const failable<int, std::string> rc) { + if (!hasValue(rc)) + return HTTP_INTERNAL_SERVER_ERROR; + return rc; +} + +/** + * HTTP request handler entry point. */ int handler(request_rec *r) { if(strcmp(r->handler, "mod_tuscany")) @@ -468,19 +451,19 @@ int handler(request_rec *r) { ap_should_client_block(r); if(r->read_chunked == true && r->remaining == 0) r->chunked = true; - apr_table_setn(r->headers_out, "Connection", "close"); + //apr_table_setn(r->headers_out, "Connection", "close"); // Handle HTTP method if (r->header_only) return OK; if(r->method_number == M_GET) - return get(r); + return reportStatus(get(r)); if(r->method_number == M_POST) - return post(r); + return reportStatus(post(r)); if(r->method_number == M_PUT) - return put(r); + return reportStatus(put(r)); if(r->method_number == M_DELETE) - return del(r); + return reportStatus(del(r)); return HTTP_NOT_IMPLEMENTED; } diff --git a/cpp/sca/modules/json/json-test.cpp b/cpp/sca/modules/json/json-test.cpp index bf0ecab1f4..0d4e8f6a16 100644 --- a/cpp/sca/modules/json/json-test.cpp +++ b/cpp/sca/modules/json/json-test.cpp @@ -43,7 +43,7 @@ bool testJSEval() { return true; } -std::ostringstream* jsonWriter(std::ostringstream* os, const std::string& s) { +std::ostringstream* jsonWriter(const std::string& s, std::ostringstream* os) { (*os) << s; return os; } @@ -57,7 +57,7 @@ bool testJSON() { const list<value> cr = mklist<value>(mklist<value> (attribute, "name", std::string("jdoe")), cons<value>(element, cons<value>("address", ad)), cons<value>(element, cons<value>("account", ac))); const list<value> c = mklist<value>(cons<value>(element, cons<value>("customer", cr))); std::ostringstream os; - write<std::ostringstream*>(cx, jsonWriter, &os, c); + writeJSON<std::ostringstream*>(jsonWriter, &os, c, cx); assert(os.str() == "{\"customer\":{\"name\":\"jdoe\",\"address\":{\"city\":\"san francisco\",\"state\":\"ca\"},\"account\":{\"id\":\"1234\",\"balance\":1000}}}"); } { @@ -65,16 +65,16 @@ bool testJSON() { const list<value> l = mklist<value> (mklist<value> (element, "phones", phones), mklist<value> (element, "lastName", std::string("test\ttab")), mklist<value> (element, "firstName", std::string("test1"))); std::ostringstream os; - write<std::ostringstream*>(cx, jsonWriter, &os, l); + writeJSON<std::ostringstream*>(jsonWriter, &os, l, cx); assert(os.str() == "{\"phones\":[\"408-1234\",\"650-1234\"],\"lastName\":\"test\\u0009tab\",\"firstName\":\"test1\"}"); std::istringstream is(os.str()); const list<std::string> il = streamList(is); - const list<value> r = read(cx, il); + const list<value> r = readJSON(il, cx); assert(r == l); std::ostringstream wos; - write(write(cx, r), wos); + write(writeJSON(r, cx), wos); assert(wos.str() == os.str()); } return true; @@ -84,7 +84,7 @@ bool testJSONRPC() { JSONContext cx; { const std::string lm("{\"id\": 1, \"method\": \"system.listMethods\", \"params\": []}"); - const list<value> e = read(cx, mklist(lm)); + const list<value> e = readJSON(mklist(lm), cx); const list<value> v = elementsToValues(e); assert(assoc<value>("id", v) == mklist<value>("id", 1)); assert(assoc<value>("method", v) == mklist<value>("method", std::string("system.listMethods"))); @@ -92,16 +92,16 @@ bool testJSONRPC() { } { const std::string i("{\"id\":3,\"result\":[{\"price\":\"$2.99\",\"name\":\"Apple\"},{\"price\":\"$3.55\",\"name\":\"Orange\"},{\"price\":\"$1.55\",\"name\":\"Pear\"}]}"); - const list<value> e = read(cx, mklist(i)); + const list<value> e = readJSON(mklist(i), cx); const std::string i2("{\"id\":3,\"result\":{\"0\":{\"price\":\"$2.99\",\"name\":\"Apple\"},\"1\":{\"price\":\"$3.55\",\"name\":\"Orange\"},\"2\":{\"price\":\"$1.55\",\"name\":\"Pear\"}}}"); - const list<value> e2 = read(cx, mklist(i)); + const list<value> e2 = readJSON(mklist(i), cx); assert(e == e2); } { const std::string i("{\"id\":3,\"result\":[{\"price\":\"$2.99\",\"name\":\"Apple\"},{\"price\":\"$3.55\",\"name\":\"Orange\"},{\"price\":\"$1.55\",\"name\":\"Pear\"}]}"); - const list<value> e = read(cx, mklist(i)); + const list<value> e = readJSON(mklist(i), cx); std::ostringstream os; - write(write(cx, e), os); + write(writeJSON(e, cx), os); assert(os.str() == i); const list<value> v = elementsToValues(e); const list<value> r = valuesToElements(v); @@ -111,7 +111,7 @@ bool testJSONRPC() { const list<value> r = mklist<value>(mklist<value>("id", 1), mklist<value>("result", mklist<value>(std::string("Service.get"), std::string("Service.getTotal")))); const list<value> e = valuesToElements(r); std::ostringstream os; - write(write(cx, e), os); + write(writeJSON(e, cx), os); assert(os.str() == "{\"id\":1,\"result\":[\"Service.get\",\"Service.getTotal\"]}"); } return true; diff --git a/cpp/sca/modules/json/json.hpp b/cpp/sca/modules/json/json.hpp index 8e4666196e..0d21cfe359 100644 --- a/cpp/sca/modules/json/json.hpp +++ b/cpp/sca/modules/json/json.hpp @@ -142,9 +142,9 @@ private: /** * Converts JS properties to values. */ -const list<value> jsPropertiesToValues(const JSONContext& cx, const list<value>& propertiesSoFar, JSObject* o, JSObject* i) { +const list<value> jsPropertiesToValues(const list<value>& propertiesSoFar, JSObject* o, JSObject* i, const JSONContext& cx) { - const value jsValToValue(const JSONContext& cx, const jsval& jsv); + const value jsValToValue(const jsval& jsv, const JSONContext& cx); jsid id; if(!JS_NextProperty(cx, i, &id) || id == JSVAL_VOID) @@ -152,20 +152,20 @@ const list<value> jsPropertiesToValues(const JSONContext& cx, const list<value>& jsval jsv; if(!JS_GetPropertyById(cx, o, id, &jsv)) return propertiesSoFar; - const value val = jsValToValue(cx, jsv); + const value val = jsValToValue(jsv, cx); jsval idv; JS_IdToValue(cx, id, &idv); if(JSVAL_IS_STRING(idv)) { const value type = isList(val)? element : element; - return jsPropertiesToValues(cx, cons<value> (mklist<value> (type, JS_GetStringBytes(JSVAL_TO_STRING(idv)), val), propertiesSoFar), o, i); + return jsPropertiesToValues(cons<value> (mklist<value> (type, JS_GetStringBytes(JSVAL_TO_STRING(idv)), val), propertiesSoFar), o, i, cx); } - return jsPropertiesToValues(cx, cons(val, propertiesSoFar), o, i); + return jsPropertiesToValues(cons(val, propertiesSoFar), o, i, cx); } /** * Converts a JS val to a value. */ -const value jsValToValue(const JSONContext& cx, const jsval& jsv) { +const value jsValToValue(const jsval& jsv, const JSONContext& cx) { switch(JS_TypeOfValue(cx, jsv)) { case JSTYPE_STRING: { return value(std::string(JS_GetStringBytes(JSVAL_TO_STRING(jsv)))); @@ -183,7 +183,7 @@ const value jsValToValue(const JSONContext& cx, const jsval& jsv) { JSObject* i = JS_NewPropertyIterator(cx, o); if(i == NULL) return value(list<value> ()); - const value pv = jsPropertiesToValues(cx, list<value> (), o, i); + const value pv = jsPropertiesToValues(list<value> (), o, i, cx); return pv; } default: { @@ -195,44 +195,44 @@ const value jsValToValue(const JSONContext& cx, const jsval& jsv) { /** * Consumes JSON strings and populates a JS object. */ -failable<bool, std::string> consume(const JSONContext& cx, JSONParser* parser, const list<std::string>& ilist) { +failable<bool, std::string> consume(JSONParser* parser, const list<std::string>& ilist, const JSONContext& cx) { if (isNil(ilist)) return true; JSString* jstr = JS_NewStringCopyZ(cx, car(ilist).c_str()); if(!JS_ConsumeJSONText(cx, parser, JS_GetStringChars(jstr), JS_GetStringLength(jstr))) return "JS_ConsumeJSONText failed"; - return consume(cx, parser, cdr(ilist)); + return consume(parser, cdr(ilist), cx); } /** * Convert a list of strings representing a JSON document to a list of values. */ -const failable<list<value>, std::string> read(const JSONContext& cx, const list<std::string>& ilist) { +const failable<list<value>, std::string> readJSON(const list<std::string>& ilist, const JSONContext& cx) { jsval val; JSONParser* parser = JS_BeginJSONParse(cx, &val); if(parser == NULL) return std::string("JS_BeginJSONParse failed"); - const failable<bool, std::string> consumed = consume(cx, parser, ilist); + const failable<bool, std::string> consumed = consume(parser, ilist, cx); if(!JS_FinishJSONParse(cx, parser, JSVAL_NULL)) return std::string("JS_FinishJSONParse failed"); if(!hasValue(consumed)) return std::string(consumed); - return list<value>(jsValToValue(cx, val)); + return list<value>(jsValToValue(val, cx)); } /** * Converts a list of values to JS array elements. */ -JSObject* valuesToJSElements(const JSONContext& cx, JSObject* a, const list<value>& l, int i) { - const jsval valueToJSVal(const JSONContext& cx, const value& val); +JSObject* valuesToJSElements(JSObject* a, const list<value>& l, int i, const JSONContext& cx) { + const jsval valueToJSVal(const value& val, const JSONContext& cx); if (isNil(l)) return a; - jsval pv = valueToJSVal(cx, car(l)); + jsval pv = valueToJSVal(car(l), cx); JS_SetElement(cx, a, i, &pv); - return valuesToJSElements(cx, a, cdr(l), ++i); + return valuesToJSElements(a, cdr(l), ++i, cx); } /** @@ -255,26 +255,26 @@ const bool isJSArray(const list<value>& l) { /** * Converts a list of values to JS properties. */ -JSObject* valuesToJSProperties(const JSONContext& cx, JSObject* o, const list<value>& l) { - const jsval valueToJSVal(const JSONContext& cx, const value& val); +JSObject* valuesToJSProperties(JSObject* o, const list<value>& l, const JSONContext& cx) { + const jsval valueToJSVal(const value& val, const JSONContext& cx); if(isNil(l)) return o; const list<value> p = car(l); - jsval pv = valueToJSVal(cx, caddr(p)); + jsval pv = valueToJSVal(caddr(p), cx); JS_SetProperty(cx, o, ((std::string)cadr(p)).c_str(), &pv); - return valuesToJSProperties(cx, o, cdr(l)); + return valuesToJSProperties(o, cdr(l), cx); } /** * Converts a value to a JS val. */ -const jsval valueToJSVal(const JSONContext& cx, const value& val) { +const jsval valueToJSVal(const value& val, const JSONContext& cx) { switch(type(val)) { case value::String: case value::Symbol: { return STRING_TO_JSVAL(JS_NewStringCopyZ(cx, ((std::string)val).c_str())); } - case value::Boolean: { + case value::Bool: { return BOOLEAN_TO_JSVAL((bool)val); } case value::Number: { @@ -282,8 +282,8 @@ const jsval valueToJSVal(const JSONContext& cx, const value& val) { } case value::List: { if (isJSArray(val)) - return OBJECT_TO_JSVAL(valuesToJSElements(cx, JS_NewArrayObject(cx, 0, NULL), val, 0)); - return OBJECT_TO_JSVAL(valuesToJSProperties(cx, JS_NewObject(cx, NULL, NULL, NULL), val)); + return OBJECT_TO_JSVAL(valuesToJSElements(JS_NewArrayObject(cx, 0, NULL), val, 0, cx)); + return OBJECT_TO_JSVAL(valuesToJSProperties(JS_NewObject(cx, NULL, NULL, NULL), val, cx)); } default: { return JSVAL_VOID; @@ -291,7 +291,7 @@ const jsval valueToJSVal(const JSONContext& cx, const value& val) { } } -const failable<bool, std::string> writeList(const JSONContext& cx, const list<value>& l, JSObject* o) { +const failable<bool, std::string> writeList(const list<value>& l, JSObject* o, const JSONContext& cx) { if (isNil(l)) return true; @@ -299,14 +299,14 @@ const failable<bool, std::string> writeList(const JSONContext& cx, const list<va const value token(car(l)); if (isTaggedList(token, attribute)) { - jsval pv = valueToJSVal(cx, attributeValue(token)); + jsval pv = valueToJSVal(attributeValue(token), cx); JS_SetProperty(cx, o, std::string(attributeName(token)).c_str(), &pv); } else if (isTaggedList(token, element)) { // Write the value of an element if (elementHasValue(token)) { - jsval pv = valueToJSVal(cx, elementValue(token)); + jsval pv = valueToJSVal(elementValue(token), cx); JS_SetProperty(cx, o, std::string(elementName(token)).c_str(), &pv); } else { @@ -317,14 +317,14 @@ const failable<bool, std::string> writeList(const JSONContext& cx, const list<va JS_SetProperty(cx, o, std::string(elementName(token)).c_str(), &pv); // Write its children - const failable<bool, std::string> w = writeList(cx, elementChildren(token), child); + const failable<bool, std::string> w = writeList(elementChildren(token), child, cx); if (!hasValue(w)) return w; } } // Go on - return writeList(cx, cdr(l), o); + return writeList(cdr(l), o, cx); } /** @@ -332,10 +332,10 @@ const failable<bool, std::string> writeList(const JSONContext& cx, const list<va */ template<typename R> class WriteContext { public: - WriteContext(const JSONContext& cx, const lambda<R(R, std::string)>& reduce, const R& accum) : cx(cx), reduce(reduce), accum(accum) { + WriteContext(const lambda<R(std::string, R)>& reduce, const R& accum, const JSONContext& cx) : cx(cx), reduce(reduce), accum(accum) { } const JSONContext& cx; - const lambda<R(R, std::string)> reduce; + const lambda<R(std::string, R)> reduce; R accum; }; @@ -345,21 +345,21 @@ public: template<typename R> JSBool writeCallback(const jschar *buf, uint32 len, void *data) { WriteContext<R>& wcx = *(static_cast<WriteContext<R>*> (data)); JSString* jstr = JS_NewUCStringCopyN(wcx.cx, buf, len); - wcx.accum = wcx.reduce(wcx.accum, std::string(JS_GetStringBytes(jstr), JS_GetStringLength(jstr))); + wcx.accum = wcx.reduce(std::string(JS_GetStringBytes(jstr), JS_GetStringLength(jstr)), wcx.accum); return JS_TRUE; } /** * Convert a list of values to a JSON document. */ -template<typename R> const failable<R, std::string> write(const JSONContext& cx, const lambda<R(R, std::string)>& reduce, const R& initial, const list<value>& l) { +template<typename R> const failable<R, std::string> writeJSON(const lambda<R(std::string, R)>& reduce, const R& initial, const list<value>& l, const JSONContext& cx) { JSObject* o = JS_NewObject(cx, NULL, NULL, NULL); jsval val = OBJECT_TO_JSVAL(o); - const failable<bool, std::string> w = writeList(cx, l, o); + const failable<bool, std::string> w = writeList(l, o, cx); if (!hasValue(w)) return std::string(w); - WriteContext<R> wcx(cx, reduce, initial); + WriteContext<R> wcx(reduce, initial, cx); if (!JS_Stringify(cx, &val, NULL, JSVAL_NULL, writeCallback<R>, &wcx)) return std::string("JS_Stringify failed"); return wcx.accum; @@ -368,17 +368,29 @@ template<typename R> const failable<R, std::string> write(const JSONContext& cx, /** * Convert a list of values to a list of strings representing a JSON document. */ -const list<std::string> writeStrings(const list<std::string>& listSoFar, const std::string& s) { - return cons(s, listSoFar); -} - -const failable<list<std::string>, std::string> write(const JSONContext& cx, const list<value>& l) { - const failable<list<std::string>, std::string> ls = write<list<std::string> >(cx, writeStrings, list<std::string>(), l); +const failable<list<std::string>, std::string> writeJSON(const list<value>& l, const JSONContext& cx) { + const failable<list<std::string>, std::string> ls = writeJSON<list<std::string> >(rcons<std::string>, list<std::string>(), l, cx); if (!hasValue(ls)) return ls; return reverse(list<std::string>(ls)); } +/** + * Convert a function + params to a JSON request. + */ +const failable<list<std::string>, std::string> jsonRequest(const value& id, const value& func, const value& params, json::JSONContext& cx) { + const list<value> r = mklist<value>(mklist<value>("id", id), mklist<value>("method", func), mklist<value>("params", params)); + failable<list<std::string>, std::string> ls = writeJSON(valuesToElements(r), cx); + return ls; +} + +/** + * Convert a value to a JSON result. + */ +const failable<list<std::string>, std::string> jsonResult(const value& id, const value& val, JSONContext& cx) { + return writeJSON(valuesToElements(mklist<value>(mklist<value>("id", id), mklist<value>("result", val))), cx); +} + } } diff --git a/cpp/sca/test/store-script/htdocs/store.html b/cpp/sca/test/store-script/htdocs/store.html index 98d6ec8e8e..4d300f1adb 100644 --- a/cpp/sca/test/store-script/htdocs/store.html +++ b/cpp/sca/test/store-script/htdocs/store.html @@ -158,7 +158,7 @@ <br>
<input type="button" onClick="checkoutCart()" value="Checkout">
<input type="button" onClick="deleteCart()" value="Empty">
- <a href="../ShoppingCart/Cart/">(feed)</a>
+ <a href="../ShoppingCart/">(feed)</a>
</form>
</div>
</body>
diff --git a/cpp/sca/test/store-script/htdocs/store.js b/cpp/sca/test/store-script/htdocs/store.js index 64749ce6c1..9cb09f4b78 100644 --- a/cpp/sca/test/store-script/htdocs/store.js +++ b/cpp/sca/test/store-script/htdocs/store.js @@ -651,8 +651,8 @@ tuscany.sca.Property = function (name) { tuscany.sca.referenceMap = new Object(); tuscany.sca.referenceMap.catalog = new JSONRpcClient("/Catalog").Service; -tuscany.sca.referenceMap.shoppingCart = new AtomClient("/ShoppingCart/Cart"); -tuscany.sca.referenceMap.shoppingTotal = new JSONRpcClient("/ShoppingCart/Total").Service; +tuscany.sca.referenceMap.shoppingCart = new AtomClient("/ShoppingCart"); +tuscany.sca.referenceMap.shoppingTotal = new JSONRpcClient("/Total").Service; tuscany.sca.Reference = function (name) { return tuscany.sca.referenceMap[name]; } diff --git a/cpp/sca/test/store-script/store-http-test b/cpp/sca/test/store-script/store-http-test new file mode 100755 index 0000000000..4a5a3df685 --- /dev/null +++ b/cpp/sca/test/store-script/store-http-test @@ -0,0 +1,60 @@ +#!/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..." + +# Setup +../../modules/http/httpd-conf tmp 8092 htdocs +cat >>tmp/conf/httpd.conf <<EOF +<Location /Catalog> +SetHandler mod_tuscany +SCAContribution `pwd`/ +SCAComponent store +SCAImplementation store.scm +</Location> + +<Location /Total> +SetHandler mod_tuscany +SCAContribution `pwd`/ +SCAComponent store +SCAImplementation store.scm +</Location> + +<Location /ShoppingCart> +SetHandler mod_tuscany +SCAContribution `pwd`/ +SCAComponent store +SCAImplementation store.scm +</Location> +EOF +apachectl -k start -d `pwd`/tmp +sleep 1 + +# Test HTTP GET +curl http://localhost:8092/store.html 2>/dev/null >tmp/store.html +diff tmp/store.html htdocs/store.html +rc=$? + +# Cleanup +apachectl -k stop -d `pwd`/tmp +sleep 1 +if [ "$rc" = "0" ]; then + echo "OK" +fi +return $rc diff --git a/cpp/sca/test/store-script/store-script-test.cpp b/cpp/sca/test/store-script/store-script-test.cpp index 5808bd889e..5a29d8caba 100644 --- a/cpp/sca/test/store-script/store-script-test.cpp +++ b/cpp/sca/test/store-script/store-script-test.cpp @@ -55,7 +55,7 @@ bool testScript() { } const value evalLoop(std::istream& is, const value& req, eval::Env& globalEnv, const gc_pool& pool) { - value in = eval::read(is); + value in = eval::readValue(is); if(isNil(in)) return eval::evalExpr(req, globalEnv, pool); eval::evalExpr(in, globalEnv, pool); @@ -66,13 +66,12 @@ bool testEval() { { std::ifstream is("store.scm", std::ios_base::in); std::ostringstream os; - eval::setupEvalOut(os); + eval::setupDisplay(os); gc_pool pool; eval::Env globalEnv = eval::setupEnvironment(pool); const value req(mklist<value>("storeui_service", std::string("getcatalog"))); const value val = evalLoop(is, req, globalEnv, pool); - eval::cleanupEnvironment(globalEnv); std::ostringstream vs; vs << val; @@ -82,13 +81,12 @@ bool testEval() { { std::ifstream is("store.scm", std::ios_base::in); std::ostringstream os; - eval::setupEvalOut(os); + eval::setupDisplay(os); gc_pool pool; eval::Env globalEnv = eval::setupEnvironment(pool); const value req(mklist<value>("storeui_service", std::string("gettotal"))); const value res = evalLoop(is, req, globalEnv, pool); - eval::cleanupEnvironment(globalEnv); std::ostringstream rs; rs << res; diff --git a/cpp/sca/test/store-script/store.scm b/cpp/sca/test/store-script/store.scm index 99ae33481c..709fd943f1 100644 --- a/cpp/sca/test/store-script/store.scm +++ b/cpp/sca/test/store-script/store.scm @@ -32,7 +32,7 @@ (list (list 'javaClass "services.Item") (list 'name "Apple") (list 'currency code) (list 'symbol symbol) (list 'price 2.99)) (list (list 'javaClass "services.Item") (list 'name "Orange") (list 'currency code) (list 'symbol symbol) (list 'price 3.55)) (list (list 'javaClass "services.Item") (list 'name "Pear") (list 'currency code) (list 'symbol symbol) (list 'price 1.55)) - ) + ) ) (define (catalog_impl converter op args) @@ -48,11 +48,11 @@ (; "Cart implementation") (define (cart_post content item) - (cons (cons "Item" (list "123456789" item)) content) + (cons (cons "Item" (list (uuid) item)) content) ) (define (cart_getall content) - (cons "Sample Feed" (cons "123" content)) + (cons "Sample Feed" (cons (uuid) content)) ) (define (cart_getentry id) @@ -125,13 +125,26 @@ (; "Store UI JSON-RPC interop test case") (define (system.listMethods) (list "Service.get" "Service.getTotal")) + (define (Service.get) (storeui_service "getcatalog")) + (define (.get) (storeui_service "getcatalog")) + (define (Service.getTotal) (storeui_service "gettotal")) (; "Store UI ATOMPub interop test case") (define (getall) (storeui_service "getall" added2)) + (define (get id) (storeui_service "getentry" id)) -(define (post entry) (display entry)) + +(define (post entry) + (display entry) + (uuid) +) + +(define (delete . args) + (display args) + true +) |