本文概述
- 安装与配置
- Bottle框架基础
- 建立你的REST API
- POST:资源创建
- GET:资源清单
- PUT:资源更新
- 删除:资源删除
- 最后一步:激活API
- 奖励:跨源资源共享(CORS)
- 包起来
Bottle是一个简约的Python Web框架。它轻巧, 快速且易于使用, 非常适合构建RESTful服务。 Andriy Kornatskyy进行的粗略比较使它在响应时间和吞吐量(每秒请求数)方面跻身前三名。在我自己对DigitalOcean可用的虚拟服务器进行的测试中, 我发现uWSGI服务器堆栈和Bottle的组合每次请求的开销可以低至140μs。
在本文中, 我将提供有关如何使用Bottle建立RESTful API服务的演练。
文章图片
安装与配置 Bottle框架由于重量轻而实现了令人印象深刻的性能。实际上, 整个库是作为一个文件模块分发的。这意味着它不会像其他框架那样牵手, 但它也更加灵活, 可以适应各种不同的技术堆栈。因此, Bottle最适合于那些性能和可定制性非常重要的项目, 而无需考虑更耐用的框架节省时间的优势。
Bottle的灵活性使得对该平台的设置进行了深入的描述, 因为它可能无法反映你自己的堆栈, 因此它毫无用处。但是, 这里适合快速浏览这些选项以及在何处了解有关如何设置它们的更多信息:
安装
安装Bottle就像安装其他Python软件包一样容易。你的选择是:
- 使用系统的程序包管理器在系统上安装。 Debian Jessie(目前稳定)将版本0.12打包为python-bottle。
- 使用带有pip install bottle的Python Package Index在系统上安装。
- 在虚拟环境上安装(推荐)。
在Bash中, 使用Python 3创建环境:
$ virtualenv -p `which python3` env
禁止使用-p`which python3`参数将导致安装系统上存在的默认Python解释器-通常为2.7。支持Python 2.7, 但本教程假定使用Python 3.4。
现在激活环境并安装Bottle:
$ . env/bin/activate
$ pip install bottle
而已。Bottle子已安装并可以使用。如果你不熟悉virtualenv或pip, 则其文档是一流的。看一看!他们是值得的。
服务器
Bottle符合Python的标准Web服务器网关接口(WSGI), 这意味着它可以与任何符合WSGI的服务器一起使用。其中包括uWSGI, Tornado, Gunicorn, Apache, Amazon Beanstalk, Google App Engine等。
设置它的正确方法因每??个环境而略有不同。 Bottle公开了一个符合WSGI接口的对象, 并且必须将服务器配置为与此对象进行交互。
要了解有关如何设置服务器的更多信息, 请在此处参考服务器的文档和Bottle的文档。
数据库
Bottle与数据库无关, 并且不在乎数据来自何处。如果你想在应用程序中使用数据库, 则Python包索引提供了几个有趣的选项, 例如SQLAlchemy, PyMongo, MongoEngine, CouchDB和Boto for DynamoDB。你只需要适当的适配器即可使其与你选择的数据库一起使用。
Bottle框架基础 现在, 让我们看看如何在Bottle中制作基本应用。对于代码示例, 我将假定Python> = 3.4。但是, 我在这里写的大部分内容也可以在Python 2.7上使用。
Bottle中的一个基本应用程序如下所示:
import bottleapp = application = bottle.default_app()if __name__ == '__main__':
bottle.run(host = '127.0.0.1', port = 8000)
当我说基本的时候, 我的意思是说这个程序甚至都不给你” Hello World” 。 (你上次访问REST界面时回答” Hello World?” 的时间是什么?)对127.0.0.1:8000的所有HTTP请求都将收到404 Not Found响应状态。
Bottle中的应用
Bottle可能创建了多个应用程序实例, 但是为了方便起见, 为你创建了第一个实例。这是默认应用。 Bottle将这些实例保存在模块内部的堆栈中。每当你对Bottle执行某项操作(例如运行该应用或附加路线)而没有指定你要谈论的是哪个应用时, 该应用均指默认应用。实际上, app = application = bottle.default_app()行甚至不需要在此基本应用程序中存在, 但在那里, 因此我们可以轻松地使用Gunicorn, uWSGI或某些通用WSGI服务器调用默认应用程序。
一开始可能会出现多个应用程序的可能性, 但它们为Bottle增加了灵活性。对于应用程序的不同模块, 你可以通过实例化其他Bottle类并根据需要使用不同的配置来创建专门的Bottle应用程序。通过Bottle的URL路由器, 可以通过不同的URL访问这些不同的应用程序。我们不会在本教程中对此进行深入研究, 但是建议你在此处和此处阅读Bottle的文档。
服务器调用
脚本的最后一行使用指定的服务器运行Bottle。如果未指定服务器(如此处所示), 则默认服务器为Python的内置WSGI参考服务器, 仅适用于开发目的。可以这样使用其他服务器:
bottle.run(server='gunicorn', host = '127.0.0.1', port = 8000)
这是语法糖, 你可以通过运行此脚本来启动应用。例如, 如果此文件名为main.py, 则只需运行python main.py即可启动应用程序。 Bottle包含大量可以以这种方式使用的服务器适配器。
某些WSGI服务器没有Bottle适配器。这些可以通过服务器自己的运行命令启动。例如, 在uWSGI上, 你要做的就是这样调用uwsgi:
$ uwsgi --http :8000 --wsgi-file main.py
关于文件结构的注释
Bottle完全由你决定应用的文件结构。我发现我的文件结构政策因项目而异, 但往往基于MVC理念。
建立你的REST API 当然, 没有人需要只为每个请求的URI返回404的服务器。我已答应过你会建立REST API, 让我们开始吧。
假设你想构建一个操作一组名称的界面。在实际的应用程序中, 你可能会为此使用数据库, 但是在此示例中, 我们将仅使用内存中的集合数据结构。
我们API的框架可能看起来像这样。你可以将此代码放在项目中的任何位置, 但我的建议是使用单独的API文件, 例如api / names.py。
from bottle import request, response
from bottle import post, get, put, delete_names = set()# the set of names@post('/names')
def creation_handler():
'''Handles name creation'''
pass@get('/names')
def listing_handler():
'''Handles name listing'''
pass@put('/names/<
name>
')
def update_handler(name):
'''Handles name updates'''
pass@delete('/names/<
name>
')
def delete_handler(name):
'''Handles name deletions'''
pass
路由
如我们所见, Bottle中的路由是使用装饰器完成的。导入的装饰器针对这四个动作发布, 获取, 放置和删除寄存器处理程序。了解如何将这些工作分解如下:
- 以上所有装饰器都是default_app路由装饰器的快捷方式。例如, @get()装饰器将bottle.default_app()。get()应用于处理程序。
- default_app上的路由方法都是route()的所有快捷方式。因此default_app()。get(‘ /’ )等同于default_app()。route(method =’ GET’ , ‘ /’ )。
@route装饰器的一项有用功能是, 例如, 如果你希望使用相同的处理程序来处理对象的更新和删除操作, 则只需传递其处理的方法列表, 如下所示:
@route('/names/<
name>
', method=['PUT', 'DELETE'])
def update_delete_handler(name):
'''Handles name updates and deletions'''
pass
好吧, 让我们实现其中一些处理程序。
文章图片
RESTful API是现代Web开发的主要内容。使用Bottle后端为你的API客户提供有效的调和服务。
鸣叫
POST:资源创建 我们的POST处理程序可能如下所示:
import re, jsonnamepattern = re.compile(r'^[a-zA-Z\d]{1, 64}$')@post('/names')
def creation_handler():
'''Handles name creation'''try:
# parse input data
try:
data = http://www.srcmini.com/request.json()
except:
raise ValueErrorif data is None:
raise ValueError# extract and validate name
try:
if namepattern.match(data['name']) is None:
raise ValueError
name = data['name']
except (TypeError, KeyError):
raise ValueError# check for existence
if name in _names:
raise KeyErrorexcept ValueError:
# if bad request data, return 400 Bad Request
response.status = 400
returnexcept KeyError:
# if name already exists, return 409 Conflict
response.status = 409
return# add name
_names.add(name)# return 200 Success
response.headers['Content-Type'] = 'application/json'
return json.dumps({'name': name})
好吧, 很多。让我们逐部分回顾这些步骤。
身体解析
此API要求用户在主体上发布JSON字符串, 并带有名为” name” 的属性。
【使用Bottle框架构建Rest API】早先从bottle导入的请求对象始终指向当前请求, 并保存所有请求数据。它的主体属性包含请求主体的字节流, 可由能够读取流对象的任何函数(例如读取文件)访问该字节流。
request.json()方法检查请求的标头是否为” application / json” 内容类型, 并解析正文(如果正确)。如果Bottle检测到格式错误的主体(例如:空或内容类型错误), 则此方法返回None, 因此我们引发ValueError。如果JSON解析器检测到格式错误的JSON内容;它引发了一个异常, 我们再次捕获并引发该异常, 这是一个ValueError。
对象解析与验证
如果没有错误, 我们已将请求的正文转换为data变量引用的Python对象。如果我们收到了带有” 名称” 键的字典, 则可以通过data [‘ name’ ]进行访问。如果我们收到没有此键的字典, 则尝试访问它会导致KeyError异常。如果我们没有收到字典以外的任何内容, 则会收到TypeError异常。如果发生这些错误中的任何一个, 我们再次将其重新引发为ValueError, 表示输入错误。
要检查名称键的格式是否正确, 我们应该针对正则表达式掩码(例如我们在此处创建的名称模式掩码)进行测试。如果键名不是字符串, 则namepattern.match()会引发TypeError, 如果不匹配, 则将返回None。
在此示例中, 使用掩码时, 名称必须是ASCII字母数字, 且不得包含1到64个字符的空格。例如, 这是一种简单的验证, 它不会测试包含垃圾数据的对象。可以通过使用诸如FormEncode之类的工具来实现更复杂和完整的验证。
测试存在性
满足请求之前的最后一个测试是给定名称是否已存在于集合中。在结构更复杂的应用中, 该测试可能应该由专用模块完成, 并通过专门的异常向我们的API发出信号, 但是由于我们是直接操作集合, 因此我们必须在此处进行。
我们通过引发KeyError来表示名称的存在。
错误回应
就像请求对象保存所有请求数据一样, 响应对象对响应数据也执行相同的操作。有两种设置响应状态的方法:
response.status = 400
和:
response.status = '400 Bad Request'
在我们的示例中, 我们选择了更简单的形式, 但是第二种形式可以用于指定错误的文字说明。在内部, Bottle将拆分第二个字符串并适当设置数字代码。
成功回应
如果所有步骤都成功, 我们通过将名称添加到set _names, 设置Content-Type响应标头并返回响应来满足请求。该函数返回的任何字符串都将被视为200 Success响应的响应主体, 因此我们只需使用json.dumps生成一个即可。
GET:资源清单 从名称创建开始, 我们将实现名称列表处理程序:
@get('/names')
def listing_handler():
'''Handles name listing'''response.headers['Content-Type'] = 'application/json'
response.headers['Cache-Control'] = 'no-cache'
return json.dumps({'names': list(_names)})
列出名称要容易得多, 不是吗?与名称创建相比, 这里没有太多要做。只需设置一些响应标头并返回所有名称的JSON表示形式, 就可以完成。
PUT:资源更新 现在, 让我们看看如何实现更新方法。它与create方法没有太大区别, 但是我们使用此示例介绍URI参数。
@put('/names/<
oldname>
')
def update_handler(name):
'''Handles name updates'''try:
# parse input data
try:
data = http://www.srcmini.com/json.load(utf8reader(request.body))
except:
raise ValueError# extract and validate new name
try:
if namepattern.match(data['name']) is None:
raise ValueError
newname = data['name']
except (TypeError, KeyError):
raise ValueError# check if updated name exists
if oldname not in _names:
raise KeyError(404)# check if new name exists
if name in _names:
raise KeyError(409)except ValueError:
response.status = 400
return
except KeyError as e:
response.status = e.args[0]
return# add new name and remove old name
_names.remove(oldname)
_names.add(newname)# return 200 Success
response.headers['Content-Type'] = 'application/json'
return json.dumps({'name': newname})
update动作的主体架构与创建动作的主体架构相同, 但是现在我们在URI中还有一个新的oldname参数, 如路由@put(‘ / names / < oldname> ’ )所定义。
URI参数
如你所见, Bottle的URI参数表示非常简单。你可以使用任意数量的参数来构建URI。 Bottle自动从URI中提取它们并将它们传递给请求处理程序:
@get('/<
param1>
/<
param2>
')
def handler(param1, param2):
pass
使用级联路由装饰器, 你可以使用可选参数构建URI:
@get('/<
param1>
')
@get('/<
param1>
/<
param2>
')
def handler(param1, param2 = None)
pass
此外, Bottle允许在URI中使用以下路由过滤器:
- int
仅匹配可以转换为int的参数, 并将转换后的值传递给处理程序:@get(‘ / < param:int> ’ )def handler(param):pass
- float
与int相同, 但具有浮点值:@get(‘ / < param:float> ’ )def handler(param):pass
- re(正则表达式)
仅匹配与给定正则表达式匹配的参数:@get(‘ / < param:re:^ [a-z] + $> ’ )def handler(param):pass
- 路径
以灵活的方式匹配URI路径的子段:@get(‘ / < param:path> / id> ’ )def handler(param):pass匹配:/ x / id, 将x作为参数传递。 / x / y / id, 将x / y作为参数传递。删除:资源删除 与GET方法一样, DELETE方法给我们带来的消息很少。只需注意, 不设置状态就返回None将返回一个带有空主体和200状态代码的响应。
@delete('/names/<
name>
')
def delete_handler(name):
'''Handles name updates'''try:
# Check if name exists
if name not in _names:
raise KeyError
except KeyError:
response.status = 404
return# Remove name
_names.remove(name)
return
最后一步:激活API 假设我们已经将名称API保存为api / names.py, 我们现在可以在主应用程序文件main.py中启用这些路由。
import bottle
from api import namesapp = application = bottle.default_app()if __name__ == '__main__':
bottle.run(host = '127.0.0.1', port = 8000)
请注意, 我们仅导入了名称模块。由于我们已经使用默认应用程序附带的URI装饰了所有方法, 因此无需进行任何进一步的设置。我们的方法已经到位, 随时可以使用。
文章图片
你可以使用Curl或Postman之类的工具来使用API??并对其进行手动测试。 (如果你使用的是Curl, 则可以使用JSON格式化程序使响应看起来不太混乱。)
奖励:跨源资源共享(CORS) 构建REST API的一个常见原因是通过AJAX与JavaScript前端进行通信。对于某些应用程序, 应允许这些请求来自任何域, 而不仅仅是你API的本地域。默认情况下, 大多数浏览器均禁止这种行为, 因此, 让我向你展示如何在Bottle中设置跨域资源共享(CORS), 以实现以下目的:
from bottle import hook, route, response_allow_origin = '*'
_allow_methods = 'PUT, GET, POST, DELETE, OPTIONS'
_allow_headers = 'Authorization, Origin, Accept, Content-Type, X-Requested-With'@hook('after_request')
def enable_cors():
'''Add headers to enable CORS'''response.headers['Access-Control-Allow-Origin'] = _allow_origin
response.headers['Access-Control-Allow-Methods'] = _allow_methods
response.headers['Access-Control-Allow-Headers'] = _allow_headers@route('/', method = 'OPTIONS')
@route('/<
path:path>
', method = 'OPTIONS')
def options_handler(path = None):
return
挂钩装饰器允许我们在每个请求之前或之后调用一个函数。在我们的情况下, 要启用CORS, 我们必须为每个响应设置Access-Control-Allow-Origin, -Allow-Methods和-Allow-Headers标头。这些指示请求者我们将满足指示的请求。
另外, 客户端可以向服务器发出OPTIONS HTTP请求, 以查看它是否确实可以使用其他方法发出请求。在此示例示例中, 我们以200状态代码和空主体响应所有OPTIONS请求。
要启用此功能, 只需将其保存并从主模块导入即可。
包起来 这里的所有都是它的!
在本教程中, 我尝试介绍了使用Bottle Web框架为Python应用程序创建REST API的基本步骤。
你可以通过访问其教程和API参考文档来加深对这个小而强大的框架的了解。
推荐阅读
- 查找使用@RequestMapping注释的函数的完全限定URL
- 测试你的Go App(正确入门)
- 编写代码以重写代码(jscodeshift)
- Web Audio API教程(为什么可以编写代码())
- Webpack或Browserify&Gulp(哪个更好())
- Sass Mixins(保持样式表干燥)
- 好的代码的六诫(编写经受时间考验的代码)
- WordPress开发人员最常犯的10个错误
- PostCSS(Sass高级开发教程)