Tornado 简单入门教程(一)——Demo1

CHENGKANG

前面的话

Demo1是一个简单的博客系统(=。=什么网站都叫系统)。我们从这个简单的系统入手,去了解P+T+M网站的内部逻辑,并记住一些“规则”,方便我们进一步自己开发。

“规则”这个词特意打上了双引号,目的是想借此声明一点:本教程内不会将各语句背后的原理逐一讲明(事实上我也讲不清楚哈哈)。我的着重点将在“怎样快速学会使用这个‘框架’去搭建我们想要的网站”,即“怎样快速上手一个工具”。由于本人在技术上研究不深入不细致,所以用词或者内容上难免有不规范或错误之处,能理解的就自行理解哈。当然愿意斧正的欢迎指出。

对了,本教程默认读者是有web开发基础的,明白“渲染”、“get请求”、“post请求”等分别是什么意思。

讲解模式

基本的是:

  • 列出项目目录结构
  • 展示源码,通过部分源码注释(红色字)讲解
  • 列表项目
  • 根据网站逻辑结合“代码回顾”进行讲解

希望大家复制源码(记得把红字注释删除)根据项目目录结构创建项目,或者直接将附件中的代码包拷到你的项目目录,跟着讲解一步一步试验。

OK,开始。

Demo1项目目录结构

demo1
    demo.py
    -static
        -css
            style.css
        -img
            bg.jpg
            logo.png
    -templates
        index.html
        blog.html

源码

demo.py

#!/usr/bin/env python

# -*- coding: utf-8 -*-
#用于规定字符编码,想要中文正常最好就加上这句

import os.path
import tornado.auth
import tornado.escape
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
#巴拉巴拉import一大堆tornado的东西,反正都有用,原封不动即可

import pymongo
#这里是导入MongoDB

define(“port”, default=8002, help=”run on the given port”, type=int)
#定义监听的端口,随便挑个喜欢的数字吧

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
        (r”/”, MainHandler),
        (r”/blog”, BlogHandler),
        ]

    settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), “templates”),
    static_path=os.path.join(os.path.dirname(__file__), “static”),
    debug=True,
    )

    conn = pymongo.Connection(“localhost”, 12345)
    self.db = conn[“demo”]
    tornado.web.Application.__init__(self, handlers, **settings)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(“index.html”,)

    def post(self):
        import time
        title = self.get_argument(‘title’, None)
        content = self.get_argument(‘content’, None)
        blog = dict()
        if title and content:
            blog[‘title’] = title
            blog[‘content’] = content
            blog[‘date’] = int(time.time())
            coll = self.application.db.blog
            coll.insert(blog)
            self.redirect(‘/blog’)
        self.redirect(‘/’)

class BlogHandler(tornado.web.RequestHandler):
    def get(self):
        coll = self.application.db.blog
        blog = coll.find_one()
        if blog:
            self.render(“blog.html”,
            page_title = blog[‘title’],
            blog = blog,
            )
        else:
            self.redirect(‘/’)

def main():
    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == “__main__”:
    main()

index.html

<!DOCTYPE html>
<html lang=”zh-CN”>    
    <head>        
    <meta charset=”utf-8″>
    <title>B06 Innovation Space</title>
    <link rel=”stylesheet” type=”text/css” href=”{{ static_url(“css/style.css”) }}”>
    <script type=”text/javascript”>
               window.setInterval(function() {
                   go_to();
               },
               100);        
               function go_to() {        
                   if( document.getElementById(“myArticle”).style.height < (document.getElementById(“myArticle”).scrollHeight – 4 ) + “px”)    
                               document.getElementById(“myArticle”).style.height = document.getElementById(“myArticle”).scrollHeight + “px”;
    }
    </script>
    </head>
    <body>
        <div class=”main”>
            <img class=”logo” src=”{{ static_url(“img/logo.png”) }}”>
            <div class=”container”>            
                <h1>欢迎访问B06创新实验室的博客</h1>            
                <div class=”content”>            
                    <form method=”post”>                
                        <p>文章标题:</p>                
                        <input type=”text” class=”Title” name=”title” placeholder=”在这里输入你的标题” />                
                        <p>文章正文:</p>                
                        <textarea type=”text” class=”Article” id=”myArticle” name=”content” placeholder=”在这里输入你的正文”></textarea>                
                        <br/>                
                        <input type=”submit” class=”Article Button Submit” value=”发   布”/>                
                    </form>            
                </div>            
            </div>            
        </div>        
    </body>        
</html>

blog.html

<!DOCTYPE html>    
<html lang=”zh-CN”> 
    <head>        
        <meta charset=”utf-8″>            
        <title>{{ page_title }}</title>            
        <link rel=”stylesheet” type=”text/css” href=”{{ static_url(“css/style.css”) }}”>        
    </head>        
    <body>        
        <div class=”main”>        
            <img class=”logo” src=”{{ static_url(“img/logo.png”) }}”>        
            <div class=”container”>        
                <h1>欢迎访问B06创新实验室的博客</h1>        
                <div class=”content”>        
                    <div class=”Title”>        
                        <p>        
                            {{ blog[‘title’] }}        
                            <span class=”Time”>{{ locale.format_date(blog[‘date’], relative=False) }}</span>        
                        </p>        
                    </div>        
                    <div class=”Article”>            
                        <p>{{ blog[‘content’] }}</p>            
                    </div>        
                </div>            
            </div>            
        </div>        
    </body>    
</html>

部署项目

从头开始说。
部署用Python开发的网站,需要在服务器上运行一个主文件,比如demo1的部署:

打开终端,cd到项目文件夹,执行python demo.py命令,此时python就在设定好的默认端口8002运行了我们这个网站。
demo11.png
代码回顾:

define(“port”, default=8002, help=”run on the given port”, type=int)

此时打开浏览器,访问http://127.0.0.1:8002,我们可以发现网站已经可以正常访问。

再看终端窗口,发现已经接收到了一个get请求。
demo13.png
服务器是怎么样根据我们的请求然后输出给我们相应页面的呢?

Handlers 和 settings

代码回顾:

class Application(tornado.web.Application):
    def __init__(self):
       handlers = [
                   (r”/”, MainHandler),
                   (r”/blog”, BlogHandler),
       ]
       settings = dict(
                   template_path=os.path.join(os.path.dirname(__file__), “templates”),    
                   static_path=os.path.join(os.path.dirname(__file__), “static”),    
                   debug=True,    
                   )

        conn = pymongo.Connection(“localhost”, 12345)    
        self.db = conn[“demo”]
        tornado.web.Application.__init__(self, handlers, **settings)

我们看到在Application中初始化了两个东西(数据库连接另说),分别是handlerssettings

顾名思义settings就是项目的各种设置,其中template_path用于指定我们之后要渲染的html文件的文件夹位置,而static_path用于指定之后要用的的一些引用文件(如css文件、js文件、图片等)的文件夹位置 (我偏不解释debug是干什么的:-) )。
handler,我百度翻译了一下,是“处理者”的意思。它的作用就是处理我们向服务器提交的请求。怎么处理呢?
(r”/”, MainHandler)为例,双引号中间是要访问页面的相对路径,而后面的XxxHandler表示这个路径对应的“处理者类”。初始化如上handlers后,当我们访问http://127.0.0.1:8002/时,我们的请求会被交给MainHandler处理;同样的,当我们访问http://127.0.0.1:8002/blog时,我们的请求将会被交给BlogHandler处理。也就是说handlers用于设定url与处理类间的映射关系。

在之后的几个demo里面,通过对更多handler的设置,我们会慢慢对handler了解得更清楚一些,不要着急。反正大概意思就是一个网址对应一个handler呗。

数据库连接

代码回顾:

class Application(tornado.web.Application):
       ……

       conn = pymongo.Connection(“localhost”, 12345)

       self.db = conn[“demo”]

       tornado.web.Application.__init__(self, handlers, **settings)

然后说一下这个部分。聪明的人一看就知道这是数据库连接嘛。

第一句:参数一,数据库服务器地址,我们现在是本地服务器所以用localhost;参数二,端口号,端口号当然是你本地MongoDB使用的端口号。这样就连接上MongoDB了。

第二句:self.db = conn[“demo”],选择一个数据库,把数据库名字放在双引号中间。我的数据库就叫做demo。最后一句就是把之前的设置都初始化嘛,init就是初始化,聪明人都懂的。

服务器处理请求

OK,回过头来,刚才说到当用户访问一个页面的时候,根据我们初始化的handler,服务器会将不同的请求分发给不同的处理类进行处理。

我们的Demo1包含两个页面。首页有一个表单,提交表单后跳转至博客页面。我们先来看看访问首页时是怎么处理的。

当我们访问http://127.0.0.1:8002/时,我们的请求会被交给MainHandler处理,即执行这个类里对应的内容。

代码回顾:

class MainHandler(tornado.web.RequestHandler):

处理类的定义规则是class XxxxHandler(tornado.web.RequestHandler)Request请求,Handler处理者,处理请求的一个类嘛。括号里面的东西肯定跟tornado、跟网络什么的有关,乖乖地复制就好了。

代码回顾:

def get(self):#用于处理get请求,默认参数是self

   self.render(“index.html”,)

def post(self):#用于处理post请求,默认参数是self
   import time
   title = self.get_argument(‘title’, None)
   content = self.get_argument(‘content’, None)
   blog = dict()
   if title or content:
       blog[‘title’] = title
       blog[‘content’] = content
       blog[‘date’] = int(time.time())
       coll = self.application.db.blog
       coll.insert(blog)
       self.redirect(‘/blog/’)
       self.redirect(‘/’)

我们看到MainHandler类里定义了getpost两个方法。

学习tornado让我很感动的一点就是,它让我明白了其实我们在访问网页的时候归根结底就是向网站发出了两种请求(这个认识可能比较浅薄)。

一种是get请求,即打开某个页面。
另一种是post请求,即向某个页面提交表单(数据)。

tornado中,默认使用getpost函数分别处理两种请求。所以当我们访问http://127.0.0.1:8002/时,服务器就会执行MainHandler中的get函数。

代码回顾:

def get(self):
    self.render(“index.html”,)

render就是渲染的意思。这一句就是:在浏览器中渲染出index.html这个文件的内容。index.html文件在哪里呢?从项目目录结构中可以看到,它在templates文件夹中。还记得settings中定义的templates_path吗?我们在render()中的双引号内填入templates文件夹中文件的相对路径即可,服务器会根据templates_path自动补全路径,找到文件,并将之渲染出来。

插一句:在用tornado开发时,我们常用的目录结构正如demo1的目录结构。根目录下放置python文件,templates文件夹中放置html文件,static文件夹中放置引用文件。

所以,当我们访问http://127.0.0.1:8002/时,最终看到的就是下图:
demo12.png
代码回顾:

<form method=”post”>

此时,我们看到了首页的表单。来看看index.html的源码:表单的提交方式是post;请求的页面(action)没有填写,表示提交到当前页面。现在将表单填充,点击发布。
demo14.png
此时查看终端窗口,我们发现,服务器收到了一个post请求。
demo15.png
因为表单仍提交到当前页面,所以还是由MainHandler处理。而此时的请求类型为post,所以服务器将执行MainHandler中的post方法。

代码回顾:

def post(self):
    import time
    title = self.get_argument(‘title’, None)
    content = self.get_argument(‘content’, None)
    blog = dict()
    if title and content:
                blog[‘title’] = title
                blog[‘content’] = content
                blog[‘date’] = int(time.time())
                coll = self.application.db.blog
                coll.insert(blog)
                self.redirect(‘/blog’)

tornado中通过self.get_argument()获取表单数据,其中第一个参数为数据名称,第二个参数为当没有获取到该表单数据时的替代值。
post方法分别获取表单中titlecontent两个数据,进行简单的判断,当二者均不为空时,将其存入预定义的blog变量中,并且给blog[‘date’]赋值为当前的时间戳。
import time载入时间相关的的一个类,time.time()获取当前时间戳。 coll = self.application.db.blog获取数据库中的名为blogcollection
coll.insert(blog)blog变量插入collection中。
self.redirect(‘/blog’)页面跳转至博客页。在使用redirect函数时,参数为页面相对路径。

简单来说:post方法接收了表单数据并将其插入对应数据集中,然后页面跳转到博客页。

页面跳转,本质就是访问跳转后的页面,即向此页面发送get请求。我们看一下终端窗口:
demo16.png
也就是说self.redirect(‘/blog’)这一句,就是访问http://127.0.0.1:8002/blog,服务器得到get请求,然后让BlogHandler对其进行处理,执行get方法。

代码回顾:

def get(self):
    coll = self.application.db.blog
    blog = coll.find_one()
    if blog:
        self.render(“blog.html”,
                    page_title = blog[‘title’],
                    blog = blog,
                    )
    else:
        self.redirect(‘/’)

那我们看看函数内容。

coll = self.application.db.blog依旧是获取名字为blog的数据集 blog = coll.find_one()获取一条数据。因为我们是一个最简版的demo,所以就获取一条数据。
if判断数据是否存在,存在则渲染博客页面,不存在则跳转至首页。
render函数第一个参数是要渲染的html文件名,后面的参数为传递到页面的数据,参数间用逗号隔开。
page_title = blog[‘title’]我选择用博客标题作为title,所以传了一个page_title过去。 blog = blog把博客内容传递过去。

什么叫把数据传递到html文件去呢?就是把我们动态获取的数据库数据和html文件一起渲染,把数据在html代码中输出来。

For一个sample:-):

代码回顾:

<div class=”Title”>
    <p>
        {{ blog[‘title’] }}
        <span class=”Time”>{{ locale.format_date(blog[‘date’], relative=False) }}</span>
    </p>
</div>
<div class=”Article”>
    <p>{{ blog[‘content’] }}</p>
</div>

在html文件中可以通过特殊语法输出我们传递过来的数据。

{{ 变量名 }},双大括号中加上变量名,就是输出该变量。比如:{{ blog[‘title’] }}就是输出blog中的title值;{{blog[‘content’] }}就是输出blog中的content值;还有{{ page_title }}就是输出page_title的值。

(关于Tornadohtml文件里面对Python语句的使用方法,我会在另外一篇总结中写出来。链接:《Tornado,在模板里使用Python语句》

locale.format_date()是一个时间格式化函数。locale.format_date(blog[‘date’], relative=False) }}是将blog[‘date’]的值格式化输出。
所以最终渲染出的页面如下图:
demo17.png


至此,一个最简单的博客系统就完成了。

阅读 40.8k

CHENGKANG
小学生。

LOADING...

1k 声望
65 粉丝
0 条评论

LOADING...

1k 声望
65 粉丝
文章目录
宣传栏