1. 程式人生 > >一起寫一個 Web 伺服器(2)

一起寫一個 Web 伺服器(2)

轉自:http://python.jobbole.com/81523/

還記得嗎?在本系列第一部分我問過你:“怎樣在你的剛完成的WEB伺服器下執行 Django 應用、Flask 應用和 Pyramid 應用?在不單獨修改伺服器來適應這些不同的WEB框架的情況下。”往下看,來找出答案。

過去,你所選擇的一個Python Web框架會限制你選擇可用的Web伺服器,反之亦然。如果框架和伺服器設計的是可以一起工作的,那就很好:

但是,當你試著結合沒有設計成可以一起工作的伺服器和框架時,你可能要面對(可能你已經面對了)下面這種問題:

基本上,你只能用可以在一起工作的部分,而不是你想用的部分。

那麼,怎樣確保在不修改Web伺服器和Web框架下,用你的Web伺服器執行不同的Web框架?答案就是Python Web伺服器閘道器介面(或者縮寫為WSGI,讀作“wizgy”)。

WSGI允許開發者把框架的選擇和伺服器的選擇分開。現在你可以真正地混合、匹配Web伺服器和Web框架了。例如,你可以在Gunicorn或者Nginx/uWSGI或者Waitress上面執行Django,Flask,或Pyramid。真正的混合和匹配喲,感謝WSGI伺服器和框架兩者都支援:

就這樣,WSGI成了我在本系列第一部分和本文開頭重複問的問題的答案。你的Web伺服器必須實現WSGI介面的伺服器端,所有的現代Python Web框架已經實現 了WSGI介面的框架端了,這就讓你可以不用修改伺服器程式碼,適應某個框架。

現在你瞭解了Web伺服器和WEb框架支援的WSGI允許你選擇一對兒合適的(伺服器和框架),它對伺服器和框架的開發者也有益,因為他們可以專注於他們特定的領域,而不是越俎代庖。其他語言也有相似的介面:例如,Java有Servlet API,Ruby有Rack。

一切都還不錯,但我打賭你會說:“秀程式碼給我看!” 好吧,看看這個漂亮且簡約的WSGI伺服器實現:

Python# Tested with Python 2.7.9, Linux & Mac OS X import socket import StringIO import sys class WSGIServer(object): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 1 def __init__(self, server_address): # Create a listening socket self.listen_socket = listen_socket = socket.socket( self.address_family, self.socket_type ) # Allow to reuse the same address listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Bind listen_socket.bind(server_address) # Activate listen_socket.listen(self.request_queue_size) # Get server host name and port host, port = self.listen_socket.getsockname()[:2] self.server_name = socket.getfqdn(host) self.server_port = port # Return headers set by Web framework/Web application self.headers_set = [] def set_app(self, application): self.application = application def serve_forever(self): listen_socket = self.listen_socket while True: # New client connection self.client_connection, client_address = listen_socket.accept() # Handle one request and close the client connection. Then # loop over to wait for another client connection self.handle_one_request() def handle_one_request(self): self.request_data = request_data = self.client_connection.recv(1024) # Print formatted request data a la 'curl -v' print(''.join( '< {line}\n'.format(line=line) for line in request_data.splitlines() )) self.parse_request(request_data) # Construct environment dictionary using request data env = self.get_environ() # It's time to call our application callable and get # back a result that will become HTTP response body result = self.application(env, self.start_response) # Construct a response and send it back to the client self.finish_response(result) def parse_request(self, text): request_line = text.splitlines()[0] request_line = request_line.rstrip('\r\n') # Break down the request line into components (self.request_method, # GET self.path, # /hello self.request_version # HTTP/1.1 ) = request_line.split() def get_environ(self): env = {} # The following code snippet does not follow PEP8 conventions # but it's formatted the way it is for demonstration purposes # to emphasize the required variables and their values # # Required WSGI variables env['wsgi.version'] = (1, 0) env['wsgi.url_scheme'] = 'http' env['wsgi.input'] = StringIO.StringIO(self.request_data) env['wsgi.errors'] = sys.stderr env['wsgi.multithread'] = False env['wsgi.multiprocess'] = False env['wsgi.run_once'] = False # Required CGI variables env['REQUEST_METHOD'] = self.request_method # GET env['PATH_INFO'] = self.path # /hello env['SERVER_NAME'] = self.server_name # localhost env['SERVER_PORT'] = str(self.server_port) # 8888 return env def start_response(self, status, response_headers, exc_info=None): # Add necessary server headers server_headers = [ ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'), ('Server', 'WSGIServer 0.2'), ] self.headers_set = [status, response_headers + server_headers] # To adhere to WSGI specification the start_response must return # a 'write' callable. We simplicity's sake we'll ignore that detail # for now. # return self.finish_response def finish_response(self, result): try: status, response_headers = self.headers_set response = 'HTTP/1.1 {status}\r\n'.format(status=status) for header in response_headers: response += '{0}: {1}\r\n'.format(*header) response += '\r\n' for data in result: response += data # Print formatted response data a la 'curl -v' print(''.join( '> {line}\n'.format(line=line) for line in response.splitlines() )) self.client_connection.sendall(response) finally: self.client_connection.close() SERVER_ADDRESS = (HOST, PORT) = '', 8888 def make_server(server_address, application): server = WSGIServer(server_address) server.set_app(application) return server if __name__ == '__main__': if len(sys.argv) < 2: sys.exit('Provide a WSGI application object as module:callable') app_path = sys.argv[1] module, application = app_path.split(':') module = __import__(module) application = getattr(module, application) httpd = make_server(SERVER_ADDRESS, application) print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT)) httpd.serve_forever()
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143# Tested with Python 2.7.9, Linux & Mac OS XimportsocketimportStringIOimportsysclassWSGIServer(object):address_family=socket.AF_INETsocket_type=socket.SOCK_STREAMrequest_queue_size=1def__init__(self,server_address):# Create a listening socketself.listen_socket=listen_socket=socket.socket(self.address_family,self.socket_type)# Allow to reuse the same addresslisten_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)# Bindlisten_socket.bind(server_address)# Activatelisten_socket.listen(self.request_queue_size)# Get server host name and porthost,port=self.listen_socket.getsockname()[:2]self.server_name=socket.getfqdn(host)self.server_port=port# Return headers set by Web framework/Web applicationself.headers_set=[]defset_app(self,application):self.application=applicationdefserve_forever(self):listen_socket=self.listen_socketwhileTrue:# New client connectionself.client_connection,client_address=listen_socket.accept()# Handle one request and close the client connection. Then# loop over to wait for another client connectionself.handle_one_request()defhandle_one_request(self):self.request_data=request_data=self.client_connection.recv(1024)# Print formatted request data a la 'curl -v'print(''.join('< {line}\n'.format(line=line)forline inrequest_data.splitlines()))self.parse_request(request_data)# Construct environment dictionary using request dataenv=self.get_environ()# It's time to call our application callable and get# back a result that will become HTTP response bodyresult=self.application(env,self.start_response)# Construct a response and send it back to the clientself.finish_response(result)defparse_request(self,text):request_line=text.splitlines()[0]request_line=request_line.rstrip('\r\n')# Break down the request line into components(self.request_method,# GETself.path,# /helloself.request_version# HTTP/1.1)=request_line.split()defget_environ(self):env={}# The following code snippet does not follow PEP8 conventions# but it's formatted the way it is for demonstration purposes# to emphasize the required variables and their values## Required WSGI variablesenv['wsgi.version']=(1,0)env['wsgi.url_scheme']='http'env['wsgi.input']=StringIO.StringIO(self.request_data)env['wsgi.errors']=sys.stderrenv['wsgi.multithread']=Falseenv['wsgi.multiprocess']=Falseenv['wsgi.run_once']=False# Required CGI variablesenv['REQUEST_METHOD']=self.request_method# GETenv['PATH_INFO']=self.path# /helloenv['SERVER_NAME']=self.server_name# localhostenv['SERVER_PORT']=str(self.server_port)# 8888returnenvdefstart_response(self,status,response_headers,exc_info=None):# Add necessary server headersserver_headers=[('Date','Tue, 31 Mar 2015 12:54:48 GMT'),('Server','WSGIServer 0.2'),]self.headers_set=[status,response_headers+server_headers]# To adhere to WSGI specification the start_response must return# a 'write' callable. We simplicity's sake we'll ignore that detail# for now.# return self.finish_responsedeffinish_response(self,result):try:status,response_headers=self.headers_setresponse='HTTP/1.1 {status}\r\n'.format(status=status)forheader inresponse_headers:response+='{0}: {1}\r\n'.format(*header)response+='\r\n'fordata inresult:response+=data# Print formatted response data a la 'curl -v'print(''.join('> {line}\n'.format(line=line)forline inresponse.splitlines()))self.client_connection.sendall(response)finally:self.client_connection.close()SERVER_ADDRESS=(HOST,PORT)='',8888defmake_server(server_address,application):server=WSGIServer(server_address)server.set_app(application)returnserverif__name__=='__main__':iflen(sys.argv)<2:sys.exit('Provide a WSGI application object as module:callable')app_path=sys.argv[1]module,application=app_path.split(':')module=__import__(module)application=getattr(module,application)httpd=make_server(SERVER_ADDRESS,application)print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))httpd.serve_forever()

它明顯比本系列第一部分中的伺服器程式碼大,但為了方便你理解,而不陷入具體細節,它也足夠小了(只有150行不到)。上面的伺服器還做了別的事 – 它可以執行你喜歡的Web框架寫的基本的Web應用,可以是Pyramid,Flask,Django,或者其他的Python WSGI框架。

不信?自己試試看。把上面的程式碼儲存成webserver2.py或者直接從Github上下載。如果你不帶引數地直接執行它,它就會報怨然後退出。

Python$ python webserver2.py Provide a WSGI application object as module:callable
12$python webserver2.pyProvideaWSGI application objectasmodule:callable

它真的想給Web框架提供服務,從這開始有趣起來。要執行伺服器你唯一需要做的是安裝Python。但是要執行使用Pyramid,Flask,和Django寫的應用,你得先安裝這些框架。一起安裝這三個吧。我比較喜歡使用virtualenv。跟著以下步驟來建立和啟用一個虛擬環境,然後安裝這三個Web框架。

Python$ [sudo] pip install virtualenv $ mkdir ~/envs $ virtualenv ~/envs/lsbaws/ $ cd ~/envs/lsbaws/ $ ls bin include lib $ source bin/activate (lsbaws) $ pip install pyramid (lsbaws) $ pip install flask (lsbaws) $ pip install django
12345678910$[sudo]pip install virtualenv$mkdir~/envs$virtualenv~/envs/lsbaws/$cd~/envs/lsbaws/$lsbininclude  lib$source bin/activate(lsbaws)$pip install pyramid(lsbaws)$pip install flask(lsbaws)$pip install django

此時你需要建立一個Web應用。我們先拿Pyramid開始吧。儲存以下程式碼到儲存webserver2.py時相同的目錄。命名為pyramidapp.py。或者直接從Github上下載:

Pythonfrom pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response( 'Hello world from Pyramid!\n', content_type='text/plain', ) config = Configurator() config.add_route('hello', '/hello') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app()
1234567891011121314frompyramid.config importConfiguratorfrompyramid.response importResponsedefhello_world(request):returnResponse('Hello world from Pyramid!\n',content_type='text/plain',)config=Configurator()config.add_route('hello','/hello')config.add_view(hello_world,route_name='hello')app=config.make_wsgi_app()

現在你已經準備好用完全屬於自己的Web伺服器來執行Pyramid應用了:

Python(lsbaws) $ python webserver2.py pyramidapp:app WSGIServer: Serving HTTP on port 8888 ...
12(lsbaws)$python webserver2.py pyramidapp:appWSGIServer:Serving HTTP on port8888...

剛才你告訴你的伺服器從python模組‘pyramidapp’中載入可呼叫的‘app’,現在你的伺服器準備好了接受請求然後轉發它們給你的Pyramid應用。目前應用只處理一個路由:/hello 路由。在瀏覽器裡輸入http://localhost:8888/hello地址,按回車鍵,觀察結果:

你也可以在命令列下使用‘curl’工具來測試伺服器:

Python$ curl -v http://localhost:8888/hello ...
12$curl-vhttp://localhost:8888/hello...

檢查伺服器和curl輸出了什麼到標準輸出。

現在弄Flask。按照相同的步驟。

Pythonfrom flask import Flask from flask import Response flask_app = Flask('flaskapp') @flask_app.route('/hello') def hello_world(): return Response( 'Hello world from Flask!\n', mimetype='text/plain' ) app = flask_app.wsgi_app
12345678910111213fromflask importFlaskfromflask importResponseflask_app=Flask('flaskapp')@flask_app.route('/hello')defhello_world():returnResponse('Hello world from Flask!\n',mimetype='text/plain')app=flask_app.wsgi_app

儲存以上程式碼為flaskapp.py或者從Github上下載它。然後像這樣執行伺服器:

Python(lsbaws) $ python webserver2.py flaskapp:app WSGIServer: Serving HTTP on port 8888 ...
12(lsbaws)$python webserver2.py flaskapp:appWSGIServer:Serving HTTP on port8888...

現在在瀏覽器裡輸入http://localhost:8888/hello然後按回車:

再一次,試試‘curl’,看看伺服器返回了一條Flask應用產生的訊息:

Python$ curl -v http://localhost:8888/hello ...
12$curl-vhttp://localhost:8888/hello...

伺服器也能處理Django應用嗎?試試吧!儘管這有點複雜,但我還是推薦克隆整個倉庫,然後使用djangoapp.py,它是GitHub倉庫的一部分。以下的原始碼,簡單地把Django ‘helloworld’ 工程(使用Django的django-admin.py啟動專案預建立的)新增到當前Python路徑,然後匯入了工程的WSGI應用。

Pythonimport sys sys.path.insert(0, './helloworld') from helloworld import wsgi app = wsgi.application

相關推薦

一起一個 Web 伺服器2

轉自:http://python.jobbole.com/81523/還記得嗎?在本系列第一部分我問過你:“怎樣在你的剛完成的WEB伺服器下執行 Django 應用、Flask 應用和 Pyramid 應用?在不單獨修改伺服器來適應這些不同的WEB框架的情況下。”往下看,來找

一起一個Web伺服器3

轉自:http://python.jobbole.com/81820/“發明創造時,我們學得最多” —— Piaget在本系列第二部分,你已經創造了一個可以處理基本的 HTTP GET 請求的 WSGI 伺服器。我還問了你一個問題,“怎麼讓伺服器在同一時間處理多個請求?”在本

一起一個 Web 伺服器1

轉自:http://python.jobbole.com/81524/有天一個女士出門散步,路過一個建築工地,看到三個男人在幹活。她問第一個男人,“你在幹什麼呢?”,第一個男人被問得很煩,咆哮道,“你沒看到我在碼磚嗎?”。她對回答不滿意,然後問第二個男人他在幹什麼。第二個男人

自己動手開發一個 Web 伺服器

在第二部分中,你開發了一個能夠處理HTTPGET請求的簡易WSGI伺服器。在上一篇的最後,我問了你一個問題:“怎樣讓伺服器一次處理多個請求?”讀完本文,你就能夠完美地回答這個問題。接下來,請你做好準備,因為本文的內容非常多,節奏也很快。文中的所有程式碼都可以在Github倉庫下載。 首先,我們簡單回憶一下

Tomcat原始碼分析 ----- 手一個web伺服器

作為後端開發人員,在實際的工作中我們會非常高頻地使用到web伺服器。而tomcat作為web伺服器領域中舉足輕重的一個web框架,又是不能不學習和了解的。 tomcat其實是一個web框架,那麼其內部是怎麼實現的呢?如果不用tomcat我們能自己實現一個web伺服器嗎? 首先,tomcat內部的實現是

每天一個python段子2:一句話http伺服器

0x00 Python版本: python2 ipv4 python -m SimpleHTTPServer 8080 ipv6 python -c "import socket,SocketServer,CGIHTTPServer;SocketServer.TCPSe

SSH:利用Struts2+Hibernate4+Spring4+SQLServer框架,搭建一個前後端web網站2

百度編輯器編輯文章 利用百度編輯器實現文章的編寫,實現效果如下: 可以看到利用百度編輯可以很好的實現文字的排版效果,同時還可以多圖上傳以及新增錨點和上傳視屏。 配置的一些關鍵點: 當從

1000行程式碼手Web伺服器

1000行程式碼手寫Web伺服器(包括HTTP伺服器和Servlet容器) 具備的功能(均為簡化版的實現): HTTP Protocol 實現了HTTP協議 Servlet ServletContext Request 封裝HTTP請求報文 Respon

Anaconda+django出第一個web app

圖片 things tab tro ogr 參考 min sent clas 在安裝好Anaconda和django之後,我們就可以開始創建自己的第一個Web app,那麽首先創建一個空文件夾,之後創建的文件都在這個文件夾內。 啟動命令行進入此文件夾內,可以先通過如下命令查

Anaconda+django出第一個web app

down lock 下載 解決 RKE 分享圖片 tinymce 今天 分享 今天開始學習網頁風格和設計,就像python有Web框架一樣,也有一些CSS框架。對於CSS框架,我們可以使用默認的樣式,也可以在原基礎上編輯修改。本教程使用的是materialize這個CSS框

Anaconda+django出第一個web app

HERE obj short ren 混亂 all err strong anaconda 今天學習如何寫一個註冊用戶的界面。 上一節的導航欄中我們修改了導航欄右側的文字為register並將路徑設置為/register,內容如下: <li><a hre

Anaconda+django出第一個web app

one reg url cat AMM filter import tex nac 今天繼續學習外鍵的使用。 當我們有了category、series和很多tutorials時,我們查看某個tutorial,可能需要這樣的路徑http://127.0.0.1:8000/c

暑假自學JAVA Web心得2

聲明 代碼 請求 區別 處理請求 nbsp 編譯器 心得 最終 3.JSP腳本 1.JSP中應用代碼片段 格式:<% Java代碼或是腳本代碼 %> 在頁面請求處理 期間被執行。通過java代碼可以定義變量或是流程控制語句,通過腳本代碼可以應用JSP的內置對象

如何搭建一個web網站

團隊合作 是的 轉換 們的 web服務 ons lang 用戶 域名 前言: 由於新生軍訓結束,作為學生會的一個技術部的老油條,這時候得幫幫他們了。 大多數新生都是奔著能做一些小東西,能夠被大家,被其他人用,為目的進入了技術部,部門主要負責做院系微信運營,順帶做開發。前兩任

一起框架-控制反轉Ioc概述

val 程序設計 log font 修改 style 編程思想 調用方法 cal 控制反轉概述 控制反轉(Inversion of Control,英文縮寫為IoC),就是將代碼的調用的控制權,由調用方轉移給被調用方。 如圖:修改代碼A類的代碼,才能將B類的對象換成C類

一個python程序2

小結 nts 技術 數學公式 spa 但是 漂亮 num 回車 輸入和輸出 輸出 用print加上字符串,就可以向屏幕上輸出指定的文字。比如輸出‘hello, world‘,用代碼實現如下: >>> print ‘hello, world‘ print語

WEB測試2--WEB核心技術之WEB工作過程---URL

class wpa quest www. gpo 類型 pos item src web工作過程,首先談到url地址解析。如下圖:包括5個部分 1.協議類型 https 2.主機名 www.zhihu.com (通過DNS解析出主機名) 3.端口號 圖中端口號為443

新手上手Tensorflow之手數字識別應用2

本系列為應用TensorFlow實現手寫數字識別應用的全過程的程式碼實現及細節討論。按照實現流程,分為如下幾部分: 1. 模型訓練並儲存模型 2. 通過滑鼠輸入數字並儲存 2. 影象預處理 4. 讀入模型對輸入的圖片進行識別 本文重點討論模型的儲存以及讀入問題。 關於Tens

web安全2-- CSRF跨站點請求偽造

CSRF(Cross-site request forgery跨站請求偽造,也被稱成為“one click attack”或者session riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。  一、CSRF攻擊原理 CSRF攻擊原理比較簡單,如圖1所示。其中W

web伺服器併發、非堵塞、epoll

返回瀏覽器請求的頁面: 這是用網路助手模擬:tcp伺服器,然後用瀏覽器,連結伺服器,伺服器接收到瀏覽器的請求。 我們需要返回瀏覽器請求的頁面,我們就需要用正則表示式,將上面的一大串字串提取請求的頁面。 步驟: 1.切割字串。 2.正則表示式提取。 im