這個來自以前作的培訓,刪減了一些業務相關的,參考了不少資料( 參考資料列表),謝謝前輩們,麼麼噠 😘
目前先後端的通訊通常都是經過協議來完成的,這裏介紹和前端開發相關的各種協議。javascript
HTTP 協議是Hyper Text Transfer Protocol(超文本傳輸協議)的縮寫,是萬維網的數據通訊的基礎。設計HTTP最初的目的是爲了提供一種發佈和接收HTML頁面的方法,如今基本上什麼類型的文件均可以傳輸了,好比圖片、CSS、JS、數據報文等。html
HTTP通常基於TCP/IP通訊協議來傳遞數據,它有以下特色:前端
HTTP使用統一資源標識符(Uniform Resource Identifiers, URI)來傳輸數據和創建鏈接。URL是一種特殊類型的URI,包含了用於查找某個資源的足夠的信息。一個完整的URL通常包括如下幾部分:html5
協議
域名
端口
虛擬目錄
文件名
參數
錨
java
好比:http://test.google.com:80/test/test.html?query=admin#home
git
一般一個HTTP請求消息包含以下內容:請求行、請求頭、空行、消息主體。程序員
好比:github
GET /books/?sex=man&name=Professional HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive
複製代碼
GET /books/?sex=man&name=Professional HTTP/1.1
用來講明請求類型,要訪問的資源以及所使用的HTTP版本。比較常見的請求類型有GET,POST,PUT,DELETE,OPTIONS等。帶有請求數據的POST請求:web
POST / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
sex=man&name=Professional 複製代碼
通常狀況下,服務器接收並處理客戶端發過來的請求後會返回一個HTTP的響應消息。一般一個HTTP請求消息包含以下內容:狀態行、消息報頭、空行、響應正文。typescript
好比:
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
<html>
<head></head>
<body>
<!--body goes here-->
</body>
</html>複製代碼
狀態碼由三位數字組成,第一個數字定義了響應的類別,共分五種類別:
常見的狀態碼有以下幾種:
HTTP2即超文本傳輸協議2.0版本,是HTTP協議的下一個版本。由於標準委員會不打算再發布子版本了,因此直接叫HTTP/2,而不叫HTTP/2.0。
HTTP2相對於以前的HTTP協議有如下幾個優勢:
HTTP 1.1會讓資源排隊加載,以下圖所示:
但當咱們開啓了HTTP/2以後,有了TCP多路複用,個數幾乎沒有限制了,以下圖所示:
HTTP/2 將 HTTP 協議通訊分解爲二進制編碼幀的交換,這些幀對應着特定數據流中的消息。全部這些都在一個 TCP 鏈接內複用。這是 HTTP/2 協議全部其餘功能和性能優化的基礎。
目前支持HTTP2協議傳輸的瀏覽器依然不多,隨着技術的發展和瀏覽器的更新迭代,HTTP2的時代終會到來,但咱們依然不能在短期內企圖經過它來幫咱們進行頁面優化。
HTTPS(超文本傳輸安全協議 Hypertext Transfer Protocol Secure)經由HTTP進行通訊,但利用SSL/TLS來加密數據包。HTTPS的主要思想是在不安全的網絡上建立一安全信道。一般,HTTP 直接和 TCP 通訊。當使用 SSL 時,則演變成先和 SSL 通訊,再由 SSL 和 TCP 通訊了。簡言之,所謂 HTTPS,其實就是身披 SSL 協議這層外殼的 HTTP。HTTP的URL由「http://」起始且默認使用端口80,HTTPS的URL由「https://」起始且默認使用端口443。
SSL 是獨立於 HTTP 的協議,因此不光是 HTTP 協議,其餘運行在應用層的 SMTP 和 Telnet 等協議都可配合 SSL(Secure Socket Layer) 協議使用。
HTTP是不安全的,攻擊者經過監聽和中間人攻擊等手段,能夠獲取網站賬戶和敏感信息等。人們對 HTTPS 有一個廣泛的錯誤認識,認爲只有處理敏感通訊的網站才須要 HTTPS。 每一個未受保護的 HTTP 請求均可能暴露與用戶行爲和身份有關的信息。儘管訪問一次未受保護的網站可能看上去無害,但一些入侵者會查看彙總的用戶瀏覽活動,以推斷他們的行爲和意圖,從而進行去匿名化攻擊,查出匿名用戶的身份。例如,員工可能在閱讀未受保護的醫療文章時不經意地向其僱主泄露敏感的健康信息。
加密和解密同用一個密鑰的方式稱爲共享密鑰加密(Common key crypto system),也被叫作對稱密鑰加密。
以共享密鑰方式加密時必須將密鑰也發給對方。可究竟怎樣才能安全地轉交?在互聯網上轉發密鑰時,若是通訊被監聽那麼密鑰就可會落入攻擊者之手,同時也就失去了加密的意義。
SSL 採用一種叫作公開密鑰加密(Public-key cryptography)的加密處理方式。公開密鑰加密使用一對非對稱的密鑰。一把叫作私有密鑰(private key),另外一把叫作公開密鑰(public key)。顧名思義,私有密鑰不能讓其餘任何人知道,而公開密鑰則能夠隨意發佈,任何人均可以得到。
使用公開密鑰加密方式,發送密文的一方使用對方的公開密鑰進行加密處理,對方收到被加密的信息後,再使用本身的私有密鑰進行解密。利用這種方式,不須要發送用來解密的私有密鑰,也沒必要擔憂密鑰被攻擊者竊聽而盜走。
HTTPS 採用混合加密機制:
公開密鑰加密方式仍是存在一些問題的。那就是沒法證實公開密鑰自己就是貨真價實的公開密鑰。
證書頒發機構 (CA) 是一個組織,對公鑰和與公共 DNS 名稱之間的映射進行證明。例如,客戶端如何知道特定公鑰是否爲 www.foobar.com 的真實公鑰?按理說,沒法知道。CA 證明特定密鑰是特定網站的真實密鑰,它使用本身的私鑰來加密簽名該網站的公鑰。此簽名在計算上是沒法僞造的。瀏覽器(和其餘客戶端)維護信任錨存儲庫,它包含知名 CA 擁有的公鑰,而且它們使用這些公鑰來加密驗證 CA 的簽名。
最後使用共享密鑰來進行之後的通訊,詳細流程:
在實際的前端應用項目中,除了使用應答模式的HTTP協議進行普通網絡資源文件的請求加載外,有時也須要創建客戶端與服務端之間的實時鏈接進行通訊,例如網頁實時聊天的應用場景,這就必須涉及瀏覽器端的實時通訊協議了。對於這些對實時性要求較高的應用場景,普通的HTTP協議就並不適用。雖然前端能夠經過Ajax定時向服務端輪詢的方式來持續獲取服務端的消息,可是這種方式效率相對較低。
WebSocket是瀏覽器端和服務器端創建實時鏈接的一種通訊協議,能夠在服務器和瀏覽器端創建相似Socket方式的消息通訊。相對於HTTP1.1協議,WebSocket協議的優點是方便服務器和瀏覽器之間的雙向數據實時通訊。
這裏只是相似Socket方式,WebSocket 是創建在 TCP/IP 協議之上,屬於應用層的協議,而 Socket 是在應用層和傳輸層中的一個抽象層,它是將 TCP/IP 層的複雜操做抽象成幾個簡單的接口來提供給應用層調用。簡單回顧一下7層網絡模型:
簡單來講,咱們在傳輸數據時,能夠只使用(傳輸層)TCP/IP協議,可是那樣的話,若是沒有應用層,便沒法識別數據內容。若是想要使傳輸的數據有意義,則必須使用到應用層協議。應用層協議有不少,好比HTTP、FTP、TELNET等,也能夠本身定義應用層協議。WEB使用HTTP協議做應用層協議,以封裝HTTP文本信息,而後使用TCP/IP作傳輸層協議將它發到網絡上。
TCP/IP只是一個協議棧,就像操做系統的運行機制同樣,必需要具體實現,同時還要提供對外的操做接口。這個就像操做系統會提供標準的編程接口,好比win32編程接口同樣,TCP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口。Socket的出現只是使得程序員更方便地使用TCP/IP協議棧而已,是對TCP/IP協議的抽象,從而造成了咱們知道的一些最基本的函數接口,好比create、listen、connect、accept、send、read和write等等。
WebSocket 的實現分爲握手,數據發送/讀取,關閉鏈接。
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
}; 複製代碼
在線演示:html5demos.com/web-socket/
DDP ( Distributed Data Protocol,分佈式數據協議)是一種新型的客戶端與服務器端的實時通訊協議,因爲兼容性的緣由,目前使用還不普遍。
DDP使用JSON的數據格式在客戶端和瀏覽器之間進行數據傳輸通訊,因此對於前端開發者來講使用很是方便。有名的Meteor Web框架的雙向實時數據更新機制底層使用的就是DDP,這種協議模式下客戶端可向服務器端發起遠程過程調用,客戶端也能夠訂閱服務端數據,在服務端數據變化時,服務器會向客戶端發起通知,觸發瀏覽器響應的操做。
//建立服務端
const DDPClient = require('ddp');
const client = new DDPClient({
host: 'localhost',
port: 3000
});
//監聽消息
client.on('message', function(data, flags){
console.log('[DDP消息]: ', data);
});
//建立鏈接
client.connect(function(){
client.subscribe('post',[],function(){
console.log('[post訂閱消息]');
});
});複製代碼
REST (Representational State Transfer,表述性狀態轉化)並非某一種具體的協議,而是定義了一種網絡應用軟件之間的架構關係並提出了一套與之對應的網絡之間交互調用的規則。與之相似的例如早期的WebSevice,WebSevice如今基本都不用了。
在REST形式的軟件應用服務中,每一個資源都有一個與之對應的URI地址,資源自己都是方法調用的目標,方法列表對全部資源都是同樣的,並且這些方法都推薦使用HTTP協議的標準方法,例如GET、POST、PUT、DELETE等。若是一個網絡應用軟件的設計是按照REST定義的,咱們就能夠認爲它使用的交互調用的方法設計遵循RESTful規範。換種方式理解,RESTful 是一種軟件架構之間交互調用數據的協議風格規範,它建議以一種通用的方式來定義和管理數據交互調用接口。
例如:對於書籍book的記錄管理接口,有增、刪、改、查操做,因而咱們定義接口:
看上去好像沒有什麼問題。後來,另外一個項目也有相似的接口定義,卻可能叫做:
接着有一天,項目負責人可能會說,要升級接口來知足新的需求,因而咱們又添加了:
這樣用起來是沒有什麼問題,可是這些隨意的定義會增長數據接口維護難度和項目繼續開發的成本。
這時,咱們或許會考慮使用文檔或規範,規定必定要使用add來添加,新的接口版本號放前面 path/v2/addBook,開發的人必須嚴格按照文檔規範去寫。這樣作很好,但依然不夠完善,緣由有如下幾點:
這時若是有一個風格更好的通用規範來定義數據交互接口,就不用這麼麻煩了。因此咱們徹底能夠利用RESTful設計的規範特性來解決上面遇到的問題。對於書籍記錄操做接口的命名能夠以下操做:
HTTP方法 | URI | 描述 |
---|---|---|
POST | path/v1/book | 新增書籍信息 |
DELETE | path/v1/book | 刪除書籍信息 |
PUT | path/v1/book | 更新書籍信息 |
GET | path/v1/book | 獲取書籍信息 |
使用RESTful規範來從新設計接口後,一切就變得很清晰天然,這樣新的工程師接手項目時,只要他足夠了解RESTful規範,幾乎沒有時間成本。即便他不瞭解RESTful規範,也能夠很快地去了解,這就能夠避免他去讀那份看似完善其實冗長雜的文檔。
這裏涉及到了幾個設計的原則:
RESTful API 的主要設計原則就是這些,總結來講就是結合HTTP的固有方式來表徵資源的狀態變化描述,而不是經過動詞加名詞的方式來設計。
Github RESTful API:developer.github.com/v3/
GraphQL 對 API 中的數據提供了一套易於理解的完整描述,使得客戶端可以準確地得到它須要的數據,並且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進。
GraphQL 解決的最重要的3個問題分別是:
一個簡單的示例:咱們要作一個星球大戰人物信息展現的UI界面:須要顯示人物的姓名,出生年份,星球名稱以及全部他們參演的電影的名稱。
這個 UI 的 JSON 數據可能相似於:
{
"data": {
"person": {
"name": "Darth Vader",
"birthYear": "41.9BBY",
"planet": {
"name": "Tatooine"
},
"films": [
{ "title": "A New Hope" },
{ "title": "The Empire Strikes Back" },
{ "title": "Return of the Jedi" },
{ "title": "Revenge of the Sith" }
]
}
}
}複製代碼
若是使用 React.js ,通常會這樣表示視圖:
// The Container Component:
<PersonProfile person={data.person} ></PersonProfile>
// The PersonProfile Component:
Name: {person.name}
Birth Year: {person.birthYear}
Planet: {person.planet.name}
Films: {person.films.map(film => film.title)}複製代碼
若是使用 RESTful API,咱們可能這樣請求數據:
一、獲取人物信息:
GET - /people/{id}複製代碼
{
"name": "Darth Vader",
"birthYear": "41.9BBY",
"planetId": 1
"filmIds": [1, 2, 3, 6],
...
}複製代碼
二、獲取星球信息:
GET - /planets/1複製代碼
三、獲取全部電影信息:
GET - /films/1
GET - /films/2
GET - /films/3
GET - /films/6複製代碼
咱們須要發送6個請求才能獲取到全部須要的數據,每一個獲取數據的方法都是命令式的。每一個接口返回的信息還有不少字段不是咱們所須要的。爲了解決這個問題,咱們可能會新增長一個接口,好比:
GET - /people/{id}/films複製代碼
可是這樣就不是純粹的RESTful API了,並且後端要額外的建立這個接口,用來知足前端的數據要求,若是增減字段或對象,後端還要添加接口或者從新編碼。
若是使用GraphQL,咱們能夠這樣來查詢:
GET or POST - /graphql?query={...}複製代碼
好比參數使用:
{
person(personID: 4) {
name,
birthYear,
homeworld {
name
},
filmConnection {
films {
title
}
}
}
}複製代碼
一個請求就完成了全部數據的獲取。
GraphQL的靈活性也會帶來一些問題,好比增長複雜度,資源耗盡攻擊,N+1查詢等。FB針對N+1給出了 dataloader 的方案。
Github GraphQL API:developer.github.com/v4/
在線調試:developer.github.com/v4/explorer…
Hybrid App是在Native App應用的基礎上結合了Web App應用所造成的模式,通常稱之爲混合App。從技術開發上來看,相比於傳統的桌面瀏覽器端的Web App,它具備如下幾方面的特徵:
在HTML5中調用Native程序通常有幾種較通用的方法:
1、經過URI請求。
Native應用可在移動端系統中註冊一個Scheme協議的URI,這個URI可在系統的任意地方受權訪問來調起一段原生方法或一個原生的界面。一樣,Native 的WebView控件中的JavaScript腳本的請求也能夠匹配調用這一通用的Scheme協議。例如咱們經過對 window.location.href 賦值或使用iframe的方式發送一個URI的請求,這個請求能夠被Native應用的系統捕獲並調起Native應用註冊匹配的這個Scheme協議內容。
好比微信的scheme爲(weixin://)。
2、經過addJavascriptInterface(Android)或JavaScriptCore(iOS)注入方法到頁面中調用。
Android,原生Webview須要先註冊可供前端調用的JS函數:
// Android容器容許JS腳本,必需要
webSettings.setJavaScriptEnabled(true);
// Android容器設置僑連對象
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");
// Android4.2版本及以上,本地方法要加上註解@JavascriptInterface,不然會找不到方法。
private Object getJSBridge(){
Object insertObj = new Object(){
@JavascriptInterface
public String foo(){
return "foo";
}
@JavascriptInterface
public String foo2(final String param){
return "foo2:" + param;
}
};
return insertObj;
}複製代碼
而後H5中便可調用原生中註冊的函數:
// 調用方法一
window.JSBridge.foo(); // 返回:'foo'
// 調用方法二
window.JSBridge.foo2('test'); // 返回:'foo2:test'複製代碼
iOS,須要引入JavaScriptCore庫:
#import <JavaScriptCore/JavaScriptCore.h>複製代碼
而後原生須要註冊API:
//webview加載完畢後設置一些js接口
-(void)webViewDidFinishLoad:(UIWebView *)webView{
[self hideProgress];
[self setJSInterface];
}
-(void)setJSInterface{
JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 註冊名爲foo的api方法
context[@"foo"] = ^() {
//獲取參數
NSArray *args = [JSContext currentArguments];
NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
//作一些本身的邏輯
//返回一個值 'foo:'+title
return [NSString stringWithFormat:@"foo:%@", title];
};
}複製代碼
以後前端就能夠調用了:
// 調用方法,用top是確保調用到最頂級,由於iframe要用top才能拿到頂級
window.top.foo('test'); // 返回:'foo:test'複製代碼
3、改寫瀏覽器原有對象。
經過修改原來瀏覽器的window某些方法,而後攔截固定規則的參數,而後分發給Java對應的方法去處理。這裏經常使用的是如下四個方法:
須要先使用JavaScript 在HTML5頁面全局中聲明相對應的方法。
而後Native向HTML5發起調用,Android平臺通常經過loadUrl,iOS一般經過stringByEvaluatingJavaScriptFromString實現。
Android調HTML5:
// 異步執行JS代碼,並獲取返回值
mWebView.evaluateJavascript("javascript: 方法名('參數,須要轉爲字符串')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// 這裏的value即爲對應JS方法的返回值
}
});複製代碼
iOS調HTML5:
// 能夠取得JS函數執行的返回值
// 方法必須是Html頁面綁定在最頂層的window上對象的
// 如window.top.foo
[webView stringByEvaluatingJavaScriptFromString:@"方法名(參數);"];複製代碼
JSBridge是HTML5與Native通訊的橋樑,其做用是實現HTML5與Native間的雙向通訊。JSBridge綜合了上面的技術,更多的是一種形式、一種思想,各家的實現方式也略有差別。好比微信JSSDK,就是基於WeixinJSBridge,微信瀏覽器中的頁面,經過WeixinJSBridge調用微信提供的一些原生功能。