summaryrefslogtreecommitdiffstats
path: root/sca-cpp
diff options
context:
space:
mode:
authorjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2010-07-06 09:06:07 +0000
committerjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2010-07-06 09:06:07 +0000
commit4b05b5d35a0757ba9815897c18fba1e563fdca2a (patch)
tree9d0e63102e65da0746f5370674387c5ebb34faad /sca-cpp
parent0389017140b0af23e061fdd66d7467da75f95e83 (diff)
Add support for HTTPD mass dynamic virtual hosting. Components can now be deployed, resolved, wired and invoked within a virtual host.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@960847 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'sca-cpp')
-rw-r--r--sca-cpp/trunk/configure.ac1
-rwxr-xr-xsca-cpp/trunk/modules/http/httpd-conf18
-rwxr-xr-xsca-cpp/trunk/modules/http/httpd-ssl-conf4
-rw-r--r--sca-cpp/trunk/modules/http/httpd.hpp29
-rw-r--r--sca-cpp/trunk/modules/server/mod-eval.hpp226
-rw-r--r--sca-cpp/trunk/modules/server/mod-wiring.cpp170
-rwxr-xr-xsca-cpp/trunk/modules/server/server-conf3
-rw-r--r--sca-cpp/trunk/samples/Makefile.am2
-rw-r--r--sca-cpp/trunk/samples/store-python/Makefile.am2
-rw-r--r--sca-cpp/trunk/samples/store-vhost/Makefile.am28
-rw-r--r--sca-cpp/trunk/samples/store-vhost/currency-converter.py29
-rw-r--r--sca-cpp/trunk/samples/store-vhost/fruits-catalog.py30
-rw-r--r--sca-cpp/trunk/samples/store-vhost/htdocs/domains/jane/index.html157
-rw-r--r--sca-cpp/trunk/samples/store-vhost/htdocs/domains/joe/index.html157
-rw-r--r--sca-cpp/trunk/samples/store-vhost/htdocs/index.html157
-rwxr-xr-xsca-cpp/trunk/samples/store-vhost/server-test43
-rw-r--r--sca-cpp/trunk/samples/store-vhost/shopping-cart.py75
-rwxr-xr-xsca-cpp/trunk/samples/store-vhost/ssl-start34
-rwxr-xr-xsca-cpp/trunk/samples/store-vhost/start31
-rwxr-xr-xsca-cpp/trunk/samples/store-vhost/stop21
-rw-r--r--sca-cpp/trunk/samples/store-vhost/store.composite69
-rw-r--r--sca-cpp/trunk/samples/store-vhost/store.py40
-rwxr-xr-xsca-cpp/trunk/samples/store-vhost/uec2-start46
23 files changed, 1249 insertions, 123 deletions
diff --git a/sca-cpp/trunk/configure.ac b/sca-cpp/trunk/configure.ac
index 38a529ed21..b94e540e51 100644
--- a/sca-cpp/trunk/configure.ac
+++ b/sca-cpp/trunk/configure.ac
@@ -859,6 +859,7 @@ AC_CONFIG_FILES([Makefile
samples/store-gae/Makefile
samples/store-sql/Makefile
samples/store-nosql/Makefile
+ samples/store-vhost/Makefile
doc/Makefile
doc/Doxyfile
ubuntu/Makefile
diff --git a/sca-cpp/trunk/modules/http/httpd-conf b/sca-cpp/trunk/modules/http/httpd-conf
index 2ef922145f..a6ba048f67 100755
--- a/sca-cpp/trunk/modules/http/httpd-conf
+++ b/sca-cpp/trunk/modules/http/httpd-conf
@@ -30,6 +30,13 @@ htdocs=`readlink -f $4`
user=`id -un`
group=`id -gn`
modules_prefix=`cat $here/httpd-modules.prefix`
+if [ "$5" = "vhost" ]; then
+ vhost="VirtualDocumentRoot $htdocs/domains/%1/"
+ maxr="MaxRequestsPerChild 1"
+else
+ vhost="#VirtualDocumentRoot $htdocs/domains/%1/"
+ maxr="#MaxRequestsPerChild 1"
+fi
mkdir -p $root
mkdir -p $root/logs
@@ -39,8 +46,6 @@ cat >$root/conf/httpd.conf <<EOF
# Set server name
ServerName http://$host:$pport
-UseCanonicalName On
-UseCanonicalPhysicalPort off
PidFile $root/logs/httpd.pid
# Minimal set of modules
@@ -68,6 +73,7 @@ LoadModule setenvif_module ${modules_prefix}/modules/mod_setenvif.so
<IfModule !log_config_module>
LoadModule log_config_module ${modules_prefix}/modules/mod_log_config.so
</IfModule>
+LoadModule vhost_alias_module ${modules_prefix}/modules/mod_vhost_alias.so
# Basic security precautions
User $user
@@ -121,7 +127,15 @@ Allow from all
Listen $port
<VirtualHost _default_:$port>
ServerName http://$host:$pport
+UseCanonicalName Off
+UseCanonicalPhysicalPort Off
+
+# Setup mass dynamic virtual hosting
+$vhost
</VirtualHost>
+# Isolate dynamic virtual hosts
+$maxr
+
EOF
diff --git a/sca-cpp/trunk/modules/http/httpd-ssl-conf b/sca-cpp/trunk/modules/http/httpd-ssl-conf
index df1d8042bc..04ca61dba0 100755
--- a/sca-cpp/trunk/modules/http/httpd-ssl-conf
+++ b/sca-cpp/trunk/modules/http/httpd-ssl-conf
@@ -55,8 +55,8 @@ SSLRandomSeed connect builtin
Listen $sslport
<VirtualHost _default_:$sslport>
ServerName https://$host:$sslpport
-UseCanonicalName On
-UseCanonicalPhysicalPort off
+UseCanonicalName Off
+UseCanonicalPhysicalPort Off
# Enable SSL
SSLEngine on
diff --git a/sca-cpp/trunk/modules/http/httpd.hpp b/sca-cpp/trunk/modules/http/httpd.hpp
index 93137be4e2..bd4f6e8ada 100644
--- a/sca-cpp/trunk/modules/http/httpd.hpp
+++ b/sca-cpp/trunk/modules/http/httpd.hpp
@@ -75,6 +75,31 @@ template<typename C> C& serverConf(const cmd_parms *cmd, const module* mod) {
/**
+ * Return the name of a server.
+ */
+const string serverName(const server_rec* s) {
+ ostringstream n;
+ n << (s->server_scheme != NULL? s->server_scheme : "http") << "://"
+ << (s->server_hostname != NULL? s->server_hostname : "localhost") << ":"
+ << (s->port != 0? s->port : 80)
+ << (s->path != NULL? string(s->path, s->pathlen) : "");
+ return str(n);
+}
+
+/**
+ * Determine the name of a server from an HTTP request.
+ */
+const string serverName(request_rec* r) {
+ ostringstream n;
+ const char* hn = ap_get_server_name(r);
+ n << (r->server->server_scheme != NULL? r->server->server_scheme : "http") << "://"
+ << (hn != NULL? hn : (r->server->server_hostname != NULL? r->server->server_hostname : "localhost")) << ":"
+ << (r->server->port != 0? r->server->port : 80)
+ << (r->server->path != NULL? string(r->server->path, r->server->pathlen) : "");
+ return str(n);
+}
+
+/**
* Return the content type of a request.
*/
const char* optional(const char* s) {
@@ -295,7 +320,7 @@ const bool redirectFilters(ap_filter_t* f, request_rec* from, request_rec* to) {
}
/**
- * Create an HTTPD redirect request.
+ * Create an HTTPD internal redirect request.
* Similar to httpd/modules/http/http_request.c::internal_internal_redirect.
*/
extern "C" {
@@ -388,7 +413,7 @@ const int internalRedirect(request_rec* nr) {
}
/**
- * Create and process an HTTPD redirect request.
+ * Create and process an HTTPD internal redirect request.
*/
const int internalRedirect(const string& uri, request_rec* r) {
const failable<request_rec*, int> nr = httpd::internalRedirectRequest(uri, r);
diff --git a/sca-cpp/trunk/modules/server/mod-eval.hpp b/sca-cpp/trunk/modules/server/mod-eval.hpp
index 14e5f40e9b..464e0eabf1 100644
--- a/sca-cpp/trunk/modules/server/mod-eval.hpp
+++ b/sca-cpp/trunk/modules/server/mod-eval.hpp
@@ -53,14 +53,16 @@ namespace modeval {
*/
class ServerConf {
public:
- ServerConf(server_rec* s) : s(s), wiringServerName(""), contributionPath(""), compositeName(""), ca(""), cert(""), key("") {
+ ServerConf(server_rec* s) : server(s), wiringServerName(""), contributionPath(""), compositeName(""), virtualHostContributionPath(""), virtualHostCompositeName(""), ca(""), cert(""), key("") {
}
- const server_rec* s;
+ server_rec* server;
lambda<value(const list<value>&)> lifecycle;
string wiringServerName;
string contributionPath;
string compositeName;
+ string virtualHostContributionPath;
+ string virtualHostCompositeName;
string ca;
string cert;
string key;
@@ -69,6 +71,13 @@ public:
};
/**
+ * Return true if a server contains a composite configuration.
+ */
+const bool hasCompositeConf(const ServerConf& sc) {
+ return sc.contributionPath != "" && sc.compositeName != "";
+}
+
+/**
* Convert a result represented as a content + failure pair to a
* failable monad.
*/
@@ -241,6 +250,19 @@ int translate(request_rec *r) {
}
/**
+ * Convert a list of component references to a list of HTTP proxy lambdas.
+ */
+const value mkrefProxy(const value& ref, const string& base, const string& ca, const string& cert, const string& key) {
+ return lambda<value(const list<value>&)>(http::proxy(base + string(scdl::name(ref)), ca, cert, key));
+}
+
+const list<value> refProxies(const list<value>& refs, const string& base, const string& ca, const string& cert, const string& key) {
+ if (isNil(refs))
+ return refs;
+ return cons(mkrefProxy(car(refs), base, ca, cert, key), refProxies(cdr(refs), base, ca, cert, key));
+}
+
+/**
* Store current HTTP request for access from property lambda functions.
*/
#ifdef WANT_THREADS
@@ -248,63 +270,18 @@ __thread
#endif
const request_rec* currentRequest = NULL;
-class scoped_request {
+class ScopedRequest {
public:
- scoped_request(const request_rec* r) {
+ ScopedRequest(const request_rec* r) {
currentRequest = r;
}
- ~scoped_request() {
+ ~ScopedRequest() {
currentRequest = NULL;
}
};
/**
- * HTTP request handler.
- */
-int handler(request_rec *r) {
- if(strcmp(r->handler, "mod_tuscany_eval"))
- return DECLINED;
- gc_scoped_pool pool(r->pool);
- scoped_request sr(r);
- httpdDebugRequest(r, "modeval::handler::input");
-
- // Get the component implementation lambda
- const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval);
- const list<value> path(pathValues(r->uri));
- const list<value> impl(assoctree<value>(cadr(path), sc.implTree));
- if (isNil(impl))
- return httpd::reportStatus(mkfailure<int>(string("Couldn't find component implementation")));
-
- // Handle HTTP method
- const lambda<value(const list<value>&)> l(cadr<value>(impl));
- if (r->header_only)
- return OK;
- if(r->method_number == M_GET)
- return httpd::reportStatus(get(r, l));
- if(r->method_number == M_POST)
- return httpd::reportStatus(post(r, l));
- if(r->method_number == M_PUT)
- return httpd::reportStatus(put(r, l));
- if(r->method_number == M_DELETE)
- return httpd::reportStatus(del(r, l));
- return HTTP_NOT_IMPLEMENTED;
-}
-
-/**
- * Convert a list of component references to a list of HTTP proxy lambdas.
- */
-const value mkrefProxy(const value& ref, const string& base, const string& ca, const string& cert, const string& key) {
- return lambda<value(const list<value>&)>(http::proxy(base + string(scdl::name(ref)), ca, cert, key));
-}
-
-const list<value> refProxies(const list<value>& refs, const string& base, const string& ca, const string& cert, const string& key) {
- if (isNil(refs))
- return refs;
- return cons(mkrefProxy(car(refs), base, ca, cert, key), refProxies(cdr(refs), base, ca, cert, key));
-}
-
-/**
* Convert a list of component properties to a list of lambda functions that just return
* the property value. The user and email properties are configured with the values
* from the HTTP request, if any.
@@ -374,20 +351,14 @@ struct implementationFailure {
}
};
-const value evalComponent(ServerConf& sc, server_rec& server, const value& comp) {
+const value evalComponent(ServerConf& sc, const value& comp) {
extern const failable<lambda<value(const list<value>&)> > evalImplementation(const string& cpath, const value& impl, const list<value>& px, const lambda<value(const list<value>&)>& lifecycle);
const value impl = scdl::implementation(comp);
// Convert component references to configured proxy lambdas
ostringstream base;
- if (sc.wiringServerName == "")
- base << (server.server_scheme == NULL? "http" : server.server_scheme)
- << "://" << (server.server_hostname == NULL? "localhost" : server.server_hostname)
- << ":" << (server.port == 0? 80 : server.port)
- << "/references/" << string(scdl::name(comp)) << "/";
- else
- base << sc.wiringServerName << "/references/" << string(scdl::name(comp)) << "/";
+ base << sc.wiringServerName << "/references/" << string(scdl::name(comp)) << "/";
const list<value> rpx(refProxies(scdl::references(comp), str(base), sc.ca, sc.cert, sc.key));
// Convert component proxies to configured proxy lambdas
@@ -403,10 +374,10 @@ const value evalComponent(ServerConf& sc, server_rec& server, const value& comp)
/**
* Return a list of component-name + configured-implementation pairs.
*/
-const list<value> componentToImplementationAssoc(ServerConf& sc, server_rec& server, const list<value>& c) {
+const list<value> componentToImplementationAssoc(ServerConf& sc, const list<value>& c) {
if (isNil(c))
return c;
- return cons<value>(mklist<value>(scdl::name(car(c)), evalComponent(sc, server, car(c))), componentToImplementationAssoc(sc, server, cdr(c)));
+ return cons<value>(mklist<value>(scdl::name(car(c)), evalComponent(sc, car(c))), componentToImplementationAssoc(sc, cdr(c)));
}
/**
@@ -447,8 +418,8 @@ const failable<list<value> > applyLifecycleExpr(const list<value>& impls, const
/**
* Configure the components declared in the deployed composite.
*/
-const failable<bool> confComponents(ServerConf& sc, server_rec& server) {
- if (sc.contributionPath == "" || sc.compositeName == "")
+const failable<bool> confComponents(ServerConf& sc) {
+ if (!hasCompositeConf(sc))
return false;
// Chdir to the deployed contribution
@@ -459,7 +430,7 @@ const failable<bool> confComponents(ServerConf& sc, server_rec& server) {
const failable<list<value> > comps = readComponents(sc.contributionPath + sc.compositeName);
if (!hasContent(comps))
return mkfailure<bool>(reason(comps));
- sc.implementations = componentToImplementationAssoc(sc, server, content(comps));
+ sc.implementations = componentToImplementationAssoc(sc, content(comps));
debug(sc.implementations, "modeval::confComponents::implementations");
// Store the implementation lambda functions in a tree for fast retrieval
@@ -486,6 +457,107 @@ const failable<bool> startComponents(ServerConf& sc) {
}
/**
+ * Virtual host scoped server configuration.
+ */
+class VirtualHostConf {
+public:
+ VirtualHostConf(const ServerConf& ssc) : sc(ssc.server) {
+ sc.contributionPath = ssc.virtualHostContributionPath;
+ sc.compositeName = ssc.virtualHostCompositeName;
+ sc.wiringServerName = ssc.wiringServerName;
+ sc.ca = ssc.ca;
+ sc.cert = ssc.cert;
+ sc.key = ssc.key;
+ }
+
+ ~VirtualHostConf() {
+ extern const failable<bool> virtualHostCleanup(const ServerConf& sc);
+ virtualHostCleanup(sc);
+ }
+
+ ServerConf sc;
+};
+
+/**
+ * Configure and start the components deployed in a virtual host.
+ */
+const failable<bool> virtualHostConfig(ServerConf& sc, request_rec* r) {
+ extern const value applyLifecycle(const list<value>&);
+
+ // Determine the server name and wiring server name
+ debug(httpd::serverName(sc.server), "modeval::virtualHostConfig::serverName");
+ debug(httpd::serverName(r), "modeval::virtualHostConfig::virtualHostName");
+ sc.wiringServerName = httpd::serverName(r);
+ debug(sc.wiringServerName, "modeval::virtualHostConfig::wiringServerName");
+
+ // Configure the deployed components
+ debug(sc.contributionPath, "modeval::virtualHostConfig::contributionPath");
+ debug(sc.compositeName, "modeval::virtualHostConfig::compositeName");
+ const failable<bool> cr = confComponents(sc);
+ if (!hasContent(cr))
+ return cr;
+
+ // Start the configured components
+ return startComponents(sc);
+}
+
+/**
+ * Cleanup a virtual host.
+ */
+const failable<bool> virtualHostCleanup(const ServerConf& sc) {
+ if (!hasCompositeConf(sc))
+ return true;
+ debug("modeval::virtualHostCleanup");
+
+ // Stop the component implementations
+ applyLifecycleExpr(sc.implementations, mklist<value>("stop"));
+ return true;
+}
+
+/**
+ * HTTP request handler.
+ */
+int handler(request_rec *r) {
+ if(strcmp(r->handler, "mod_tuscany_eval"))
+ return DECLINED;
+ gc_scoped_pool pool(r->pool);
+ ScopedRequest sr(r);
+ httpdDebugRequest(r, "modeval::handler::input");
+
+ // Get the server configuration
+ const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_eval);
+
+ // Process dynamic virtual host configuration, if any
+ VirtualHostConf vhc(sc);
+ const bool hasv = hasCompositeConf(vhc.sc);
+ if (hasv) {
+ const failable<bool> cr = virtualHostConfig(vhc.sc, r);
+ if (!hasContent(cr))
+ return httpd::reportStatus(mkfailure<int>(reason(cr)));
+ }
+
+ // Get the component implementation lambda
+ const list<value> path(pathValues(r->uri));
+ const list<value> impl(assoctree<value>(cadr(path), hasv? vhc.sc.implTree : sc.implTree));
+ if (isNil(impl))
+ return httpd::reportStatus(mkfailure<int>(string("Couldn't find component implementation")));
+
+ // Handle HTTP method
+ const lambda<value(const list<value>&)> l(cadr<value>(impl));
+ if (r->header_only)
+ return OK;
+ if(r->method_number == M_GET)
+ return httpd::reportStatus(get(r, l));
+ if(r->method_number == M_POST)
+ return httpd::reportStatus(post(r, l));
+ if(r->method_number == M_PUT)
+ return httpd::reportStatus(put(r, l));
+ if(r->method_number == M_DELETE)
+ return httpd::reportStatus(del(r, l));
+ return HTTP_NOT_IMPLEMENTED;
+}
+
+/**
* Cleanup callback, called when the server is stopped or restarted.
*/
apr_status_t serverCleanup(void* v) {
@@ -512,10 +584,14 @@ apr_status_t serverCleanup(void* v) {
const int postConfigMerge(const ServerConf& mainsc, server_rec* s) {
if (s == NULL)
return OK;
+ ostringstream sname;
+ debug(httpd::serverName(s), "modeval::postConfigMerge::serverName");
ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_eval);
sc.wiringServerName = mainsc.wiringServerName;
sc.contributionPath = mainsc.contributionPath;
sc.compositeName = mainsc.compositeName;
+ sc.virtualHostContributionPath = mainsc.virtualHostContributionPath;
+ sc.virtualHostCompositeName = mainsc.virtualHostCompositeName;
sc.ca = mainsc.ca;
sc.cert = mainsc.cert;
sc.key = mainsc.key;
@@ -528,7 +604,13 @@ int postConfig(apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp,
extern const value applyLifecycle(const list<value>&);
gc_scoped_pool pool(p);
+
+ // Get the server configuration and determine the wiring server name
ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_eval);
+ debug(httpd::serverName(s), "modeval::postConfig::serverName");
+ if (sc.wiringServerName == "")
+ sc.wiringServerName = httpd::serverName(s);
+ debug(sc.wiringServerName, "modeval::postConfig::wiringServerName");
// Count the calls to post config
const string k("tuscany::modeval::postConfig");
@@ -539,6 +621,7 @@ int postConfig(apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp,
// count == 1 is the first start, count > 1 is a restart
if (count == 0)
return OK;
+
if (count == 1) {
debug("modeval::postConfig::start");
const failable<value> r = failableResult(applyLifecycle(mklist<value>("start")));
@@ -555,10 +638,9 @@ int postConfig(apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp,
}
// Configure the deployed components
- debug(sc.wiringServerName, "modeval::postConfig::wiringServerName");
debug(sc.contributionPath, "modeval::postConfig::contributionPath");
debug(sc.compositeName, "modeval::postConfig::compositeName");
- const failable<bool> res = confComponents(sc, *s);
+ const failable<bool> res = confComponents(sc);
if (!hasContent(res)) {
cerr << "[Tuscany] Due to one or more errors mod_tuscany_eval loading failed. Causing apache to stop loading." << endl;
return -1;
@@ -614,6 +696,18 @@ const char* confComposite(cmd_parms *cmd, unused void *c, const char *arg) {
sc.compositeName = arg;
return NULL;
}
+const char* confVirtualContribution(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval);
+ sc.virtualHostContributionPath = arg;
+ return NULL;
+}
+const char* confVirtualComposite(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval);
+ sc.virtualHostCompositeName = arg;
+ return NULL;
+}
const char* confCAFile(cmd_parms *cmd, unused void *c, const char *arg) {
gc_scoped_pool pool(cmd->pool);
ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_eval);
@@ -646,6 +740,8 @@ const command_rec commands[] = {
AP_INIT_TAKE1("SCAWiringServerName", (const char*(*)())confWiringServerName, NULL, RSRC_CONF, "SCA wiring server name"),
AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, RSRC_CONF, "SCA contribution location"),
AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, RSRC_CONF, "SCA composite location"),
+ AP_INIT_TAKE1("SCAVirtualContribution", (const char*(*)())confVirtualContribution, NULL, RSRC_CONF, "SCA virtual host contribution location"),
+ AP_INIT_TAKE1("SCAVirtualComposite", (const char*(*)())confVirtualComposite, NULL, RSRC_CONF, "SCA virtual composite location"),
AP_INIT_TAKE12("SCASetEnv", (const char*(*)())confEnv, NULL, OR_FILEINFO, "Environment variable name and optional value"),
AP_INIT_TAKE1("SCASSLCACertificateFile", (const char*(*)())confCAFile, NULL, RSRC_CONF, "SSL CA certificate file"),
AP_INIT_TAKE1("SCASSLCertificateFile", (const char*(*)())confCertFile, NULL, RSRC_CONF, "SSL certificate file"),
diff --git a/sca-cpp/trunk/modules/server/mod-wiring.cpp b/sca-cpp/trunk/modules/server/mod-wiring.cpp
index 7f41a7e03d..5b2aba73b3 100644
--- a/sca-cpp/trunk/modules/server/mod-wiring.cpp
+++ b/sca-cpp/trunk/modules/server/mod-wiring.cpp
@@ -44,23 +44,32 @@ namespace server {
namespace modwiring {
/**
+ * Set to true to wire using mod_proxy, false to wire using HTTP client redirects.
+ */
+const bool useModProxy = true;
+
+/**
* Server configuration.
*/
class ServerConf {
public:
- ServerConf(server_rec* s) : s(s), contributionPath(""), compositeName("") {
+ ServerConf(server_rec* s) : server(s), contributionPath(""), compositeName(""), virtualHostContributionPath(""), virtualHostCompositeName("") {
}
- const server_rec* s;
+ server_rec* server;
string contributionPath;
string compositeName;
+ string virtualHostContributionPath;
+ string virtualHostCompositeName;
list<value> references;
list<value> services;
};
/**
- * Set to true to wire using mod_proxy, false to wire using HTTP client redirects.
+ * Return true if a server contains a composite configuration.
*/
-const bool useModProxy = true;
+const bool hasCompositeConf(const ServerConf& sc) {
+ return sc.contributionPath != "" && sc.compositeName != "";
+}
/**
* Returns true if a URI is absolute.
@@ -73,12 +82,11 @@ const bool isAbsolute(const string& uri) {
* Route a /references/component-name/reference-name request,
* to the target of the component reference.
*/
-int translateReference(request_rec *r) {
+int translateReference(const ServerConf& sc, request_rec *r) {
httpdDebugRequest(r, "modwiring::translateReference::input");
debug(r->uri, "modwiring::translateReference::uri");
// Find the requested component
- const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_wiring);
const list<value> rpath(pathValues(r->uri));
const list<value> comp(assoctree(cadr(rpath), sc.references));
if (isNil(comp))
@@ -138,12 +146,11 @@ const list<value> assocPath(const value& k, const list<value>& tree) {
/**
* Route a service request to the component providing the requested service.
*/
-int translateService(request_rec *r) {
+int translateService(const ServerConf& sc, request_rec *r) {
httpdDebugRequest(r, "modwiring::translateService::input");
debug(r->uri, "modwiring::translateService::uri");
// Find the requested component
- const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_wiring);
debug(sc.services, "modwiring::translateService::services");
const list<value> p(pathValues(r->uri));
const list<value> svc(assocPath(p, sc.services));
@@ -166,44 +173,6 @@ int translateService(request_rec *r) {
}
/**
- * Translate an HTTP service or reference request and route it
- * to the target component.
- */
-int translate(request_rec *r) {
- gc_scoped_pool pool(r->pool);
- if (!strncmp(r->uri, "/components/", 12) != 0)
- return DECLINED;
-
- // Translate a component reference request
- if (!strncmp(r->uri, "/references/", 12) != 0)
- return translateReference(r);
-
- // Translate a service request
- return translateService(r);
-}
-
-/**
- * HTTP request handler, redirect to a target component.
- */
-int handler(request_rec *r) {
- gc_scoped_pool pool(r->pool);
- if(strcmp(r->handler, "mod_tuscany_wiring"))
- return DECLINED;
- httpdDebugRequest(r, "modwiring::handler::input");
-
- // Do an internal redirect
- if (r->filename == NULL || strncmp(r->filename, "/redirect:", 10) != 0)
- return DECLINED;
- debug(r->uri, "modwiring::handler::uri");
- debug(r->filename, "modwiring::handler::filename");
- debug(r->path_info, "modwiring::handler::path info");
-
- if (r->args == NULL)
- return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info)), r);
- return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info), string(r->args)), r);
-}
-
-/**
* Read the components declared in a composite.
*/
const failable<list<value> > readComponents(const string& path) {
@@ -264,7 +233,7 @@ const list<value> uriToComponentAssoc(const list<value>& c) {
* Configure the components declared in the server's deployment composite.
*/
const bool confComponents(ServerConf& sc) {
- if (sc.contributionPath == "" || sc.compositeName == "")
+ if (!hasCompositeConf(sc))
return true;
// Read the component configuration and store the references and service URIs
@@ -283,21 +252,109 @@ const bool confComponents(ServerConf& sc) {
}
/**
+ * Virtual host scoped server configuration.
+ */
+class VirtualHostConf {
+public:
+ VirtualHostConf(const ServerConf& ssc) : sc(ssc.server) {
+ sc.contributionPath = ssc.virtualHostContributionPath;
+ sc.compositeName = ssc.virtualHostCompositeName;
+ }
+
+ ~VirtualHostConf() {
+ }
+
+ ServerConf sc;
+};
+
+/**
+ * Configure and start the components deployed in a virtual host.
+ */
+const failable<bool> virtualHostConfig(ServerConf& sc, request_rec* r) {
+ debug(httpd::serverName(sc.server), "modwiring::virtualHostConfig::serverName");
+ debug(httpd::serverName(r), "modwiring::virtualHostConfig::virtualHostName");
+
+ // Configure the wiring for the deployed components
+ debug(sc.contributionPath, "modwiring::virtualHostConfig::contributionPath");
+ debug(sc.compositeName, "modwiring::virtualHostConfig::compositeName");
+ confComponents(sc);
+ return true;
+}
+
+/**
+ * Translate an HTTP service or reference request and route it
+ * to the target component.
+ */
+int translate(request_rec *r) {
+ gc_scoped_pool pool(r->pool);
+
+ // No translation needed for a component request
+ if (!strncmp(r->uri, "/components/", 12))
+ return DECLINED;
+
+ // Get the server configuration
+ const ServerConf& sc = httpd::serverConf<ServerConf>(r, &mod_tuscany_wiring);
+
+ // Process dynamic virtual host configuration, if any
+ VirtualHostConf vhc(sc);
+ const bool hasv = hasCompositeConf(vhc.sc);
+ if (hasv) {
+ const failable<bool> cr = virtualHostConfig(vhc.sc, r);
+ if (!hasContent(cr))
+ return -1;
+ }
+
+ // Translate a component reference request
+ if (!strncmp(r->uri, "/references/", 12))
+ return translateReference(hasv? vhc.sc: sc, r);
+
+ // Translate a service request
+ return translateService(hasv? vhc.sc : sc, r);
+}
+
+/**
+ * HTTP request handler, redirect to a target component.
+ */
+int handler(request_rec *r) {
+ gc_scoped_pool pool(r->pool);
+ if(strcmp(r->handler, "mod_tuscany_wiring"))
+ return DECLINED;
+ httpdDebugRequest(r, "modwiring::handler::input");
+
+ // Do an internal redirect
+ if (r->filename == NULL || strncmp(r->filename, "/redirect:", 10) != 0)
+ return DECLINED;
+ debug(r->uri, "modwiring::handler::uri");
+ debug(r->filename, "modwiring::handler::filename");
+ debug(r->path_info, "modwiring::handler::path info");
+
+ if (r->args == NULL)
+ return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info)), r);
+ return httpd::internalRedirect(httpd::redirectURI(string(r->filename + 10), string(r->path_info), string(r->args)), r);
+}
+
+/**
* Called after all the configuration commands have been run.
* Process the server configuration and configure the wiring for the deployed components.
*/
const int postConfigMerge(const ServerConf& mainsc, server_rec* s) {
if (s == NULL)
return OK;
+ ostringstream sname;
+ debug(httpd::serverName(s), "modwiring::postConfigMerge::serverName");
ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_wiring);
sc.contributionPath = mainsc.contributionPath;
sc.compositeName = mainsc.compositeName;
+ sc.virtualHostContributionPath = mainsc.virtualHostContributionPath;
+ sc.virtualHostCompositeName = mainsc.virtualHostCompositeName;
sc.references = mainsc.references;
sc.services = mainsc.services;
return postConfigMerge(mainsc, s->next);
}
int postConfig(unused apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t *ptemp, server_rec *s) {
+ gc_scoped_pool pool(p);
+
// Count the calls to post config, skip the first one as
// postConfig is always called twice
const string k("tuscany::modwiring::postConfig");
@@ -308,6 +365,7 @@ int postConfig(unused apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t
// Configure the wiring for the deployed components
ServerConf& sc = httpd::serverConf<ServerConf>(s, &mod_tuscany_wiring);
+ debug(httpd::serverName(s), "modwiring::postConfig::serverName");
debug(sc.contributionPath, "modwiring::postConfig::contributionPath");
debug(sc.compositeName, "modwiring::postConfig::compositeName");
confComponents(sc);
@@ -319,9 +377,9 @@ int postConfig(unused apr_pool_t *p, unused apr_pool_t *plog, unused apr_pool_t
/**
* Child process initialization.
*/
-void childInit(apr_pool_t* p, server_rec* svr_rec) {
+void childInit(apr_pool_t* p, server_rec* s) {
gc_scoped_pool pool(p);
- ServerConf *conf = (ServerConf*)ap_get_module_config(svr_rec->module_config, &mod_tuscany_wiring);
+ ServerConf *conf = (ServerConf*)ap_get_module_config(s->module_config, &mod_tuscany_wiring);
if(conf == NULL) {
cerr << "[Tuscany] Due to one or more errors mod_tuscany_wiring loading failed. Causing apache to stop loading." << endl;
exit(APEXIT_CHILDFATAL);
@@ -343,6 +401,18 @@ const char *confComposite(cmd_parms *cmd, unused void *c, const char *arg) {
sc.compositeName = arg;
return NULL;
}
+const char *confVirtualContribution(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring);
+ sc.virtualHostContributionPath = arg;
+ return NULL;
+}
+const char *confVirtualComposite(cmd_parms *cmd, unused void *c, const char *arg) {
+ gc_scoped_pool pool(cmd->pool);
+ ServerConf& sc = httpd::serverConf<ServerConf>(cmd, &mod_tuscany_wiring);
+ sc.virtualHostCompositeName = arg;
+ return NULL;
+}
/**
* HTTP server module declaration.
@@ -350,6 +420,8 @@ const char *confComposite(cmd_parms *cmd, unused void *c, const char *arg) {
const command_rec commands[] = {
AP_INIT_TAKE1("SCAContribution", (const char*(*)())confContribution, NULL, RSRC_CONF, "SCA contribution location"),
AP_INIT_TAKE1("SCAComposite", (const char*(*)())confComposite, NULL, RSRC_CONF, "SCA composite location"),
+ AP_INIT_TAKE1("SCAVirtualContribution", (const char*(*)())confVirtualContribution, NULL, RSRC_CONF, "SCA virtual host contribution location"),
+ AP_INIT_TAKE1("SCAVirtualComposite", (const char*(*)())confVirtualComposite, NULL, RSRC_CONF, "SCA virtual host composite location"),
{NULL, NULL, NULL, 0, NO_ARGS, NULL}
};
diff --git a/sca-cpp/trunk/modules/server/server-conf b/sca-cpp/trunk/modules/server/server-conf
index 8ab6934d00..cff1ab1965 100755
--- a/sca-cpp/trunk/modules/server/server-conf
+++ b/sca-cpp/trunk/modules/server/server-conf
@@ -39,8 +39,9 @@ fi
cat >>$root/conf/httpd.conf <<EOF
# Support for SCA component wiring
LoadModule mod_tuscany_wiring $here/libmod_tuscany_wiring.so
-SCAWiringServerName $host:$port
+#SCAWiringServerName $host:$port
+# Serve HTTP binding JavaScript client code
Alias /js/tuscany-ref.js $here/htdocs/js/tuscany-ref.js
EOF
diff --git a/sca-cpp/trunk/samples/Makefile.am b/sca-cpp/trunk/samples/Makefile.am
index e2b74075f8..ff212b7eab 100644
--- a/sca-cpp/trunk/samples/Makefile.am
+++ b/sca-cpp/trunk/samples/Makefile.am
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
-SUBDIRS = store-scheme store-cpp store-python store-java store-gae store-sql store-nosql
+SUBDIRS = store-scheme store-cpp store-python store-java store-gae store-sql store-nosql store-vhost
sample_DATA = README
sampledir=$(prefix)/samples
diff --git a/sca-cpp/trunk/samples/store-python/Makefile.am b/sca-cpp/trunk/samples/store-python/Makefile.am
index fe389e7eed..7a38fa50f3 100644
--- a/sca-cpp/trunk/samples/store-python/Makefile.am
+++ b/sca-cpp/trunk/samples/store-python/Makefile.am
@@ -17,7 +17,7 @@
if WANT_PYTHON
-dist_sample_SCRIPTS = start stop ssl-start
+dist_sample_SCRIPTS = start stop ssl-start uec2-start
sampledir = $(prefix)/samples/store-python
nobase_dist_sample_DATA = currency-converter.py fruits-catalog.py shopping-cart.py store.py store.composite htdocs/*.html
diff --git a/sca-cpp/trunk/samples/store-vhost/Makefile.am b/sca-cpp/trunk/samples/store-vhost/Makefile.am
new file mode 100644
index 0000000000..61933afd35
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/Makefile.am
@@ -0,0 +1,28 @@
+# 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.
+
+if WANT_PYTHON
+
+dist_sample_SCRIPTS = start stop ssl-start uec2-start
+sampledir = $(prefix)/samples/store-vhost
+
+nobase_dist_sample_DATA = currency-converter.py fruits-catalog.py shopping-cart.py store.py store.composite htdocs/*.html htdocs/domains/joe/*.html htdocs/domains/jane/*.html
+
+dist_noinst_SCRIPTS = server-test
+#TESTS = server-test
+
+endif
diff --git a/sca-cpp/trunk/samples/store-vhost/currency-converter.py b/sca-cpp/trunk/samples/store-vhost/currency-converter.py
new file mode 100644
index 0000000000..2fded8f616
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/currency-converter.py
@@ -0,0 +1,29 @@
+# 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.
+
+# Currency converter implementation
+
+def convert(fr, to, amount):
+ if to == "EUR":
+ return amount * 0.70
+ return amount
+
+def symbol(currency):
+ if currency == "EUR":
+ return "E"
+ return "$"
+
diff --git a/sca-cpp/trunk/samples/store-vhost/fruits-catalog.py b/sca-cpp/trunk/samples/store-vhost/fruits-catalog.py
new file mode 100644
index 0000000000..2a6d726fdc
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/fruits-catalog.py
@@ -0,0 +1,30 @@
+# 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.
+
+# Catalog implementation
+
+def getcatalog(converter, currencyCode):
+ code = currencyCode()
+ def convert(price):
+ return converter("convert", "USD", code, price)
+ symbol = converter("symbol", code)
+ return (
+ (("'javaClass", "services.Item"), ("'name", "Apple"), ("'currencyCode", code), ("'currencySymbol", symbol), ("'price", convert(2.99))),
+ (("'javaClass", "services.Item"), ("'name", "Orange"), ("'currencyCode", code), ("'currencySymbol", symbol), ("'price", convert(3.55))),
+ (("'javaClass", "services.Item"), ("'name", "Pear"), ("'currencyCode", code), ("'currencySymbol", symbol), ("'price", convert(1.55)))
+ )
+
diff --git a/sca-cpp/trunk/samples/store-vhost/htdocs/domains/jane/index.html b/sca-cpp/trunk/samples/store-vhost/htdocs/domains/jane/index.html
new file mode 100644
index 0000000000..9fc3247e22
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/htdocs/domains/jane/index.html
@@ -0,0 +1,157 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<html>
+<head>
+<title>Store</title>
+
+<script type="text/javascript" src="/js/tuscany-ref.js"></script>
+
+<script type="text/javascript">
+var component = new tuscany.sca.Component("Store");
+
+//@Reference
+var catalog = new tuscany.sca.Reference("catalog");
+
+//@Reference
+var shoppingCart = new tuscany.sca.Reference("shoppingCart");
+
+//@Reference
+var shoppingTotal = new tuscany.sca.Reference("shoppingTotal");
+
+var catalogItems;
+
+function catalog_getcatalogResponse(items, exception) {
+ if (exception){
+ alert(exception.message);
+ return;
+ }
+ var catalog = "";
+ for (var i=0; i<items.length; i++) {
+ var item = items[i].name + ' - ' + items[i].price;
+ catalog += '<input name="items" type="checkbox" value="' +
+ item + '">' + item + ' <br>';
+ }
+ document.getElementById('catalog').innerHTML=catalog;
+ catalogItems = items;
+
+}
+
+function shoppingCart_getResponse(feed) {
+ if (feed != null) {
+ var entries = feed.getElementsByTagName("entry");
+ var list = "";
+ for (var i=0; i<entries.length; i++) {
+ var content = entries[i].getElementsByTagName("content")[0];
+ var name = content.getElementsByTagName("name")[0].firstChild.nodeValue;
+ var price = content.getElementsByTagName("price")[0].firstChild.nodeValue;
+ list += name + ' - ' + price + ' <br>';
+ }
+ document.getElementById("shoppingCart").innerHTML = list;
+
+ shoppingTotal.apply("gettotal", shoppingTotal_gettotalResponse);
+ }
+}
+
+function shoppingTotal_gettotalResponse(total, exception) {
+ if (exception) {
+ alert(exception.message);
+ return;
+ }
+ document.getElementById('total').innerHTML = total;
+}
+
+function shoppingCart_postResponse(entry) {
+ shoppingCart.get("", shoppingCart_getResponse);
+}
+
+function addToCart() {
+ var items = document.catalogForm.items;
+ var j = 0;
+ for (var i=0; i<items.length; i++)
+ if (items[i].checked) {
+ var entry = '<entry xmlns="http://www.w3.org/2005/Atom"><title type="text">Item</title><content type="application/xml">' +
+ '<item>' +
+ '<javaClass>' + catalogItems[i].javaClass + '</javaClass>' +
+ '<name>' + catalogItems[i].name + '</name>' +
+ '<currencyCode>' + catalogItems[i].currencyCode + '</currencyCode>' +
+ '<currencySymbol>' + catalogItems[i].currencySymbol + '</currencySymbol>' +
+ '<price>' + catalogItems[i].price + '</price>' +
+ '</item>' +
+ '</content></entry>';
+ shoppingCart.post(entry, shoppingCart_postResponse);
+ items[i].checked = false;
+ }
+}
+function checkoutCart() {
+ document.getElementById('store').innerHTML='<h2>' +
+ 'Thanks for Shopping With Us!</h2>'+
+ '<h2>Your Order</h2>'+
+ '<form name="orderForm">'+
+ document.getElementById('shoppingCart').innerHTML+
+ '<br>'+
+ document.getElementById('total').innerHTML+
+ '<br>'+
+ '<br>'+
+ '<input type="submit" value="Continue Shopping">'+
+ '</form>';
+ shoppingCart.del("", null);
+}
+function deleteCart() {
+ shoppingCart.del("", null);
+ document.getElementById('shoppingCart').innerHTML = "";
+ document.getElementById('total').innerHTML = "";
+}
+
+function init() {
+ try {
+ catalog.apply("getcatalog", catalog_getcatalogResponse);
+ shoppingCart.get("", shoppingCart_getResponse);
+ } catch(e){
+ alert(e);
+ }
+}
+</script>
+
+</head>
+
+<body onload="init()">
+<h1>Jane's Store</h1>
+<div id="store">
+<h2>Catalog</h2>
+<form name="catalogForm">
+<div id="catalog" ></div>
+<br>
+<input type="button" onClick="addToCart()" value="Add to Cart">
+</form>
+<br>
+
+<h2>Your Shopping Cart</h2>
+<form name="shoppingCartForm">
+<div id="shoppingCart"></div>
+<br>
+<div id="total"></div>
+<br>
+<input type="button" onClick="checkoutCart()" value="Checkout">
+<input type="button" onClick="deleteCart()" value="Empty">
+<a href="shoppingCart/">(feed)</a>
+</form>
+</div>
+
+</body>
+</html>
diff --git a/sca-cpp/trunk/samples/store-vhost/htdocs/domains/joe/index.html b/sca-cpp/trunk/samples/store-vhost/htdocs/domains/joe/index.html
new file mode 100644
index 0000000000..e8b722d0d6
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/htdocs/domains/joe/index.html
@@ -0,0 +1,157 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<html>
+<head>
+<title>Store</title>
+
+<script type="text/javascript" src="/js/tuscany-ref.js"></script>
+
+<script type="text/javascript">
+var component = new tuscany.sca.Component("Store");
+
+//@Reference
+var catalog = new tuscany.sca.Reference("catalog");
+
+//@Reference
+var shoppingCart = new tuscany.sca.Reference("shoppingCart");
+
+//@Reference
+var shoppingTotal = new tuscany.sca.Reference("shoppingTotal");
+
+var catalogItems;
+
+function catalog_getcatalogResponse(items, exception) {
+ if (exception){
+ alert(exception.message);
+ return;
+ }
+ var catalog = "";
+ for (var i=0; i<items.length; i++) {
+ var item = items[i].name + ' - ' + items[i].price;
+ catalog += '<input name="items" type="checkbox" value="' +
+ item + '">' + item + ' <br>';
+ }
+ document.getElementById('catalog').innerHTML=catalog;
+ catalogItems = items;
+
+}
+
+function shoppingCart_getResponse(feed) {
+ if (feed != null) {
+ var entries = feed.getElementsByTagName("entry");
+ var list = "";
+ for (var i=0; i<entries.length; i++) {
+ var content = entries[i].getElementsByTagName("content")[0];
+ var name = content.getElementsByTagName("name")[0].firstChild.nodeValue;
+ var price = content.getElementsByTagName("price")[0].firstChild.nodeValue;
+ list += name + ' - ' + price + ' <br>';
+ }
+ document.getElementById("shoppingCart").innerHTML = list;
+
+ shoppingTotal.apply("gettotal", shoppingTotal_gettotalResponse);
+ }
+}
+
+function shoppingTotal_gettotalResponse(total, exception) {
+ if (exception) {
+ alert(exception.message);
+ return;
+ }
+ document.getElementById('total').innerHTML = total;
+}
+
+function shoppingCart_postResponse(entry) {
+ shoppingCart.get("", shoppingCart_getResponse);
+}
+
+function addToCart() {
+ var items = document.catalogForm.items;
+ var j = 0;
+ for (var i=0; i<items.length; i++)
+ if (items[i].checked) {
+ var entry = '<entry xmlns="http://www.w3.org/2005/Atom"><title type="text">Item</title><content type="application/xml">' +
+ '<item>' +
+ '<javaClass>' + catalogItems[i].javaClass + '</javaClass>' +
+ '<name>' + catalogItems[i].name + '</name>' +
+ '<currencyCode>' + catalogItems[i].currencyCode + '</currencyCode>' +
+ '<currencySymbol>' + catalogItems[i].currencySymbol + '</currencySymbol>' +
+ '<price>' + catalogItems[i].price + '</price>' +
+ '</item>' +
+ '</content></entry>';
+ shoppingCart.post(entry, shoppingCart_postResponse);
+ items[i].checked = false;
+ }
+}
+function checkoutCart() {
+ document.getElementById('store').innerHTML='<h2>' +
+ 'Thanks for Shopping With Us!</h2>'+
+ '<h2>Your Order</h2>'+
+ '<form name="orderForm">'+
+ document.getElementById('shoppingCart').innerHTML+
+ '<br>'+
+ document.getElementById('total').innerHTML+
+ '<br>'+
+ '<br>'+
+ '<input type="submit" value="Continue Shopping">'+
+ '</form>';
+ shoppingCart.del("", null);
+}
+function deleteCart() {
+ shoppingCart.del("", null);
+ document.getElementById('shoppingCart').innerHTML = "";
+ document.getElementById('total').innerHTML = "";
+}
+
+function init() {
+ try {
+ catalog.apply("getcatalog", catalog_getcatalogResponse);
+ shoppingCart.get("", shoppingCart_getResponse);
+ } catch(e){
+ alert(e);
+ }
+}
+</script>
+
+</head>
+
+<body onload="init()">
+<h1>Joe's Store</h1>
+<div id="store">
+<h2>Catalog</h2>
+<form name="catalogForm">
+<div id="catalog" ></div>
+<br>
+<input type="button" onClick="addToCart()" value="Add to Cart">
+</form>
+<br>
+
+<h2>Your Shopping Cart</h2>
+<form name="shoppingCartForm">
+<div id="shoppingCart"></div>
+<br>
+<div id="total"></div>
+<br>
+<input type="button" onClick="checkoutCart()" value="Checkout">
+<input type="button" onClick="deleteCart()" value="Empty">
+<a href="shoppingCart/">(feed)</a>
+</form>
+</div>
+
+</body>
+</html>
diff --git a/sca-cpp/trunk/samples/store-vhost/htdocs/index.html b/sca-cpp/trunk/samples/store-vhost/htdocs/index.html
new file mode 100644
index 0000000000..c27e54f753
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/htdocs/index.html
@@ -0,0 +1,157 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<html>
+<head>
+<title>Store</title>
+
+<script type="text/javascript" src="/js/tuscany-ref.js"></script>
+
+<script type="text/javascript">
+var component = new tuscany.sca.Component("Store");
+
+//@Reference
+var catalog = new tuscany.sca.Reference("catalog");
+
+//@Reference
+var shoppingCart = new tuscany.sca.Reference("shoppingCart");
+
+//@Reference
+var shoppingTotal = new tuscany.sca.Reference("shoppingTotal");
+
+var catalogItems;
+
+function catalog_getcatalogResponse(items, exception) {
+ if (exception){
+ alert(exception.message);
+ return;
+ }
+ var catalog = "";
+ for (var i=0; i<items.length; i++) {
+ var item = items[i].name + ' - ' + items[i].price;
+ catalog += '<input name="items" type="checkbox" value="' +
+ item + '">' + item + ' <br>';
+ }
+ document.getElementById('catalog').innerHTML=catalog;
+ catalogItems = items;
+
+}
+
+function shoppingCart_getResponse(feed) {
+ if (feed != null) {
+ var entries = feed.getElementsByTagName("entry");
+ var list = "";
+ for (var i=0; i<entries.length; i++) {
+ var content = entries[i].getElementsByTagName("content")[0];
+ var name = content.getElementsByTagName("name")[0].firstChild.nodeValue;
+ var price = content.getElementsByTagName("price")[0].firstChild.nodeValue;
+ list += name + ' - ' + price + ' <br>';
+ }
+ document.getElementById("shoppingCart").innerHTML = list;
+
+ shoppingTotal.apply("gettotal", shoppingTotal_gettotalResponse);
+ }
+}
+
+function shoppingTotal_gettotalResponse(total, exception) {
+ if (exception) {
+ alert(exception.message);
+ return;
+ }
+ document.getElementById('total').innerHTML = total;
+}
+
+function shoppingCart_postResponse(entry) {
+ shoppingCart.get("", shoppingCart_getResponse);
+}
+
+function addToCart() {
+ var items = document.catalogForm.items;
+ var j = 0;
+ for (var i=0; i<items.length; i++)
+ if (items[i].checked) {
+ var entry = '<entry xmlns="http://www.w3.org/2005/Atom"><title type="text">Item</title><content type="application/xml">' +
+ '<item>' +
+ '<javaClass>' + catalogItems[i].javaClass + '</javaClass>' +
+ '<name>' + catalogItems[i].name + '</name>' +
+ '<currencyCode>' + catalogItems[i].currencyCode + '</currencyCode>' +
+ '<currencySymbol>' + catalogItems[i].currencySymbol + '</currencySymbol>' +
+ '<price>' + catalogItems[i].price + '</price>' +
+ '</item>' +
+ '</content></entry>';
+ shoppingCart.post(entry, shoppingCart_postResponse);
+ items[i].checked = false;
+ }
+}
+function checkoutCart() {
+ document.getElementById('store').innerHTML='<h2>' +
+ 'Thanks for Shopping With Us!</h2>'+
+ '<h2>Your Order</h2>'+
+ '<form name="orderForm">'+
+ document.getElementById('shoppingCart').innerHTML+
+ '<br>'+
+ document.getElementById('total').innerHTML+
+ '<br>'+
+ '<br>'+
+ '<input type="submit" value="Continue Shopping">'+
+ '</form>';
+ shoppingCart.del("", null);
+}
+function deleteCart() {
+ shoppingCart.del("", null);
+ document.getElementById('shoppingCart').innerHTML = "";
+ document.getElementById('total').innerHTML = "";
+}
+
+function init() {
+ try {
+ catalog.apply("getcatalog", catalog_getcatalogResponse);
+ shoppingCart.get("", shoppingCart_getResponse);
+ } catch(e){
+ alert(e);
+ }
+}
+</script>
+
+</head>
+
+<body onload="init()">
+<h1>Store</h1>
+<div id="store">
+<h2>Catalog</h2>
+<form name="catalogForm">
+<div id="catalog" ></div>
+<br>
+<input type="button" onClick="addToCart()" value="Add to Cart">
+</form>
+<br>
+
+<h2>Your Shopping Cart</h2>
+<form name="shoppingCartForm">
+<div id="shoppingCart"></div>
+<br>
+<div id="total"></div>
+<br>
+<input type="button" onClick="checkoutCart()" value="Checkout">
+<input type="button" onClick="deleteCart()" value="Empty">
+<a href="shoppingCart/">(feed)</a>
+</form>
+</div>
+
+</body>
+</html>
diff --git a/sca-cpp/trunk/samples/store-vhost/server-test b/sca-cpp/trunk/samples/store-vhost/server-test
new file mode 100755
index 0000000000..26efd14c0e
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/server-test
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+echo "Testing..."
+here=`readlink -f $0`; here=`dirname $here`
+curl_prefix=`cat $here/../../modules/http/curl.prefix`
+
+# Setup
+./start
+sleep 2
+
+# For this test to work, add the following line to your etc/hosts
+127.0.0.1 jane.store.com joe.store.com
+
+# Test HTTP GET
+$curl_prefix/bin/curl http://jane.store.com:8090/ 2>/dev/null >tmp/jane.html
+diff tmp/jane.html htdocs/domains/jane/index.html
+rc=$?
+
+# Cleanup
+./stop
+sleep 2
+
+if [ "$rc" = "0" ]; then
+ echo "OK"
+fi
+return $rc
diff --git a/sca-cpp/trunk/samples/store-vhost/shopping-cart.py b/sca-cpp/trunk/samples/store-vhost/shopping-cart.py
new file mode 100644
index 0000000000..405adb85bf
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/shopping-cart.py
@@ -0,0 +1,75 @@
+# 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.
+
+# Shopping cart implementation
+import uuid
+import sys
+
+cartId = "1234"
+
+# Get the shopping cart from the cache
+# Return an empty cart if not found
+def getcart(id, cache):
+ cart = cache("get", (id,))
+ if cart is None:
+ return ()
+ return cart
+
+# Post a new item to the cart, create a new cart if necessary
+def post(collection, item, cache):
+ id = str(uuid.uuid1())
+ cart = ((item[0], id, item[2]),) + getcart(cartId, cache)
+ cache("put", (cartId,), cart)
+ return (id,)
+
+
+# Find an item in the cart
+def find(id, cart):
+ if cart == ():
+ return ("Item", "0", ())
+ elif id == cart[0][1]:
+ return cart[0]
+ else:
+ return find(id, cart[1:])
+
+# Get items from the cart
+def get(id, cache):
+ if id == ():
+ return ("Your Cart", cartId) + getcart(cartId, cache)
+ return find(id[0], getcart(cartId, cache))
+
+# Delete items from the cart
+def delete(id, cache):
+ if id == ():
+ return cache("delete", (cartId,))
+ return True
+
+# Return the price of an item
+def price(item):
+ return float(filter(lambda x: x[0] == "'price", item[2])[0][1])
+
+# Sum the prices of a list of items
+def sum(items):
+ if items == ():
+ return 0
+ return price(items[0]) + sum(items[1:])
+
+# Return the total price of the items in the cart
+def gettotal(cache):
+ cart = getcart(cartId, cache)
+ return sum(cart)
+
diff --git a/sca-cpp/trunk/samples/store-vhost/ssl-start b/sca-cpp/trunk/samples/store-vhost/ssl-start
new file mode 100755
index 0000000000..1e70d6edfb
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/ssl-start
@@ -0,0 +1,34 @@
+#!/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.
+
+../../modules/http/httpd-ca-conf tmp localhost
+../../modules/http/httpd-cert-conf tmp localhost
+../../modules/http/httpd-conf tmp localhost 8090 htdocs
+../../modules/http/httpd-ssl-conf tmp localhost 8453 htdocs
+../../modules/server/server-conf tmp
+../../modules/python/python-conf tmp
+cat >>tmp/conf/httpd.conf <<EOF
+# Configure SCA Composite
+SCAContribution `pwd`/
+SCAComposite store.composite
+
+EOF
+
+../../components/cache/memcached-start
+../../modules/http/httpd-start tmp
diff --git a/sca-cpp/trunk/samples/store-vhost/start b/sca-cpp/trunk/samples/store-vhost/start
new file mode 100755
index 0000000000..6cbea136b3
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/start
@@ -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.
+
+../../modules/http/httpd-conf tmp localhost 8090 htdocs vhost
+../../modules/server/server-conf tmp
+../../modules/python/python-conf tmp
+cat >>tmp/conf/httpd.conf <<EOF
+# Configure SCA Composite for mass dynamic virtual Hosting
+SCAVirtualContribution `pwd`/
+SCAVirtualComposite store.composite
+
+EOF
+
+../../components/cache/memcached-start
+../../modules/http/httpd-start tmp
diff --git a/sca-cpp/trunk/samples/store-vhost/stop b/sca-cpp/trunk/samples/store-vhost/stop
new file mode 100755
index 0000000000..a59273b8ed
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/stop
@@ -0,0 +1,21 @@
+#!/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.
+
+../../modules/http/httpd-stop tmp
+../../components/cache/memcached-stop
diff --git a/sca-cpp/trunk/samples/store-vhost/store.composite b/sca-cpp/trunk/samples/store-vhost/store.composite
new file mode 100644
index 0000000000..045ebe6ec5
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/store.composite
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912"
+ xmlns:t="http://tuscany.apache.org/xmlns/sca/1.1"
+ targetNamespace="http://store"
+ name="store">
+
+ <component name="Store">
+ <t:implementation.python script="store.py"/>
+ <service name="Widget">
+ <t:binding.http uri="store"/>
+ </service>
+ <reference name="catalog" target="Catalog"/>
+ <reference name="shoppingCart" target="ShoppingCart/Cart"/>
+ <reference name="shoppingTotal" target="ShoppingCart/Total"/>
+ </component>
+
+ <component name="Catalog">
+ <t:implementation.python script="fruits-catalog.py"/>
+ <property name="currencyCode">USD</property>
+ <service name="Catalog">
+ <t:binding.jsonrpc uri="catalog"/>
+ </service>
+ <reference name="currencyConverter" target="CurrencyConverter"/>
+ </component>
+
+ <component name="ShoppingCart">
+ <t:implementation.python script="shopping-cart.py"/>
+ <service name="ShoppingCart">
+ <t:binding.atom uri="shoppingCart"/>
+ </service>
+ <service name="Total">
+ <t:binding.jsonrpc uri="total"/>
+ </service>
+ <reference name="cache" target="Cache"/>
+ </component>
+
+ <component name="CurrencyConverter">
+ <t:implementation.python script="currency-converter.py"/>
+ <service name="CurrencyConverter">
+ <t:binding.jsonrpc uri="currencyConverter"/>
+ </service>
+ </component>
+
+ <component name="Cache">
+ <implementation.cpp path="../../components/cache" library="libmemcache"/>
+ <service name="Cache">
+ <t:binding.atom uri="cache"/>
+ </service>
+ </component>
+
+</composite>
diff --git a/sca-cpp/trunk/samples/store-vhost/store.py b/sca-cpp/trunk/samples/store-vhost/store.py
new file mode 100644
index 0000000000..0b4e0f72fd
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/store.py
@@ -0,0 +1,40 @@
+# 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.
+
+# Store implementation
+
+def post(item, catalog, shoppingCart, shoppingTotal):
+ return shoppingCart("post", item)
+
+def getall(catalog, shoppingCart, shoppingTotal):
+ return shoppingCart("getall")
+
+def get(id, catalog, shoppingCart, shoppingTotal):
+ return shoppingCart("get", id)
+
+def getcatalog(catalog, shoppingCart, shoppingTotal):
+ return catalog("getcatalog")
+
+def gettotal(catalog, shoppingCart, shoppingTotal):
+ return shoppingCart("gettotal")
+
+def deleteall(catalog, shoppingCart, shoppingTotal):
+ return shoppingCart("deleteall")
+
+def delete(id, catalog, shoppingCart, shoppingTotal):
+ return shoppingCart("delete", id)
+
diff --git a/sca-cpp/trunk/samples/store-vhost/uec2-start b/sca-cpp/trunk/samples/store-vhost/uec2-start
new file mode 100755
index 0000000000..2e59815637
--- /dev/null
+++ b/sca-cpp/trunk/samples/store-vhost/uec2-start
@@ -0,0 +1,46 @@
+#!/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.
+
+# Pass your EC2 public host name
+if [ "$1" != "" ]; then
+ host=$1
+else
+ host="localhost"
+fi
+
+# Ports 80, 443, 8090, 8453 need to be open
+sudo ../../ubuntu/ip-redirect 80 8090
+sudo ../../ubuntu/ip-redirect 443 8453
+
+../../modules/http/httpd-ca-conf tmp $host
+../../modules/http/httpd-cert-conf tmp $host
+../../modules/http/httpd-conf tmp $host 8090/80 htdocs
+../../modules/http/httpd-ssl-conf tmp $host 8453/443 htdocs
+../../modules/server/server-conf tmp
+../../modules/python/python-conf tmp
+cat >>tmp/conf/httpd.conf <<EOF
+# Configure SCA Composite
+SCAContribution `pwd`/
+SCAComposite store.composite
+
+EOF
+
+../../components/cache/memcached-start
+../../modules/http/httpd-start tmp
+