之前Python程序语言,前端的三剑客HTML+CSS+JS以及各种JS框架都学过了.也用Python写过简单的socket程序,现在终于要开始写正式的web应用,将前后端联系起来了.
在互联网上的web通信,基于的协议是HTTP协议,而不是我们通过socket自定义写的客户端和服务器.所有的web浏览器,都是支持HTTP协议的,我们做的网站,也必须支持HTTP协议.这里讲了HTTP协议的一些知识,还买了图解HTTP这本书来参考.
这里关键是要理解HTTP通信分为请求和响应两种数据格式.

HTTP请求:
HTTP请求

HTTP响应:
HTTP响应

之前刚学web的时候,其实写过一个HTTP协议的socket简略版,这里可以回顾一下当时的内容.

通过编写符合HTTP协议要求的socket程序,逐步从最简单的返回字符串,到返回网页,再到通过替换字符串的方式返回动态网页(当然显示时间这种功能一般通过JS在浏览器里实现),逐步模拟了web服务器的方式.

WEB框架

由于Web通信的协议已经是HTTP,而浏览器是HTTP客户端,对于我们来讲,只要编写符合HTTP标准的服务端即可.

Web框架实际上,就是一个提供了socket服务端的基本工具程序,供你开发自己的web服务端程序.

Web框架的功能有:

  1. 负责与浏览器收发消息(socket通信)
  2. 根据用户访问不同的路径和信息执行不同的函数
  3. 从HTML文件中读取内容(或者生成HTML数据),并且完成字符串的替换

Python中Web框架的分类:

  • 框架自带1,2,3全部功能                 –> Tornado
  • 框架自带2和3,使用别人的1          –> Django
  • 框架自带2,使用第三方的1和3      –> Flask

为何要学Django开发,因为Django相比Flask或者其他框架,提供的功能更加全面,可以涉及到Web开发的各个方面.

Django初探

前边已经知道,Django需要第三方的socket通信服务.这个通信服务并不是Python标准库的socket模块,因为socket模块提供的是基础的socket编程,只适用到工作在传输层的TCP和UDP协议,发送和接收的具体内容都需要自己设定.而HTTP是一个应用层的协议,并且已经有了适合HTTP协议的浏览器,因此Django使用的是WSGI(Web Server Gateway Interface)Web服务器模块来与浏览器进行通信,Python标准库的WSGI模块是wsgiref. 有了wsgiref,直接就可以建立起符合HTTP协议的服务器, 而无需再关心相对底层的socket.

用WSGIREF模块简单写一个服务器

from wsgiref.simple_server import make_server


def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # 设置HTTP响应的状态码和头信息
    url = environ['PATH_INFO']  # 取到用户输入的url

    return [b'this is WSGI server', ]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8090, run_server)
    httpd.serve_forever()

安装Django和运行Django服务

Django官网上列出了目前的最新版本和LTS(长期服务支持版本).目前的LTS版本是1.11(支持到python3.6),下一个LTS版本是2.2,会在3年以后发布,所以现在就开始学习1.11版本的Django吧.
在windows下边安装Django相对简单,我们是通过Pycharm来安装,注意勾选将Django安装到Python的第三方库目录中,然后选择安装特定版本-1.11.x版本即可.
在CentOS下安装,CentOS默认的python版本是2.7,虽然Django 1.11版本也支持2.7,但是为了后端写起来方便,最好升级到3.6版本,再安装Django,具体可以看这里.

Django安装完之后,会在python的第三方库目录下边(python/scripts)下边生成django-admin.exe和django-admin.py文件.在命令行下使用:

django-admin startproject mysite

就可以创建一个名为mysite的django项目.这个项目的目录会位于操作系统的用户目录之下.目录结构如下:

mysite/
├── manage.py  # 管理文件
└── mysite  # 项目目录
    ├── __init__.py
    ├── settings.py  # 配置
    ├── urls.py  # 路由 --> URL和函数的对应关系
    └── wsgi.py  # runserver命令就使用wsgiref模块做简单的web server

建立项目之后,就可以通过命令行来启动Django服务器:

python manage.py runserver 127.0.0.1:8000

注意,在有了项目之后,对该项目的运行和管理,就是通过项目目录中的manage.py来进行了,在命令行下用python解释器执行manage.py带上参数即可.刚才的命令就启动了本地IP地址8000端口的Web服务,此时连接一下,便可以发现有响应,说明Django项目建立成功.在建立第一个站点的过程中,会来逐步学习项目内部的各个文件使用.

Pycharm中建立Django项目和启动Django服务

命令行下使用Django不太方便,Pycharm专业版直接支持Django开发.
Pycharm中安装好Django之后,新建一个Django项目.解释器直接选下边的解释器,暂时还不需要使用vlenv来搭建虚拟环境.最好选择一个干净的目录作为项目目录.
之后可以发现Pycharm自动打开了settings.py和urls.py文件供编辑.
settings.py保存了这个Django项目的设置,而urls.py保存了这个网站不同的路径对应的功能(函数),本质就和之前编写的简单版的web框架类似.
启动Django服务,由于是一个项目,所以并不是启动seetings.py和urls.py这些Django内部的文件,Pycharm把命令行模式启动Django服务的方式做到了右上角启动Django站点的小三角图标里.
此时先点启动,可以看到执行结果里出现:

D:\Software\PyCharm\bin\runnerw.exe C:\Users\Minko\AppData\Local\Programs\Python\Python36\python.exe D:/Mysite/manage.py runserver 8000
Performing system checks...

System check identified no issues (0 silenced).

You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
July 10, 2018 - 10:47:08
Django version 1.11.14, using settings 'Mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

实际上就是Pycharm代替我们自己在命令行里执行了刚才的启动服务器的命令.还有一些提示,缺少一些模块应用,先不用管.可以看到系统提示服务运行时间,Django版本,用的设置,以及服务的IP地址和端口.
之后在浏览器里尝试连接,浏览器显示:

It worked!
Congratulations on your first Django-powered page.
Next, start your first app by running python manage.py startapp [app_label].
You're seeing this message because you have DEBUG = True in your Django settings file and you haven't configured any URLs. Get to work!

说明成功连接到Django服务器,虽然有一些提示表示这个站点还没有任何功能,但Django服务已经正常运行了.

返回一个HTML页面

Django 服务已经完成了,现在来建立第一个站点,只要能返回一个HTML页面,就可以说建立了第一个站点.
在Pycharm的项目里,找到urls.py文件,这个是页面路径与对应函数的关系,在里面试着写自己的第一个函数.

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

这个urlpatterns是一个列表,里边用url参数形式记录了正则匹配的路径和对应的函数关系,以此来访问不同的路径激活不同的功能模块,来添加一下路径和自定义的函数:

from django.shortcuts import HttpResponse

def myfunc(request):
    '''request参数保存了所有和用户浏览器请求相关的数据'''
    return HttpResponse(
        '<h1 style="color: maroon; text-align: center;">First Message</h1>') 
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^minko/', myfunc), ]

导入的HttpResponse,是将函数返回的信息封装成HTTP协议的消息,不用自己去将结果再处理成HTTP的方式.在mufunc的返回值里写上想要返回给浏览器的响应数据,浏览器就可以拿到结果.
urlpatterns里加上了我们自定义的/minko/路径和mufunc函数名,告诉Django当浏览器访问 127.0.0.1:8000/minko/的时候,执行myfunc函数,并且将结果返回给浏览器.
Django是动态的,在完成上述代码之后,无需重新启动Django服务,直接用浏览器访问,即可得到First Message以H1标题大小居中显示的页面.

可以发现,返回的字符串实际上是HTTP响应的正文部分,而且是按照text/html来解释的,那么就想到,其实可以传递一个页面.
将之前写的test.html页面放到站点文件夹下边的templates目录,然后编写一个函数并且增加到urlpatterns中:

def index_html(request):
    with open("./templates/test.html", "r", encoding="utf-8") as f:
        data = f.read()
    return HttpResponse(data)

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^minko/', myfunc),
    url(r'^index/', index_html),
]

用浏览器访问127.0.0.1:8000/index,发现返回了页面,但是样式全部丢失了.这里引出了两个问题:

  1. 检查了test.html文件,发现其中的样式是绝对路径,并没有通过cdn加速来引用,那么引用的文件需要放在哪里呢?
  2. Django肯定考虑到了文件的传输,有没有更简便的方法传输文件呢?

这个时候就需要知道Django对于静态引用文件的设置.打开settings.py, 找到:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

所有和HTML文件相关的设置都在TEMPLATES里.注意到其中的DIRS中的BASE_DIR,按住CTRL点击,跳到settings.py的文件头,可以发现定义了如下内容:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

可见,__file__指的是settings.py本身,先取绝对路径,再取上边一层,是mysite目录,再取上边一层,是mysite项目目录,说明Django的默认初始目录就是项目的根目录,也就是manage.py和mysite子目录所在的目录.
再回到TEMPLATES里边来,DIRS键对应的目录就是恰好就是下边的templates目录.

再来看Django中发回文件的简单做法:

from django.shortcuts import render

def index_html(request):
    return render(request, "test.html")

注意,上边已经知道了默认的静态文件的目录,所以直接加参数test.html即可,Django默认就会到templates目录内去寻找test.html文件.

现在已经知道了为什么刚才要将test.html文件放到templates目录下,就是因为这个目录是默认的去寻找HTML文件的目录.此时访问链接,依然发现没有样式,这是为什么呢?
原因就是Django需要对这种外部样式表,js等静态文件,需要专门配置静态文件的目录.尝试将bootstrap目录和jQuer拷贝到templates目录下并在test.html文件里写上路径,发现这样是不行的(原因是按照这个路径去向Django请求文件,由于没有配置静态文件的路径,所以Django不会发送这些文件).

再打开settings.py,在最下边追加:

# 存放静态文件的目录的别名
STATIC_URL = "/static/"

# 所有静态文件都放在下边配置的文件夹里
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

然后,在项目目录下建立static文件夹,将jQuery和Bootstrap目录copy进去.
将其中的引用链接修改为:

<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>

再次访问链接,发现HTML页面和样式都显示正常了.这说明,Django的HTML文件在访问路径的时候,开始的目录也是项目目录,让其去指定的static目录下去寻找静态文件即可.
通过两个目录,就分离了HTML文件和所使用的静态文件,这和很多大型Web网站将图片,视频等文件存放于文件服务器上,web服务器上存放HTML文件是类似的做法.
这就实现了我们的第一个Django站点.