博客开发简记(3):从页面到后端结构的部署

既然已经可以访问django,那我们就可以搞点事情了哦,至少来个helloworld吧。

(一)helloworld

有两个基本的知识点:

  1. 在浏览器发起一个请求(get),去到django,django调用urls.py来解析地址或参数,所以你可以改成urls.py,来决定对不同的参数作出不同的函数处理。
  2. 如果返回一个HttpResponse给浏览器,那浏览器就可以看到展示,所以HttpResponse可以是一个页面。

基于这两个知识点,我们就改一下urls.py,在请求主页面时,调用一个函数,并且让这个函数返回helloworld的内容。

urls.py的主要内容如下:

from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from django.http import HttpResponse

def dosomething(request):
    return HttpResponse('<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>你好</title><body>hello world 喂世界 nihao</body><head></html>')

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

这里定义一个dosomething的函数,注意这个函数是带一个参数的,因为django会传递request给它。然后dosomething返回了一个HttpResponse对象,里面就是一些简单的文字了。

./manage.py runserver启动django后,在浏览器请求一下,可以看到这样的效果:
helloworld的展示

这就是一个简单的helloworld程序,太好改了吧。但这里有个问题,上面那段python代码,里面出现一段html,有什么问题吗?html标识的是一个界面,比如用什么标题、用什么字体、用几号大小,等等,这是界面设计的东西,而把界面设计跟代码逻辑或业务逻辑混在一起,除非你能容忍混乱而且不影响开发效率,否则界面跟代码逻辑就要分开。那界面提到哪里去呢,就是模板了,就是一个html文件,这里演示一下。

先mkdir创建一个templates文件夹,以后就放html文件了,然后创建一个main.html,这样设计这个界面(好简单!):

<h1>hello,{{yourname}}</h1>

{{yourname}}表示引用一个变量,变量名叫yourname,注意要用双符号来引用变量。在界面中使用变量,这太寻常了,那变量的定义还有值从哪里来的?就是业务逻辑给的呀,这就是界面与逻辑分离的表现,即界面使用逻辑给的变量值,逻辑给界面提供数据。

好了,然后,自然是要在代码中定义这个yourname变量了,而且代码中要展示这个html,代码如下:

from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from django.shortcuts import render

def dosomething(request):
    dict = {}
    dict['yourname'] = '广州小程'
    return render(request, 'main.html', dict)

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

在接到请求时,还是调用dosomething函数,然后返回一个render,看名字就知道是要渲染绘制一个界面出来,这个界面是什么呢?就是第二个参数指定的main.html,然后第三个参数就是传递给界面的数据,它是一个键值对集,明显,一定要包括yourname这个变量,而且事先给了变量值。

最后要注意一个路径的问题,就是render怎么找到main.html的问题,这个要在settings.py中进行设置,然后来看一下现在的目录结构:
增加main.html的目录结构

按这个结构,要指定的路径就是:项目目录+/myblog/templates

在settings.py中找到 TEMPLATES 这个dict,然后把DIRS的值改成这样:

'DIRS': [BASE_DIR+'/myblog/templates',],

ok,一切就绪,浏览器请求一下,看到这样的效果:
业务与界面发离的展示效果

好了,界面你也知道怎么写了,不就是写html嘛。但是,我要的博客可没有这么简单,需要一步步来开发吗?

在你做一个事情,特别是一个常见的事情之前,看看别人是怎么做的,或许能大大节省你的时间成本。于是,你会发现,博客这种日常操作,在github上有大量的项目。

(二)博客项目

于是,这里我直接使用这个项目:https://github.com/liangliangyy/DjangoBlog

按这个地址的介绍来部署即可:https://www.lylinux.net/article/2019/8/5/58.html

这里有个知识点,为什么有了djangoblog,还要用到nginx跟gunicorn呢?它们是什么关系呢?

djangoblog,简单来说就是一个web应用,也可以说是一个web框架。作为一个应用,djangoblog当然可以runserver起来并且占用80端口等,然后浏览器就可以访问到他。但是,在实际数据交互的场景中,让web应用把负载均衡、高并发之类的事情也做了,是不合适的,应用只应该做自己的业务。所以,还需要一个重要的角色,这个角色就是web服务器,而nginx就是一个web服务器,浏览器的所有请求先到达nginx,nginx先做一些前置的处理(比如静态页面拦截、负载平衡之类),但是,由于nginx不懂业务啊,所以它还是要调用到djangoblog(web框架或应用),但这个调用不是直接的调用,而是经过中间件,比如gunicorn。gunicorn或wsgi(网关接口),解决了nginx与django交互的问题,因为web服务器与web框架的通信,要遵守一种协议,而gunicorn正是实现了这种协议。实际上,gunicorn本身也能做为web服务器(类似于nginx),但因能力有限,一般会把这个角色让位于nginx等。所以,简单来说,一种常见的后端结构就是:nginx+gunicorn+django。

另外,由于gunicorn支持nginx与django的通信,不可或缺,如果它退出了,有必要即刻启动起来,于是引入supervisor。supervisor监控gunicorn,保证后者的拉起,而且后者以子进程挂在supervisor进程内。于是这个结构也可以说是:nginx+supervisor+gunicorn+django。

最终用浏览器访问,一个网站就出来了:
djangoblog的样子

(三)后端知识

补充一些知识点。

(a)supervisor与gunicorn
supervisor是监控并管理gunicorn的,如果你想停止gunicorn,你只需要把对应的supervisor服务给stop掉就可以了,gunicorn进程会自动停止。

用这个命令先看一下supervisor监控了哪些服务:

supervisorctl status

然后就可以停止这个服务:

supervisorctl stop 服务名

这时服务对应的gunicorn会自动停止,用lsof -i:8000不再看到进程。 对应于stop,还有start、reload。

当然,如果想直接kill掉gunicorn进程,也可以,先找到它的根pid:

pstree -ap|grep gunicorn

再kill掉:

kill -9 pid

但是,如果supervisor是运行状态,kill掉的gunicorn即刻就会被拉起,一个新的pid的gunicorn又会出现。

(b)supervisor的配置
诸多key-value的配置选项,请自行搜索了解,这里简单说两个。

command,就是supervisor要执行的命令,比如执行某个程序或脚本,比如执行一句python语句等等,涉及到的文件,你可以写完整路径,如果写相对路径则要组合directory这个选项。

directory,在执行command之前,先cd到这个目录,看情况使用(也可不用)。

(c)supervisor的日志
如果遇到supervisor有什么异常而你一时想不出原因,这时看一下它的日志输出(包括你故意print出来的日志--因为supervisor是python程序所以你当然可以print),也许能帮到你,那日志在哪里呢? 默认在/var/log/supervisor目录下面。如果在配置supervisor时指定了标准输出路径,比如stdout_logfile字段,那就多了一个日志。对于分析问题,这两个日志都应该关注。

(d)supervisor的进程关系
这样查看supervisor的进程关系:

service supervisor status

如果已经设置好supervisor的配置,比如指定启动gunicorn,那就可以看到在supervisor进程下面挂上了gunicorn进程(一般两个,一主一从)。

(e)nginx的配置
nginx的所有配置都在:/etc/nginx 目录,而你新增加的配置应该放在 /etc/nginx/sites-enabled 目录下,这个目录已经有一个叫default的默认配置文件,你的新配置可以任意命名,都将替代default配置,但你需要重新启动nginx。

可以这样重启nginx:

service nginx reload/restart 或:
/etc/init.d/nginx reload

如果不放心,也可以先stop(浏览器请求一下)再start:

service nginx stop
service nginx start

注意,nginx明明stop掉了,用ps也看不到进程了,这时浏览器再请求可能还是看到页面,这可能是浏览器端的缓存。

配置中的error_log字段,指定了nginx出错时的记录文件,这个文件可以帮助你分析nginx的错误。默认不设置这个字段的情况下,错误日志文件是/var/log/nginx/error.log。

如果只是想验证nginx是否可以正常使用,可以写一个最简单的配置,比如这样:

server {
        listen 80;
        server_name www.freep2p.cn;
        root /root/python;
    }

然后在/root/python目录创建一个index.html文件,比如文件内容可以这么简单:

<html><p2>hello world</p2></html> 

重启nginx后用 www.freep2p.cn 访问它,就可以看到hello world。

对于nginx的运行情况,可以用下面的命令来查看:

systemctl status nginx.service

(f)定位问题的一个关键
层级一多,问题定位就变得复杂。除了考虑去除层级,比如直接手动调用gunicorn不经supervisor等,这个办法有效外,还有一个关键的点,就是分析日志。

有两个日志要上心,一个是nginx的输出日志(在配置中有写,或者使用默认的路径即/var/log/nginx/error.log),另一个是supervisor的输出日志(如上介绍,有两个),supervisor的日志,包括了supervisor跟gunicorn的表现,还有请求应答的情况。

(g)可能遇到的问题

(1)Internal Server Error
对于nginx,如果提示“Internal Server Error”,是什么原因呢? 这个提示(对应错误码是500),意思是服务内部出问题了,但至少说明,你访问到nginx了,只是触发了错误。对于这里的结构,nginx是要调用supervisor+gunicorn的,而gunicorn要调用djangoblog框架,是哪一步出了问题呢?

用lsof -i:8000,可以看到gunicorn已经运行起来了,而nginx的配置相对是简单的,不太像会出问题,那gunicorn到djangoblog那一步怎么样?gunicorn的配置是不是有问题呢?

先把supervisor停止掉,按上面介绍的命令即可做到,supervisor给stop掉后,gunicorn也会自动结束(lsof -i:8000看不到进程),如果这时用浏览器请求一下,会看到这样的提示:

502 Bad Gateway

也就是网关接口出问题了,而实际就是gunicorn给停止了。

尝试用最简单的nginx的配置,能正常访问,所以排除是nginx的问题。

先把supervisor给stop掉,也就是不使用supervisor来启用gunicorn,而是直接执行gunicorn_start.sh脚本,再访问nginx,发现,正常了!

所以,从gunicorn到djangoblog没有问题,问题出在supervisor启用gunicorn引入了问题。

从理论来看,按这里使用的supervisor的配置,supervisor解释执行脚本(不一定用bash),再以exec命令启动gunicorn,而exec的意思就是不另起进程,而是使用当前进程,只要gunicorn不退出(本意就是不退出的),那么当前进程声明的变量以及用source切换至python虚拟环境就是生效的,不用怀疑source不生效。

这时,一定要分析日志。 在supervisor的标准输出/var/log/djangoblog.log中,可以看到这样的提示:
内部错误时supervisor的日志提示

也就是找不到HOST变量!

根据上面的提示,这个是database的设置,代码如下图:
supervisor要读取的环境变量

也就是os.environ.get不到环境变量。

手动执行sh脚本,相当在shell中交互执行,实际是bash进程启动了gunicorn,而supervisor是python程序,它解释sh脚本启动了gunicorn,这两种方式是不同的,至少bash跟supervisor使用的环境变量的配置就不同,这个下面就来证明。

先来测试一下supervisor当前的环境变量吧。

把supervisor执行的命令设置一下:

command=python3 -c "import os; print(os.environ)"

然后:

tail -10 /var/log/djangoblog.log

你可以看到已经有一些变量了,但是,没有代码里面的那三个变量,因为还没有设置(设置到~/.bashrc中是无效的)。这样设置到supervisor,同样是在supervisor的配置中修改:

environment = DJANGO_MYSQL_USER='root',DJANGO_MYSQL_PASSWORD='xxx',...

也就是增加environment字段,值就是新增的环境变量,以逗号分隔。

再启supervisor,这个问题就得到解决。

(2)403 forbidden
如果error的log提示,“xxx” is forbidden,一个可能是在这个目录下面找不到index.html。


总结一下,本文介绍了怎么简单写helloworld界面,怎么使用开源的博客项目,也重点介绍了后端的知识点。

至此,解决了后端结构的部署问题,网站框架也好了,接下来就是页面个性化类的问题了。

posted on 2019-10-14 10:38 广州小程 阅读(...) 评论(...) 编辑 收藏

导航

统计