def service_wrapper(func): @functools.wraps(func) def wrapper(environ, start_response): _handle_notify_before(environ, FROM_ENVIRON, notify_before) # 'service' passes the WSGI request straight through # to the handler so there's almost no point in # setting up the environment. However, I can conceive # of tools which might access 'environ' directly, and # I want to be consistent with the simple* interfaces. new_request(environ) result = func(environ, start_response) # You need to make sure you sent the correct content-type! result, ctype, length = convert_body(result, None, encoding, writer) result = _handle_notify_after(environ, result, notify_after) return result pth = path if pth is None: pth = func.__name__ # If an outer WSGI wrapper was specified, place it around the service wrapper being created if wsgi_wrapper: wrapper = wsgi_wrapper(wrapper) #For purposes of inspection (not a good idea to change these otherwise you'll lose sync with the values closed over) wrapper.content_type = content_type wrapper.encoding = encoding wrapper.writer = writer registry.register_service(service_id, pth, wrapper, query_template=query_template) return wrapper
def register_pipeline(ident, path=None, stages=None, doc=None): if not stages: raise TypeError("a pipeline must have stages") stages = [_normalize_stage(stage) for stage in stages] # Should I check that the dependent stages are already registered? pipeline = Pipeline(ident, path, stages, doc) registry.register_service(ident, path, pipeline, doc)
def method_dispatcher_wrapper(func): # Have to handle a missing docstring here as otherwise # the registry will try to get it from the dispatcher. doc = inspect.getdoc(func) or "" pth = path if pth is None: pth = func.__name__ dispatcher = service_method_dispatcher(pth, wsgi_wrapper) registry.register_service(service_id, pth, dispatcher, doc) return service_dispatcher_decorator(dispatcher)
# This is a very simple implementation of conditional GET with # the Last-Modified header. It makes media files a bit speedier # because the files are only read off disk for the first request # (assuming the browser/client supports conditional GET). mtime = formatdate(os.stat(filename).st_mtime, usegmt=True) headers = [('Last-Modified', mtime)] if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime: status = '304 Not Modified' output = () else: status = '200 OK' mime_type = mimetypes.guess_type(filename)[0] if mime_type: headers.append(('Content-Type', mime_type)) output = [fp.read()] fp.close() start_response(status, headers) return output import akara if not akara.module_config(): akara.logger.warn("No configuration section found for %r" % (__name__, )) paths = akara.module_config().get("paths", {}) for path, root in paths.items(): handler = MediaHandler(root) registry.register_service(SERVICE_ID, path, handler)
def simple_service(method, service_id, path=None, content_type=None, encoding="utf-8", writer="xml", allow_repeated_args=False, query_template=None, wsgi_wrapper=None, notify_before=None, notify_after=None): """Add the function as an Akara resource These affect how the resource is registered in Akara method - the supported HTTP method (either "GET" or "POST") service_id - a string which identifies this service; should be a URL path - the local URL path to the resource (must not at present contain a '/') If None, use the function's name as the path. query_template - An Akara URL service template (based on OpenSource; see akara.opensource) Can be used to help consumers compose resources withing this service. The same template is used for all HTTP methods These control how to turn the return value into an HTTP response content_type - the response content-type. If not specified, and if "Content-Type" is not listed in akara.response.headers then infer the content-type based on what the decorated function returns. (See akara.services.convert_body for details) encoding - Used to convert a returned Unicode string or an Amara tree to the bytes used in the HTTP response writer - Used to serialize the Amara tree for the HTTP response. This must be a name which can be used as an Amara.writer.lookup. This affects how to convert the QUERY_STRING into function call parameters allow_repeated_args - The query string may have multiple items with the same name, as in "?a=x&a=y&a=z&b=w". If True, this is converted into a function call parameter like "f(a=['x','y','z'], b=['w'])". If False then this is treated as an error. Suppose the query string contains no repeated arguments, as in "?a=x&b=w". If allow_repeated_args is True then the function is called as as "f(a=['x'], b=['w'])" and if False, like "f(a='x', b='w')". A simple_service decorated function can get request information from akara.request and use akara.response to set the HTTP reponse code and the HTTP response headers. Here is an example of use: @simple_service("GET", "http://example.com/get_date") def date(format="%Y-%m-%d %H:%M:%S"): '''get the current date''' import datetime return datetime.datetime.now().strftime(format) which can be called with URLs like: http://localhost:8880/date http://localhost:8880/date?format=%25m-%25d-%25Y Integration with other WSGI components: The @simple_service decorator creates and returns a low-level handler function that conforms to the WSGI calling conventions. However, it is not safe to directly use the resulting handler with arbitrary third-party WSGI components (e.g., to wrap the Akara handler with an WSGI middleware component). This is because Akara handlers return values other than sequences of byte-strings. For example, they might return XML trees, Unicode, or other data types that would not be correctly interpreted by other WSGI components. To integrate other WSGI components with Akara, use the wsgi_wrapper argument to @simple_service. For example: def wrapper(app): # Create an WSGI wrapper around WSGI application app ... return wrapped_app @simple_service("GET", "http://example.com/get_date", wsgi_wrapper=wrapper) def date(format): ... When specified, Akara will do the following: - Arrange to have the wsgi_wrapper placed at the outermost layer of Akara's processing. That is, control will pass into the WSGI wrapper before any Akara-specific processing related to the @simple_service handler takes place. - Ensure that all output returned back to the WSGI wrapper strictly conforms to the WSGI standard (is a sequence of bytes) The wrapper function given with wsgi_wrapper should accept a function as input and return an WSGI application as output. This application should be a callable that accepts (environ, start_response). See implementation notes in the code below. """ _no_slashes(path) _check_is_valid_method(method) if method not in ("GET", "POST"): raise ValueError( "simple_service only supports GET and POST methods, not %s" % (method,)) def service_wrapper(func): @functools.wraps(func) def wrapper(environ, start_response): try: if environ.get("REQUEST_METHOD") != method: if method == "GET": raise _HTTP405(["GET"]) else: raise _HTTP405(["POST"]) args, kwargs = _get_function_args(environ, allow_repeated_args) except _HTTPError, err: return err.make_wsgi_response(environ, start_response) if args: body = args[0] else: body = "" _handle_notify_before(environ, body, notify_before) new_request(environ) result = func(*args, **kwargs) result, ctype, clength = convert_body(result, content_type, encoding, writer) send_headers(start_response, ctype, clength) result = _handle_notify_after(environ, result, notify_after) return result pth = path if pth is None: pth = func.__name__ # Construct the default query template, if needed and possible. qt = query_template if qt is None and method == "GET" and not allow_repeated_args: qt = _make_query_template(func) if qt is not None: qt = pth + qt # If an wsgi_wrapper was given, wrapper the service wrapper with it if wsgi_wrapper: wrapper = wsgi_wrapper(wrapper) #For purposes of inspection (not a good idea to change these otherwise you'll lose sync with the values closed over) wrapper.content_type = content_type wrapper.encoding = encoding wrapper.writer = writer registry.register_service(service_id, pth, wrapper, query_template=qt) return wrapper
# This is a very simple implementation of conditional GET with # the Last-Modified header. It makes media files a bit speedier # because the files are only read off disk for the first request # (assuming the browser/client supports conditional GET). mtime = formatdate(os.stat(filename).st_mtime, usegmt=True) headers = [('Last-Modified', mtime)] if environ.get('HTTP_IF_MODIFIED_SINCE', None) == mtime: status = '304 Not Modified' output = () else: status = '200 OK' mime_type = mimetypes.guess_type(filename)[0] if mime_type: headers.append(('Content-Type', mime_type)) output = [fp.read()] fp.close() start_response(status, headers) return output import akara if not akara.module_config(): akara.logger.warn("No configuration section found for %r" % (__name__,)) paths = akara.module_config().get("paths", {}) for path, root in paths.items(): handler = MediaHandler(root) registry.register_service(SERVICE_ID, path, handler)
def simple_service(method, service_id, path=None, content_type=None, encoding="utf-8", writer="xml", allow_repeated_args=False, query_template=None, wsgi_wrapper=None, notify_before=None, notify_after=None): """Add the function as an Akara resource These affect how the resource is registered in Akara method - the supported HTTP method (either "GET" or "POST") service_id - a string which identifies this service; should be a URL path - the local URL path to the resource (must not at present contain a '/') If None, use the function's name as the path. query_template - An Akara URL service template (based on OpenSource; see akara.opensource) Can be used to help consumers compose resources withing this service. The same template is used for all HTTP methods These control how to turn the return value into an HTTP response content_type - the response content-type. If not specified, and if "Content-Type" is not listed in akara.response.headers then infer the content-type based on what the decorated function returns. (See akara.services.convert_body for details) encoding - Used to convert a returned Unicode string or an Amara tree to the bytes used in the HTTP response writer - Used to serialize the Amara tree for the HTTP response. This must be a name which can be used as an Amara.writer.lookup. This affects how to convert the QUERY_STRING into function call parameters allow_repeated_args - The query string may have multiple items with the same name, as in "?a=x&a=y&a=z&b=w". If True, this is converted into a function call parameter like "f(a=['x','y','z'], b=['w'])". If False then this is treated as an error. Suppose the query string contains no repeated arguments, as in "?a=x&b=w". If allow_repeated_args is True then the function is called as as "f(a=['x'], b=['w'])" and if False, like "f(a='x', b='w')". A simple_service decorated function can get request information from akara.request and use akara.response to set the HTTP reponse code and the HTTP response headers. Here is an example of use: @simple_service("GET", "http://example.com/get_date") def date(format="%Y-%m-%d %H:%M:%S"): '''get the current date''' import datetime return datetime.datetime.now().strftime(format) which can be called with URLs like: http://localhost:8880/date http://localhost:8880/date?format=%25m-%25d-%25Y Integration with other WSGI components: The @simple_service decorator creates and returns a low-level handler function that conforms to the WSGI calling conventions. However, it is not safe to directly use the resulting handler with arbitrary third-party WSGI components (e.g., to wrap the Akara handler with an WSGI middleware component). This is because Akara handlers return values other than sequences of byte-strings. For example, they might return XML trees, Unicode, or other data types that would not be correctly interpreted by other WSGI components. To integrate other WSGI components with Akara, use the wsgi_wrapper argument to @simple_service. For example: def wrapper(app): # Create an WSGI wrapper around WSGI application app ... return wrapped_app @simple_service("GET", "http://example.com/get_date", wsgi_wrapper=wrapper) def date(format): ... When specified, Akara will do the following: - Arrange to have the wsgi_wrapper placed at the outermost layer of Akara's processing. That is, control will pass into the WSGI wrapper before any Akara-specific processing related to the @simple_service handler takes place. - Ensure that all output returned back to the WSGI wrapper strictly conforms to the WSGI standard (is a sequence of bytes) The wrapper function given with wsgi_wrapper should accept a function as input and return an WSGI application as output. This application should be a callable that accepts (environ, start_response). See implementation notes in the code below. """ _no_slashes(path) _check_is_valid_method(method) if method not in ("GET", "POST"): raise ValueError( "simple_service only supports GET and POST methods, not %s" % (method, )) def service_wrapper(func): @functools.wraps(func) def wrapper(environ, start_response): try: if environ.get("REQUEST_METHOD") != method: if method == "GET": raise _HTTP405(["GET"]) else: raise _HTTP405(["POST"]) args, kwargs = _get_function_args(environ, allow_repeated_args) except _HTTPError, err: return err.make_wsgi_response(environ, start_response) if args: body = args[0] else: body = "" _handle_notify_before(environ, body, notify_before) new_request(environ) result = func(*args, **kwargs) result, ctype, clength = convert_body(result, content_type, encoding, writer) send_headers(start_response, ctype, clength) result = _handle_notify_after(environ, result, notify_after) return result pth = path if pth is None: pth = func.__name__ # Construct the default query template, if needed and possible. qt = query_template if qt is None and method == "GET" and not allow_repeated_args: qt = _make_query_template(func) if qt is not None: qt = pth + qt # If an wsgi_wrapper was given, wrapper the service wrapper with it if wsgi_wrapper: wrapper = wsgi_wrapper(wrapper) #For purposes of inspection (not a good idea to change these otherwise you'll lose sync with the values closed over) wrapper.content_type = content_type wrapper.encoding = encoding wrapper.writer = writer registry.register_service(service_id, pth, wrapper, query_template=qt) return wrapper