Django实战:Python Web典型模块与项目开发
上QQ阅读APP看书,第一时间看更新

第1章 Python和Web开发框架

Django是一个开放源代码的Web开发框架,完全用Python开发。它对常用的Web开发模式进行了高度封装,为常见的编程任务提供了捷径;通过减少重复的代码,使程序员能够专注于Web 应用上的关键性的业务开发。因此使用 Django 能在较短的时间内构建并维护质量上乘的Web应用。Django必须运行在Python环境中,可见二者密不可分。

1.1 Python简介

Python由吉多·范罗苏姆(Guido van Rossum)创造,Python被设计成一种跨平台的计算机程序设计语言,是一种面向对象的动态类型语言。自20世纪90年代初Python诞生至今,它已广泛应用于系统管理的任务处理和Web编程。

Python的设计理念是“优雅”“明确”“简单”,它是一种功能强大的编程语言,主要有以下特点。

●Python具有解释型、交互式、面向对象这3个特征。

●Python有极其简单、明确的语法,关键字较少,结构简单。

●Python可跨平台,在Linux、Windows和macOS等操作系统中都能很好地运行。

●Python提供所有主流的商业数据库的接口。

●Python提供了一个很好的结构,支持大型程序开发。

●Python是自由/开放源码的软件之一。

1.2 Web开发框架基本知识

Web开发框架是用于Web开发的成套软件架构。Web开发框架会为Web应用提供成套的功能支持,即一套开发和部署网站的方案。使用Web开发框架,程序员可以只关注业务逻辑代码的编写,其他功能使用框架已有的功能即可,这减少了程序员的代码编写量。

Web服务本质上是由socket(socket是一种通信机制,通过绑定IP地址和端口产生一个通信链,实现计算机间的通信)服务端向socket客户端提供HTTP响应,而浏览器就是一个socket客户端,它向Web发出请求。Django本身是一个Web开发框架,它连接socket两端(服务端、客户端)进行数据交换,当然这种交换按照指定的协议进行,也就是HTTP(Hyper Text Transfer Protocol,超文本传输协议)。

1.2.1 Web应用本质

网络中不同的计算机间进行通信必须经过IP地址和端口。为了降低网络通信开发的复杂度,人们在TCP/IP 4层结构中的应用层与传输层之间加了一层,这个层就是socket层。它把复杂的TCP/IP进行了封装,并提供了一组服务的接口。

网络中服务器主机会提供一种或多种服务,每一种服务打开一个socket,并绑定到一个端口上,也就是说不同的端口对应于不同的服务(如Web服务一般用到80端口),客户端向那个端口发送请求,就会得到相应的服务。

当用户在浏览器地址栏中输入网址(URL,即Uniform Resource Locator,统一资源定位符)并按下Enter键,这个动作称为发送Web请求,在网络上会有一台与网址相对应的服务器按用户请求做出响应,把请求资源发送给用户。这台接收Web请求并做出响应的服务器称为Web服务器,它把用户请求的资源以HTML(Hyper Text Markup Language,超文本标记语言)文件的形式传递到用户的浏览器中,用户就看到网页了。

如上所述,Web应用主要做的事情就是发送HTML文件到浏览器,其核心功能则通过socket服务完成。因此,Web服务器本质上是一个socket服务端,而浏览器本质上是一个socket客户端。

以下用代码来简单说明Web开发框架的运行方式。

        # 导入socket模块
        import socket
        # 建立socket服务
        sk=socket.socket()
        # 绑定IP与端口号,这是绑定本机端口
        sk.bind(('127.0.0.1',8000))
        # 进行监听
        sk.listen()
        print('socket服务开始运行……')
        while True:
          # 接收socket客户端连接
          conn,addr=sk.accept()
          # 接收socket客户端数据
          data=conn.recv(1024)
          # print(data)
          # 向客户端发送消息,字符串前加字母b表示以字节形式传递
          conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
          # 向客户端发送消息,bytes()函数把字符串转换成字节形式
         conn.send(bytes("我是socket服务端,我已接到你的请求。",encoding='utf-8'))
  

以上代码主要实现如下过程。

(1)建立socket服务,绑定IP与端口号,并启动监听进程,这样就把本地计算机设置成socket服务端。

(2)服务启动后,通过循环语句,持续接收浏览器(socket客户端)发送的信息。

(3)socket服务端与浏览器以字节形式在网络上传递消息,在发送字符串前必须将其转化成字节形式。

(4)socket 服务端与浏览器的消息传递必须依照HTTP 格式,conn.send(b"HTTP/1.1 200 OK\r\n\r\n")这句代码把字符串按照HTTP格式向浏览器传递,主要格式为HTTP/1.1 200 OK,字符串后面跟两对回车符和换行符“\r\n\r\n”,这样其后的字符串就能显示在浏览器中。

在命令行终端输入python test1.py运行代码启动socket服务,如图1.1所示。

图1.1 启动socket服务

这时在浏览器中输入http://127.0.0.1:8000/,socket服务端收到请求后,返回相关信息。由于是按照HTTP格式返回信息,所以信息能显示在浏览器上,如图1.2所示。

图1.2 浏览器显示socket服务端发回的信息

1.2.2 Web开发框架核心功能

1.2.1 节中的代码没有实现根据浏览器地址栏中的URL不同而做出不同响应,本节我们对代码进行改进与完善,实现Web开发框架核心功能,完善后的代码如下。

            import socket
            def index(url):
              # 读取文件,并对占位符进行替换
              # with用法:在退出with代码块后自动关闭with打开的文件
              with open('index.html', 'r',encoding='utf-8') as f:
                rd = f.read()
                rd = rd.replace("$@index$@", "首页")
              # 替换后的文本以字节形式返回
              return bytes(rd,encoding='utf-8')
            def test(url):
              with open('test.html', 'r',encoding='utf-8') as f:
                rd = f.read()
                rd = rd.replace("$@test$@", "测试")
              return bytes(rd,encoding='utf-8')
            def fun404(url):
              ret = "<h1>not found!</h1>"
              return bytes(ret, encoding='utf-8')
            # 定义变量url_func,建立了URL与函数名的对应关系
            url_func=[
              ("/index/",index),
              ("/test/",test),
            ]
            # 建立socket服务
            sk=socket.socket()
            # 绑定IP与端口号,这里是绑定本机端口
            sk.bind(('127.0.0.1',8000))
            # 进行监听
            sk.listen()
            print('socket服务开始运行……')
            while True:
              # 接收socket客户端连接
              conn, addr = sk.accept()
              """
              下面语句中data变量接收socket客户端(浏览器)数据,这个数据有固定格式
              数据是HTTP请求数据格式,第一行格式为GET /index/ HTTP/1.1\r\n
  

该行以\r\n结尾,各字符串以空格分隔

              """
              data = conn.recv(1024)
              # 输出socket服务端接收的浏览器发来的消息格式
              print(data)
              if not data:
              # 如果客户端没有发送新的数据,就重新开始,不再向下执行
              # 防止后面语句对空字符进行操作而抛出异常
                continue
              # 把收到的数据由字节形式转换成字符串,一般用到的编码格式为utf-8
              data_str = str(data, encoding='utf-8')
              # 以\r\n分隔每一行
              line = data_str.split("\r\n")
              # print(line[0])
              # 取出第一行字符串(line[0]),然后用空格再次分隔字符串
              # 提示:在Django中的索引从0开始
              v1 = line[0].split()
              # 取出路径,路径字符串在第2个位置上(以空格分隔)
              url = v1[1]
              """
              向客户端发消息,字符串前加字母b表示以字节形式传递
              在HTTP/1.1 200 OK\r\n\r\n之后的内容以HTTP格式显示在浏览器中
              """
              conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
              func=None
              """
              用for循环取出url_func中的每一项,它是由URL和函数名组成的元组向客户端发消息
              """
              for i in url_func:
                if i[0] == url:
                  # 取出对应函数名
                  func = i[1]
                  break
              if func:
                func = func
              else:
                func = fun404
              # 函数名加上括号,表示执行函数
              rep = func(url)
              # 把函数返回的值向客户端发送
              conn.send(rep)
              conn.close()
  

上述代码的相关说明如下。

(1)以上代码定义了两个函数——index()和test(),还定义了一个列表类型的变量,列表中每项都是元组,其列出URL与函数名的对应关系。程序流程主要是:根据传入的参数(URL),读取相应的HTML文件,并根据占位符(本例中用两个$@包含一个变量名表示一个占位符,形如$@index$@)进行替换实现网页动态显示。

(2)index()函数和test()函数读取相应的HTML文件并进行占位符替换,同时以字节形式返回替换后的文本,进行替换的HTML文件index.html所含代码如下,请注意占位符$@index$@的位置。

            <!DOCTYPE html>
            <html lang="en">
            <head>
            <meta charset="UTF-8">
            <title>index页面</title>
            </head>
            <body>
              <h1>$@index$@</h1>
            </body>
            </html>
  

test.html的代码与index.html的代码相似,此处不再列举。

(3)代码还增加了一个fun404()函数来处理无对应关系的路径。

(4)在while True代码块中增加了对浏览器(socket客户端)传来的消息的处理,解析出浏览器地址栏中URL的路径,要正确解析路径必须了解浏览器传给socket服务端的消息格式。这里以在浏览器地址栏中输入http://127.0.0.1:8000/index/为例,通过print语句可以看到socket服务端收到的消息格式如下。

            GET /index/ HTTP/1.1\r\n
            Host: 127.0.0.1:8000\r\n
            Connection: keep-alive\r\n
            User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
            Chrome/63.0.3239.132 Safari/537.36\r\n
            Accept-Encoding: gzip, deflate, br\r\n
            Accept-Language: zh-CN,zh;q=0.9\r\n\r\n
  

可以看到消息格式以\r\n分隔每行,每行中各项再以空格进行分隔,通过这个格式即可理解代码是如何解析路径的。

运行python test2.py以进行测试,实现了针对不同请求进行响应的过程,如图1.3所示。

图1.3 socket服务端对浏览器的不同请求做出不同响应

1.2.3 HTTP简单介绍

为了使读者能有效地理解代码,这里有必要简单介绍一下HTTP。HTTP就是浏览器(客户端)与Web服务器交流的语言,它是一种双方都认可的格式或规则,也就是这种语言是双方都能“听得懂”的语言,因此协议就是一种格式,这种格式让双方都知道对方想表达什么意思、想做什么事。

HTTP消息格式有请求和响应两种,HTTP请求和响应都包含Header和Body两部分,其中Body是可选的。

1.2.4 HTTP请求消息格式

HTTP请求(Request)消息包含请求头(Header)和请求体(Body)。请求头每行以“\r\n”结尾,请求头第一行以空格分隔的字符串分别代表请求方法、路径、HTTP等信息。第二个字符串就是路径,是一个较为重要的字符串,由此可推知浏览器地址栏中的URL。请求头从第二行开始都是“头字段名:值\r\n”的形式。请求头与请求体之间以“\r\n”分隔,请求体可以有也可以没有。以下是HTTP请求消息格式的示意代码。

            请求方法 路径 HTTP/1.1\r\n # 请求方法包括GET、POST等
            头字段名:值\r\n
            头字段名:值\r\n
            …
            头字段名:值\r\n
            \r\n
            请求体 # 请求体可以有,可以没有
  

以下是实例代码,其中,GET 表示请求方式,请求的路径是/index/,应用的协议是HTTP,协议的版本是1.1。

            GET /index/ HTTP/1.1\r\n
            Host: 127.0.0.1:8000\r\n
            Connection: keep-alive\r\n
            User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
            Chrome/63.0.3239.132 Safari/537.36\r\n
            Accept-Encoding: gzip, deflate, br\r\n
            Accept-Language: zh-CN,zh;q=0.9\r\n
            \r\n
            … # 请求体可以有,可以没有
  

1.2.5 HTTP响应消息格式

HTTP响应(Response)消息包含响应头(Header)和响应正文(Body)。响应头每行以“\r\n”结尾,响应头第一行包含代表HTTP、状态码和状态描述符等信息的3个字符串,这3个字符串以空格作为分隔符。响应头从第二行开始都是“头字段名:值\r\n”的形式。响应头与响应正文之间以“\r\n”分隔,响应正文就是显示在浏览器中的HTML格式的内容。以下是HTTP响应消息格式的示意代码。

            HTTP/1.1 状态码 状态描述符\r\n
            头字段名:值\r\n
            头字段名:值\r\n
            …
            头字段名:值\r\n
            \r\n
            响应正文 # 响应正文,就是HTML格式的内容
  

以下是实例代码,其中,HTTP/1.1表示HTTP的版本是1.1,状态码200表示响应正常,OK是响应成功的描述字符串,另外还有服务器信息Server、响应时间Date等内容。

            HTTP/1.1 200 OK\r\n
            Server: openresty/1.9.15.1\r\n
            Date: Fri, 05 Jul 2019 07:58:16 GMT\r\n
            Content-Type: text/html; charset=utf-8\r\n
            Transfer-Encoding: chunked\r\n
            \r\n
            … # 响应正文
  

1.3 Python Web开发框架

1.2节我们用Python代码的形式解释了如何用socket来实现Web开发框架的流程,Web开发框架的本质就是用HTTP实现socket服务端与浏览器的通信功能。这些功能可以概括为3步。

(1)socket服务端与客户端(浏览器)收/发socket消息,按照HTTP来解析消息。

(2)建立URL与要执行的函数的对应关系,这里的函数包含业务逻辑代码。

(3)载入HTML文件当作模板,对其中特殊符号标识的字符串进行替换并发给浏览器显示。

不理解或看不懂本章关于Web开发框架的代码对于Django开发影响不太大,读者不用担心。对该代码的理解有利于从Web开发本质与原理层面理解Django,会提高Django开发效率。

Python中的Web框架一般实现3种核心功能。

●收发消息(socket功能)。

●根据用户不同路径执行不同的函数。

●从HTML文件中取出内容,并且完成字符串的替换。

目前主流的Python Web开发框架主要有Django、Tornado和Flask这3种。

① Django是目前最流行的Web开发框架之一,该框架包含以上3种核心功能中的第二、第三种功能,这两种功能可以很容易地通过编写代码或配置来实现,第一种功能使用第三方工具实现。Django是Python中最全能的Web开发框架之一,功能完备,在可维护性和开发速度上具有优势。

② Tornado包含以上3种核心功能,但需要开发人员通过代码实现。该框架最大的特点是采用异步处理,是非阻塞式、高并发处理框架,性能强大,可以每秒处理数以千计的连接。Tornado是实现实时Web服务的理想框架。其缺点是相比于Django,诸多内容需要开发人员自己去编写。随着项目越来越大,Tornado将有越来越多的功能需要开发人员来实现。

③ Flask可实现以上3种核心功能中的第二种功能,第一、第三种功能使用第三方工具实现,是轻量级的开发框架。Flask的特点是使用简单的核心,并使用插件扩展其他功能,因此Flask是一个面向简单需求和小型应用的微框架。

1.4 小结

本章简单介绍了Python的特点,并介绍了Web开发框架基本知识、HTTP以及常见的Python Web开发框架,使读者对Django的原理有所了解。