前言:本次嘗試源於我們內部的一個項目,由於前端邏輯比較復雜,就打算將前后端分開來開發。由於之前用Django開發過軟件,對AngularJS(Angular 1.0版)也有一定的了解,因此就將技術路線鎖定在了這兩個開源工具上,用Angular做前端(這次是Angular 2),Django做后端。
這個系列計划分為以下四個部分:
(1): 分離 or 不分離,it's the question
(2):前端組件化——Angular
(3):后端服務化——Django REST框架
(4):前后端的通信——跨域請求
前后端的關系
前后端是否分離是我們設計網站應用程序架構時需要想清楚的首要問題。這直接影響了我們的開發流程,技術路線,甚至后期的維護。
要搞清楚是否需要分離,首先要明白前后端各自最主要的職責是什么。
關於前端
- 之所以叫做前端,是因為相對於服務器端,這部分是直接與用戶接觸的;
- 前端三件寶:html, css, JavaScript;
- 主要職責:負責網站內容的展示,接受用戶的輸入並作出相應的反饋(包括用戶輸入有效性的驗證,將輸入的數據傳給服務器,接受服務器傳回的數據並作出正確反應);
關於后端
- 對前端的請求作出響應;
- 數據的驗證(再次驗證);
- 數據的預處理和分析(可以調用系統資源);
- 數據的持久化(保存到數據庫或文件系統);
- 當然還有很多其他的功能,例如保存負載均衡等,但是最重要的一個工作是為前端提供數據;
- 后端的開發語言比前端的選擇空間大得多,例如Java、Ruby,PHP等,當然還有Python.
其實就在於 模板的與數據結合的位置,以及, 模板的控制權在誰手里。
不分離的情況
之前用Django開發軟件時,並沒有深刻的感受到Django在網站開發上作為一個框架的完美主義(大包大攬)。最近回顧之前做的一些工作時,才發現了這一事實。利用Django,不用分離出前端,也可以做出非常棒的網站。因為Django中自帶開發一個網站的全部要素——基本配置、路由、視圖、模型,表單,模板,模板的渲染等。
如果前后端放在一起,使用Django的模板系統(模板的控制權在后端),這樣做最大的好處就是數據可以從前端的表單(Form)直接提交給后端的視圖函數(View function);數據在視圖函數中經過處理后可以直接傳遞給模板(template),最終將渲染后的模板展示給用戶。在這個過程中,數據流如下:
html -> Python -> (JavaScript) -> html
- html -> Python:通常情況下,是從html接受表單的輸入,然后使用PUT或POST方法直接提交給Django的視圖函數;
- Python -> JavaScript:數據經過視圖函數的處理,最后可以轉換成JSON格式傳遞給JS;
- JavaScript -> html:由JS進一步處理后的數據可以通過對html中DOM的操作反映到前端,也可以使用一些前端框架(例如AngularJS的雙向綁定)將數據傳給html;
- Python -> html:數據也可以經過視圖函數處理后,直接傳給模板(JSON格式或由字典構成的數組),在模板中可以使用{{ data }}這種格式直接消費視圖函數傳遞的數據。
在整個過程中,數據的交換都是同源的,這樣操作方便,安全性也高。同源就是指http請求之間的協議、域名和端口都相同。
此時模板的控制權和數據與模板的結合位置都在后端。
當需要分離時。。。
前后端統一有很多好處,開發起來也省時省力。那么在什么情況下需要分離呢?
- 前端邏輯比較復雜;
- 對UI的要求比較高(並不是說Django的模板丑,而是自己的css技術不夠);
- 提高代碼的可維護性和可擴展性;
分離之后,Django就不用處理模板的渲染,路由這些事務了。后端專注於提供數據,更重要的職責是維護系統架構的穩定,保證數據的安全。前端人員專注於交互,快速響應UI的變化。
程序的結構
分離前程序其實就是一個單一的Django程序,一切基本上都可以用Django搞定。但是分離之后,Django就只負責后端的數據,前端就完全不由它管了。如果前端使用Angular,那么此時就相當於有兩個程序:前端的Angular程序,后端的Django程序。那么問題就來了,這兩個程序之間怎么交換數據呢?這時候有一個方法就是將后端設計成REST(Representational State Transfer,又稱具象狀態傳輸)結構。
此外還有一個問題需要解決,分離前數據是在同一個程序內部傳遞的,屬於同源傳遞,這是瀏覽器默認就支持的行為;但是分離后,數據需要在兩個不同的程序之間傳遞,這兩個程序必定是不同源的,至少是端口不同,為了安全方面的考慮,這樣的操作是被限制的。這時就需要解決跨源資源請求的問題,現在用的比較多的是CORS(Cross-Origin Resource Sharing,跨源資源分享)。
圖1:前后端分離后,它們通過http協議進行通信
如圖1所示,前后端分離后,客戶端通過http協議,使用GET、POST、PUT、DELETE等動詞對資源發出請求(Request);服務器端收到請求后,完成相應的操作或通過http協議返回客戶端需要的資源(Response)。
具象狀態傳輸,REST
REST(Representational State Transfer,又稱具象狀態傳輸)是Roy Thomas Fielding博士於2000年在他的博士論文中提出來的一種萬維網軟件架構風格,目的是便於不同軟件/程序在網絡(例如互聯網)中互相傳遞信息。目前在三種主流的Web服務實現方案中,因為REST模式與復雜的SOAP和XML-RPC相比更加簡潔,越來越多的web服務開始采用REST風格設計和實現。
需要注意的是,REST是設計風格而不是標准。REST通常基於使用HTTP,URI,和XML以及HTML這些現有的廣泛流行的協議和標准。
要理解什么是REST,我們需要理解下面幾個概念:
- 資源(Resources):REST是"表現層狀態轉化",其實它省略了主語。"表現層"其實指的是"資源"的"表現層"。那么什么是資源呢?就是我們平常上網訪問的一張圖片、一個文檔、一個視頻等。這些資源我們通過URI來定位,也就是一個URI(Uniform Resource Identifier,統一資源標志符)表示一個資源。
- 表現層(Representation):資源是一個具體的實體信息,可以有多種的展現方式。而把實體展現出來就是表現層,例如一個txt文本信息,他可以輸出成html、json、xml等格式,一個圖片也可以jpg、png等方式展現,這個就是表現層的意思。URI確定一個資源,但是如何確定它的具體表現形式呢?應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段才是對"表現層"的描述。
- 狀態轉化(State Transfer):訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,肯定涉及到數據和狀態的變化。而HTTP協議是無狀態的,那么這些狀態肯定保存在服務器端,所以如果客戶端想要通知服務器端改變數據和狀態的變化,肯定要通過某種方式來通知它。客戶端能通知服務器端的手段,只能是HTTP協議。具體來說,就是HTTP協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源,如圖2所示。
綜合上面的解釋,總結一下什么是RESTful架構:
(1)每一個URI代表一種資源;
(2)客戶端和服務器之間,傳遞這種資源的某種表現層;
(3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"(對於瀏覽器前的用戶來說就是,對界面的操作得到了恰當的反饋)。
圖2:客戶端通過http協議中的幾個動詞對資源進行操作
REST架構風格最重要的架構約束有6個:
-
客戶-服務器(Client-Server):通信只能由客戶端單方面發起,表現為請求-響應的形式。
-
無狀態(Stateless):通信的會話狀態(Session State)應該全部由客戶端負責維護。
-
緩存(Cache):響應內容可以在通信鏈的某處被緩存,以改善網絡效率。
-
統一接口(Uniform Interface):通信鏈的組件之間通過統一的接口相互通信,以提高交互的可見性。
-
分層系統(Layered System):通過限制組件的行為(即每個組件只能“看到”與其交互的緊鄰層),將架構分解為若干等級的層。
-
按需代碼(Code-On-Demand,可選):支持通過下載並執行一些代碼(例如Java Applet、Flash或JavaScript),對客戶端的功能進行擴展。
跨域資源共享,CORS
CORS是一個W3C標准,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
實現條件
CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。
在Django中的實現
在Django中,需要安裝一個包來支持CORS操作,django-cors-headers可以幫我們添加需要的頭信息,從而讓Django支持跨域資源共享。具體可以參考django-cors-headers的官方文檔。本文也會在后面詳細介紹。
接下來會介紹Angular... 敬請期待。
Reference
https://zh.wikipedia.org/wiki/REST
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/08.3.md
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://github.com/ottoyiu/django-cors-headers