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.pswp.cn/news/251825.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/251825.shtml
英文地址,請注明出處:http://en.pswp.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…