summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xbin/ryu-manager10
-rw-r--r--ryu/app/rest.py230
-rw-r--r--ryu/app/wsapi.py584
-rw-r--r--ryu/app/wsgi.py98
4 files changed, 198 insertions, 724 deletions
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 <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")
-
- 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)
-
- return WSPathExtractResult(value={'dpid': dpid, 'port': port})
-
-
-class restapi(app_manager.RyuApp):
- _CONTEXTS = {
- 'network': network.Network,
- }
-
- 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()
-
- def list_networks_handler(self, request, _data):
- request.setHeader("Content-Type", 'application/json')
- return json.dumps(self.nw.list_networks())
-
- def create_network_handler(self, request, data):
- network_id = data['{network-id}']
+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:
self.nw.create_network(network_id)
except NetworkAlreadyExist:
- request.setResponseCode(409)
+ return Response(status=409)
+ else:
+ return Response(status=200)
- return ""
-
- def update_network_handler(self, _request, data):
- network_id = data['{network-id}']
+ def update(self, req, network_id, **_kwargs):
self.nw.update_network(network_id)
- return ""
+ return Response(status=200)
- def remove_network_handler(self, request, data):
- network_id = data['{network-id}']
+ 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:
- request.setResponseCode(404)
+ return Response(status=404)
+
+ return Response(status=200)
- return ""
- def list_ports_handler(self, request, data):
- network_id = data['{network-id}']
+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:
- body = json.dumps(self.nw.list_ports(network_id))
+ self.nw.create_port(network_id, int(dpid), int(port_id))
except NetworkNotFound:
- body = ""
- request.setResponseCode(404)
-
- request.setHeader("Content-Type", 'application/json')
- return body
+ return Response(status=404)
+ except PortAlreadyExist:
+ return Response(status=409)
- def create_port_handler(self, request, data):
- network_id = data['{network-id}']
- dpid = data['{dpid}_{port-id}']['dpid']
- port = data['{dpid}_{port-id}']['port']
+ return Response(status=200)
+ def update(self, req, network_id, dpid, port_id, **_kwargs):
try:
- self.nw.create_port(network_id, dpid, port)
+ self.nw.update_port(network_id, int(dpid), int(port_id))
except NetworkNotFound:
- request.setResponseCode(404)
- except PortAlreadyExist:
- request.setResponseCode(409)
+ return Response(status=404)
- 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']
+ return Response(status=200)
+ def lists(self, req, network_id, **_kwargs):
try:
- self.nw.update_port(network_id, dpid, port)
+ body = json.dumps(self.nw.list_ports(network_id))
except NetworkNotFound:
- request.setResponseCode(404)
-
- return ""
+ return Response(status=404)
- def remove_port_handler(self, request, data):
- network_id = data['{network-id}']
- dpid = data['{dpid}_{port-id}']['dpid']
- port = data['{dpid}_{port-id}']['port']
+ 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, dpid, port)
+ self.nw.remove_port(network_id, int(dpid), int(port_id))
except (NetworkNotFound, PortNotFound):
- request.setResponseCode(404)
+ return Response(status=404)
- return ""
+ return Response(status=200)
- 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")
+class restapi(app_manager.RyuApp):
+ _CONTEXTS = {
+ 'network': network.Network,
+ 'wsgi': WSGIApplication
+ }
- self.api.register_request(self.remove_port_handler, "DELETE",
- path_port,
- "remove a set of dpid and port")
+ def __init__(self, *args, **kwargs):
+ super(restapi, self).__init__(*args, **kwargs)
+ self.nw = kwargs['network']
+ wsgi = kwargs['wsgi']
+ mapper = wsgi.mapper
+
+ wsgi.registory['NetworkController'] = self.nw
+ uri = '/v1.0/networks'
+ mapper.connect('networks', uri,
+ controller=NetworkController, action='lists',
+ conditions=dict(method=['GET', 'HEAD']))
+
+ uri += '/{network_id}'
+ mapper.connect('networks', uri,
+ controller=NetworkController, action='create',
+ conditions=dict(method=['POST']))
+
+ mapper.connect('networks', uri,
+ controller=NetworkController, action='update',
+ conditions=dict(method=['PUT']))
+
+ mapper.connect('networks', uri,
+ controller=NetworkController, action='delete',
+ conditions=dict(method=['DELETE']))
+
+ wsgi.registory['PortController'] = self.nw
+ mapper.connect('networks', uri,
+ controller=PortController, action='lists',
+ conditions=dict(method=['GET']))
+
+ 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']))
+
+ 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 <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()
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 <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