Flask 与 HTTP

323 阅读1分钟

获取请求中得查询字符串

from flask import Flask, request

app = Flask(__name__)

@app.route('/hello')
def hello():
  name = request.args.get("name", "Flask") # 获取查询参数name得值,如果没有则使用后面得默认值 Flask
  return "<h1>Hello, %s!" % name


查看程序中定义得所有路由

$ flask routes

设置监听得HTTP方法

@app.route("/hello", methods= ['GET','POST'])
def hello():
  return "<h1>Hello, Flask!</h1>"

若 是 get,post请求之外得请求,flask返回 405错误(Method Not Allowed)

Flask内置得URL变量转换器

@app.route('goback/<int:year>')
def go_back(year):
  return "<p>Welcome to %d!</p>" % (2018 - year)    
@app.route("/colors/<any(blur, white, red):color>")
def three_colors(color):
    return '<p>Love is patient and kind. Love is not jealous or boastful or proud or rude.</p>'

传入一个预先定义得列表

colors = ['blue','white','red']
@app.route('/colors/<any(%s):color>' % str(colors)[1:-1])

请求钩子

  • before_first_request:注册一个函数,在处理第一个请求前运行
  • before_request:注册一个函数吗,在处理每个请求前运行
  • after_request:注册一个函数,如果没有未处理得异常抛出,会在每个请求结束后运行
  • teardown_request:注册一个函数,即使有未处理得异常退出,会在每个请求结束后运行。如果发生异常,会传入异常对象作为参数到注册得函数中
  • after_this_request:在视图函数内注册一个函数,会在这个请求结束后运行

响应

Flask会先判断是否可以找到与请求URL相匹配得路由,如果么有则返回404响应。如果找到,则调用对应得视图函数,视图函数得返回值构成了响应报文得主体内容,正确返回时状态码默认为200、Flask会调用make_response()方法将视图函数返回值转换为响应对象。

普通响应

@app.route('/hello')
def hello():
  ...
  return "Hello Flask"

含有状态码得响应

@app.route('/hello')
def hello():
  ...
  return "Hello Flask" ,201

重定向响应

场景:当某个用户在没经过认证得情况下访问需要登录后才能访问得资源

生成302状态码得重定向响应,并且将首部中的Location字段设置成重定向目标URL:

@app.route('/hello')
def hello():
  ...
  return "", 302, {'Location':"http://www.example.com"}
from flask import Flask, redirect

@app.route('/hello')
def hello():
   return redirect('http://www.example.com')
   return redirect('http://www.example.com',code=303) # code默认为302重定向得状态码,可以自定义

手动返回错误响应

abort()

from flask import Flask, abort

@app.route('/404')
def not_found():
  abort(404)

响应格式得查看

from flask import make_response

@app.route('/foo')
def foo():
  response = make_response("hello world!")
  response.mimetype = "text/plain"
  return response

responses.headers['Content-Type'] = "text/xml; charset=utf-8"

Json得用法

from flask import Flask, make_response, json

@app.route('/foo')
def foo():
  data = {
    "name":"Grey li",
    "gender":"male"
  }
  response = make_response(json.dumps(data))
  response.mimetype = 'application/json'
  return response

from flask import jsonify

@app.route('/foo')
def foo():
  return jsonify(name='Grey li', gender = 'male')
  return jsonify({name: 'Grey Li', gender: 'male'})

jsonify() 默认生成200得响应,可以自定义

jsonify(messages = 'Error!'), 501

Cookie

在Flask中,要是想在响应中添加一个cookie,最方便的方法就是在Response类提供的set_cookie()方法。我们需要先使用make_response()方法手动生成一个响应对象,传入响应主体作为参数。这个响应对象默认实例化内置的Response类。


from flask import Flask, make_response

@app.route('/set/<name>')
def set_cookie(name):
  response = make_response(redirect(url_for('hello')))
  response.set_cookie('name', name)
  return response

当浏览器保存了服务器端设置的cookie后,浏览器再次发送到该服务器的请求会自动携带设置的Cookie信息,Cookie的内容储存在请求首部的Cookie字段中。

读取cookie

from flask import Flask, request

@app.route("/")
@app.route("/hello")
def hello():
  name = request.args.get("name")
  if name is None:
    name = request.cookies.gey("name","Human") # 如果cookie中没有,则使用Human
  return "<h1>Hello, %s!</h1>" % name

Session:安全的Cookie

在编程中,session指用户会话(user session),又称对话(dialogue),即服务器和客户端/浏览器之间或桌面程序和用户之间建立得交互行动。

  1. 设置程序密钥
app.secret_key = 'secret string'
或者在 系统环境变量里面添加、`.env`文件中添加
SECRET_KEY=secret string
在程序脚本中用os模块提供的`getenv()`方法获取
import os 
...
app.secret_key = os.getenv('SECRET_KEY', 'secret string') # 第二个参数为 没有获取到环境变量时使用得默认值
  1. 模拟用户认证
from flask import Flask, redirect, session, url_for

@app.route('/login')
  session['logged_in'] = True # 写入session
  return redirect(url_for('hello'))

from flask import Flask, redirect, session, url_for, request

app = Flask(__name__)

app.secret_key = 'secret string' # 加一个session可以读取得密钥

@app.route('/')
@app.route('/hello')
def hello():
  return "Hello"

@app.route('/login')
def login():
  session['logged_in'] = True # 写入session
  return redirect(url_for('hello'))

  1. 按照用户的登录状态显示不同的内容
app.secret_key = 'secret string'
@app.route('/')
@app.route('/hello')
def hello():
  name = request.args.get('name')
  if name is None:
    name = request.cookies.get('name', "Human")
    response = "<h1>Hello, %s </h1>" % name
    
    if 'logged_in' in session:
      response += '[Auth]'
    else:
      response += '[unAuth]'
    return response

  1. 模拟管理后台
from flask import abort

@app.route('/admin')
def admin():
  if "logged_in" not in session:
    abort(403) # 手动抛出异常
  return 'Welcome to admin page.'
  1. 登出用户
@app.route('/logout')
def logout():
    if 'logged_in' in session:
        session.pop('logged_in')
    return redirect(url_for('hello'))

session cookie 会在用户关闭浏览器时删除 通过 session.permanent属性设为True可以将session得有效期延长为 Flask.permanent.session_lifetime属性对应得datetime.timedelta对象,也可以通过配置变量 PERMANENT_SESSION_LIFETIME设置,默认为31天。


Flask上下文

Version:0.9 StartHTML:0000000105 EndHTML:0000001293 StartFragment:0000000141 EndFragment:0000001253我们可以把编程中的上下文理解为当前环境(environment)的快照(snapshot)。如果把一个Flask程序比作一条可怜的生活在鱼缸里的鱼的话,那么它当然离不开身边的环境。

  • 程序上下文(application context)
  • 请求上下文 (request context)
  1. current_app: 程序上下文:指向处理请求得当前程序实例
  2. g:替代python得全局变量用法,确保仅在当前请求中可用,用于储存全局数据,每次请求都会重设
  3. request:封装客户端发出得请求报文数据
  4. session:用于记住请求之间的数据,通过签名得Cookie实现

g 得实现

from flask import g

@app.before_request
def get_name():
  g.name = request.args.get('name')

激活上下文

  • 使用 flask run命令启动程序
  • 使用 app.run()启动程序
  • 使用@app.cli.command()装饰器注册得flaks命令时
  • 使用flask shell 启动python shell时

当请求进入时,Flask会自动激活请求上下文,这时我们可以使用request和session变量。另外,当请求上下文被激活时,程序上下文也被自动激活。当请求处理完毕后,请求上下文和程序上下文也会自动销毁。也就是说,在请求处理时这两者拥有相同的生命周期。

手动激活上下文

  • 使用 app.app_context()
from app import app
from flask import current_app
with app.app_context():
  current_app.name
 ----
 'app'

  • 显式使用 push()方法推送(激活)上下文,执行完之后 使用 pop()销毁上下文
from app import app
from flask import current_app
app_ctx = app.app_context()
app_ctx.push()
current_app.name
----
'app'
---
app_ctx.pop()
  • 通过test_request_context()方法临时创建
from app import app
from flask import request
with app.test_request_context('/hello'):
  request.method
 ----
 "GET"

上下文钩子

teardown_appcontext 钩子,使用它注册的回调函数会在程序上下文被销毁时调用

  • re:需要在每个请求处理结束后销毁数据库连接
@app.teardown_appcontext
def teardown_db(exception):
  ...
  db.close()

使用app.teardown_appcontext装饰器注册的回调函数需要接收异常对象作为参数,当请求被正常处理时这个参数值将是None,这个函数的返回值将被忽略。

重定向回前一个页面

场景:用户登录完成后,返回得是登录前得页面,而不是从首页开始。

↓:这种是使用固定链接来每次返回主页,但是我们并不想这么做,所以需要另想办法:

from flask import Flask, redirect, session, url_for, request

app = Flask(__name__)

@app.route('/hello')
def hello():
    return "Welcome!"

@app.route('/foo')
def foo():
    return "<h1>Foo page</h1><a href='%s'> Do Something</a>" % url_for('do_something') # 点击链接后返回 do_something函数调用hello页面方法

@app.route('/bar')
def bar():
    return "<h1>Bar page</h1><a href='%s'> Do Something</a>" % url_for('do_something')

@app.route('/do_something')
def do_something():
    return redirect(url_for('hello'))

我们可以使用得方式如下:

  1. 获取上一个页面得URL
    • HTTP referer
      • request.referrer (没打错 就是这么写的,这样是正确的调用函数拼写方式)
return redirect(request.referrer)

# 因为不可避免得因素(防火墙软件,浏览器设置关闭referrer字段),所以需要添加一个备选得地址,以免报错
return redirect(request.referrer or url_for('/hello'))

  1. 查询函数
    1. 在参数中加 next :包含当前页面URL

      next=request.full_path → 获取完整得路径

from flask import Flask, redirect, session, url_for, request

app = Flask(__name__)

@app.route('/hello')
def hello():
    return "Welcome!"

@app.route('/foo')
def foo():
    return "<h1>Foo page</h1><a href='%s'> Do something</a>" % url_for('do_something', \ 
        next=request.full_path)

@app.route('/bar')
def bar():
    return "<h1>Bar page</h1><a href='%s'> Do something</a>" % url_for('do_something',  \ 
        next=request.full_path) # 加入这个参数 是获取当前页面得完整路径,之后好返回

@app.route('/do_something')
def do_something():
    return redirect(request.args.get("next", url_for('hello'))) # 获取值得时候,如果没有获取到 则使用第二个参数
1. 优化参数,使得 获取上一个页面得url和查询函数融合到一起
from flask import Flask, redirect, session, url_for, request

app = Flask(__name__)

@app.route('/hello')
def hello():
    return "Welcome!"

@app.route('/foo')
def foo():
    return "<h1>Foo page</h1><a href='%s'> Do something</a>" % url_for('do_something', next=request.full_path)

@app.route('/bar')
def bar():
    return "<h1>Bar page</h1><a href='%s'> Do something</a>" % url_for('do_something', next=request.full_path)

=========================================================================
def redirect_back(default='helo', **kwargs):
    for target in request.args.get('next'), request.referrer:
        if target:
            return redirect(target)
    return redirect(url_for(default, **kwargs))

@app.route('/do_something_and_redirect')
def do_something():
    # return redirect(request.args.get("next", url_for('hello')))
    return redirect_back()

对URL进行安全认证

因为如果是只用next()来拼接得话,那么被人窃取得几率或者是被人篡改得几率就比较大。

from urllib.parse import urlparse, urljoin

def is_safe_url(target):
    ref_url = urlparse(request.host_url)
    test_url = urlparse(urljoin(request.host_url, target))
    return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc

def redirect_back(default='helo', **kwargs):
    for target in request.args.get('next'), request.referrer:
        if not target:
            continue
        if is_safe_url(target):
            return redirect(target)
    return redirect(url_for(default, **kwargs))


使用Ajax技术

AJAX指异步Javascript和XML(Asynchronous JavaScript AndXML),它不是编程语言或通信协议,而是一系列技术的组合体。简单来说,AJAX基于XMLHttpRequest(xhr.spec.whatwg.org/)让我们可以在不重载页面的情况下和服务器进行数据交换。加上JavaScript和DOM(Document Object Model,文档对象模型),我们就可以在接收到响应数据后局部更新页面。而XML指的则是数据的交互格式,也可以是纯文本(Plain Text)、HTML或JSON。顺便说一句,XMLHttpRequest不仅支持HTTP协议,还支持FILE和FTP协议。

 使用Ajax技术时得流程如下:
  1. 当单击 删除 按钮时,客户端在后台发送一个异步请求,页面不变,在接收响应前可以进行其他操作
  2. 服务器端接收请求后执行删除操作,返回提示消息或是无内容得204响应。
  3. 客户端接收到得响应,使用JavaScript更新页面,移除资源对应得页面元素。

使用全局JQuery函数ajax()发送Ajax请求

返回局部数据

  1. 纯文本或局部HTML模板
@app.route("/comments/<int:post_id>")
def get_comments(post_id):
  ...
  return render_template('comments.html')

  1. Json数据
@app.route("/profile/<int:user_id>")
def get_profile(post_id):
  ...
  return jsonify(username=username, bio=bio)
  1. 空值
@app.route("/post/delete/<int:post_id>", methods=['DELETE'])
def delete_post(post_id):
  ...
  return '', 204
  1. 异步加载长文章
from jinja2.utils import generate_lorem_ipsum 

from flask import Flask

app = Flask(__name__)

@app.route('/post') 
def show_post(): 
    post_body = generate_lorem_ipsum(n=2) # 生成两段随机文本 
    return ''' 
<h1>A very long post</h1> 
<div class="body">%s</div> 
<button id="load">Load More</button> 
<script src="https://code.jquery.com/jquery-3.3.1.min.js">
</script> 
<script type="text/javascript"> 
$(function() { 
  $('#load').click(function() { 
     $.ajax({ 
         url: '/more', // 目标URL 
          type: 'get', // 请求方法 
          success: function(data){ // 返回2XX响应后触发的回调函数 
             $('.body').append(data); // 将返回的响应插入到页面中 
             } 
        }) 
    }) 
})
</script>''' % post_body

@app.route("/more")
def load_post():
    return generate_lorem_ipsum(n=1) # 生成一段随机文本

HTTP服务端推送(server push)

在某些场景下,我们需要的通信模式是服务器端的主动推送(server push)。比如,一个聊天室有很多个用户,当某个用户发送消息后,服务器接收到这个请求,然后把消息推送给聊天室的所有用户。类似这种关注实时性的情况还有很多,比如社交网站在导航栏时显示新提醒和私信的数量,用户的在线状态更新,股价行情监控、显示商品库存信息、多人游戏、文档协作等。


如何有效放置 代码得稳定性

  1. sql注入
  2. xss攻击
  3. csrf攻击