說起較大型系統的源碼閱讀,算上目前進行中的Swift,也就只有兩次經驗(去年的上半年有閱讀過學習過Openfire的源碼)。雖說還是菜鳥層級啦,但兩次也可以總結經驗嘛:P,哈哈~
我的這個經驗呢,就是對於這種伺服器端的源碼,最好首先對系統的“啟動過程”和請求到來時的“data flow”進行一遍跟蹤閱讀,瞭解程式的運作流程以及各個關鍵類、方法之間的關係,然後再從這條主線進行各個分叉流程的細緻學習。這種方式一來目的性比較強,代碼比較容易看得下去,二來不會有越看越茫然的感覺,總體上是逐漸清晰瞭解細節的過程。
好啦,廢話說完咯,進入正題。這兩周開始了swift源碼(swift 1.8.0)的學習,首先就按照以上所述的流程進行了一遍粗淺的閱讀,現在總結一下。
Swift啟動流程=========================================================
在ubuntu上部署完畢swift後,我們知道可以執行指令碼 startmain 啟動swift,那麼這條命令執行後,swift究竟做了哪些啟動相關的工作呢?
1. 概況
startmain 指令碼實際上是執行了以下這條命令:
swift-init main start
即為swift執行初始化工作,而該命令實際上又分別執行了swift-proxy-server、swift-account-server、swift-container-server、swift-object-server 這四個swift bin中的python命令。接下來,讓我們看一下在swift代碼中都做了些什麼。
2. swift-init
swift-init 是啟動swift的指令碼,改指令碼可以跟幾個參數:swift-init [主體] [動作]。在“swift-init main start”中main就對應主體,動作即為start。
1)swift-init main start 執行後,首先看swift-init代碼 line61~line62,在這裡對命令和參數進行了分割,從而決定接下來的執行任務:
command = args[- 1] #command此處為startservers = args[:-1] #servers此處為main
2)在swift-init代碼line 70,建立了Manager的執行個體,Manager是直接管理各個servers的類:
manager = Manager(servers, run_dir=options.run_dir) # 這裡執行了Manager的__init__
3)由於在上一步中,swift-init建立了Manager執行個體,因此調用了Manager的__init__初始化方法,在__init__方法中,我們可以看到對命令參數servers(這裡是“main”)的處理(line 137)。
elif server == 'main': server_names.update(MAIN_SERVERS)
其中MAIN_SERVERS為manager.py中定義的全域數組,指明了swift中servers的啟動指令碼(line 37)
MAIN_SERVERS = ['proxy-server', 'account-server', 'container-server', 'object-server']
在Manager.__init__最後add servers的時候,就建立了Swift中的四個Server:swift-proxy -server、swift-account-server、swift-container-server、swift-object-server 4)Manager執行個體建立完畢後,回到swift-init中,在line 72,調用了manager的run_command方法,繼而擷取了start命令:
status = manager.run_command(command, **options.__dict__)
5)在manager.py的run_cmmand方法中(line 162),最終執行了start方法,即執行了swift-proxy -server、swift-account-server、swift-container-server、swift-object-server的__main__,也就是start方法中調用的launch方法列印了這堆東西:
到這裡,swift中的各個servers就被啟動起來了,接下來讓我們以proxy-server為例,看看在這些swift-xx-server中都做了些什麼。
3. swift-xx-server的啟動
在swift-xx-server中最終調用了相應的xx.server.py中相應類的__init__(以proxy為例):
run_wsgi(conf_file, 'proxy-server', default_port=8080 , **options)
我們可以看到swift的HTTP互動是基於WSGI實現的,並且它使用了paste deploy進行設定檔的管理,從而通過xx-server.conf設定檔load相應的wsgi app。在run_wsgi方法中,主要執行了以下四個步驟:
1)run_wsgi方法根據參數load設定檔
2)根據設定檔綁定的連接埠建立socket,將socket與server綁定
3)run_server方法調用proxy的server的__init__方法
4)__init__方法根據設定檔初始化server的內部狀態,從而gets the server ready to handle requests
至此,Swift中的各個servers就已經stand by啦!只待request的到來!OK,那麼接下來讓我們來看一下當一個request到來時,swift中的data flow又是怎樣的吧 : )
Swift Data Flow=========================================================
在上文中呢,我已經提到過swift的HTTP互動架構是基於wsgi實現的,並且使用paste deploy實現通過conf來load app,因此如果你希望可以更好的瞭解swift的互動流程和wsgi-style application標準,那麼建議你先學習一下 wsgi標準 以及 paste deploy 的設定檔含義。
首先,來看一張俺畫滴圖!這張圖基本上就把整個請求過程的data flow說清楚啦!(原諒我無恥的打了浮水印,哈哈哈哈哈)
讓我們按照圖中的流程來解釋吧。
1. Send a Request
首先,使用者也就是Client向swift發出一個request,該請求通過proxy-server.conf中配置的bind_port進入swift;
2. WSGI middleware: Pipeline
這裡先給出一份proxy-server.conf的配置內容,我們來結合這個paste deploy設定檔進行pipeline的說明:
[DEFAULT]bind_port = 8080user = rootlog_facility = LOG_LOCAL1eventlet_debug = true[pipeline:main]pipeline = healthcheck cache tempauth proxy-logging proxy-server[app:proxy-server]use = egg:swift#proxyallow_account_management = trueaccount_autocreate = true[filter:tempauth]use = egg:swift#tempauthuser_admin_admin = admin .admin .reseller_adminuser_test_tester = testing .adminuser_test2_tester2 = testing2 .adminuser_test_tester3 = testing3[filter:healthcheck]use = egg:swift#healthcheck[filter:cache]use = egg:swift#memcache[filter:proxy-logging]use = egg:swift#proxy_logging
請求需要經過wsgi pipeline中的層層過濾器才能最終到達請求的應用:proxy-server。請注意設定檔中加粗的部分:
[pipeline:main]表明這裡是application的入口,pipeline代表一些列的filters,main則類似各種程式設計語言中的main方法指明入口。
pipeline = filter1 filter2 ... filterN application 的最後一個元素必須是一個符合wsgi的callable對象,具體到這個例子中,表明請求必須經過healthcheck、cache tempauth、proxy-logging這四個filter的過濾後,才能到達proxy-server。如果請求沒有通過任何一個過濾器,則請求將會被返回相應的錯誤資訊,不能到達proxy-server。
這些filters和最後的app都必須是符合WSGI標準的callable application,在程式執行時,依次調用它們的__call__方法,讓我們以healthcheck為例,來看看它的__call__:
def __call__(self, env, start_response): req = Request(env) try: if req.path == '/healthcheck': handler = self.GET if self.disable_path and os.path.exists(self.disable_path): handler = self.DISABLED return handler(req)(env, start_response) except UnicodeError: # definitely, this is not /healthcheck pass return self.app(env, start_response)
我們可以看到,這個方法必須包含兩個參數(這裡是類中的方法,因此還有一個self)env和start_response,env是wsgi中的環境變數字典,start_response用於產生response的結果,這個方法返回的self.app(env, start_response)的這個app其實就是它的下一個filter:cache,cache也會以相同的方式執行,直到調用proxy-server,proxy-server負責產生相應的Response並返回。
3. Proxy-Server.__call__
假設Client發出的這個request非常的合法,他通過了所有的filters,現在終於到達了proxy-server,那接下來又會發生什麼呢?
proxy-server這個application實際上對應swift源碼proxy包下的server.py中的Application類,如同我們在上文中說到的,這裡會調用Application.__call__。在Application.__call__中,對請求做了一些校正,合法後,就調用它的handle_request方法,顧名思義的來handle這個request。
我們知道在swift中請求可以針對account、container和object,顯然它們所對應的控制器是不一樣的,因此在handle_request方法中又繼續調用了get_controller方法。
get_congroller方法根據request path中的http://xxxx/account[container/object]/xxx的子路徑來判斷這個request想要請求的部分,從而擷取對應的controller(AccountController、ContainerController、ObjectController)。細心的你一定會檢查一下swift源碼中的account包、container包、obj包中的server.py吧,沒錯AccountController、ContainerController、ObjectController就分別位於這些server.py中!
4. Controllers
現在,我們已經確定了這個request對應的controller啦!那麼它的請求動作又是什麼呢?HEAD?GET?其實這一切已經在HTTP Verb中定義了,因此在handle_request方法中會繼續調用controller執行個體中對應的XXX方法(如HEAD/PUT/GET/POST/DELETE),這些方法最終對這個request做出處理,產生響應,返回相應的Request!
5. 返回Request
最後這個Request就沿著原路一層層的返回到Client。Swift也繼續該幹嘛幹嘛等待它來自四面八法的Requests。
至此,一個比較outline的請求處理過程就算介紹完畢了。我在這裡省略了很多的細節和校正工作,只是為了讓大家跟我一樣,在diving into swift source code前Crowdsourced Security Testing道它的水大概有多深,路線大概是個什麼樣子的。至於每一步的細節,和精準的角度,還需要在後續一步步的來看,一步步的充實、學習。
==================================全文完結分割啦啦啦啦======================================
本文寫於慌亂之際,說慌亂,是因為我對Python實在是一知半解,很多地方都是現學的(比如WSGI、pastedeploy),再加上這是對swift源碼第一遍的粗淺瞭解,所以難免有些不正確的地方,麻煩各位有什麼想法的話多多交流,一起diving啦 =D