问题

你想使用一个简单的REST接口通过网络远程控制或访问你的应用程序,但是你又不想自己去安装一个完整的web框架。

解决方案

构建一个REST风格的接口最简单的方法是创建一个基于WSGI标准(PEP 3333)的很小的库,下面是一个例子:

# 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

为了使用这个调度器,你只需要编写不同的处理器,就像下面这样:

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

要测试下这个服务器,你可以使用一个浏览器或 urllib 和它交互。例如:

>>> 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>
>>>

讨论

在编写REST接口时,通常都是服务于普通的HTTP请求。但是跟那些功能完整的网站相比,你通常只需要处理数据。 这些数据以各种标准格式编码,比如XML、JSON或CSV。 尽管程序看上去很简单,但是以这种方式提供的API对于很多应用程序来讲是非常有用的。

例如,长期运行的程序可能会使用一个REST API来实现监控或诊断。 大数据应用程序可以使用REST来构建一个数据查询或提取系统。 REST还能用来控制硬件设备比如机器人、传感器、工厂或灯泡。 更重要的是,REST API已经被大量客户端编程环境所支持,比如Javascript, Android, iOS等。 因此,利用这种接口可以让你开发出更加复杂的应用程序。

为了实现一个简单的REST接口,你只需让你的程序代码满足Python的WSGI标准即可。 WSGI被标准库支持,同时也被绝大部分第三方web框架支持。 因此,如果你的代码遵循这个标准,在后面的使用过程中就会更加的灵活!

在WSGI中,你可以像下面这样约定的方式以一个可调用对象形式来实现你的程序。

import cgi

def wsgi_app(environ, start_response):
pass

environ 属性是一个字典,包含了从web服务器如Apache[参考Internet RFC 3875]提供的CGI接口中获取的值。 要将这些不同的值提取出来,你可以像这么这样写:

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)

我们展示了一些常见的值。environ['REQUEST_METHOD'] 代表请求类型如GET、POST、HEAD等。environ['PATH_INFO'] 表示被请求资源的路径。 调用 cgi.FieldStorage() 可以从请求中提取查询参数并将它们放入一个类字典对象中以便后面使用。

start_response 参数是一个为了初始化一个请求对象而必须被调用的函数。 第一个参数是返回的HTTP状态值,第二个参数是一个(名,值)元组列表,用来构建返回的HTTP头。例如:

def wsgi_app(environ, start_response):
pass
start_response('200 OK', [('Content-type', 'text/plain')])

为了返回数据,一个WSGI程序必须返回一个字节字符串序列。可以像下面这样使用一个列表来完成:

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

或者,你还可以使用 yield :

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__() 方法。例如:

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

我们已经在上面使用这种技术创建 PathDispatcher 类。 这个分发器仅仅只是管理一个字典,将(方法,路径)对映射到处理器函数上面。 当一个请求到来时,它的方法和路径被提取出来,然后被分发到对应的处理器上面去。 另外,任何查询变量会被解析后放到一个字典中,以 environ['params'] 形式存储。 后面这个步骤太常见,所以建议你在分发器里面完成,这样可以省掉很多重复代码。 使用分发器的时候,你只需简单的创建一个实例,然后通过它注册各种WSGI形式的函数。 编写这些函数应该超级简单了,只要你遵循 start_response() 函数的编写规则,并且最后返回字节字符串即可。

当编写这种函数的时候还需注意的一点就是对于字符串模板的使用。 没人愿意写那种到处混合着 print() 函数 、XML和大量格式化操作的代码。 我们上面使用了三引号包含的预先定义好的字符串模板。 这种方式的可以让我们很容易的在以后修改输出格式(只需要修改模板本身,而不用动任何使用它的地方)。

最后,使用WSGI还有一个很重要的部分就是没有什么地方是针对特定web服务器的。 因为标准对于服务器和框架是中立的,你可以将你的程序放入任何类型服务器中。 我们使用下面的代码测试测试本节代码:

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

参考:https://python3-cookbook.readthedocs.io/zh_CN/latest/c11/p05_creating_simple_rest_based_interface.html

使用WSGI创建REST接口的更多相关文章

  1. 创建DirectInput接口对象说明---(void **)&m_pDI

    读别人代码时遇到的,起初没明白过来这是怎么回事,后来忽然想明白了. if (FAILED(DirectInput8Create(appInstance, DIRECTINPUT_VERSION, II ...

  2. 【转】SoapUI5.0创建WebService接口模拟服务端

    原文:http://blog.csdn.net/a19881029/article/details/26348627 使用SoapUI创建WebService接口模拟服务端需要接口描述文件 MathU ...

  3. sysfs_create_group创建sysfs接口

    在调试驱动,可能需要对驱动里的某些变量进行读写,或函数调用.可通过sysfs接口创建驱动对应的属性,使得可以在用户空间通过sysfs接口的show和store函数与硬件交互: Syss接口可通过sys ...

  4. SoapUI5.0创建WebService接口模拟服务端(转)

    转载自:https://blog.csdn.net/a19881029/article/details/26348627 使用SoapUI创建WebService接口模拟服务端需要接口描述文件 Mat ...

  5. 创建一个接口Shape,其中有抽象方法area,类Circle 、Rectangle实现area方法计算其面积并返回。又有Star实现Shape的area方法,其返回值是0,Star类另有一返回值boolean型方法isStar;在main方法里创建一个Vector,根据随机数的不同向其中加入Shape的不同子类对象(如是1,生成Circle对象;如是2,生成Rectangle对象;如是3,生成S

    题目补充: 创建一个接口Shape,其中有抽象方法area,类Circle .Rectangle实现area方法计算其面积并返回. 又有Star实现Shape的area方法,其返回值是0,Star类另 ...

  6. Eclipse 创建 Java 接口

    打开新建 Java 接口向导 新建 Java 接口向导可以创建新的 Java 接口.打开向导的方式有: 点击 File 菜单并选择 New > Interface 在 Package Explo ...

  7. Eclipse 创建 Java 接口---Eclipse教程第11课

    打开新建 Java 接口向导 新建 Java 接口向导可以创建新的 Java 接口.打开向导的方式有: 点击 File 菜单并选择 New > Interface 在 Package Explo ...

  8. 【JMeter4.0学习(三)】之SoapUI创建WebService接口模拟服务端以及JMeter对SOAP协议性能测试脚本开发

    目录: 创建WebService接口模拟服务端 下载SoapUI 新建MathUtil.wsdl文件 创建一个SOAP项目 接口模拟服务端配置以及启动 JMeter对SOAP协议性能测试脚本开发 [阐 ...

  9. .Net core使用XRPC创建远程接口的Actor对象

    Actor是一种高并发处理模型,每个Actor都有着自己的状态有序消息处理机制,所以在业务处理的情况并不需要制定锁的机制,从而达到更高效的处理能性.XRPC是一个基于远程接口调用的RPC组件,它可以简 ...

随机推荐

  1. Matlab获取文件夹下所有文件名并将数据按矩阵赋值给变量

    一.获取一个文件夹下所有文件名: fileFolder=fullfile('D:\MATLAB\bin\trc'); dirOutput=dir(fullfile(fileFolder,'*.trc' ...

  2. Rvm 进行gem安装时必须输入密码Your user account isn't allowed to install to the system RubyGems 解决方案

    今天开发过程中,从master拉下代码后重启项目,想用控制台时,却发现需要密码??并且三次密码确认后还是疯狂报错. 当时第一想到是rvm版本不一致,随即则检查了版本跟gem生成,当确认rvm版本无误时 ...

  3. flutter 安卓再次点击返回退出应用

    安卓手机点击实体或者虚拟返回键,会返回上一级,当到达最上层是,点击返回退出应用,为了防止用户连续点击返回,导致应用退出,在用户点击返回到最上层时,如果再次点击返回,第一次不退出,并提升用户再次点击退出 ...

  4. A simple dynamic library implemented in C# 4.0 to deal with XML structure

    https://github.com/cardinals/XmlToObjectParser A simple dynamic library implemented in C# 4.0 to dea ...

  5. 3-3 man手册介绍

    man手册介绍 内容表示的意义: 各部分功能说明: SECTION: name:命令的名称及功能描述: SYNOPSIS:命令使用格式摘要: DESCRIPTION:详细描述信息: OPTIONS:选 ...

  6. LeetCode.1207-唯一的元素出现次数(Unique Number of Occurrences)

    这是小川的第次更新,第篇原创 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第269题(顺位题号是1207).给定一个整数数组arr,当且仅当该数组中每个元素的出现次数唯一时,返回tr ...

  7. xss 学习(一)

    存储型 .反射型.DOM 型这是最常见的三种分类: 存储型存储型XSS也叫持久型XSS,存储的意思就是Payload是有经过存储的,当一个页面存在存储型XSS的时候,XSS注入成功后,那么每次访问该页 ...

  8. Jquery的深浅拷贝涉及到的知识点

    1.安全的类型检测 Object.prototype.toString.call(obj) => "[object NativeConstructorName]" /** * ...

  9. JS小时倒计时

    let t1 = new Date("2019-11-26 15:51:00");// 从什么时间开始 let t2 = ));// 延迟几个小时 let interval = w ...

  10. spark 1.6.0 安装与配置(spark1.6.0、Ubuntu14.04、hadoop2.6.0、scala2.10.6、jdk1.7)

    前几天刚着实研究spark,spark安装与配置是入门的关键,本人也是根据网上各位大神的教程,尝试配置,发现版本对应最为关键.现将自己的安装与配置过程介绍如下,如有兴趣的同学可以尝试安装.所谓工欲善其 ...