| #!/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 wsgiref.util import request_uri |
| from wsgiref.util import FileWrapper |
| from os import environ |
| import os.path |
| import hashlib |
| from sys import stderr, argv |
| from util import * |
| from scdl import * |
| from atomutil import * |
| from jsonutil import * |
| |
| # Cache the deployed components between requests |
| comps = None |
| |
| # Return the path of an HTTP request |
| def requestPath(e): |
| return e.get("PATH_INFO", "") |
| |
| # Return the method of an HTTP request |
| def requestMethod(e): |
| return e.get("REQUEST_METHOD", "") |
| |
| # Return the method of an HTTP request |
| def requestContentType(e): |
| return e.get("CONTENT_TYPE", "") |
| |
| # Return the request body input stream |
| def requestBody(e): |
| i = e.get("wsgi.input", None) |
| if i == None: |
| return () |
| l = int(e.get("CONTENT_LENGTH", "0")) |
| return (i.read(l),) |
| |
| def requestIfNoneMatch(e): |
| return e.get("HTTP_IF_NONE_MATCH", ""); |
| |
| # Hash a list of strings into an MD5 signature |
| def md5update(md, s): |
| if isNil(s): |
| return md.hexdigest() |
| md.update(car(s)) |
| return md5update(md, cdr(s)) |
| |
| def md5(s): |
| return md5update(hashlib.md5(), s) |
| |
| # Return an HTTP success result |
| def result(e, r, st, h = (), b = None): |
| if st == 201: |
| r("201 Created", list(h)) |
| return () |
| |
| if st == 200: |
| if b == None: |
| r("200 OK", list(h)) |
| return () |
| |
| # Handle etags to minimize bandwidth usage |
| md = md5(b) |
| if (md == requestIfNoneMatch(e)): |
| r("304 Not Modified", list((("Etag", md), ("Expires", "Tue, 01 Jan 1980 00:00:00 GMT")))) |
| return () |
| r("200 OK", list(h + (("Etag", md), ("Expires", "Tue, 01 Jan 1980 00:00:00 GMT")))) |
| return b |
| |
| if st == 301: |
| r("301 Moved Permanently", list(h)) |
| |
| return failure(e, r, 500) |
| |
| # Return an HTTP failure result |
| def failure(e, r, st): |
| s = "404 Not Found" if st == 404 else str(st) + " " + "Internal Server Error" |
| r(s, list((("Content-type", "text/html"),))) |
| return ("<html><head><title>"+ s + "</title></head><body><h1>" + s[4:] + "</h1></body></html>",) |
| |
| # Return a static file |
| def fileresult(e, r, ct, f): |
| |
| # Read the file, return a 404 if not found |
| p = "htdocs" + f |
| if not os.path.exists(p): |
| return failure(e, r, 404) |
| c = tuple(FileWrapper(open("htdocs" + f))) |
| |
| # Handle etags to minimize bandwidth usage |
| md = md5(c) |
| r("200 OK", list((("Content-type", ct),("Etag", md)))) |
| return c |
| |
| # Converts the args received in a POST to a list of key value pairs |
| def postArgs(a): |
| if isNil(a): |
| return ((),) |
| l = car(a); |
| return cons(l, postArgs(cdr(a))) |
| |
| # Return the URL used to sign out |
| def signout(ruri): |
| try: |
| from google.appengine.api import users |
| return users.create_logout_url(ruri) |
| except: |
| return None |
| |
| # Return the URL used to sign in |
| def signin(ruri): |
| try: |
| from google.appengine.api import users |
| return users.create_login_url(ruri) |
| except: |
| return None |
| |
| # WSGI application function |
| def application(e, r): |
| m = requestMethod(e) |
| fpath = requestPath(e) |
| |
| # Serve static files |
| if m == "GET": |
| if fpath.endswith(".html"): |
| return fileresult(e, r, "text/html", fpath) |
| if fpath.endswith(".css"): |
| return fileresult(e, r, "text/css", fpath) |
| if fpath.endswith(".js"): |
| return fileresult(e, r, "application/x-javascript", fpath) |
| if fpath.endswith(".png"): |
| return fileresult(e, r, "image/png", fpath) |
| if fpath == "/": |
| return result(e, r, 301, (("Location", "/index.html"),)) |
| |
| # Debug hook |
| if fpath == "/debug": |
| return result(e, r, 200, (("Content-type", "text/plain"),), ("Debug",)) |
| |
| # Sign in and out |
| if fpath == "/login": |
| redir = signin("/") |
| if redir: |
| return result(e, r, 301, (("Location", redir),)) |
| if fpath == "/logout": |
| redir = signout(signin("/")) |
| if redir: |
| return result(e, r, 301, (("Location", redir),)) |
| |
| # Find the requested component |
| path = tokens(fpath) |
| uc = uriToComponent(path, comps) |
| uri = car(uc) |
| if uri == None: |
| return failure(e, r, 404) |
| comp = cadr(uc) |
| |
| # Call the requested component function |
| id = path[len(uri):] |
| if m == "GET": |
| v = comp("get", id) |
| |
| # Write a simple value as a JSON value |
| if not isList(v): |
| return result(e, r, 200, (("Content-type", "application/json"),), writeJSON(valuesToElements((("'value", v),)))) |
| |
| # Write an empty list as a JSON empty value |
| if not isList(v): |
| return result(e, r, 200, (("Content-type", "application/json"),), writeJSON(())) |
| |
| # Write content-type / content-list pair |
| if isString(car(v)) and not isNil(cdr(v)) and isList(cadr(v)): |
| return result(e, r, 200, (("Content-type", car(v)),), cadr(v)) |
| |
| # Convert list of values to element values |
| ve = valuesToElements(v) |
| |
| # Write an assoc result as a JSON value |
| if isList(car(ve)) and not isNil(car(ve)): |
| el = car(ve) |
| if isSymbol(car(el)) and car(el) == element and not isNil(cdr(el)) and isSymbol(cadr(el)): |
| if cadr(el) == "'feed": |
| return result(e, r, 200, (("Content-type", "application/atom+xml"),), writeATOMFeed(ve)) |
| if cadr(el) == "'entry": |
| return result(e, r, 200, (("Content-type", "application/atom+xml"),), writeATOMEntry(ve)) |
| |
| # Write a JSON value |
| return result(e, r, 200, (("Content-type", "application/json"),), writeJSON(ve)) |
| |
| if m == "POST": |
| ct = requestContentType(e) |
| |
| # Handle a JSON-RPC function call |
| if contains(ct, "application/json-rpc") or contains(ct, "text/plain") or contains(ct, "application/x-www-form-urlencoded"): |
| print >> stderr, "Handling JSON-RPC request" |
| json = elementsToValues(readJSON(requestBody(e))) |
| args = postArgs(json) |
| jid = cadr(assoc("'id", args)) |
| func = funcName(cadr(assoc("'method", args))) |
| params = cadr(assoc("'params", args)) |
| v = comp(func, *params) |
| return result(e, r, 200, (("Content-type", "application/json-rpc"),), jsonResult(jid, v)) |
| |
| # Handle an ATOM entry POST |
| if contains(ct, "application/atom+xml"): |
| ae = elementsToValues(readATOMEntry(requestBody(e))) |
| v = comp("post", id, ae) |
| if isNil(v): |
| return failure(e, r, 500) |
| return result(e, r, 201, (("Location", request_uri(e) + "/" + "/".join(v)),)) |
| return failure(e, r, 500) |
| |
| if m == "PUT": |
| # Handle an ATOM entry PUT |
| ae = elementsToValues(readATOMEntry(requestBody(e))) |
| v = comp("put", id, ae) |
| if v == False: |
| return failure(e, r, 404) |
| return result(e, r, 200) |
| |
| if m == "PATCH": |
| # Handle an ATOM entry PATCH |
| ae = elementsToValues(readATOMEntry(requestBody(e))) |
| v = comp("patch", id, ae) |
| if v == False: |
| return failure(e, r, 404) |
| return result(e, r, 200) |
| |
| if m == "DELETE": |
| v = comp("delete", id) |
| if v == False: |
| return failure(e, r, 404) |
| return result(e, r, 200) |
| |
| return failure(e, r, 500) |
| |
| # Return the WSGI server type |
| def serverType(e): |
| return e.get("SERVER_SOFTWARE", "") |
| |
| def main(): |
| # Read the deployed composite and evaluate the configured components |
| global comps |
| if comps == None: |
| domain = "domain.composite" if os.path.exists("domain.composite") else "domain-test.composite" |
| comps = evalComponents(components(parse(domain))) |
| |
| # Handle the WSGI request with the WSGI runtime |
| st = serverType(environ) |
| if contains(st, "App Engine") or contains(st, "Development"): |
| from google.appengine.ext.webapp.util import run_wsgi_app |
| run_wsgi_app(application) |
| elif st != "": |
| CGIHandler().run(application) |
| else: |
| make_server("", int(argv[1]), application).serve_forever() |
| |
| # Run the WSGI application |
| if __name__ == "__main__": |
| main() |
| |