发布于 2015-08-30 07:55:07 | 147 次阅读 | 评论: 0 | 来源: 网络整理

问题

You want to be able to control or interact with your program remotely over the network using a simple REST-based interface. However, you don’t want to do it by installing a full-fledged web programming framework.


解决方案

One of the easiest ways to build REST-based interfaces is to create a tiny library based on the WSGI standard, as described in PEP 3333. Here is an example:

# resty.py

import cgi

def notfound_404(environ, start_response):
start_response(‘404 Not Found’, [ (‘Content-type’, ‘text/plain’) ]) return [b’Not Found’]
class PathDispatcher:
def __init__(self):
self.pathmap = { }
def __call__(self, environ, start_response):

path = environ[‘PATH_INFO’] params = cgi.FieldStorage(environ[‘wsgi.input’],

environ=environ)

method = environ[‘REQUEST_METHOD’].lower() environ[‘params’] = { key: params.getvalue(key) for key in params } handler = self.pathmap.get((method,path), notfound_404) return handler(environ, start_response)

def register(self, method, path, function):
self.pathmap[method.lower(), path] = function return function

To use this dispatcher, you simply write different handlers, such as the following:

import time

_hello_resp = ‘’‘<html>

<head>
<title>Hello {name}</title>

</head> <body>

<h1>Hello {name}!</h1>

</body>

</html>’‘’

def hello_world(environ, start_response):
start_response(‘200 OK’, [ (‘Content-type’,’text/html’)]) params = environ[‘params’] resp = _hello_resp.format(name=params.get(‘name’)) yield resp.encode(‘utf-8’)

_localtime_resp = ‘’‘<?xml version=”1.0”?> <time>

<year>{t.tm_year}</year> <month>{t.tm_mon}</month> <day>{t.tm_mday}</day> <hour>{t.tm_hour}</hour> <minute>{t.tm_min}</minute> <second>{t.tm_sec}</second>

</time>’‘’

def localtime(environ, start_response):
start_response(‘200 OK’, [ (‘Content-type’, ‘application/xml’) ]) resp = _localtime_resp.format(t=time.localtime()) yield resp.encode(‘utf-8’)
if __name__ == ‘__main__’:

from resty import PathDispatcher from wsgiref.simple_server import make_server

# Create the dispatcher and register functions dispatcher = PathDispatcher() dispatcher.register(‘GET’, ‘/hello’, hello_world) dispatcher.register(‘GET’, ‘/localtime’, localtime)

# Launch a basic server httpd = make_server(‘’, 8080, dispatcher) print(‘Serving on port 8080...’) httpd.serve_forever()

To test your server, you can interact with it using a browser or urllib. For example:

>>> u = urlopen('http://localhost:8080/hello?name=Guido')
>>> print(u.read().decode('utf-8'))
<html>
  <head>
     <title>Hello Guido</title>
   </head>
   <body>
     <h1>Hello Guido!</h1>
   </body>
</html>
>>> u = urlopen('http://localhost:8080/localtime')
>>> print(u.read().decode('utf-8'))
<?xml version="1.0"?>
<time>
<year>2012</year> <month>11</month> <day>24</day> <hour>14</hour> <minute>49</minute> <second>17</second>

</time> >>>


讨论

In REST-based interfaces, you are typically writing programs that respond to common HTTP requests. However, unlike a full-fledged website, you’re often just pushing data around. This data might be encoded in a variety of standard formats such as XML, JSON, or CSV. Although it seems minimal, providing an API in this manner can be a very useful thing for a wide variety of applications. For example, long-running programs might use a REST API to implement monitoring or diagnostics. Big data applications can use REST to build a query/data extraction system. REST can even be used to control hardware devices, such as robots, sensors, mills, or lightbulbs. What’s more, REST APIs are well supported by various client-side programming environments, such as Javascript, Android, iOS, and so forth. Thus, hav‐ ing such an interface can be a way to encourage the development of more complex applications that interface with your code. For implementing a simple REST interface, it is often easy enough to base your code on the Python WSGI standard. WSGI is supported by the standard library, but also by most third-party web frameworks. Thus, if you use it, there is a lot of flexibility in how your code might be used later. In WSGI, you simply implement applications in the form of a callable that accepts this calling convention:

import cgi

def wsgi_app(environ, start_response):
...

The environ argument is a dictionary that contains values inspired by the CGI interface provided by various web servers such as Apache [see Internet RFC 3875]. To extract different fields, you would write code like this:

def wsgi_app(environ, start_response):
method = environ[‘REQUEST_METHOD’] path = environ[‘PATH_INFO’] # Parse the query parameters params = cgi.FieldStorage(environ[‘wsgi.input’], environ=environ) ...

A few common values are shown here. environ[‘REQUEST_METHOD’] is the type of re‐ quest (e.g., GET, POST, HEAD, etc.). environ[‘PATH_INFO’] is the path or the resource being requested. The call to cgi.FieldStorage() extracts supplied query parameters from the request and puts them into a dictionary-like object for later use. The start_response argument is a function that must be called to initiate a response. The first argument is the resulting HTTP status. The second argument is a list of (name, value) tuples that make up the HTTP headers of the response. For example:

def wsgi_app(environ, start_response):
... start_response(‘200 OK’, [(‘Content-type’, ‘text/plain’)])

To return data, an WSGI application must return a sequence of byte strings. This can be done using a list like this:

def wsgi_app(environ, start_response):
... start_response(‘200 OK’, [(‘Content-type’, ‘text/plain’)]) resp = [] resp.append(b’Hello Worldn’) resp.append(b’Goodbye!n’) return resp

Alternatively, you can use yield:

def wsgi_app(environ, start_response):
... start_response(‘200 OK’, [(‘Content-type’, ‘text/plain’)]) yield b’Hello Worldn’ yield b’Goodbye!n’

It’s important to emphasize that byte strings must be used in the result. If the response consists of text, it will need to be encoded into bytes first. Of course, there is no re‐ quirement that the returned value be text—you could easily write an application func‐ tion that creates images. Although WSGI applications are commonly defined as a function, as shown, an instance may also be used as long as it implements a suitable __call__() method. For example:

class WSGIApplication:
def __init__(self):
...
def __call__(self, environ, start_response)
...

This technique has been used to create the PathDispatcher class in the recipe. The dispatcher does nothing more than manage a dictionary mapping (method, path) pairs to handler functions. When a request arrives, the method and path are extracted and used to dispatch to a handler. In addition, any query variables are parsed and put into

a dictionary that is stored as environ[‘params’] (this latter step is so common, it makes a lot of sense to simply do it in the dispatcher in order to avoid a lot of replicated code). To use the dispatcher, you simply create an instance and register various WSGI-style application functions with it, as shown in the recipe. Writing these functions should be extremely straightforward, as you follow the rules concerning the start_response() function and produce output as byte strings. One thing to consider when writing such functions is the careful use of string templates. Nobody likes to work with code that is a tangled mess of print() functions, XML, and various formatting operations. In the solution, triple-quoted string templates are being defined and used internally. This particular approach makes it easier to change the format of the output later (just change the template as opposed to any of the code that uses it). Finally, an important part of using WSGI is that nothing in the implementation is spe‐ cific to a particular web server. That is actually the whole idea—since the standard is server and framework neutral, you should be able to plug your application into a wide variety of servers. In the recipe, the following code is used for testing:

if __name__ == ‘__main__’:

from wsgiref.simple_server import make_server

# Create the dispatcher and register functions dispatcher = PathDispatcher() ...

# Launch a basic server httpd = make_server(‘’, 8080, dispatcher) print(‘Serving on port 8080...’) httpd.serve_forever()

This will create a simple server that you can use to see if your implementation works. Later on, when you’re ready to scale things up to a larger level, you will change this code to work with a particular server. WSGI is an intentionally minimal specification. As such, it doesn’t provide any support for more advanced concepts such as authentication, cookies, redirection, and so forth. These are not hard to implement yourself. However, if you want just a bit more support, you might consider third-party libraries, such as WebOb or Paste.

最新网友评论  共有(0)条评论 发布评论 返回顶部

Copyright © 2007-2017 PHPERZ.COM All Rights Reserved   冀ICP备14009818号  版权声明  广告服务