4.2 一个简单的持久化 Web 服务
现在我们知道编写一个 Web 服务,可以访问 MongoDB 数据库中的数据。首先,我们要编写一个只从 MongoDB 读取数据的 Web 服务。然后,我们写一个可以读写数据的服务。
4.2.1 只读字典
我们将要创建的应用是一个基于 Web 的简单字典。你发送一个指定单词的请求,然后返回这个单词的定义。一个典型的交互看起来是下面这样的:
$ curl http://localhost:8000/oarlock
{definition: "A device attached to a rowboat to hold the oars in place",
"word": "oarlock"}
这个 Web 服务将从 MongoDB 数据库中取得数据。具体来说,我们将根据 word 属性查询文档。在我们查看 Web 应用本身的源码之前,先让我们从 Python 解释器中向数据库添加一些单词。
>>> import pymongo
>>> conn = pymongo.Connection("localhost", 27017)
>>> db = conn.example
>>> db.words.insert({"word": "oarlock", "definition": "A device attached to a rowboat to hold the oars in place"})
ObjectId('4eb1d1f8136fc4be90000000')
>>> db.words.insert({"word": "seminomadic", "definition": "Only partial
ly nomadic"})
ObjectId('4eb1d356136fc4be90000001')
>>> db.words.insert({"word": "perturb", "definition": "Bother, unsettle
, modify"})
ObjectId('4eb1d39d136fc4be90000002')
代码清单 4-1 是我们这个词典 Web 服务的源码,在这个代码中我们查询刚才添加的单词然后使用其定义作为响应。
代码清单 4-1 一个词典 Web 服务:definitions_readonly.py
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import pymongo
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [(r"/(\w+)", WordHandler)]
conn = pymongo.Connection("localhost", 27017)
self.db = conn["example"]
tornado.web.Application.__init__(self, handlers, debug=True)
class WordHandler(tornado.web.RequestHandler):
def get(self, word):
coll = self.application.db.words
word_doc = coll.find_one({"word": word})
if word_doc:
del word_doc["_id"]
self.write(word_doc)
else:
self.set_status(404)
self.write({"error": "word not found"})
if __name__ == "__main__":
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
在命令行中像下面这样运行这个程序:
$ python definitions_readonly.py
现在使用 curl 或者你的浏览器来向应用发送一个请求。
$ curl http://localhost:8000/perturb
{"definition": "Bother, unsettle, modify", "word": "perturb"}
如果我们请求一个数据库中没有添加的单词,会得到一个 404 错误以及一个错误信息:
$ curl http://localhost:8000/snorkle
{"error": "word not found"}
那么这个程序是如何工作的呢?让我们看看这个程序的主线。开始,我们在程序的最上面导入了 import pymongo 库。然后我们在我们的 TornadoApplication 对象的 init 方法中实例化了一个 pymongo 连接对象。我们在 Application 对象中创建了一个 db 属性,指向 MongoDB 的 example 数据库。下面是相关的代码:
conn = pymongo.Connection("localhost", 27017)
self.db = conn["example"]
一旦我们在 Application 对象中添加了 db 属性,我们就可以在任何 RequestHandler 对象中使用 self.application.db 访问它。实际上,这正是我们为了取出 pymongo 的 words 集合对象而在 WordHandler 中 get 方法所做的事情。
def get(self, word):
coll = self.application.db.words
word_doc = coll.find_one({"word": word})
if word_doc:
del word_doc["_id"]
self.write(word_doc)
else:
self.set_status(404)
self.write({"error": "word not found"})
在我们将集合对象指定给变量 coll 后,我们使用用户在 HTTP 路径中请求的单词调用 find_one 方法。如果我们发现这个单词,则从字典中删除_id 键(以便 Python 的 json 库可以将其序列化),然后将其传递给 RequestHandler 的 write 方法。write 方法将会自动序列化字典为 JSON 格式。
如果 find_one 方法没有匹配任何对象,则返回 None。在这种情况下,我们将响应状态设置为 404,并且写一个简短的 JSON 来提示用户这个单词在数据库中没有找到。
4.2.2 写字典
从字典里查询单词很有趣,但是在交互解释器中添加单词的过程却很麻烦。我们例子的下一步是使 HTTP 请求网站服务时能够创建和修改单词。
它的工作流程是:发出一个特定单词的 POST 请求,将根据请求中给出的定义修改已经存在的定义。如果这个单词并不存在,则创建它。例如,创建一个新的单词:
$ curl -d definition=a+leg+shirt http://localhost:8000/pants
{"definition": "a leg shirt", "word": "pants"}
我们可以使用一个 GET 请求来获得已创建单词的定义:
$ curl http://localhost:8000/pants
{"definition": "a leg shirt", "word": "pants"}
我们可以发出一个带有一个单词定义域的 POST 请求来修改一个已经存在的单词(就和我们创建一个新单词时使用的参数一样):
$ curl -d definition=a+boat+wizard http://localhost:8000/oarlock
{"definition": "a boat wizard", "word": "oarlock"}
代码清单 4-2 是我们的词典 Web 服务的读写版本的源代码。
代码清单 4-2 一个读写字典服务:definitions_readwrite.py
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import pymongo
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [(r"/(\w+)", WordHandler)]
conn = pymongo.Connection("localhost", 27017)
self.db = conn["definitions"]
tornado.web.Application.__init__(self, handlers, debug=True)
class WordHandler(tornado.web.RequestHandler):
def get(self, word):
coll = self.application.db.words
word_doc = coll.find_one({"word": word})
if word_doc:
del word_doc["_id"]
self.write(word_doc)
else:
self.set_status(404)
def post(self, word):
definition = self.get_argument("definition")
coll = self.application.db.words
word_doc = coll.find_one({"word": word})
if word_doc:
word_doc['definition'] = definition
coll.save(word_doc)
else:
word_doc = {'word': word, 'definition': definition}
coll.insert(word_doc)
del word_doc["_id"]
self.write(word_doc)
if __name__ == "__main__":
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
除了在 WordHandler 中添加了一个 post 方法之外,这个源代码和只读服务的版本完全一样。让我们详细看看这个方法吧:
def post(self, word):
definition = self.get_argument("definition")
coll = self.application.db.words
word_doc = coll.find_one({"word": word})
if word_doc:
word_doc['definition'] = definition
coll.save(word_doc)
else:
word_doc = {'word': word, 'definition': definition}
coll.insert(word_doc)
del word_doc["_id"]
self.write(word_doc)
我们首先做的事情是使用 get_argument 方法取得 POST 请求中传递的 definition 参数。然后,就像在 get 方法一样,我们尝试使用 find_one 方法从数据库中加载给定单词的文档。如果发现这个单词的文档,我们将 definition 条目的值设置为从 POST 参数中取得的值,然后调用集合对象的 save 方法将改变写到数据库中。如果没有发现文档,则创建一个新文档,并使用 insert 方法将其保存到数据库中。无论上述哪种情况,在数据库操作执行之后,我们在响应中写文档(注意首先要删掉_id 属性)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论