From 32cb6a21a06c5ee5689313a85df77d6b8ad485fd Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Mon, 4 Jun 2012 14:29:55 +0900 Subject: [PATCH] Replace wsapi Ryu uses NOX's code based on twisted for web service. It's much cleaner to use webob since Ryu doesn't use twisted framework. Let's give up the NOX compatibility (incomplete) and go with the cleaner code. Signed-off-by: FUJITA Tomonori Reviewed-by: Isaku Yamahata --- bin/ryu-manager | 10 +- ryu/app/rest.py | 254 +++++++++------------ ryu/app/wsapi.py | 584 ----------------------------------------------- ryu/app/wsgi.py | 98 ++++++++ 4 files changed, 210 insertions(+), 736 deletions(-) delete mode 100644 ryu/app/wsapi.py create mode 100644 ryu/app/wsgi.py diff --git a/bin/ryu-manager b/bin/ryu-manager index b1947680..2437275d 100755 --- a/bin/ryu-manager +++ b/bin/ryu-manager @@ -28,7 +28,7 @@ from ryu import log log.early_init_log(logging.DEBUG) from ryu import utils -from ryu.app import wsapi +from ryu.app import wsgi from ryu.base.app_manager import AppManager from ryu.controller import controller @@ -58,10 +58,10 @@ def main(): thr = gevent.spawn_later(0, ctlr) services.append(thr) - # NOX webservice API - ws = wsapi.wsapi() - thr = gevent.spawn_later(0, ws) - services.append(thr) + webapp = wsgi.start_service(app_mgr) + if webapp: + thr = gevent.spawn_later(0, webapp) + services.append(thr) gevent.joinall(services) app_mgr.close() diff --git a/ryu/app/rest.py b/ryu/app/rest.py index 762a2c01..19a39dc1 100644 --- a/ryu/app/rest.py +++ b/ryu/app/rest.py @@ -1,5 +1,5 @@ -# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. -# Copyright (C) 2011 Isaku Yamahata +# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2012 Isaku Yamahata # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,14 +15,16 @@ # limitations under the License. import json -from ryu.exception import NetworkNotFound, NetworkAlreadyExist -from ryu.exception import PortNotFound, PortAlreadyExist -from ryu.app.wsapi import WSPathComponent -from ryu.app.wsapi import WSPathExtractResult -from ryu.app.wsapi import WSPathStaticString -from ryu.app.wsapi import wsapi +from webob import Request, Response + from ryu.base import app_manager from ryu.controller import network +from ryu.exception import NetworkNotFound, NetworkAlreadyExist +from ryu.exception import PortNotFound, PortAlreadyExist +from ryu.app.wsgi import ControllerBase, WSGIApplication + +## TODO:XXX +## define db interface and store those information into db # REST API @@ -62,162 +64,120 @@ from ryu.controller import network # -class WSPathNetwork(WSPathComponent): - """ Match a network id string """ - - def __str__(self): - return "{network-id}" - - def extract(self, pc, _data): - if pc == None: - return WSPathExtractResult(error="End of requested URI") - - return WSPathExtractResult(value=pc) - - -class WSPathPort(WSPathComponent): - """ Match a {dpid}_{port-id} string """ - - def __str__(self): - return "{dpid}_{port-id}" - - def extract(self, pc, _data): - if pc == None: - return WSPathExtractResult(error="End of requested URI") +class NetworkController(ControllerBase): + def __init__(self, req, link, data, **config): + super(NetworkController, self).__init__(req, link, data, **config) + self.nw = data + def create(self, req, network_id, **_kwargs): try: - dpid_str, port_str = pc.split('_') - dpid = int(dpid_str, 16) - port = int(port_str) - except ValueError: - return WSPathExtractResult(error="Invalid format: %s" % pc) + self.nw.create_network(network_id) + except NetworkAlreadyExist: + return Response(status=409) + else: + return Response(status=200) - return WSPathExtractResult(value={'dpid': dpid, 'port': port}) + def update(self, req, network_id, **_kwargs): + self.nw.update_network(network_id) + return Response(status=200) + + def lists(self, req, **_kwargs): + body = json.dumps(self.nw.list_networks()) + return Response(content_type='application/json', body=body) + + def delete(self, req, network_id, **_kwargs): + try: + self.nw.remove_network(network_id) + except NetworkNotFound: + return Response(status=404) + + return Response(status=200) + + +class PortController(ControllerBase): + def __init__(self, req, link, data, **config): + super(PortController, self).__init__(req, link, data, **config) + self.nw = data + + def create(self, req, network_id, dpid, port_id, **_kwargs): + try: + self.nw.create_port(network_id, int(dpid), int(port_id)) + except NetworkNotFound: + return Response(status=404) + except PortAlreadyExist: + return Response(status=409) + + return Response(status=200) + + def update(self, req, network_id, dpid, port_id, **_kwargs): + try: + self.nw.update_port(network_id, int(dpid), int(port_id)) + except NetworkNotFound: + return Response(status=404) + + return Response(status=200) + + def lists(self, req, network_id, **_kwargs): + try: + body = json.dumps(self.nw.list_ports(network_id)) + except NetworkNotFound: + return Response(status=404) + + return Response(content_type='application/json', body=body) + + def delete(self, req, network_id, dpid, port_id, **_kwargs): + try: + self.nw.remove_port(network_id, int(dpid), int(port_id)) + except (NetworkNotFound, PortNotFound): + return Response(status=404) + + return Response(status=200) class restapi(app_manager.RyuApp): _CONTEXTS = { 'network': network.Network, + 'wsgi': WSGIApplication } def __init__(self, *args, **kwargs): super(restapi, self).__init__(*args, **kwargs) - self.ws = wsapi() - self.api = self.ws.get_version("1.0") self.nw = kwargs['network'] - self.register() + wsgi = kwargs['wsgi'] + mapper = wsgi.mapper - def list_networks_handler(self, request, _data): - request.setHeader("Content-Type", 'application/json') - return json.dumps(self.nw.list_networks()) + wsgi.registory['NetworkController'] = self.nw + uri = '/v1.0/networks' + mapper.connect('networks', uri, + controller=NetworkController, action='lists', + conditions=dict(method=['GET', 'HEAD'])) - def create_network_handler(self, request, data): - network_id = data['{network-id}'] + uri += '/{network_id}' + mapper.connect('networks', uri, + controller=NetworkController, action='create', + conditions=dict(method=['POST'])) - try: - self.nw.create_network(network_id) - except NetworkAlreadyExist: - request.setResponseCode(409) + mapper.connect('networks', uri, + controller=NetworkController, action='update', + conditions=dict(method=['PUT'])) - return "" + mapper.connect('networks', uri, + controller=NetworkController, action='delete', + conditions=dict(method=['DELETE'])) - def update_network_handler(self, _request, data): - network_id = data['{network-id}'] - self.nw.update_network(network_id) - return "" + wsgi.registory['PortController'] = self.nw + mapper.connect('networks', uri, + controller=PortController, action='lists', + conditions=dict(method=['GET'])) - def remove_network_handler(self, request, data): - network_id = data['{network-id}'] + uri += '/{dpid}_{port_id}' + mapper.connect('ports', uri, + controller=PortController, action='create', + conditions=dict(method=['POST'])) + mapper.connect('ports', uri, + controller=PortController, action='update', + conditions=dict(method=['PUT'])) - try: - self.nw.remove_network(network_id) - except NetworkNotFound: - request.setResponseCode(404) - - return "" - - def list_ports_handler(self, request, data): - network_id = data['{network-id}'] - - try: - body = json.dumps(self.nw.list_ports(network_id)) - except NetworkNotFound: - body = "" - request.setResponseCode(404) - - request.setHeader("Content-Type", 'application/json') - return body - - def create_port_handler(self, request, data): - network_id = data['{network-id}'] - dpid = data['{dpid}_{port-id}']['dpid'] - port = data['{dpid}_{port-id}']['port'] - - try: - self.nw.create_port(network_id, dpid, port) - except NetworkNotFound: - request.setResponseCode(404) - except PortAlreadyExist: - request.setResponseCode(409) - - return "" - - def update_port_handler(self, request, data): - network_id = data['{network-id}'] - dpid = data['{dpid}_{port-id}']['dpid'] - port = data['{dpid}_{port-id}']['port'] - - try: - self.nw.update_port(network_id, dpid, port) - except NetworkNotFound: - request.setResponseCode(404) - - return "" - - def remove_port_handler(self, request, data): - network_id = data['{network-id}'] - dpid = data['{dpid}_{port-id}']['dpid'] - port = data['{dpid}_{port-id}']['port'] - - try: - self.nw.remove_port(network_id, dpid, port) - except (NetworkNotFound, PortNotFound): - request.setResponseCode(404) - - return "" - - def register(self): - path_networks = (WSPathStaticString('networks'), ) - self.api.register_request(self.list_networks_handler, "GET", - path_networks, - "get the list of networks") - - path_network = path_networks + (WSPathNetwork(), ) - self.api.register_request(self.create_network_handler, "POST", - path_network, - "register a new network") - - self.api.register_request(self.update_network_handler, "PUT", - path_network, - "update a network") - - self.api.register_request(self.remove_network_handler, "DELETE", - path_network, - "remove a network") - - self.api.register_request(self.list_ports_handler, "GET", - path_network, - "get the list of sets of dpid and port") - - path_port = path_network + (WSPathPort(), ) - self.api.register_request(self.create_port_handler, "POST", - path_port, - "register a new set of dpid and port") - - self.api.register_request(self.update_port_handler, "PUT", - path_port, - "update a set of dpid and port") - - self.api.register_request(self.remove_port_handler, "DELETE", - path_port, - "remove a set of dpid and port") + mapper.connect('ports', uri, + controller=PortController, action='delete', + conditions=dict(method=['DELETE'])) diff --git a/ryu/app/wsapi.py b/ryu/app/wsapi.py deleted file mode 100644 index b82b5aae..00000000 --- a/ryu/app/wsapi.py +++ /dev/null @@ -1,584 +0,0 @@ -# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. -# Copyright (C) 2011 Isaku Yamahata -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# This code is based on webservice.py from NOX project: -# Copyright 2008 (C) Nicira, Inc. - -import gflags -import logging -import re -import textwrap -import simplejson -from copy import copy -from gevent.pywsgi import WSGIServer -from webob import Request, Response - -LOG = logging.getLogger('ryu.app.wsapi') - -FLAGS = gflags.FLAGS -gflags.DEFINE_string('wsapi_host', '', 'webapp listen host') -gflags.DEFINE_integer('wsapi_port', 8080, 'webapp listen port') - -### Response functions: -# -# The following functions can be used to generate various error responses. -# These should only ever be used for the web-services interface, not the -# user-facing web interface. - - -def forbidden(request, errmsg, otherInfo={}): - """Return an error code indicating client is forbidden from accessing.""" - request.setResponseCode(403) - request.setHeader("Content-Type", "application/json") - d = copy(otherInfo) - d["displayError"] = errmsg - return simplejson.dumps(d) - - -def badRequest(request, errmsg, otherInfo={}): - """Return an error indicating a problem in data from the client.""" - request.setResponseCode(400, "Bad request") - request.setHeader("Content-Type", "application/json") - d = copy(otherInfo) - d["displayError"] = "The server did not understand the request." - d["error"] = errmsg - return simplejson.dumps(d) - - -def conflictError(request, errmsg, otherURI=None, otherInfo={}): - """Return an error indicating something conflicts with the request.""" - if otherURI != None: - request.setResponseCode(409, "Conflicts with another resource") - request.setHeader("Location", otherURI.encode("utf-8")) - else: - request.setResponseCode(409, "Internal server conflict") - request.setHeader("Content-Type", "application/json") - d = copy(otherInfo) - d["displayError"] = "Request failed due to simultaneous access." - d["error"] = errmsg - d["otherURI"] = otherURI - return simplejson.dumps(d) - - -def internalError(request, errmsg, otherInfo={}): - """Return an error code indicating an error in the server.""" - request.setResponseCode(500) - request.setHeader("Content-Type", "application/json") - d = copy(otherInfo) - d["displayError"] = \ - "The server failed while attempting to perform request." - d["error"] = errmsg - return simplejson.dumps(d) - - -def notFound(request, errmsg, otherInfo={}): - """Return an error indicating a resource could not be found.""" - request.setResponseCode(404, "Resource not found") - request.setHeader("Content-Type", "application/json") - d = copy(otherInfo) - d["displayError"] = "The server does not have data for the request." - d["error"] = errmsg - return simplejson.dumps(d) - - -def methodNotAllowed(request, errmsg, valid_methods, otherInfo={}): - """Return an error indicating this request method is not allowed.""" - request.setResponseCode(405, "Method not allowed") - method_txt = ", ".join(valid_methods) - request.setHeader("Allow", method_txt) - request.setHeader("Content-Type", "application/json") - d = copy(otherInfo) - d["displayError"] = "The server can not perform this operation." - d["error"] = errmsg - d["validMethods"] = valid_methods - return simplejson.dumps(d) - - -def unauthorized(request, errmsg="", otherInfo={}): - """Return an error indicating a client was not authorized.""" - request.setResponseCode(401, "Unauthorized") - request.setHeader("Content-Type", "application/json") - if errmsg != "": - errmsg = ": " + errmsg - d = copy(otherInfo) - d["displayError"] = "Unauthorized%s\n\n" % (errmsg, ) - d["error"] = errmsg - d["loginInstructions"] = \ - "You must login using 'POST /ws.v1/login' nd pass the resulting " + \ - "cookie with\neach equest." - return simplejson.dumps(d) - - -### Message Body handling -# -def json_parse_message_body(request): - content = request.content.read() - content_type = request.getHeader("content-type") - if content_type == None or content_type.find("application/json") == -1: - e = ["The message body must have Content-Type application/json\n", - "instead of %s. " % content_type] - if content_type == "application/x-www-form-urlencoded": - e.append("The web\nserver decoded the message body as:\n\n") - e.append(str(request.args)) - else: - e.append("The message body was:\n\n") - e.append(content) - LOG.error("".join(e)) - return None - if len(content) == 0: - LOG.error("Message body was empty. " - "It should be valid JSON encoded data for this request.") - return None - try: - data = simplejson.loads(content) - except: - LOG.error("Message body is not valid json data. " - "It was:\n\n%s" % (content,)) - return None - return data - - -class WhitespaceNormalizer: - def __init__(self): - self._re = re.compile("\s+") - - def normalize_whitespace(self, s): - return self._re.sub(" ", s).strip() - - -class WSPathTreeNode: - _wsn = WhitespaceNormalizer() - - def __init__(self, parent, path_component): - self.path_component = path_component - self._handlers = {} - self._parent = parent - self._children = [] - self._tw = textwrap.TextWrapper() - self._tw.width = 78 - self._tw.initial_indent = " " * 4 - self._tw.subsequent_indent = self._tw.initial_indent - - def parent(self): - return self._parent() - - def _matching_child(self, path_component): - for c in self._children: - if str(c.path_component) == str(path_component): - return c - return None - - def has_child(self, path_component): - return self._matching_child(path_component) != None - - def add_child(self, path_component): - c = self._matching_child(path_component) - if c == None: - c = WSPathTreeNode(self, path_component) - self._children.append(c) - return c - - def path_str(self): - if self._parent == None: - return "" - return self._parent.path_str() + "/" + str(self.path_component) - - def set_handler(self, request_method, handler, doc): - if request_method in self._handlers: - raise KeyError("%s %s is already handled by '%s'" % - (request_method, self.path_str(), - repr(self._handlers[request_method][0]))) - d = self._wsn.normalize_whitespace(doc) - d = self._tw.fill(d) - self._handlers[request_method] = (handler, d) - - def interface_doc(self, base_path): - msg = [] - p = base_path + self.path_str() - for k in self._handlers: - msg.extend((k, " ", p, "\n")) - doc = self._handlers[k][1] - if doc != None: - msg.extend((doc, "\n\n")) - for c in self._children: - msg.append(c.interface_doc(base_path)) - return "".join(msg) - - def handle(self, t): - s = t.next_path_string() - if s != None: - r = None - if len(self._children) == 0: - t.request_uri_too_long() - for c in self._children: - r = c.path_component.extract(s, t.data) - if r.error == None: - t.data[str(c.path_component)] = r.value - t.failed_paths = [] - r = c.handle(t) - break - else: - t.failed_paths.append((c.path_str(), r.error)) - if len(t.failed_paths) > 0: - return t.invalid_request() - return r - else: - try: - h, d = self._handlers[t.request_method()] - except KeyError: - return t.unsupported_method(self._handlers.keys()) - return t.call_handler(h) - - -class WSPathTraversal: - - def __init__(self, request): - self._request = request - self._pathiter = iter(request.postpath) - self.data = {} - self.failed_paths = [] - - def request_method(self): - return self._request.method - - def next_path_string(self): - try: - return self._pathiter.next() - except StopIteration: - return None - - def call_handler(self, handler): - try: - return handler(self._request, self.data) - except Exception, e: - LOG.error("caught unhandled exception with path '%s' : %s" % \ - (str(self._request.postpath), e)) - internalError(self._request, "Unhandled server error") - - def _error_wrapper(self, l): - msg = [] - msg.append("You submitted the following request.\n\n") - msg.append(" %s %s\n\n" % - (self._request.method, self._request.path)) - msg.append("This request is not valid. ") - msg.extend(l) - msg.append("\n\nYou can get a list of all valid requests with the ") - msg.append("following request.\n\n ") - msg.append("GET /" + "/".join(self._request.prepath) + "/doc") - return "".join(msg) - - def request_uri_too_long(self): - e = ["The request URI path extended beyond all available URIs."] - return notFound(self._request, self._error_wrapper(e)) - - def unsupported_method(self, valid_methods): - if len(valid_methods) > 0: - e = ["This URI only supports the following methods.\n\n "] - e.append(", ".join(valid_methods)) - else: - e = ["There are no supported request methods\non this URI. "] - e.append("It is only used as part of longer URI paths.") - return methodNotAllowed(self._request, self._error_wrapper(e), - valid_methods) - - def invalid_request(self): - e = [] - if len(self.failed_paths) > 0: - e.append("The following paths were evaluated and failed\n") - e.append("for the indicated reason.") - for p, m in self.failed_paths: - e.append("\n\n - %s\n %s" % (p, m)) - return notFound(self._request, self._error_wrapper(e)) - - -### Registering for requests -# -class WSRequestHandler: - """Class to determine appropriate handler for a web services request.""" - - def __init__(self): - self._path_tree = WSPathTreeNode(None, None) - - def register(self, handler, request_method, path_components, doc=None): - """Register a web services request handler. - - The parameters are: - - - handler: a function to be called when the specified request - method and path component list are matched. It must - have the signature: - - handler(request, extracted_data) - - Here the 'request' parameter is a twisted request object - to be used to output the result and extracted_data is a - dictionary of data extracted by the WSPath subclass - instances in the 'path_components' parameter indexed - by str(path_component_instance). - - - request_method: the HTTP request method of the request to - be handled. - - - path_components: a list of 'WSPathComponent' subclasses - describing the path to be handled. - - - doc: a string describing the result of this request.""" - pn = self._path_tree - for pc in path_components: - pn = pn.add_child(pc) - pn.set_handler(request_method.upper(), handler, doc) - - def handle(self, request): - return self._path_tree.handle(WSPathTraversal(request)) - - def interface_doc(self, base_path): - """Text describing all current valid requests.""" - d = """\ -This is a RESTful web interface to NOX network applications. The applications -running on this NOX instance support the following requests.\n\n""" - - return d + self._path_tree.interface_doc(base_path) - - -class WSPathExtractResult: - def __init__(self, value=None, error=None): - self.value = value - self.error = error - - -class WSPathComponent(object): - """Base class for WS path component extractors""" - - def __init__(self): - """Initialize a path component extractor - - Currently this does nothing but that may change in the future. - Subclasses should call this to be sure.""" - super(WSPathComponent, self).__init__() - - def __str__(self): - """Get the string representation of the path component - - This is used in generating information about the available paths - and conform to the following conventions: - - - If a fixed string is being matched, it should be that string. - - In all other cases, it should be a description of what is - being extracted within angle brackets, for example, - ''. - - This string is also the key in the dictionary callbacks registered - with a WSPathParser instance receive to obtain the extracted - information.""" - err = "The '__str__' method must be implemented by subclasses." - raise NotImplementedError(err) - - def extract(self, pc, extracted_data): - """Determine if 'pc' matches this path component type - - Returns a WSPathExtractResult object with value set to the - extracted value for this path component if the extraction succeeded - or error set to an error describing why it did not succeed. - - The 'pc' parameter may have the value 'None' if all path components - have been exhausted during previous WS path parsing. This is - to allow path component types that are optional at the end - of a WS. - - The extracted_data parameter contains data extracted - from earlier path components, which can be used during the - extraction if needed. It is a dictionary keyed by the - str(path_component) for each previous path component.""" - err = "The 'extract' method must be implemented by subclasses." - raise NotImplementedError(err) - - -class WSPathStaticString(WSPathComponent): - """Match a static string in the WS path, possibly case insensitive.""" - - def __init__(self, str, case_insensitive=False): - WSPathComponent.__init__(self) - self.case_insensitive = case_insensitive - if case_insensitive: - self.str = str.lower() - else: - self.str = str - - def __str__(self): - return self.str - - def extract(self, pc, data): - if pc == None: - return WSPathExtractResult(error="End of requested URI") - - if self.case_insensitive: - if pc.lower() == self.str: - return WSPathExtractResult(value=pc) - else: - if pc == self.str: - return WSPathExtractResult(value=pc) - return WSPathExtractResult(error="'%s' != '%s'" % (pc, self.str)) - - -class WSPathRegex(WSPathComponent): - """Match a regex in the WS path. - - This can not be used directly but must be subclassed. Typically - the only thing a subclass must override is the '__str__' - method. - - The value returned from the 'extract' method is the python regular - expression match object, from subgroups in the expression can be - examined, etc.""" - def __init__(self, regexp): - WSPathComponent.__init__(self) - self.re = re.compile(regexp) - - def extract(self, pc, data): - if pc == None: - return WSPathExtractResult(error="End of requested URI") - m = re.match(pc) - if m == None: - return WSPathExtractResult(error="Regexp did not match: %s" % - self.re.pattern) - return WSPathExtractResult(value=m) - - -class WSPathTrailingSlash(WSPathComponent): - """Match a null string at a location in the WS path. - - This is typically used at the end of a WS path to require a - trailing slash.""" - - def __init__(self): - WSPathComponent.__init__(self) - - def __str__(self): - return "/" - - def extract(self, pc, data): - if pc == "": - return WSPathExtractResult(True) - else: - return WSPathExtractResult( - error="Data following expected trailing slash") - - -# match any string, and retrieve it by 'name' -# (e.g., WSPathArbitraryString('') -class WSPathArbitraryString(WSPathComponent): - def __init__(self, name): - WSPathComponent.__init__(self) - self._name = name - - def __str__(self): - return self._name - - def extract(self, pc, data): - if pc == None: - return WSPathExtractResult(error="End of requested URI") - return WSPathExtractResult(unicode(pc, 'utf-8')) - - -class WSRequest: - - def __init__(self, env, start_response): - self.env = env - self.start_response = start_response - self.version = None - self.content = env.get('wsgi.input', None) - - req = Request(env) - self.req = req - self.method = req.method - self.path = req.path - self.segs = [s for s in self.path.split('/') if s] - - self.rsp = Response(status=200) - - try: - version_str = self.segs[0] - except IndexError: - return - - p = re.compile('^v(?P.+)$') - m = p.match(version_str) - if m: - self.version = m.group('ver') - - self.prepath = [version_str] - self.postpath = self.segs[1:] - - def setHeader(self, name, value): - self.rsp.headers[name] = value - - def getHeader(self, name): - return self.req.headers[name] - - def setResponseCode(self, code, message=None): - if not isinstance(code, (int, long)): - raise TypeError("HTTP response code must be int or long") - if message: - self.rsp.status = str(code) + " " + message - else: - self.rsp.status = code - - def sendResponse(self, body): - self.rsp.body = body - return self.rsp(self.env, self.start_response) - - -class WSRes: - - def _get_interface_doc(self, request, arg): - request.setHeader("Content-Type", "text/plain") - return self.mgr.interface_doc("/" + "/".join(request.prepath)) - - def __init__(self, version='1.0'): - self.version = version - self.mgr = WSRequestHandler() - self.register_request(self._get_interface_doc, - "GET", (WSPathStaticString("doc"),), - """Get a summary of requests supported by this - web service interface.""") - - def register_request(self, handler, request_method, path_components, doc): - self.mgr.register(handler, request_method, path_components, doc) - - def render(self, request): - return self.mgr.handle(request) - - -class wsapi: - - _versions = {'1.0': WSRes('1.0')} - - @classmethod - def get_version(cls, version): - return cls._versions[version] - - def application(self, env, start_response): - wsreq = WSRequest(env, start_response) - if wsreq.version in wsapi._versions: - body = wsapi._versions[wsreq.version].render(wsreq) - else: - body = notFound(wsreq, "") - return wsreq.sendResponse(body) - - def __call__(self): - server = WSGIServer((FLAGS.wsapi_host, FLAGS.wsapi_port), - self.application) - server.serve_forever() diff --git a/ryu/app/wsgi.py b/ryu/app/wsgi.py new file mode 100644 index 00000000..e21a5cb8 --- /dev/null +++ b/ryu/app/wsgi.py @@ -0,0 +1,98 @@ +# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2012 Isaku Yamahata +# +# Licensed 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. + +import gflags +import logging +import webob.dec + +from gevent import pywsgi +from routes import Mapper +from routes.util import URLGenerator + +LOG = logging.getLogger('ryu.app.wsgi') + +FLAGS = gflags.FLAGS +gflags.DEFINE_string('wsapi_host', '', 'webapp listen host') +gflags.DEFINE_integer('wsapi_port', 8080, 'webapp listen port') + + +class ControllerBase(object): + special_vars = ['action', 'controller'] + + def __init__(self, req, link, data, **config): + self.req = req + self.link = link + for name, value in config.items(): + setattr(self, name, value) + + def __call__(self, req): + action = self.req.urlvars.get('action', 'index') + if hasattr(self, '__before__'): + self.__before__() + + kwargs = self.req.urlvars.copy() + for attr in self.special_vars: + if attr in kwargs: + del kwargs[attr] + + # LOG.debug('kwargs %s', kwargs) + return getattr(self, action)(req, **kwargs) + + +class WSGIApplication(object): + def __init__(self, **config): + self.config = config + self.mapper = Mapper() + self.registory = {} + super(WSGIApplication, self).__init__() + + @webob.dec.wsgify + def __call__(self, req): + # LOG.debug('mapper %s', self.mapper) + # LOG.debug('req: %s\n', req) + # LOG.debug('\nreq.environ: %s', req.environ) + match = self.mapper.match(environ=req.environ) + + if not match: + return webob.exc.HTTPNotFound() + + req.urlvars = match + link = URLGenerator(self.mapper, req.environ) + + data = None + name = match['controller'].__name__ + if name in self.registory: + data = self.registory[name] + + controller = match['controller'](req, link, data, **self.config) + return controller(req) + + +class WSGIServer(pywsgi.WSGIServer): + def __init__(self, application, **config): + super(WSGIServer, self).__init__((FLAGS.wsapi_host, FLAGS.wsapi_port), + application, **config) + + def __call__(self): + self.serve_forever() + + +def start_service(app_mgr): + for instance in app_mgr.contexts.values(): + if instance.__class__ == WSGIApplication: + return WSGIServer(instance) + + return None