返回介绍

4.2 一个简单的持久化 Web 服务

发布于 2025-03-08 19:24:50 字数 6802 浏览 0 评论 0 收藏 0

现在我们知道编写一个 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文