11.05 小节完成~

This commit is contained in:
XiongNeng
2015-10-08 17:23:54 +08:00
parent 2a7ec99a8f
commit 0eaca2e363

View File

@@ -1,246 +1,246 @@
===============================
11.5 生成一个简单的REST接口
11.5 创建一个简单的REST接口
===============================
----------
问题
----------
You want to be able to control or interact with your program remotely over the network
using a simple REST-based interface. However, you dont want to do it by installing a
full-fledged web programming framework.
你想使用一个简单的REST接口通过网络远程控制或访问你的应用程序但是你又不想自己去安装一个完整的web框架。
|
----------
解决方案
----------
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:
构建一个REST风格的接口最简单的方法是创建一个基于WSGI标准PEP 3333的很小的库下面是一个例子
# resty.py
.. code-block:: python
import cgi
# resty.py
def notfound_404(environ, start_response):
start_response('404 Not Found', [ ('Content-type', 'text/plain') ])
return [b'Not Found']
import cgi
class PathDispatcher:
def __init__(self):
self.pathmap = { }
def notfound_404(environ, start_response):
start_response('404 Not Found', [ ('Content-type', 'text/plain') ])
return [b'Not Found']
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)
class PathDispatcher:
def __init__(self):
self.pathmap = { }
def register(self, method, path, function):
self.pathmap[method.lower(), path] = function
return function
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)
To use this dispatcher, you simply write different handlers, such as the following:
def register(self, method, path, function):
self.pathmap[method.lower(), path] = function
return function
import time
为了使用这个调度器,你只需要编写不同的处理器,就像下面这样:
_hello_resp = '''\
<html>
<head>
<title>Hello {name}</title>
</head>
<body>
<h1>Hello {name}!</h1>
</body>
</html>'''
.. code-block:: python
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')
import time
_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>'''
_hello_resp = '''\
<html>
<head>
<title>Hello {name}</title>
</head>
<body>
<h1>Hello {name}!</h1>
</body>
</html>'''
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')
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')
if __name__ == '__main__':
from resty import PathDispatcher
from wsgiref.simple_server import make_server
_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>'''
# Create the dispatcher and register functions
dispatcher = PathDispatcher()
dispatcher.register('GET', '/hello', hello_world)
dispatcher.register('GET', '/localtime', localtime)
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')
# Launch a basic server
httpd = make_server('', 8080, dispatcher)
print('Serving on port 8080...')
httpd.serve_forever()
if __name__ == '__main__':
from resty import PathDispatcher
from wsgiref.simple_server import make_server
To test your server, you can interact with it using a browser or urllib. For example:
# Create the dispatcher and register functions
dispatcher = PathDispatcher()
dispatcher.register('GET', '/hello', hello_world)
dispatcher.register('GET', '/localtime', localtime)
>>> 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>
# Launch a basic server
httpd = make_server('', 8080, dispatcher)
print('Serving on port 8080...')
httpd.serve_forever()
<year>2012</year>
<month>11</month>
<day>24</day>
<hour>14</hour>
<minute>49</minute>
<second>17</second>
</time>
>>>
要测试下这个服务器,你可以使用一个浏览器或 ``urllib`` 和它交互。例如:
.. code-block:: python
>>> 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, youre 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. Whats 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:
在编写REST接口时通常都是服务于普通的HTTP请求。但是跟那些功能完整的网站相比你通常只需要处理数据。
这些数据以各种标准格式编码比如XML、JSON或CSV。
尽管程序看上去很简单但是以这种方式提供的API对于很多应用程序来讲是非常有用的。
import cgi
例如长期运行的程序可能会使用一个REST API来实现监控或诊断。
大数据应用程序可以使用REST来构建一个数据查询或提取系统。
REST还能用来控制硬件设备比如机器人、传感器、工厂或灯泡。
更重要的是REST API已经被大量客户端编程环境所支持比如Javascript, Android, iOS等。
因此,利用这种接口可以让你开发出更加复杂的应用程序。
def wsgi_app(environ, start_response):
...
为了实现一个简单的REST接口你只需让你的程序代码满足Python的WSGI标准即可。
WSGI被标准库支持同时也被绝大部分第三方web框架支持。
因此,如果你的代码遵循这个标准,在后面的使用过程中就会更加的灵活!
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:
在WSGI中你可以像下面这样约定的方式以一个可调用对象形式来实现你的程序。
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)
...
.. code-block:: python
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:
import cgi
def wsgi_app(environ, start_response):
...
start_response('200 OK', [('Content-type', 'text/plain')])
def wsgi_app(environ, start_response):
pass
To return data, an WSGI application must return a sequence of byte strings. This can
be done using a list like this:
``environ`` 属性是一个字典包含了从web服务器如Apache[参考Internet RFC 3875]提供的CGI接口中获取的值。
要将这些不同的值提取出来,你可以像这么这样写:
def wsgi_app(environ, start_response):
...
start_response('200 OK', [('Content-type', 'text/plain')])
resp = []
resp.append(b'Hello World\n')
resp.append(b'Goodbye!\n')
return resp
.. code-block:: python
Alternatively, you can use yield:
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)
def wsgi_app(environ, start_response):
...
start_response('200 OK', [('Content-type', 'text/plain')])
yield b'Hello World\n'
yield b'Goodbye!\n'
我们展示了一些常见的值。``environ['REQUEST_METHOD']`` 代表请求类型如GET、POST、HEAD等。
``environ['PATH_INFO']`` 表示被请求资源的路径。
调用 ``cgi.FieldStorage()`` 可以从请求中提取查询参数并将它们放入一个类字典对象中以便后面使用。
Its 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:
``start_response`` 参数是一个为了初始化一个请求对象而必须被调用的函数。
第一个参数是返回的HTTP状态值第二个参数是一个(名,值)元组列表用来构建返回的HTTP头。例如
class WSGIApplication:
def __init__(self):
...
def __call__(self, environ, start_response)
...
.. code-block:: python
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
def wsgi_app(environ, start_response):
pass
start_response('200 OK', [('Content-type', 'text/plain')])
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:
为了返回数据一个WSGI程序必须返回一个字节字符串序列。可以像下面这样使用一个列表来完成
if __name__ == '__main__':
from wsgiref.simple_server import make_server
.. code-block:: python
# Create the dispatcher and register functions
dispatcher = PathDispatcher()
...
def wsgi_app(environ, start_response):
pass
start_response('200 OK', [('Content-type', 'text/plain')])
resp = []
resp.append(b'Hello World\n')
resp.append(b'Goodbye!\n')
return resp
# Launch a basic server
httpd = make_server('', 8080, dispatcher)
print('Serving on port 8080...')
httpd.serve_forever()
或者,你还可以使用 ``yield``
This will create a simple server that you can use to see if your implementation works.
Later on, when youre 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 doesnt 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.
.. code-block:: python
def wsgi_app(environ, start_response):
pass
start_response('200 OK', [('Content-type', 'text/plain')])
yield b'Hello World\n'
yield b'Goodbye!\n'
这里要强调的一点是最后返回的必须是字节字符串。如果返回结果包含文本字符串,必须先将其编码成字节。
当然,并没有要求你返回的一点是文本,你可以很轻松的编写一个生成图片的程序。
尽管WSGI程序通常被定义成一个函数不过你也可以使用类实例来实现只要它实现了合适的 ``__call__()`` 方法。例如:
.. code-block:: python
class WSGIApplication:
def __init__(self):
...
def __call__(self, environ, start_response)
...
我们已经在上面使用这种技术创建 ``PathDispatcher`` 类。
这个分发器仅仅只是管理一个字典,将(方法,路径)对映射到处理器函数上面。
当一个请求到来时,它的方法和路径被提取出来,然后被分发到对应的处理器上面去。
另外,任何查询变量会被解析后放到一个字典中,以 ``environ['params']`` 形式存储。
后面这个步骤太常见,所以建议你在分发器里面完成,这样可以省掉很多重复代码。
使用分发器的时候你只需简单的创建一个实例然后通过它注册各种WSGI形式的函数。
编写这些函数应该超级简单了,只要你遵循 ``start_response()`` 函数的编写规则,并且最后返回字节字符串即可。
当编写这种函数的时候还需注意的一点就是对于字符串模板的使用。
没人愿意写那种到处混合着 ``print()`` 函数 、XML和大量格式化操作的代码。
我们上面使用了三引号包含的预先定义好的字符串模板。
这种方式的可以让我们很容易的在以后修改输出格式(只需要修改模板本身,而不用动任何使用它的地方)。
最后使用WSGI还有一个很重要的部分就是没有什么地方是针对特定web服务器的。
因为标准对于服务器和框架是中立的,你可以将你的程序放入任何类型服务器中。
我们使用下面的代码测试测试本节代码:
.. code-block:: python
if __name__ == '__main__':
from wsgiref.simple_server import make_server
# Create the dispatcher and register functions
dispatcher = PathDispatcher()
pass
# Launch a basic server
httpd = make_server('', 8080, dispatcher)
print('Serving on port 8080...')
httpd.serve_forever()
上面代码创建了一个简单的服务器,然后你就可以来测试下你的实现是否能正常工作。
最后,当你准备进一步扩展你的程序的时候,你可以修改这个代码,让它可以为特定服务器工作。
WSGI本身是一个很小的标准。因此它并没有提供一些高级的特性比如认证、cookies、重定向等。
这些你自己实现起来也不难。不过如果你想要更多的支持,可以考虑第三方库,比如 ``WebOb`` 或者 ``Paste``