web2
序
前面介绍过网页html的一些技术实现和大体架构,但对于后端boy,还是不太想计较那么多html方面的事,所以想把注意力集中回原先的python体系。针对简单的html的动态生成和一些个底层tcp/udp乃至上层http连接,都不用关注,只需要编写对应的接口响应函数,这种技术叫做WSGI,Web Server Gateway Interface。通常编写样式都是这样:
def application(environ, start_response): start_response('200 OK', [('content-type', 'text/html')]) return [b'<h1>Hello, wsgi!</h1>']
这么一个接口就编写完毕,但还需要进行调用,另有一个python内置了的WSGI的服务实现,叫wsgiref,可以通过这个库来调用自己编写的WSGI接口,如下:
from wsgiref.simple_server import make_server # 引入自己编写的接口 from wsgiapp import application # 实现一个监听8080端口、引用application自定义接口处理的server对象 httpd = make_server('', 8080, application) print('servint http on port 8080...') # 持续跑起来 httpd.serve_forever()
和很多程序一样,想要让它停止,就crtl+c(windows),linux就用crtl+z来终止运行。
如上就是简单的WSGI的web app效果,不用自己编写html,它会动态生成html,客户在浏览器访问127.0.0.1:8080/就可以得到服务,当然127.0.0.1就决定了客户只能是本机PC了,哈哈哈。
重写一下application,使其功能丰富点:
def application(environ, start_response): start_response('200 OK', [('content-type', 'text/html')]) # 读取url中传入信息,再构造响应 body = '<h1>Hello, {}!</h1>'.format(environ['PATH_INFO'][1:] or 'web') return [body.encode('utf-8')]
上面的框架还是比较的难维护,因为上面还没添加请求方式和不同路由的判断就还是比较简短,所以现在大多数都是用的比较流行的web框架。
一、轻便的flask
flask的使用,和上面的比较相似,都是编写对应的接口处理函数,但在使用形式上还是有差异,毕竟flask作为轻便式实现,提供了许多便利的装饰器,使用前准备:
pip install flask
from flask import Flask app = Flask(__name__) @app.route('/') def sayhello(): return 'Hello, web!' if __name__ == '__main__': app.run()
上面flask的代码实现了和最初始的web页面一样的效果,都是简单输出h1标签对应样式的hello,一开始就定义了app对象,其后的路由定义都是在app的route装饰器进行,定义完处理函数后,就run,大致就是这么个流程,如果是命令行跑起来,就需要强行要求脚本命名为app.py,如果想要自定义运行端口、ip等参数,就需要在run函数中传参了。
@app.route('/<name>') def sayhi(name): return '<h1>welcome, {}</h1>'.format(name)
嗯,简单实现就这样,再来多点。
1.1 flask的各种接口的实现
一个登录页面的实现
from flask import Flask from flask import request app = Flask(__name__) @app.route('/') def index(): # 下面的html是一个用户登录页面的实现,form标签的action属性指明了请求url,method指定请求方法 return """<form action="sign" method="post" style="text-align: center;"> <p>用户名:<input name="username"></p> <p>密码:<input name="password" type="password"></p> <p><button type="submit">sign in</button></p> </form>""" @app.route('/sign', methods=["POST"]) def sign(): # 如果是admin用户且密码为admin就返回登录后页面,否则回应登录失败。 if request.form['username'] == 'admin' and request.form['password'] == 'admin': return '<h1>Welcome to the first page!</h1>' return '<h1>Failed in Authentication</h1>' if __name__ == '__main__': app.run()
效果如下:
一个图片上传实现
from flask import jsonify from flask import redirect import os app.config['UPLOAD_FOLDER'] = 'upload' @app.route('/') @app.route('/home') def home(): return """<form action="upload" enctype="multipart/form-data" method="post"> <input type="file" name="fiiii"> <input type="submit" value="上传"> </form>""" @app.route('/upload', methods=["POST"]) def upload(): if not os.path.exists('upload'): os.makedirs('upload') f = request.files['fiiii'] print(app.config['UPLOAD_FOLDER'], f.filename) upload_path = os.path.join('upload', f.filename) f.save(upload_path) if os.path.exists(upload_path): return jsonify({'errno':0, 'msg':'上传成功'}) else: return jsonify({'errno':110, 'errmsg':'上传失败'})
关于save接口,最好拼接好保存文件名再传给save参数,不然容易出现permission error,另外就是jsonify返回响应信息中文乱码的问题,有介绍app.config['JSON_AS_ASCII'] = False
可以,但不管用。加点东西,上传图片成功后就显示图片:
@app.route('/upload', methods=["POST"]) def upload(): if not os.path.exists('upload'): os.makedirs('upload') f = request.files['fiiii'] print(app.config['UPLOAD_FOLDER'], f.filename) upload_path = os.path.join('upload', f.filename) f.save(upload_path) if os.path.exists(upload_path): return redirect('show/{}'.format(upload_path)) else: back_msg = {'errno':110, 'errmsg':'上传失败'} return jsonify(back_msg) @app.route('/show/<name>') def display(name): html = '<img src="{}" alt="{}" height="400", width="auto">'.format(name, name.split('\\')[-1].split('.')[0]) print(html) return html
但在上传成功后返回图片页面,但直接构造html img的办法好像不管用,还是要手动来构造响应,或者使用template,返回构造的html模板页面。在项目中创建templates文件夹并放置下面index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>display</title> </head> <body> <img src="{{path}}", alt="{{alt_name}}", height="400", width="auto"/> </body> </html>
接口的return语句修改一下
from flask import render_template @app.route('/upload', methods=["POST"]) def upload(): if not os.path.exists('upload'): os.makedirs('upload') f = request.files['fiiii'] upload_path = os.path.join('upload', f.filename) f.save(upload_path) if os.path.exists(upload_path): return render_template('index.html', path=upload_path, alt_name=f.filename.split('.')[0]) else: back_msg = {'errno':110, 'errmsg':'上传失败'} return jsonify(back_msg)
不过这样也不行,总是在get请求图片的时候失败,不知道为什么,单独构造html的时候也没问题啊,难受,只能用构造response的方法了。
@app.route('/upload', methods=["POST"]) def upload(): if not os.path.exists('upload'): os.makedirs('upload') f = request.files['fiiii'] upload_path = os.path.join('upload', f.filename) f.save(upload_path) if os.path.exists(upload_path): with open(upload_path, 'rb') as f: response = make_response(f.read()) response.headers['content-type'] = 'image/png' return response else: back_msg = {'errno':110, 'errmsg':'上传失败'} return jsonify(back_msg)
百度了不少,对口答案太少了,后面根据日志输出确定了图片的获取是会另外请求,而没有编写这个路由就导致了图片的html加载不完全:
@app.route('/upload', methods=["POST"]) def upload(): f = request.files['fiiii'] name = f.filename upload_path = os.path.join('upload', name) f.save(upload_path) if os.path.exists(upload_path): return render_template('index.html', path=name, alt_name=name.split('.')[0]) else: back_msg = {'errno':110, 'errmsg':'上传失败'} return jsonify(back_msg) # 错开一点,防止同名路由访问,顺带还需要修改一下index.html @app.route('/img/<name>') def send_file(name): with open('upload/'+name, 'rb') as f: response = make_response(f.read()) response.headers['content-type'] = 'image/png' return response
因为index.html中设定img的src来源为upload_path,也即是upload/pic路径,所以要针对性写一个这样的url路由,这就是send_file的来源。
本地测试html总让我有种错觉就是,html和内部的img是一体的,这个习惯要改一改。功能能跑了,完善一下,只允许图片的上传,并且根据jinja2语法加个判断,因为gif和普通图片的img属性略有不同,再修改一下样式使得它居中:
from flask import Flask from flask import request from flask import jsonify from flask import render_template from flask import make_response import os app = Flask(__name__) app.config['UPLOAD_FOLDER'] = 'upload' app.config['JSON_AS_ASCII'] = False ALLOW_PICS = ['png', 'jpeg', 'jpg', 'PNG', 'JPG', 'JPEG', 'gif', 'GIF'] @app.route('/sign') def sign(): # 下面的html是一个用户登录页面的实现,form标签的action属性指明了请求url,method指定请求方法 return """<form action="sign" method="post" style="text-align: center;"> <p>用户名:<input name="username"></p> <p>密码:<input name="password" type="password"></p> <p><button type="submit">sign in</button></p> </form>""" @app.route('/sign', methods=["POST"]) def signin(): # 如果是admin用户且密码为admin就返回登录后页面,否则回应登录失败。 if request.form['username'] == 'admin' and request.form['password'] == 'admin': return '<h1>Welcome to the first page!</h1>' return '<h1>Failed in Authentication</h1>' @app.route('/') @app.route('/home') def home(): return """<form action="upload" enctype="multipart/form-data" method="post"> <input type="file" name="fiiii"> <input type="submit" value="上传"> </form>""" @app.route('/upload', methods=["POST"]) def upload(): f = request.files['fiiii'] name = f.filename upload_path = os.path.join('upload', name) if f and name.split('.')[-1] in ALLOW_PICS: if 'gif' or 'GIF' in name: is_gif = True else: is_gif = False f.save(upload_path) if os.path.exists(upload_path): return render_template('index.html', is_gif = is_gif, path=upload_path, name=name.split('.')[0]) else: jsonify({'errno':1101, 'errmsg':'failed in saving'}) else: return jsonify({'errno':110, 'errmsg':'failed to upload, check the format'}) @app.route('/upload/<name>') def send_file(name): with open('upload/'+name, 'rb') as f: response = make_response(f.read()) response.headers['content-type'] = 'image/png' return response if __name__ == '__main__': app.run()
index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>display</title> </head> <body> {% if is_gif %} <div style="text-align:center;"><img src="{{path}}", alt="{{name}}", height="400", width="auto" loop="true" autoplay="true"/><p>{{name}}</p></div> {% else %} <div style="text-align:center"><img src="{{path}}", alt="{{name}}", height="400", width="auto"/><p>{{name}}</p></div> {% endif %} <br/> <form action="upload" enctype="multipart/form-data" method="post" align="center"> <input type="file" name="fiiii"> <input type="submit" value="上传"> </form> </body> </html>
好了,效果上就是每次上传图片成功都会展示,并且还可以继续上传图片。
1.2 摸一摸jinja2语法
首要的简单语法:
- {{ ... }},标记html中的变量,和语言沟通的变量实体;
- {% ... %},标记语句,各种逻辑语句诸如if、for等,都用这个,标记了开头需要标记结束;
- {# ... #},用来写注释语句。
简单的使用语句:
变量
<body> <!--name变量,在python脚本中就直接在render_template中传入指定参数即可--> <p>hello, {{name}}</p> </body>
分支
<body> {% if is_or_not %} <p>is_or_not为true</p> {% else %} <p>is_or_not为false</p> {% endif %} </body>
<body> {% for comment in comments %} <li>{{comment.id, comment.name}}</li> {% endfor %} </body>
注释就自然不用多言了,很明了了。继续展开,针对变量,不同类型的变量,存储的信息也不同,比如针对python的各种标准对象诸如字典、列表等等,在python里面怎么用,在这里就怎么用。除此以外,jinja2的特色就是针对变量有着过滤器的功能,形式就是{{变量|过滤器}}
,具体语义如下:
过滤器名 | 说明 |
---|---|
safe | 不转义 |
capitalize | 变量首字母大写,其余小写 |
lower | 变量小写 |
upper | 变量大写 |
title | 变量中每个单词首字母大写 |
trim | 变量首尾空格去除 |
striptags | 去除变量中的html标签 |
它的样式是变量后加上管道符|然后接api,就有点像过滤的样子才叫过滤器吧,更多的看官网吧。
模板继承
和面向对象的特性那样,jinja语法提供模板的继承,在应用当中,可以提供一个base模板,这个模板只提供关于UI界面的简单界定划分,后续的内容填充,都交给子模板。看了jinja中文网的例子,大致是这样:
给定一个base,
<!DOCTYPE html> <html> <head> {% block head %} <link rel="stylesheet" href="style.css" /> <title>{% block title %}{% endblock %} - My Webpage</title> {% endblock %} </head> <body> <div id="content">{% block content %}{% endblock %}</div> <div id="footer"> {% block footer %} © Copyright 2008 by <a href="http://domain.invalid/">you</a>. {% endblock %} </div> </body>
block和endblock包起来的区域就是可操作区域,对于我来说就是相当于另类的虚函数,可以被子模板重写。针对base的子模板扩展:
{% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %} {{ super() }} <style type="text/css"> .important { color: #336699; } </style> {% endblock %} {% block content %} <h1>Index</h1> <p class="important"> Welcome on my awesome homepage. </p> {% endblock %}
上面的extends表明继承于某某,super就有点类似python了,一样的意思,然后用上和base相同的block,再选择性重写里面的内容就ok了。因为引入了block的使用,它也支持嵌套概念,所以为了可读性的需求,运行在endblock的时候添加命名。
针对循环
在for循环中,有着方便查询当前迭代在循环中的位置的接口:
变量 | 描述 |
---|---|
loop.index | 当前循环迭代的次数,从1开始 |
loop.indexo | 从0开始计数的当前循环迭代次数 |
loop.revindex | 到循环结束还需要迭代的次数,从1开始 |
loop.revindexo | 到循环结束还需要迭代的次数,从0开始 |
loop.first | 判断是否是第一次迭代 |
loop.last | 判断是否是最后一次迭代 |
loop.length | 循环需要迭代的次数 |
除此以外,还有一些辅助性的特殊函数,比如range、cycler等,这种接口可以查看中文网。另外也还有更多的可用功能,但这里不想详述太多,烦人,要查的时候再看中文网吧。
后面的内容比较多,发布在了个人网站上,可以移步到那里:web2
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程