使用bottle框架开发了一个文档软件自助提取的网站。代码开发完成后,本地测试没有问题,但上线之后非常不稳定,网站使用一段时间后自己卡死了(多个访问造成进程阻塞)。查找原因发现,bottle自建web应用不适合用于生产环境,稳定性比较差。使用uwsgi+nginx,web应用会更加安全稳定,性能更优。
至于为什么用bottle,是因为手头有个之前自己开发的现成网站代码,当时就是用bottle写的,懒得改;Python版本是3.6.8;服务器是花98块买的阿里云ecs,买来玩的。
由于以前没用过uwsgi/nginx,而且网上关于bottle框架下应用uwsgi/nginx部署的资源很少(确实少,中文英文的都少),资料看起来非常费劲,研究了两天总算把这事理了大概,跑起来了。
项目详细情况如下:
0. 系统环境及软件版本说明
[root@aliyun ~]# uname -a
Linux aliyun 4.18.0-147.5.1.el8_1.x86_64 #1 SMP Wed Feb 5 02:00:39 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
[root@aliyun ~]# python3 --version
Python 3.6.8
[root@aliyun ~]# uwsgi --version
2.0.19.1
[root@aliyun ~]# nginx -v
nginx version: nginx/1.14.1
[root@aliyun ~]# pip3 show bottle
Name: bottle
Version: 0.12.18
Summary: Fast and simple WSGI-framework for small web-applications.
Home-page: http://bottlepy.org/
Author: Marcel Hellkamp
Author-email: [email protected]
License: MIT
Location: /usr/local/lib/python3.6/site-packages
Requires:
[root@aliyun ~]# cd /var/www/html/WEB_TRANSFER_DOCUMENTS/
[root@aliyun WEB_TRANSFER_DOCUMENTS]# ls
50x.html download error.txt __pycache__ reload test.py webpage
data Email_Custom.py main.py records.csv run.log uwsgi.ini
[root@aliyun WEB_TRANSFER_DOCUMENTS]#
1. uwsgi配置文件:uwsgi.ini
路径:/var/www/html/WEB_TRANSFER_DOCUMENTS
log路径:/var/www/html/WEB_TRANSFER_DOCUMENTS/run.log
[uwsgi]
chdir = /var/www/html/WEB_TRANSFER_DOCUMENTS
socket =0.0.0.0:8129
master = true
worker = 4
wsgi-file=main.py
callable=application
enable-threads = true
py-autoreload= 1
processes = 8
threads =1
daemonize = /var/www/html/WEB_TRANSFER_DOCUMENTS/run.log
2. Nginx 配置文件:nginx.conf
文件路径:/etc/nginx/nginx.conf
access log路径:/var/log/nginx/access.log
error log路径:/var/log/nginx/error.log
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name www.creditlife.top;
root /var/www/html/WEB_TRANSFER_DOCUMENTS;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
uwsgi_pass 0.0.0.0:8129;
include uwsgi_params;
uwsgi_send_timeout 600;
uwsgi_connect_timeout 600;
uwsgi_read_timeout 600;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
3. bottle app 主程序代码:main.py
# -*- coding: utf8 -*-
from bottle import run, route, static_file,request,template,default_app,response,redirect
import random,Email_Custom
from collections import defaultdict
import csv,datetime,bottle,traceback
user_information = {}
paths = {}
with open('data/data_sources.csv','r') as f:
content = csv.reader(f)
for items in content:
paths[items[0].strip()] =items[1:]
@route('/counter')
def counter():
try:
count = int(request.cookies.get('counter_test', '0'))
count += 1
response.set_cookie('counter_test', str(count))
return 'You visited this page %d times' % count
except :
traceback.print_exc(file=open('error.txt','a'))
@route('/')
def index():
return static_file('index.html', root='./webpage')
@route('/feedback/')
def feedback(path):
return static_file(path, root='./webpage/feedback')
@route('/')
def webpage_root(path):
return static_file(path, root='./webpage')
@route('/download/')
def download_display(path):
return static_file(path, root='./download',download=path)
@route('/images/')
def images(path):
return static_file(path, root='./webpage/images')
@route('/assets/css/')
def css_display(path):
return static_file(path, root='./webpage/assets/css')
@route('/assets/js/')
def js_display(path):
return static_file(path, root='./webpage/assets/js')
@route('/assets/fonts/')
def fonts_display(path):
return static_file(path, root='./webpage/assets/fonts')
@route('/robots')
def submit_get_mails():
try:
mail_address = request.query.email.strip()
user_ip = request.remote_addr
#response.set_cookie('param',mail_address,path='/documents')
user_information[user_ip] = mail_address
with open('records.csv','a',newline='') as code:
m = csv.writer(code,dialect='excel')
m.writerow([datetime.datetime.now().strftime('%Y-%m-%d'),datetime.datetime.now().strftime('%H:%M:%S'),user_ip,mail_address])
return static_file('document_choose.html', root='./webpage')
except Exception as e:
traceback.print_exc(file=open('error.txt','a'))
return 'something wrong'
@route('/documents')
def submit_get_document_number():
document_number = request.query.theone
user_ip = request.remote_addr
try:
#count = request.cookies.get('param')
thepaths = paths[document_number]
if thepaths:
flag = Email_Custom.sendEmail(mail_address,authorization——code,thepaths[0].strip(),open(thepaths[2],'rb').read(),user_information[user_ip],thepaths[1]) #前两个参数为个人邮箱地址和授权码,需自行补充添加。
if flag:
return ''+user_information[user_ip]+'
你好,资料已发送,请注意查收!'
else:
return '邮箱地址异常,发送失败;请检查邮箱地址后重试!
'
except Exception as e:
traceback.print_exc(file=open('error.txt','a'))
with open('error.txt','a') as code:
code.write("错误信息:"+str(e)+',来访地址:'+user_ip+ "\n")
return '发送失败。请清除缓存后重试!
如多次重试失败,请联系微信:hylan129
'
@route('/try')
def submit_try():
return ""
if __name__ == '__main__':
run(host="0.0.0.0", port=8129,debug=False,reloader=True)
else:
application = default_app()
4. 部署相关说明
1、bottle app 主程序中必须包含application函数,'application'名字固定,否则uwsgi 启动时会出现no app loaded 错误;
2、uwsgin配置时callable=application 不能少;配置端口时采用socket,socket = 0.0.0.0:8129;如果使用http,则是将uwsgi直接当作服务器使用,没法连接nginx;
3、uwsgi配置中参数daemonize建议配置,daemonize = /var/www/html/WEB_TRANSFER_DOCUMENTS/run.log;uwsgi程序后置后台运行同时指定日志路径,方便查看异常错误;
4、uwsgi参数众多,可以使用uwsgi -h命令查看相关帮助文档。
5、nginx配置文件路径不要改(建议直接在原始配置文件中更改),nginx.conf文件在nginx安装后会自动生成,而且安装完成后会提示配置文件的路径。
6、nginx配置文件中,只需要更新listen、server_name、root、uwsgi_pass、include参数即可。其他参数根据需要以及出错问题再补充;(因为我的程序后台耗时比较久,出现过超时错误,后来我增加了多个timeout参数)
7、命令运行顺序:uwsgi --ini uwsgi.ini & nginx,或者逐个命令运行;
8、使用uwsgi服务,不需要单独启动main.py(python main.py不需要);
9、部署过程中可以查看log确认具体问题,uwsgi log:/var/www/html/WEB_TRANSFER_DOCUMENTS/run.log;nginx log:/var/log/nginx/error.log;
10、web应用部署涉及使用两个端口,nginx配置的是外网访问http端口,即listen端口号;uwsgi 配置端口为uwsgi与nginx socket连接端口,即uwsgi参数:socket=0.0.0.0:8129;nginx参数:uwsgi_pass 0.0.0.0:8129;bottle app main.py中配置端口与uwsgin端口号相同,即run(host="0.0.0.0", port=8129);
5. web应用源码
登录creditlife.top网站提供邮箱地址即可下载获取。
参照文章:1:django为什么用uwsgi+nginx ;2:python web服务部署