summaryrefslogtreecommitdiffstats
path: root/cpp/sca/modules/http
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--cpp/sca/modules/http/Makefile.am (renamed from cpp/sca/modules/httpd/Makefile.am)10
-rw-r--r--cpp/sca/modules/http/curl-test.cpp73
-rw-r--r--cpp/sca/modules/http/curl.hpp150
-rwxr-xr-xcpp/sca/modules/http/http-test34
-rwxr-xr-xcpp/sca/modules/http/httpd-test38
-rw-r--r--cpp/sca/modules/http/mod.cpp (renamed from cpp/sca/modules/httpd/mod.cpp)334
-rw-r--r--cpp/sca/modules/http/test-conf/mime.types607
-rw-r--r--cpp/sca/modules/http/test-htdocs/index.html21
8 files changed, 1161 insertions, 106 deletions
diff --git a/cpp/sca/modules/httpd/Makefile.am b/cpp/sca/modules/http/Makefile.am
index 13cb3045f6..d34da0822f 100644
--- a/cpp/sca/modules/httpd/Makefile.am
+++ b/cpp/sca/modules/http/Makefile.am
@@ -15,10 +15,18 @@
# specific language governing permissions and limitations
# under the License.
+noinst_PROGRAMS = curl-test
+
libdir=$(prefix)/lib
lib_LTLIBRARIES = libmod_tuscany.la
-INCLUDES = -I. -I$(top_builddir)/kernel -I${HTTPD_INCLUDE} -I${APR_INCLUDE} -I${LIBXML2_INCLUDE} -I${LIBMOZJS_INCLUDE}
+INCLUDES = -I. -I$(top_builddir)/kernel -I${HTTPD_INCLUDE} -I${APR_INCLUDE} -I${LIBXML2_INCLUDE} -I${LIBMOZJS_INCLUDE} -I${CURL_INCLUDE}
libmod_tuscany_la_SOURCES = mod.cpp
libmod_tuscany_la_LIBADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${LIBMOZJS_LIB} -lmozjs
+
+curl_test_SOURCES = curl-test.cpp
+curl_test_LDADD = -lpthread -L${LIBXML2_LIB} -lxml2 -L${CURL_LIB} -lcurl
+
+TESTS = httpd-test http-test
+
diff --git a/cpp/sca/modules/http/curl-test.cpp b/cpp/sca/modules/http/curl-test.cpp
new file mode 100644
index 0000000000..4e23a036da
--- /dev/null
+++ b/cpp/sca/modules/http/curl-test.cpp
@@ -0,0 +1,73 @@
+/*
+ * 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 JSON data conversion functions.
+ */
+
+#include <assert.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include "slist.hpp"
+#include "curl.hpp"
+
+namespace tuscany {
+namespace http {
+
+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) {
+ (*os) << s;
+ return os;
+}
+
+const bool testCURL() {
+ {
+ std::ostringstream os;
+ const failable<std::ostringstream*, int> r = get<std::ostringstream*>(curlWriter, &os, "http://localhost:9091");
+ assert(hasValue(r));
+ assert(contains(os.str(), "It works"));
+ }
+ {
+ std::ostringstream os;
+ const failable<list<std::string>, int> r = get("http://localhost:9091");
+ assert(hasValue(r));
+ write(r, os);
+ assert(contains(os.str(), "It works"));
+ }
+ return true;
+}
+
+}
+}
+
+int main() {
+ std::cout << "Testing..." << std::endl;
+
+ tuscany::http::testCURL();
+
+ std::cout << "OK" << std::endl;
+
+ return 0;
+}
diff --git a/cpp/sca/modules/http/curl.hpp b/cpp/sca/modules/http/curl.hpp
new file mode 100644
index 0000000000..2ea23010cb
--- /dev/null
+++ b/cpp/sca/modules/http/curl.hpp
@@ -0,0 +1,150 @@
+/*
+ * 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_curl_hpp
+#define tuscany_curl_hpp
+
+/**
+ * CURL HTTP client functions.
+ */
+
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+#include <string>
+#include "list.hpp"
+#include "value.hpp"
+#include "element.hpp"
+#include "monad.hpp"
+
+namespace tuscany {
+namespace http {
+
+/**
+ * CURL library context, one per process.
+ */
+class CURLContext {
+public:
+ CURLContext() {
+ curl_global_init(CURL_GLOBAL_ALL);
+ }
+ ~CURLContext() {
+ curl_global_cleanup();
+ }
+};
+
+CURLContext curlContext;
+
+/**
+ * Represents a CURL session handle.
+ */
+class CURLHandle {
+public:
+ CURLHandle() : h(curl_easy_init()) {
+ }
+ ~CURLHandle() {
+ curl_easy_cleanup(h);
+ }
+
+ operator CURL*() const {
+ return h;
+ }
+private:
+ CURL* h;
+};
+
+/**
+ * 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) {
+ }
+ const lambda<R(R, std::string)> reduce;
+ R accum;
+};
+
+/**
+ * Called by CURL to write received data.
+ */
+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));
+ return realsize;
+}
+
+/**
+ * Called by CURL to write received header data.
+ */
+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));
+ return realsize;
+}
+
+/**
+ * HTTP GET, get a resource from a URL.
+ */
+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);
+
+ // 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_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
+ const CURLcode rc = curl_easy_perform(ch);
+ if (rc)
+ return rc;
+
+ // Return the HTTP return code or content
+ long httprc;
+ curl_easy_getinfo (ch, CURLINFO_RESPONSE_CODE, &httprc);
+ if (httprc != 200)
+ return httprc;
+ return wcx.accum;
+}
+
+/**
+ * HTTP GET, get a list of values representing a resource from a URL.
+ */
+const list<std::string> writeStringList(const list<std::string>& listSoFar, const std::string& s) {
+ return cons(s, listSoFar);
+}
+
+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));
+}
+
+}
+}
+
+#endif /* tuscany_curl_hpp */
diff --git a/cpp/sca/modules/http/http-test b/cpp/sca/modules/http/http-test
new file mode 100755
index 0000000000..b0dd21cbd1
--- /dev/null
+++ b/cpp/sca/modules/http/http-test
@@ -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.
+
+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
+EOF
+mkdir -p tmp/logs
+apachectl -k start -d `pwd`/tmp
+sleep 1
+./curl-test
+rc=$?
+apachectl -k stop -d `pwd`/tmp
+return $rc
diff --git a/cpp/sca/modules/http/httpd-test b/cpp/sca/modules/http/httpd-test
new file mode 100755
index 0000000000..3e4c5f82de
--- /dev/null
+++ b/cpp/sca/modules/http/httpd-test
@@ -0,0 +1,38 @@
+#!/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..."
+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
+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
+rc=$?
+apachectl -k stop -d `pwd`/tmp
+if [ "$rc" = "0" ]; then
+ echo "OK"
+fi
+return $rc
diff --git a/cpp/sca/modules/httpd/mod.cpp b/cpp/sca/modules/http/mod.cpp
index 81e12934e7..88891a668c 100644
--- a/cpp/sca/modules/httpd/mod.cpp
+++ b/cpp/sca/modules/http/mod.cpp
@@ -52,6 +52,7 @@
#include "value.hpp"
#include "element.hpp"
#include "monad.hpp"
+#include "../atom/atom.hpp"
#include "../json/json.hpp"
#include "../eval/driver.hpp"
@@ -60,6 +61,7 @@ extern module AP_MODULE_DECLARE_DATA mod_tuscany;
}
namespace tuscany {
+namespace httpd {
/**
* Server configuration.
@@ -106,13 +108,36 @@ const std::string implementation(request_rec* r) {
/**
* Returns an HTTP request path as a list of strings.
*/
-const list<std::string> path(const request_rec* r) {
+const list<std::string> pathTokens(const request_rec* r) {
const char* p = r->path_info;
if (p == NULL || p[0] == '\0')
return list<std::string>();
return tokenize("/", p + 1);
}
+const list<value> pathValues(const list<std::string>& l) {
+ if (isNil(l))
+ return list<value>();
+ return cons<value>(car(l), pathValues(cdr(l)));
+}
+
+const list<value> path(const request_rec* r) {
+ return pathValues(pathTokens(r));
+}
+
+/**
+ * Return the content type of a request.
+ */
+const char* optional(const char* s) {
+ if (s == NULL)
+ return "(null)";
+ return s;
+}
+
+const std::string contentType(const request_rec* r) {
+ return optional(apr_table_get(r->headers_in, "Content-Type"));
+}
+
/**
* Log HTTP request info to standard out for now, for debugging purposes.
*/
@@ -123,12 +148,6 @@ int logHeader(void* r, const char* key, const char* value) {
return 1;
}
-const char* optional(const char* s) {
- if (s == NULL)
- return "(null)";
- return s;
-}
-
const bool logRequest(request_rec* r) {
std::cout << "mod-tuscany..." << std::endl;
std::cout << "tuscany home: " << home(r) << std::endl;
@@ -137,23 +156,49 @@ const bool logRequest(request_rec* r) {
std::cout << "protocol: " << optional(r->protocol) << std::endl;
std::cout << "method: " << optional(r->method) << std::endl;
std::cout << "method number: " << r->method_number << std::endl;
- std::cout << "content type: " << optional(apr_table_get(r->headers_in, "Content-Type")) << std::endl;
+ std::cout << "content type: " << contentType(r) << std::endl;
std::cout << "content encoding: " << optional(r->content_encoding) << std::endl;
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 << "path: " << path(r) << std::endl;
+ std::cout << "path: " << pathTokens(r) << std::endl;
std::cout << "args: " << optional(r->args) << std::endl;
std::cout.flush();
return true;
}
-const value evalLoop(std::istream& is, const value& req, Env& globalEnv) {
- value in = read(is);
+/**
+ * Evaluate an expression against a component implementation.
+ */
+const value evalExprLoop(std::istream& is, const value& req, eval::Env& globalEnv) {
+ value in = eval::read(is);
if (isNil(in))
- return eval(req, globalEnv);
- eval(in, globalEnv);
- return evalLoop(is, req, globalEnv);
+ return eval::evalApply(req, globalEnv);
+ eval::evalApply(in, globalEnv);
+ return evalExprLoop(is, req, globalEnv);
+}
+
+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);
+
+ // 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;
+
+ // Evaluate the expr
+ std::cout<< "expr: " << expr << std::endl;
+ std::cout.flush();
+ const value val = evalExprLoop(is, expr, globalEnv);
+ std::cout<< "val: " << val << std::endl;
+ std::cout.flush();
+ if (isNil(val))
+ return HTTP_INTERNAL_SERVER_ERROR;
+ return val;
}
/**
@@ -178,67 +223,133 @@ const list<list<value> > queryArgs(const request_rec* r) {
const list<value> queryParams(list<list<value> > a) {
if (isNil(a))
return list<value>();
- if (car(a) == value("id") || car(a) == value("method"))
+ const list<value> p = car(a);
+ if (car(p) == value("id") || car(p) == value("method"))
return queryParams(cdr(a));
- return cons(cadr(car(a)), queryParams(cdr(a)));
+ return cons(cadr(p), queryParams(cdr(a)));
}
/**
- * Handle an HTTP GET request.
+ * Convert a value to a JSON result.
*/
-const int get(request_rec* r) {
-
- // Setup the script evaluator
- Env globalEnv = setupEnvironment();
- std::ostringstream nullos;
- setupEvalOut(nullos);
-
- // Open the component implementation
- const std::string impl = contribution(r) + implementation(r);
- std::ifstream is(impl.c_str(), std::ios_base::in);
- if (is.fail() || is.bad())
- return HTTP_NOT_FOUND;
+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);
+}
- // Extract the request id, method and params from the query string
- const list<list<value> > args = queryArgs(r);
- const value id = cadr(assoc(value("id"), args));
- const value method = std::string(cadr(assoc(value("method"), args))).c_str();
- const list<value> params = queryParams(args);
+/**
+ * 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))))));
+}
- // Build expr to evaluate
- const value expr = cons<value>(method, params);
- std::cout<< "expr: " << expr << std::endl;
- std::cout.flush();
+/**
+ * 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)));
+}
- // Evaluate the expr
- const tuscany::value val = evalLoop(is, expr, globalEnv);
- if (isNil(val))
+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;
- std::cout<< "val: " << val << std::endl;
- std::cout.flush();
+ return list<std::string>(ls);
+}
- // Convert the expr value to JSON
- const JSONContext cx;
- failable<list<std::string>, std::string> jsval = writeJSON(cx, mklist<value>(mklist<value>("id", id), mklist<value>("result", val)));
- if (!hasValue(jsval))
+/**
+ * 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);
+}
- // Send the response
- ap_set_content_type(r, "application/json-rpc");
+/**
+ * Write an HTTP result.
+ */
+const int writeResult(const failable<list<std::string>, int> ls, const std::string& ct, request_rec* r) {
+ if (!hasValue(ls))
+ return ls;
std::ostringstream os;
- write(jsval, os);
- std::string sval = os.str();
- std::string etag(ap_md5(r->pool, (const unsigned char*)sval.c_str()));
+ write(ls, os);
+ std::cout<< "content: " << 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");
- if (match != NULL && etag == match)
- r->status = HTTP_NOT_MODIFIED;
apr_table_setn(r->headers_out, "ETag", etag.c_str());
- ap_rputs(sval.c_str(), r);
-
+ if (match != NULL && etag == match) {
+ r->status = HTTP_NOT_MODIFIED;
+ return OK;
+ }
+ ap_set_content_type(r, ct.c_str());
+ ap_rputs(std::string(os.str()).c_str(), r);
return OK;
}
/**
+ * Handle an HTTP GET.
+ */
+const int get(request_rec* r) {
+
+ // Inspect the query string
+ const list<list<value> > args = queryArgs(r);
+ const list<value> ia = assoc(value("id"), args);
+ const list<value> ma = assoc(value("method"), args);
+
+ // Evaluate a JSON-RPC request and return a JSON result
+ if (!isNil(ia) && !isNil(ma)) {
+
+ // Extract the request id, method and params
+ const value id = cadr(ia);
+ const value method = 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));
+
+ // Return JSON result
+ json::JSONContext cx;
+ return writeResult(jsonResult(cx, id, val), "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);
+ }
+
+ // 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);
+
+}
+
+/**
* Read the content of a POST.
*/
const list<std::string> read(request_rec* r) {
@@ -259,71 +370,83 @@ const list<list<value> > postArgs(list<value> a) {
return cons(l, postArgs(cdr(a)));
}
+const char* url(const std::string& loc, request_rec* r) {
+ std::string u = r->uri;
+ u.append("/");
+ u.append(loc);
+ return ap_construct_url(r->pool, u.c_str(), r);
+}
+
/**
- * Handle an HTTP POST request.
+ * Convert an ATOM entry to a value.
*/
-const int post(request_rec* r) {
-
- // Setup the script evaluator
- Env globalEnv = setupEnvironment();
- std::ostringstream nullos;
- setupEvalOut(nullos);
-
- // Open the component implementation
- const std::string impl = contribution(r) + implementation(r);
- std::ifstream is(impl.c_str(), std::ios_base::in);
- if (is.fail() || is.bad())
- return HTTP_NOT_FOUND;
+const value feedEntry(const list<value> e) {
+ const list<value> v = elementsToValues(mklist<value>(caddr(e)));
+ return cons(car(e), mklist<value>(cadr(e), cdr<value>(car(v))));
+}
- // Read the JSON request
- const list<std::string> req = read(r);
- JSONContext cx;
- const list<value> json = elementsToValues(readJSON(cx, req));
- const list<list<value> > args = postArgs(json);
+/**
+ * Handle an HTTP POST.
+ */
+const int post(request_rec* r) {
+ const std::string ct = contentType(r);
- // 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 list<value> params = (list<value>)cadr(assoc(value("params"), args));
+ // Evaluate a JSON-RPC request and return a JSON result
+ 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<list<value> > args = postArgs(json);
- // Build expr to evaluate
- const value expr = cons<value>(method, params);
- std::cout<< "expr: " << expr << std::endl;
- std::cout.flush();
+ // 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 list<value> params = (list<value>)cadr(assoc(value("params"), args));
- // Evaluate the expr
- const tuscany::value val = evalLoop(is, expr, globalEnv);
- if (isNil(val))
- return HTTP_INTERNAL_SERVER_ERROR;
- std::cout<< "val: " << val << std::endl;
- std::cout.flush();
+ // Evaluate the request expression
+ const failable<value, int> val = evalExpr(cons(method, params), contribution(r), implementation(r));
- // Convert the expr value to JSON
- const list<value> result = valuesToElements(mklist<value>(mklist<value>("id", id), mklist<value>("result", val)));
- failable<list<std::string>, std::string> jsval = writeJSON(cx, result);
- if (!hasValue(jsval))
- return HTTP_INTERNAL_SERVER_ERROR;
+ // Return JSON result
+ return writeResult(jsonResult(cx, id, val), "application/json-rpc", r);
+ }
- // Send the JSON response
- ap_set_content_type(r, "application/json-rpc");
- std::ostringstream os;
- write(jsval, os);
- ap_rputs(os.str().c_str(), 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;
+ }
- return OK;
+ return HTTP_NOT_IMPLEMENTED;
}
/**
- * Handle an HTTP PUT request.
+ * Handle an HTTP PUT.
*/
const int put(request_rec* r) {
+ // TODO later
return OK;
}
/**
- * Handle an HTTP DELETE request.
+ * Handle an HTTP DELETE.
*/
const int 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));
+ if (!hasValue(val))
+ return val;
return OK;
}
@@ -426,23 +549,24 @@ void registerHooks(apr_pool_t *p) {
}
}
+}
extern "C" {
module AP_MODULE_DECLARE_DATA mod_tuscany = {
STANDARD20_MODULE_STUFF,
// dir config
- tuscany::makeDirConf,
+ tuscany::httpd::makeDirConf,
// dir merger, default is to override
NULL,
// server config
- tuscany::makeServerConf,
+ tuscany::httpd::makeServerConf,
// merge server config
NULL,
// command table
- tuscany::commands,
+ tuscany::httpd::commands,
// register hooks
- tuscany::registerHooks
+ tuscany::httpd::registerHooks
};
}
diff --git a/cpp/sca/modules/http/test-conf/mime.types b/cpp/sca/modules/http/test-conf/mime.types
new file mode 100644
index 0000000000..4279f51bca
--- /dev/null
+++ b/cpp/sca/modules/http/test-conf/mime.types
@@ -0,0 +1,607 @@
+# 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.
+
+# This file controls what Internet media types are sent to the client for
+# given file extension(s). Sending the correct media type to the client
+# is important so they know how to handle the content of the file.
+# Extra types can either be added here or by using an AddType directive
+# in your config files. For more information about Internet media types,
+# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
+# registry is at <http://www.iana.org/assignments/media-types/>.
+
+# MIME type Extensions
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atom+xml atom
+application/atomicmail
+application/batch-smtp
+application/beep+xml
+application/cals-1840
+application/cnrp+xml
+application/commonground
+application/cpl+xml
+application/cybercash
+application/dca-rft
+application/dec-dx
+application/dvcs
+application/edi-consent
+application/edifact
+application/edi-x12
+application/eshop
+application/font-tdpfr
+application/http
+application/hyperstudio
+application/iges
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc
+application/mathematica
+application/mathml+xml mathml
+application/msword doc
+application/news-message-id
+application/news-transmission
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh exe class so dll dmg
+application/oda oda
+application/ogg ogg
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted
+application/pgp-keys
+application/pgp-signature
+application/pkcs10
+application/pkcs7-mime
+application/pkcs7-signature
+application/pkix-cert
+application/pkix-crl
+application/pkixcmp
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww
+application/prs.nprend
+application/prs.plucker
+application/qsig
+application/rdf+xml rdf
+application/reginfo+xml
+application/remote-printing
+application/riscos
+application/rtf
+application/sdp
+application/set-payment
+application/set-payment-initiation
+application/set-registration
+application/set-registration-initiation
+application/sgml
+application/sgml-open-catalog
+application/sieve
+application/slate
+application/smil smi smil
+application/srgs gram
+application/srgs+xml grxml
+application/timestamp-query
+application/timestamp-reply
+application/tve-trigger
+application/vemmi
+application/vnd.3gpp.pic-bw-large
+application/vnd.3gpp.pic-bw-small
+application/vnd.3gpp.pic-bw-var
+application/vnd.3gpp.sms
+application/vnd.3m.post-it-notes
+application/vnd.accpac.simply.aso
+application/vnd.accpac.simply.imp
+application/vnd.acucobol
+application/vnd.acucorp
+application/vnd.adobe.xfdf
+application/vnd.aether.imp
+application/vnd.amiga.ami
+application/vnd.anser-web-certificate-issue-initiation
+application/vnd.anser-web-funds-transfer-initiation
+application/vnd.audiograph
+application/vnd.blueice.multipass
+application/vnd.bmi
+application/vnd.businessobjects
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.cinderella
+application/vnd.claymore
+application/vnd.commerce-battelle
+application/vnd.commonspace
+application/vnd.contact.cmsg
+application/vnd.cosmocaller
+application/vnd.criticaltools.wbs+xml
+application/vnd.ctc-posml
+application/vnd.cups-postscript
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.curl
+application/vnd.cybank
+application/vnd.data-vision.rdz
+application/vnd.dna
+application/vnd.dpgraph
+application/vnd.dreamfactory
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven
+application/vnd.epson.esf
+application/vnd.epson.msf
+application/vnd.epson.quickanime
+application/vnd.epson.salt
+application/vnd.epson.ssf
+application/vnd.ericsson.quickcall
+application/vnd.eudora.data
+application/vnd.fdf
+application/vnd.ffsns
+application/vnd.fints
+application/vnd.flographit
+application/vnd.framemaker
+application/vnd.fsc.weblaunch
+application/vnd.fujitsu.oasys
+application/vnd.fujitsu.oasys2
+application/vnd.fujitsu.oasys3
+application/vnd.fujitsu.oasysgp
+application/vnd.fujitsu.oasysprs
+application/vnd.fujixerox.ddd
+application/vnd.fujixerox.docuworks
+application/vnd.fujixerox.docuworks.binder
+application/vnd.fut-misnet
+application/vnd.grafeq
+application/vnd.groove-account
+application/vnd.groove-help
+application/vnd.groove-identity-message
+application/vnd.groove-injector
+application/vnd.groove-tool-message
+application/vnd.groove-tool-template
+application/vnd.groove-vcard
+application/vnd.hbci
+application/vnd.hhe.lesson-player
+application/vnd.hp-hpgl
+application/vnd.hp-hpid
+application/vnd.hp-hps
+application/vnd.hp-pcl
+application/vnd.hp-pclxl
+application/vnd.httphone
+application/vnd.hzn-3d-crossword
+application/vnd.ibm.afplinedata
+application/vnd.ibm.electronic-media
+application/vnd.ibm.minipay
+application/vnd.ibm.modcap
+application/vnd.ibm.rights-management
+application/vnd.ibm.secure-container
+application/vnd.informix-visionary
+application/vnd.intercon.formnet
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo
+application/vnd.intu.qfx
+application/vnd.irepository.package+xml
+application/vnd.is-xpr
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.jisp
+application/vnd.kde.karbon
+application/vnd.kde.kchart
+application/vnd.kde.kformula
+application/vnd.kde.kivio
+application/vnd.kde.kontour
+application/vnd.kde.kpresenter
+application/vnd.kde.kspread
+application/vnd.kde.kword
+application/vnd.kenameaapp
+application/vnd.koan
+application/vnd.liberty-request+xml
+application/vnd.llamagraphics.life-balance.desktop
+application/vnd.llamagraphics.life-balance.exchange+xml
+application/vnd.lotus-1-2-3
+application/vnd.lotus-approach
+application/vnd.lotus-freelance
+application/vnd.lotus-notes
+application/vnd.lotus-organizer
+application/vnd.lotus-screencam
+application/vnd.lotus-wordpro
+application/vnd.mcd
+application/vnd.mediastation.cdkey
+application/vnd.meridian-slingshot
+application/vnd.micrografx.flo
+application/vnd.micrografx.igx
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf
+application/vnd.mobius.dis
+application/vnd.mobius.mbk
+application/vnd.mobius.mqy
+application/vnd.mobius.msl
+application/vnd.mobius.plc
+application/vnd.mobius.txf
+application/vnd.mophun.application
+application/vnd.mophun.certificate
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml xul
+application/vnd.ms-artgalry
+application/vnd.ms-asf
+application/vnd.ms-excel xls
+application/vnd.ms-lrm
+application/vnd.ms-powerpoint ppt
+application/vnd.ms-project
+application/vnd.ms-tnef
+application/vnd.ms-works
+application/vnd.ms-wpl
+application/vnd.mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician
+application/vnd.netfpx
+application/vnd.noblenet-directory
+application/vnd.noblenet-sealer
+application/vnd.noblenet-web
+application/vnd.novadigm.edm
+application/vnd.novadigm.edx
+application/vnd.novadigm.ext
+application/vnd.obn
+application/vnd.osa.netdeploy
+application/vnd.palm
+application/vnd.pg.format
+application/vnd.pg.osasli
+application/vnd.powerbuilder6
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.previewsystems.box
+application/vnd.publishare-delta-tree
+application/vnd.pvi.ptid1
+application/vnd.pwg-multiplexed
+application/vnd.pwg-xhtml-print+xml
+application/vnd.quark.quarkxpress
+application/vnd.rapid
+application/vnd.s3sms
+application/vnd.sealed.net
+application/vnd.seemail
+application/vnd.shana.informed.formdata
+application/vnd.shana.informed.formtemplate
+application/vnd.shana.informed.interchange
+application/vnd.shana.informed.package
+application/vnd.smaf
+application/vnd.sss-cod
+application/vnd.sss-dtf
+application/vnd.sss-ntf
+application/vnd.street-stream
+application/vnd.svd
+application/vnd.swiftview-ics
+application/vnd.triscape.mxs
+application/vnd.trueapp
+application/vnd.truedoc
+application/vnd.ufdl
+application/vnd.uplanet.alert
+application/vnd.uplanet.alert-wbxml
+application/vnd.uplanet.bearer-choice
+application/vnd.uplanet.bearer-choice-wbxml
+application/vnd.uplanet.cacheop
+application/vnd.uplanet.cacheop-wbxml
+application/vnd.uplanet.channel
+application/vnd.uplanet.channel-wbxml
+application/vnd.uplanet.list
+application/vnd.uplanet.list-wbxml
+application/vnd.uplanet.listcmd
+application/vnd.uplanet.listcmd-wbxml
+application/vnd.uplanet.signal
+application/vnd.vcx
+application/vnd.vectorworks
+application/vnd.vidsoft.vidconference
+application/vnd.visio
+application/vnd.visionary
+application/vnd.vividence.scriptfile
+application/vnd.vsf
+application/vnd.wap.sic
+application/vnd.wap.slc
+application/vnd.wap.wbxml wbxml
+application/vnd.wap.wmlc wmlc
+application/vnd.wap.wmlscriptc wmlsc
+application/vnd.webturbo
+application/vnd.wrq-hp3000-labelled
+application/vnd.wt.stf
+application/vnd.wv.csp+wbxml
+application/vnd.xara
+application/vnd.xfdl
+application/vnd.yamaha.hv-dic
+application/vnd.yamaha.hv-script
+application/vnd.yamaha.hv-voice
+application/vnd.yellowriver-custom-menu
+application/voicexml+xml vxml
+application/watcherinfo+xml
+application/whoispp-query
+application/whoispp-response
+application/wita
+application/wordperfect5.1
+application/x-bcpio bcpio
+application/x-cdlink vcd
+application/x-chess-pgn pgn
+application/x-compress
+application/x-cpio cpio
+application/x-csh csh
+application/x-director dcr dir dxr
+application/x-dvi dvi
+application/x-futuresplash spl
+application/x-gtar gtar
+application/x-gzip
+application/x-hdf hdf
+application/x-javascript js
+application/x-koan skp skd skt skm
+application/x-latex latex
+application/x-netcdf nc cdf
+application/x-sh sh
+application/x-shar shar
+application/x-shockwave-flash swf
+application/x-stuffit sit
+application/x-sv4cpio sv4cpio
+application/x-sv4crc sv4crc
+application/x-tar tar
+application/x-tcl tcl
+application/x-tex tex
+application/x-texinfo texinfo texi
+application/x-troff t tr roff
+application/x-troff-man man
+application/x-troff-me me
+application/x-troff-ms ms
+application/x-ustar ustar
+application/x-wais-source src
+application/x400-bp
+application/xhtml+xml xhtml xht
+application/xslt+xml xslt
+application/xml xml xsl
+application/xml-dtd dtd
+application/xml-external-parsed-entity
+application/zip zip
+audio/32kadpcm
+audio/amr
+audio/amr-wb
+audio/basic au snd
+audio/cn
+audio/dat12
+audio/dsr-es201108
+audio/dvi4
+audio/evrc
+audio/evrc0
+audio/g722
+audio/g.722.1
+audio/g723
+audio/g726-16
+audio/g726-24
+audio/g726-32
+audio/g726-40
+audio/g728
+audio/g729
+audio/g729D
+audio/g729E
+audio/gsm
+audio/gsm-efr
+audio/l8
+audio/l16
+audio/l20
+audio/l24
+audio/lpc
+audio/midi mid midi kar
+audio/mpa
+audio/mpa-robust
+audio/mp4a-latm
+audio/mpeg mpga mp2 mp3
+audio/parityfec
+audio/pcma
+audio/pcmu
+audio/prs.sid
+audio/qcelp
+audio/red
+audio/smv
+audio/smv0
+audio/telephone-event
+audio/tone
+audio/vdvi
+audio/vnd.3gpp.iufp
+audio/vnd.cisco.nse
+audio/vnd.cns.anp1
+audio/vnd.cns.inf1
+audio/vnd.digital-winds
+audio/vnd.everad.plj
+audio/vnd.lucent.voice
+audio/vnd.nortel.vbk
+audio/vnd.nuera.ecelp4800
+audio/vnd.nuera.ecelp7470
+audio/vnd.nuera.ecelp9600
+audio/vnd.octel.sbc
+audio/vnd.qcelp
+audio/vnd.rhetorex.32kadpcm
+audio/vnd.vmx.cvsd
+audio/x-aiff aif aiff aifc
+audio/x-alaw-basic
+audio/x-mpegurl m3u
+audio/x-pn-realaudio ram ra
+audio/x-pn-realaudio-plugin
+application/vnd.rn-realmedia rm
+audio/x-wav wav
+chemical/x-pdb pdb
+chemical/x-xyz xyz
+image/bmp bmp
+image/cgm cgm
+image/g3fax
+image/gif gif
+image/ief ief
+image/jpeg jpeg jpg jpe
+image/naplps
+image/png png
+image/prs.btif
+image/prs.pti
+image/svg+xml svg
+image/t38
+image/tiff tiff tif
+image/tiff-fx
+image/vnd.cns.inf2
+image/vnd.djvu djvu djv
+image/vnd.dwg
+image/vnd.dxf
+image/vnd.fastbidsheet
+image/vnd.fpx
+image/vnd.fst
+image/vnd.fujixerox.edmics-mmr
+image/vnd.fujixerox.edmics-rlc
+image/vnd.globalgraphics.pgb
+image/vnd.mix
+image/vnd.ms-modi
+image/vnd.net-fpx
+image/vnd.svf
+image/vnd.wap.wbmp wbmp
+image/vnd.xiff
+image/x-cmu-raster ras
+image/x-icon ico
+image/x-portable-anymap pnm
+image/x-portable-bitmap pbm
+image/x-portable-graymap pgm
+image/x-portable-pixmap ppm
+image/x-rgb rgb
+image/x-xbitmap xbm
+image/x-xpixmap xpm
+image/x-xwindowdump xwd
+message/delivery-status
+message/disposition-notification
+message/external-body
+message/http
+message/news
+message/partial
+message/rfc822
+message/s-http
+message/sip
+message/sipfrag
+model/iges igs iges
+model/mesh msh mesh silo
+model/vnd.dwf
+model/vnd.flatland.3dml
+model/vnd.gdl
+model/vnd.gs-gdl
+model/vnd.gtw
+model/vnd.mts
+model/vnd.parasolid.transmit.binary
+model/vnd.parasolid.transmit.text
+model/vnd.vtu
+model/vrml wrl vrml
+multipart/alternative
+multipart/appledouble
+multipart/byteranges
+multipart/digest
+multipart/encrypted
+multipart/form-data
+multipart/header-set
+multipart/mixed
+multipart/parallel
+multipart/related
+multipart/report
+multipart/signed
+multipart/voice-message
+text/calendar ics ifb
+text/css css
+text/directory
+text/enriched
+text/html html htm
+text/parityfec
+text/plain asc txt
+text/prs.lines.tag
+text/rfc822-headers
+text/richtext rtx
+text/rtf rtf
+text/sgml sgml sgm
+text/t140
+text/tab-separated-values tsv
+text/uri-list
+text/vnd.abc
+text/vnd.curl
+text/vnd.dmclientscript
+text/vnd.fly
+text/vnd.fmi.flexstor
+text/vnd.in3d.3dml
+text/vnd.in3d.spot
+text/vnd.iptc.nitf
+text/vnd.iptc.newsml
+text/vnd.latex-z
+text/vnd.motorola.reflex
+text/vnd.ms-mediapackage
+text/vnd.net2phone.commcenter.command
+text/vnd.sun.j2me.app-descriptor
+text/vnd.wap.si
+text/vnd.wap.sl
+text/vnd.wap.wml wml
+text/vnd.wap.wmlscript wmls
+text/x-setext etx
+text/xml
+text/xml-external-parsed-entity
+video/bmpeg
+video/bt656
+video/celb
+video/dv
+video/h261
+video/h263
+video/h263-1998
+video/h263-2000
+video/jpeg
+video/mp1s
+video/mp2p
+video/mp2t
+video/mp4v-es
+video/mpv
+video/mpeg mpeg mpg mpe
+video/nv
+video/parityfec
+video/pointer
+video/quicktime qt mov
+video/smpte292m
+video/vnd.fvt
+video/vnd.motorola.video
+video/vnd.motorola.videop
+video/vnd.mpegurl mxu m4u
+video/vnd.nokia.interleaved-multimedia
+video/vnd.objectvideo
+video/vnd.vivo
+video/x-msvideo avi
+video/x-sgi-movie movie
+x-conference/x-cooltalk ice
diff --git a/cpp/sca/modules/http/test-htdocs/index.html b/cpp/sca/modules/http/test-htdocs/index.html
new file mode 100644
index 0000000000..1bfb3e30c2
--- /dev/null
+++ b/cpp/sca/modules/http/test-htdocs/index.html
@@ -0,0 +1,21 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+<html><body><h1>It works!</h1></body></html>
+