Strawman WSGI integration, allowing Python components to run in a Python WSGI server.

git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@917286 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
jsdelfino 2010-02-28 20:01:44 +00:00
commit 8720acd237
18 changed files with 683 additions and 1 deletions

View file

@ -0,0 +1,33 @@
# 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.
datadir=$(prefix)/modules/wsgi
if WANT_PYTHON
noinst_PROGRAMS = client-test
nobase_data_DATA = *.yaml
INCLUDES = -I${PYTHON_INCLUDE}
client_test_SOURCES = client-test.cpp
client_test_LDFLAGS = -lxml2 -lcurl -lmozjs
TESTS = server-test
endif

View file

@ -0,0 +1,25 @@
# 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.
application: wsgi-app
version: 1
runtime: python
api_version: 1
handlers:
- url: /.*
script: composite.py

View file

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* $Rev$ $Date$ */
/**
* Test HTTP client functions.
*/
#include "stream.hpp"
#include "string.hpp"
#include "../server/client-test.hpp"
namespace tuscany {
namespace server {
string testURI = "http://localhost:8090/wsgi";
}
}
int main() {
tuscany::cout << "Testing..." << tuscany::endl;
tuscany::server::testServer();
tuscany::cout << "OK" << tuscany::endl;
return 0;
}

View file

@ -0,0 +1,35 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# JSON-RPC test case
def echo(x, ref):
return ref("echo", x)
# ATOMPub test case
def get(id, ref):
return ref("get", id)
def post(collection, item, ref):
return ref("post", collection, item)
def put(id, item, ref):
return ref("put", id, item)
def delete(id, ref):
return ref("delete", id)

View file

@ -0,0 +1,103 @@
#!/usr/bin/python
# 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 deployment and integration with WSGI
from wsgiref.simple_server import make_server
from wsgiref.handlers import CGIHandler
from os import environ
from sys import stderr, argv
from util import *
from scdl import *
def requestPath(e):
return e.get("PATH_INFO", "")
def requestMethod(e):
return e.get("REQUEST_METHOD", "")
def contentType(ct):
if ct == None:
return []
return [("Content-type", ct)]
def status(st):
if st == 200:
return "200 OK"
if st == 404:
return "404 Not Found"
if st == 201:
return "201 Created"
return "500 Internal Server Error"
def result(r, st, ct = None, b = ()):
r(status(st), contentType(ct))
return b
# WSGI application function
def application(e, r):
# Read the deployed composite
comps = components(parse("domain-test.composite"))
print >> stderr, comps
# Find the requested component
path = tokens(requestPath(e))
uc = uriToComponent(path, comps)
uri = car(uc)
if uri == None:
return result(r, 404)
comp = cadr(uc)
mod = __import__(cadr(comp))
# Call the requested component function
id = path[len(uri):]
print >> stderr, id
m = requestMethod(e)
if (m == "GET"):
v = mod.get(id)
print >> stderr, v
# write returned content-type / content pair
if not isinstance(cadr(v), basestring):
return result(r, 200, car(v), cadr(v))
# TODO write an ATOM feed or entry
if nil(id):
return result(r, 200, "application/atom+xml;type=feed", ("Atom feed"))
return result(r, 200, "application/atom+xml;type=entry", ("Atom entry"))
if m == "DELETE":
v = mod.delete(id)
if v == False:
return result(r, 404)
return result(r, 200)
# TODO implement POST and PUT methods
return result(r, 500)
def serverType(e):
return e.get("SERVER_SOFTWARE", "")
# Run the WSGI application
if __name__ == "__main__":
st = serverType(environ)
if st == "":
make_server("", int(argv[1]), application).serve_forever()
else:
CGIHandler().run(application)

View file

@ -0,0 +1,42 @@
<?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://domain/test"
name="domain-test">
<component name="wsgi-test">
<t:implementation.python script="server-test.py"/>
<service name="test">
<t:binding.http uri="wsgi"/>
</service>
</component>
<component name="client-test">
<t:implementation.python script="client-test.py"/>
<service name="client">
<t:binding.http uri="client"/>
</service>
<reference name="ref" target="wsgi-test">
<t:binding.http/>
</reference>
</component>
</composite>

View file

@ -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.
# Component invocation functions
from util import *
def apply(impl, refs, func, params):
m = __import__(impl)
f = m.__getattribute__(func)
p = refs + params
return f(*p)
def refapply(comps, target):
return comps["target"]

View file

@ -0,0 +1,89 @@
# 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.
# SCDL parsing functions
from util import *
def mkcomponent(name, impl, svcs, refs):
return (name, impl, svcs, refs)
def implementation(e):
if len(e) == 0 or match(car(e), "end", "component") == True:
return ""
if match(car(e), "start", "implementation.python") == False:
return implementation(cdr(e))
if "script" in att(car(e)):
s = att(car(e))["script"]
return s[0:len(s) - 3]
return None
def binding(e):
if len(e) == 0 or match(car(e), "end", "reference") == True or match(car(e), "end", "service") == True:
return ()
if match(car(e), "start", "binding.") == False:
return binding(cdr(e))
return tokens(att(car(e))["uri"])
def references(e):
if len(e) == 0 or match(car(e), "end", "component") == True:
return ()
if match(car(e), "start", "reference") == False:
return references(cdr(e))
if "target" in att(car(e)):
return (att(car(e))["target"],) + references(cdr(e))
return cons(binding(e), references(cdr(e)))
def services(e):
if len(e) == 0 or match(car(e), "end", "component") == True:
return ()
if match(car(e), "start", "service") == False:
return services(cdr(e))
return cons(binding(e), services(cdr(e)))
def name(e):
return att(car(e))["name"]
def components(e):
if len(e) == 0:
return ()
if match(car(e), "start", "component") == False:
return components(cdr(e))
n = name(e)
return cons(mkcomponent(n, implementation(e), cons(("components", n), services(e)), references(e)), components(cdr(e)))
def nameToComponent(n, c):
if c == ():
return None
if car(car(c)) == n:
return car(c)
return nameToComponent(n, cdr(c))
def matchingURI(u, s):
if s == ():
return None
if car(s) == u[0:len(car(s))]:
return car(s)
return matchingURI(u, cdr(s))
def uriToComponent(u, c):
if c == ():
return (None, None)
m = matchingURI(u, caddr(car(c)))
if m != None:
return (m, car(c))
return uriToComponent(u, cdr(c))

View file

@ -0,0 +1,30 @@
#!/bin/sh
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Setup
./wsgi-start 8090
sleep 2
# Test
./client-test 2>/dev/null
rc=$?
# Cleanup
./wsgi-stop 8090
return $rc

View file

@ -0,0 +1,44 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# JSON-RPC test case
def echo(x):
return x
# ATOMPub test case
def get(id):
if id == ("index.html",):
return ("text/plain", ("It works!",))
if id == ():
return ("Sample Feed", "123456789",
("Item", "111", (("'javaClass", "services.Item"), ("'name", "Apple"), ("'currencyCode", "USD"), ("'currencySymbol", "$"), ("'price", 2.99))),
("Item", "222", (("'javaClass", "services.Item"), ("'name", "Orange"), ("'currencyCode", "USD"), ("'currencySymbol", "$"), ("'price", 3.55))),
("Item", "333", (("'javaClass", "services.Item"), ("name", "Pear"), ("'currencyCode", "USD"), ("'currencySymbol", "$"), ("'price", 1.55))))
entry = (("'javaClass", "services.Item"), ("'name", "Apple"), ("'currencyCode", "USD"), ("'currencySymbol", "$"), ("'price", 2.99))
return ("Item", id[0], entry)
def post(collection, item):
return ("123456789",)
def put(id, item):
return true
def delete(id):
return true

View file

@ -0,0 +1,74 @@
# 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.
# Simple utility functions
from xml.etree.cElementTree import iterparse
# Scheme-like lists
def cons(a, b):
return (a,) + b
def car(l):
return l[0]
def cdr(l):
return l[1:]
def cadr(l):
return car(cdr(l))
def cddr(l):
return cdr(cdr(l))
def caddr(l):
return car(cddr(l))
def nil(l):
return l == ()
# Scheme-like associations
def assoc(k, l):
if l == ():
return None
if k == car(car(l)):
return car(l)
return assoc(k, cdr(l))
# Currying / partial function application
def curry(f, *args):
return lambda *a: f(*(args + a))
# Element tree utility functions
def parse(file):
return map(lambda x: x, iterparse(file, events=("start", "end")))
def evt(e):
return car(e)
def elt(e):
return cadr(e)
def att(e):
return elt(e).attrib
def match(e, ev, tag):
return evt(e) == ev and elt(e).tag.find("}" + tag) != -1
# Split a path
def tokens(path):
return tuple(filter(lambda s: len(s) != 0, path.split("/")))

View file

@ -0,0 +1,69 @@
#!/bin/sh
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
echo "Testing..."
# Setup
./wsgi-start 8090
sleep 2
# Test HTTP GET
#curl http://localhost:8090/index.html 2>/dev/null >tmp/index.html
#diff tmp/index.html ../server/htdocs/index.html
#rc=$?
rc="0"
# Test ATOMPub
if [ "$rc" = "0" ]; then
curl http://localhost:8090/client/ >tmp/feed.xml 2>/dev/null
diff tmp/feed.xml ../server/htdocs/feed.xml
rc=$?
fi
if [ "$rc" = "0" ]; then
curl http://localhost:8090/client/111 >tmp/entry.xml 2>/dev/null
diff tmp/entry.xml ../server/htdocs/entry.xml
rc=$?
fi
if [ "$rc" = "0" ]; then
curl http://localhost:8090/client/ -X POST -H "Content-type: application/atom+xml" --data @../server/htdocs/entry.xml 2>/dev/null
rc=$?
fi
if [ "$rc" = "0" ]; then
curl http://localhost:8090/client/111 -X PUT -H "Content-type: application/atom+xml" --data @../server/htdocs/entry.xml 2>/dev/null
rc=$?
fi
if [ "$rc" = "0" ]; then
curl http://localhost:8090/client/111 -X DELETE 2>/dev/null
rc=$?
fi
# Test JSON-RPC
if [ "$rc" = "0" ]; then
curl http://localhost:8090/client/ -X POST -H "Content-type: application/json-rpc" --data @../server/htdocs/json-request.txt >tmp/json-result.txt 2>/dev/null
diff tmp/json-result.txt ../server/htdocs/json-result.txt
rc=$?
fi
# Cleanup
./wsgi-stop 8090
sleep 2
if [ "$rc" = "0" ]; then
echo "OK"
fi
return $rc

View file

@ -0,0 +1,26 @@
#!/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.
# Start WSGI server
here=`readlink -f $0`; here=`dirname $here`
port=$1
python_prefix=`cat $here/../python/python.prefix`
$python_prefix/bin/python composite.py $port &

View file

@ -0,0 +1,27 @@
#!/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.
# Stop WSGI server
here=`readlink -f $0`; here=`dirname $here`
port=$1
python_prefix=`cat $here/../python/python.prefix`
py="$python_prefix/bin/python composite.py $port"
kill `ps -f | grep -v grep | grep "${py}" | awk '{ print $2 }'`