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 <fujita.tomonori@lab.ntt.co.jp>
Reviewed-by: Isaku Yamahata <yamahata@valinux.co.jp>
This commit is contained in:
FUJITA Tomonori 2012-06-04 14:29:55 +09:00
parent cc8e311329
commit 32cb6a21a0
4 changed files with 210 additions and 736 deletions

View File

@ -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()

View File

@ -1,5 +1,5 @@
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2011 Isaku Yamahata <yamahata at valinux co jp>
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp>
#
# 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']))

View File

@ -1,584 +0,0 @@
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2011 Isaku Yamahata <yamahata at valinux co jp>
#
# 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 <http://www.gnu.org/licenses/>.
#
# 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,
'<existing database table name>'.
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('<hostname>')
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<ver>.+)$')
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()

98
ryu/app/wsgi.py Normal file
View File

@ -0,0 +1,98 @@
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp>
#
# 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