diff options
author | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2009-11-01 05:25:14 +0000 |
---|---|---|
committer | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2009-11-01 05:25:14 +0000 |
commit | 6b94d489977c1cb2eeddded3ee329fe6b9605d5c (patch) | |
tree | f51d8b2373102cb6c8ac9fc0e051b6f1227a414c /cpp/sca/modules | |
parent | 9f187b46ae761e8275362d6c1533e9fe79028c7b (diff) |
Minor refactoring of read/write functions and primitive procs. Added functions to help store data in memcached. Fixes to HTTP support and more tests.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@831640 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp/sca/modules')
28 files changed, 1027 insertions, 324 deletions
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); +} + } } |