Python Web开发:开发wsgi中间件

本文参考了:

  • github.com/alanctkc/ws…
  • Youtube : Creating WSGI Middleware

上篇文章简要提到:wsgi 规范中的 app 是一个可调用对象,可以通过嵌套调用的方式实现中间件的功能。这篇文章就来亲自动手实现一下。

此文的重点在于 app 端,所以 wsgi 服务器将使用python 内置module wsgiref.simple_server 中的make_server

创建 app

新建文件 app.py :

def application(environ, start_response):"""The web application."""response_body = ""for key, value in environ.items():response_body += "<p>{} : {}\n</p>".format(key, value)# Set up the response status and headersstatus = '200 OK'response_headers = [('Content-Type', 'text/html; charset=utf-8'),('Content-Length', str(len(response_body))),]start_response(status, response_headers)return [response_body.encode('utf-8')]复制代码

注意:python3中要求 response_body是 bytes,所以需要 encode()一下。在 python2中是 str,不需要 encode()。

这个 app 做的事情非常简单,把传过来的 environ 原样返回。在开始返回body 之前,调用server传过来的start_response函数。

简要说明一下为什么是 retuen [response_body]而不是 return response_body或者 return response_body.split("\n")或者return response_body.split("")?

  • 首先 wsgi 规范说明了app返回的是一个可迭代对象,列表是可迭代的。
  • 其次,对于大多数 app 来说,response_body都不会太长,服务器的内存完成足以一次性装下,所以最高效的方法就是一次性把response_body全传过去。

创建 server

新建文件server.py

from wsgiref.simple_server import make_server
from app import applicationprint("Server is running at http://localhost:8888 . Press Ctrl+C to stop.")
server = make_server('localhost', 8888, application)
server.serve_forever()复制代码

用浏览器打开 http://localhost:8888,就可以看到 environ 的详细内容。其中比较重要的我用红框框圈了起来。

第一个中间件:cors

简要了解一下 cors 的机制(详细的要比这个复杂点):

如果一个ajax请求(XMLHttpRequest)是跨域的,比如说在 http://localhost:9000页面上向运行在http://localhost:8888的服务器发起请求,浏览器就会往请求头上面加上一个ORIGIN字段,这个字段的值就是localhost:9000。(对应在app 的 environ 参数中,就是 HTTP_ORIGIN

同时,浏览器会先发出OPTIONS请求,服务器要实现这样的功能:如果想要接收这个请求的话,需要在response 的 headers里面添加一个Access-Control-Allow-Origin字段,值就是请求传过来的那个ORIGIN

浏览器发出OPTIONS请求并发现返回数据的 headers 里面有Access-Control-Allow-Origin,才会进行下一步发出真正的请求:GET,POST,WAHTERVER。

所以,CORS 是浏览器和 Server共同协作来完成的。

看一下代码:

class CORSMiddleware(object):def __init__(self, app, whitelist=None):"""Initialize the middleware for the specified app."""if whitelist is None:whitelist = []self.app = appself.whitelist = whitelistdef validate_origin(self, origin):"""Validate that the origin of the request is whitelisted."""return origin and origin in self.whitelistdef cors_response_factory(self, origin, start_response):"""Create a start_response method that includes a CORS header for thespecified origin."""def cors_allowed_response(status, response_headers, exc_info=None):"""This wraps the start_response behavior to add some headers."""response_headers.extend([('Access-Control-Allow-Origin', origin)])return start_response(status, response_headers, exc_info)return cors_allowed_responsedef cors_options_app(self, origin, environ, start_response):"""A small wsgi app that responds to preflight requests for thespecified origin."""response_body = 'ok'status = '200 OK'response_headers = [('Content-Type', 'text/plain'),('Content-Length', str(len(response_body))),('Access-Control-Allow-Origin', origin),('Access-Control-Allow-Headers', 'Content-Type'),]start_response(status, response_headers)return [response_body.encode('utf-8')]def cors_reject_app(self, origin, environ, start_response):response_body = 'rejected'status = '200 OK'response_headers = [('Content-Type', 'text/plain'),('Content-Length', str(len(response_body))),]start_response(status, response_headers)return [response_body.encode('utf-8')]def __call__(self, environ, start_response):"""Handle an individual request."""origin = environ.get('HTTP_ORIGIN')if origin:if self.validate_origin(origin):method = environ.get('REQUEST_METHOD')if method == 'OPTIONS':return self.cors_options_app(origin, environ, start_response)return self.app(environ, self.cors_response_factory(origin, start_response))else:return self.cors_reject_app(origin, environ, start_response)else:return self.app(environ, start_response)复制代码

__init__方法传入的参数有:下一层的 app(回顾一下前面说的 app 是一层一层的,所以能够实现中间件)和 client 白名单,只允许来自这个白名单内的ajax 请求。

__call__方法说明这是一个可调用对象(类也可以是可调用的),一样接收两个参数:environstart_response。首先判断一下 environ 中有没有HTTP_ORIGIN,有的话就表明属于跨域请求。如果是跨域,判断一下 origin 在不咋白名单。如果在白名单里面,如果是 OPTIONS请求,返回cors_options_app里面的对应内容(加上了Access-Control-Allow-Origin header);如果不是OPTIONS请求,调用下一层的 app。如果不在白名单,返回的是cors_reject_app

修改一下server.py:

app = CORSMiddleware(app=application,whitelist=['http://localhost:9000','http://localhost:9001']
)
server = make_server('localhost', 8000, app)
复制代码

测试 cors app

这里在运行三个客户端,[代码在此]。(github.com/liaochangji…)

运行python client.py

在浏览器打开http://localhost:9000http://localhost:9001http://localhost:9002,可以发现http://localhost:9000http://localhost:9001成功发出了请求,而http://localhost:9002失败了。

第二个中间件:请求耗时

这个比上一个要简单很多,相信现在你已经完全能够理解了:

import timeclass ResponseTimingMiddleware(object):"""A wrapper around an app to print out the response time for eachrequest."""def __init__(self, app):self.app = appdef __call__(self, environ, start_response):"""Meaure the time spent in the application."""start_time = time.time()response = self.app(environ, start_response)response_time = (time.time() - start_time) * 1000timing_text = "总共耗时: {:.10f}ms \n".format(response_time)response = [timing_text.encode('utf-8') + response[0]]return response
复制代码

再修改一下server.py

app = ResponseTimingMiddleware(CORSMiddleware(app=application,whitelist=['http://localhost:9000','http://localhost:9001'])
)
复制代码

再次访问http://localhost:8000,会看到最前面打印出了此次请求的耗时:

总结一下

我手画了一个请求图,希望对你有所帮助:

本文的所有源代码开源在 github 上:github.com/liaochangji…

希望能点个 star ~

如果你像我一样真正热爱计算机科学,喜欢研究底层逻辑,欢迎关注我的微信公众号:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/251825.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20165320 第九周学习总结

主要内容&#xff1a; 1.URL类 URL类是java.net包中的一个重要的类&#xff0c;使用URL创建对象的应用程序称为客户端程序。URL 的构造方法&#xff1a;try { URL url new URL ("http://www.google.com"); } catch (MalformedURLException e) {System.out.println(&…

Python 函数的执行流程-函数递归-匿名函数-生成器

1 函数的执行流程函数的执行需要对函数进行压栈的&#xff0c;什么是压栈呢&#xff0c;简而言之就是在函数执行时在栈中创建栈帧存放需要变量以及指针的意思。具体涉及的知识非常多&#xff0c;这里就已一个Python脚本简单进行分析。当我们运行上面代码时&#xff0c;它的执行…

python 课堂笔记-for语句

for i in range(10):print("----------",i)for j in range(10):print("world",j)if j> 5:break 转载于:https://www.cnblogs.com/leon-zyl/p/7542466.html

【2】信息的表示和处理

1.现代计算机存储和处理的信息都以二值信号表示。 2.机器为什么要使用二进制进行存储和处理&#xff1f; 答&#xff1a;二值信号能够很容易的被表示、存储、传输。例如&#xff1a; 可以表示为穿孔卡片上有洞和无洞、导线上的高压和低压&#xff0c;顺逆时针的磁场。 3.大多数…

java版b2b2c社交电商spring cloud分布式微服务(二) 服务消费者(rest+ribbon)

一、ribbon简介 Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using FeignClient then this section also applies. —–摘自官网 ribbon是一个负载均衡客…

[学习笔记]支配树

被支配树支配的恐惧 定义 显然&#xff0c;这个支配关系是一个树&#xff08;或者如果有的点不能从r到达&#xff0c;就是一个树一堆点&#xff09;。 首先不会成环&#xff0c;其次也不会是DAG 即如果A支配C&#xff0c;B支配C&#xff0c;那么A和B之间必然有支配关系 解法 首…

RBAC 权限设计(转载)

来源 &#xff1a;https://blog.csdn.net/rocher88/article/details/43190743 这是我在网上找的一些设计比较好的RBAC权限管理不知道&#xff0c;像新浪、搜狐、网易、百度、阿里巴巴、淘宝网的RBAC用户权限这一块&#xff0c;都是这种细颗粒的RBAC设计开发&#xff0c;还是把他…

54.get set

当程序查询对象属性时调用get方法,如果只有get方法那么他是一个只读属性&#xff0c;//程序对对象属性进行赋值操作时调用set方法&#xff0c;如果只有set方法那么他是是一个只读属性 <script type"text/javascript">var p {x:1.0,y:1.0,//当程序查询对象属性…

Codeforces Round #554 Div.2 E - Neko and Flashback

欧拉路径 神题啊神题&#xff01;这道题的突破口就是后两个数组每个元素是一一对应的。 也就是说&#xff0c;对于一个p的排列&#xff0c;b和c取得每一个元素的下标在p中都是一样的。 根据b和c数组的性质可以得出&#xff0c;b[i] < c[i]。 这也是我们输出-1的一个判断方法…

20172311 2017-2018-2 《程序设计与数据结构》第八周学习总结

20172311 2017-2018-2 《程序设计与数据结构》第八周学习总结 教材学习内容总结 本周对JAVA中的多态性进行了学习 多态性引用能够随时间变化指向不同类型的对象&#xff0c;是通过后绑定实现的。实现多态性的主要途径有两种&#xff1a; 1.由继承实现多态性 2.利用接口实现多态…

Linux系统安装Apache 2.4.6

http://www.cnblogs.com/kerrycode/p/3261101.html Apache简介 Apache HTTP Server&#xff08;简称Apache&#xff09;是Apache软件基金会的一个开放源码的网页服务器&#xff0c;可以在大多数计算机操作系统中运行&#xff0c;由于其多平台和安全性被广泛使用&#xff0c;是最…

深浅拷贝

lst1 ["金毛狮王", "紫衫龙王", "白眉鹰王", "青翼蝠王"] lst2 lst1 print(lst1) print(lst2) lst1.append("杨逍") print(lst1) print(lst2) # 结果: # [金毛狮王, 紫衫龙王, 白眉鹰王, 青翼蝠王, 杨逍] # [金毛狮王 紫衫…

lnmp化境开启pathinfo,支持tp5.0等访问

一、 开启pathinfo   #注释 下面这一行 #include enable-php.conf #载入新的配置文件 include enable-php-pathinfo.conf #添加如下location / {if (!-e $request_filename){rewrite ^/(.*)$ /index.php/$1 last;break;}}location ~ /index.php {fastcgi_pass 127.0.0.1:…

深度解密GO语言之反射

反射和 Interface 息息相关&#xff0c;而 Interface 是我们上一篇文章的内容。在开始正文前&#xff0c;和大家说点题外话。 上一篇关于 Interface 的文章发出后&#xff0c;获得了很多的关注和阅读。比如&#xff0c;登上了 GoCN 的每日新闻第一条&#xff1a; 可能是编辑者觉…

Python爬虫-正则表达式

正则表达式 只提取关注的数据&#xff0c;进行数据赛选 原子&#xff1a; 基本组成单位 普通的字符 非打印支付 通用字符 普通的字符 >>> import re >>> pat"yue" >>> string"http://yum.iqianyue.com" >>> rst1re.se…

openfire(一):使用idea编译openfire4.2.3源码

最近公司项目要使用openfire&#xff0c;并对源码做一些修改&#xff0c;使用的openfire版本为官网目前最新版本4.2.3&#xff0c;网上资料较少&#xff0c;踩了很多坑&#xff0c;特此记录。 1.下载源码 http://www.igniterealtime.org/downloads/source.jsp 2.使用idea导入源…

JAVA synchronized关键字锁机制(中)

synchronized 锁机制简单的用法&#xff0c;高效的执行效率使成为解决线程安全的首选。 下面总结其特性以及使用技巧&#xff0c;加深对其理解。 特性: 1. Java语言的关键字&#xff0c;当它用来修饰一个方法或者一个代码块的时候&#xff0c;能够保证在同一时刻最多只有一个线…

Python多线程豆瓣影评API接口爬虫

爬虫库 使用简单的requests库&#xff0c;这是一个阻塞的库&#xff0c;速度比较慢。 解析使用XPATH表达式 总体采用类的形式 多线程 使用concurrent.future并发模块&#xff0c;建立线程池&#xff0c;把future对象扔进去执行即可实现并发爬取效果 数据存储 使用Python ORM sq…

【自制工具类】Java删除字符串中的元素

这几天做项目需要把多个item的id存储到一个字符串中&#xff0c;保存进数据库。保存倒是简单&#xff0c;只需要判断之前是否为空&#xff0c;如果空就直接添加&#xff0c;非空则拼接个“&#xff0c;” 所以这个字符串的数据结构是这样的 String str "a,b,c,d"; 保…

DMA存储器到外设代码讲解

实验目的: bsp_dma_mtp.h #ifndef __BSP_DMA_MTP_H #define __BSP_DMA_MTP_H#include "stm32f10x.h" #include <stdio.h>// 串口工作参数宏定义 #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USAR…