打個總結:Web性能優化

前段時間優化一個公司歷史老項目的Web性能,卻引出了一系列的問題,讓我反思良多。
我通過Chrome的Lighthouse工具可以看出一些性能參數和問題反饋,我逐一對其進行優化。
根據資源請求的不同,大致可以分為前端資源性能和後端程序性能兩個方面。
先分析一下前端資源吧:

  1. Defer offscreen images。

Chrome給出的建議是:

Consider lazy-loading offscreen and hidden images after all critical resources have finished loading to lower time to interactive. Learn more.

意思是可以考慮延遲加載一些圖片或者隱藏一些圖片在所有關鍵資源完成加載后再考慮加載,通過延遲加載來降低交互時間。
lazy-load的實現方法很多,開源框架推薦:lazysizes。
當然也可以使用npm方式安裝:

npm install lazysizes
使用方式很簡單,先引入lazysize到需要的頁面:

<script src="lazysizes.min.js" async=""></script>

然後給需要被lazyload的img標籤上加新的屬性,如下:

<img
    data-sizes="auto"
    data-src="image2.jpg"
    data-srcset="image1.jpg 300w,
    image2.jpg 600w,
    image3.jpg 900w" class="lazyload" />

特別要注意,有時候太多background方式加載的圖片也會影響性能,lazysizes也可以處理這樣的圖片。方法如下,使用data-bg屬性即可:

<div class="lazyload" data-bg="/path/to/image.jpg"></div>
  1. Reduce JavaScript execution time

解決這個問題方法很多,第一個想到的就是壓縮資源。
比如壓縮js和css文件,可以考慮使用webpack工具或者gulp來壓縮大資源文件,合併一些文件資源請求。
還可以通過預加載來提高響應速度,可以在最重要的js資源文件的引入上加入預加載,代碼如下:

<link rel="preload" as="style" href="css/style.css">

通過增加preload
最後還可以異步加載資源,異步不會阻塞主進程,代碼調整也很小:

<script src="xxx" async></script>

除此之外JavaScript的執行時間過長還有可能是有大量邏輯運算,有很多頁面把一些邏輯判斷和計算都交給前端去計算,這樣也不利於渲染,建議還是把複雜邏輯和計算盡可能交給後端去處理,讓前端渲染更加“輕量”。
3. Backend response
數據接口返回有時候也很拖累響應時間,因為一些前端結構需要根據返回的數據進行組裝新的頁面結構。
這裏可以考慮找到性能真正的瓶頸,到底是數據庫查詢導致的慢,還是業務邏輯不清晰導致的冗餘,或者其他原因。
我遇到的是因為老項目的數據庫操作類不是單例,會每次產生一個數據庫連接句柄,且該頁面複雜又多的sql查詢。
我勇敢地修改着10多年歷史的代碼,編寫了單例模式的操作類,然後增加了必要的緩存機制。
然而後面我遇到了問題,首先單例類在一些特殊情形下不滿足之前的代碼需求,導致奇特的數據庫報錯,而async屬性導致一些js文件無法同步加載到位,而有一些依賴這些資源的php文件執行報錯。
最終在同事的幫助下,恢復了服務,我也再一次體會到了任何一種性能提升都要謹慎,特別是對一個古老的項目。
前人不敢動的代碼,可能是深淵。

PS:我的公眾號 成都有娃兒會同步發布該文章,也可以關注哦!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

設計模式入門

最近想學設計模式,網上說 HeadFirst 設計模式書挺好的,我來此再鞏固一篇。

故事是這樣的:小明是一個剛畢業的小伙子,他來到了一個遊戲公司實習,項目經理分配了一個實習任務給小明:

設計一個遊戲角色,角色屬性包括(攻擊力,防禦力,敏捷度…等等),以及兩個召喚師技能(閃現和引燃)。

小明想這麼簡單的嗎,如是他用了一天的時間寫好了如下代碼

public class GameRole {
    
    private int atk; // 攻擊力
    private int def; // 防禦力
    private int dex; // 敏捷度
    
    public void flash_move() {
        System.out.println("指定方向瞬移一段距離");
    }
    
    public void ignite() {
        System.out.println("使其處於燃燒狀態 5 s");
    }
}

項目經理看到小明這麼快就完成了任務,表揚了一下小明,小明心裏樂開了花。然後項目經理又布置了一項任務給小明:
再設計兩個角色,他們的召喚師技能分別為(閃現,治療),(閃現,傳送)。

小明心想這難不倒我,然後他仍然只花了一天的時間就寫好了如下代碼:

public class GameRole {
    
    private int atk; // 攻擊力
    private int def; // 防禦力
    private int dex; // 敏捷度
    
    public void flash_move() {
        System.out.println("指定方向瞬移一段距離");
    }
    
    public void ignite() {
        System.out.println("使附近100碼內隊友恢復30%的血量");
    }
}

public class GameRole {
    
    private int atk; // 攻擊力
    private int def; // 防禦力
    private int dex; // 敏捷度
    
    public void flash_move() {
        System.out.println("指定方向瞬移一段距離");
    }
    
    public void ignite() {
        System.out.println("傳送至己方非英雄單位位置處");
    }
}

小明興高采烈的跑去給項目經理看了,項目經理看到小明過來了,心裏覺得這小伙子不錯麻,這麼快就做完了。

然後項目經理便看了他的代碼,這不看不要緊,一看便指着小明罵道:你真是一個糟糕的程序員!!!然後便讓小明改代碼去了。

小明此時還不太明白,我功能都實現了啊,沒啥毛病阿,然後小明不明所以的便問了辦公室的職員,職員告訴他,你代碼冗餘度太高了。

小明一看發現果然如此,然後便花了一天的時間改成如下代碼:

public abstract class GameRole {
    
    private int atk; // 攻擊力
    private int def; // 防禦力
    private int dex; // 敏捷度
  
  // 省略 Getter and Setter method
public void flash_move() { System.out.println("指定方向瞬移一段距離"); } public abstract void skill(); }
public class Role_One extends GameRole { @Override public void skill() { // TODO Auto-generated method stub System.out.println("使其處於燃燒狀態 5 s"); } }
public class Role_Two extends GameRole { @Override public void skill() { // TODO Auto-generated method stub System.out.println("使附近100碼內隊友恢復30%的血量"); } }
public class Role_Three extends GameRole { @Override public void skill() { // TODO Auto-generated method stub System.out.println("傳送至己方非英雄單位位置處"); } }

這次小明覺得冗餘度確實降低了,然後便給項目經理看,項目經理看后覺得確實還行,便又分配了一個任務:
(遊戲用戶希望能自由選擇召喚師技能就好了),所以任務是召喚師技能任意搭配。

小明心想:我寫的這種代碼似乎不用改耶,可以交差了,於是小明便偷懶了两天,然後便上報給項目經理了。

項目經理一看,這代碼沒有變化啊,便問小明,你代碼就這?小明回答是的,然後小明又被罵的狗血淋頭。

不明所以的小明又去問問辦公室的職員,你仔細想想,如果有100個(閃現,引燃),100個(閃現,治療),100個(傳送,治療)呢?

小明突然恍然大悟,那還是有好高的冗餘度啊,經過三天思考後,小明想出了最終答案:

public abstract class GameRole {
private int atk; // 攻擊力 private int def; // 防禦力 private int dex; // 敏捷度 private Skills skill_One; private Skills skill_Two;
  // 省略 Getter and Setter method
} public class Role_Demo extends GameRole { }
public interface Skills {
    public void skill();
}

public class Flush_Move implements Skills {
    @Override
    public void skill() {
        // TODO Auto-generated method stub
        System.out.println("指定方向瞬移一段距離");
    }
}

public class Ignite implements Skills {
    @Override
    public void skill() {
        // TODO Auto-generated method stub
        System.out.println("使其處於燃燒狀態 5 s");
    }
}

public class Treat implements Skills {

    @Override
    public void skill() {
        // TODO Auto-generated method stub
        System.out.println("使附近100碼內隊友恢復30%的血量");
    }
}
public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Role_Demo role = new Role_Demo();
        role.setSkill_One(new Flush_Move());
        role.setSkill_Two(new Ignite());
        role.getSkill_One().skill();
        role.getSkill_Two().skill();
    }
}

小明寫完了,這次小明怕又被罵,便去問問職員小花了,小花說寫的不錯嗎,於是小明膽戰心驚的去交差了。

項目經理看到小明過來,看着小明的囧樣,內心是想笑的,然後看了看代碼發現這回沒啥問題了,便放心的交給它最後一個任務:

給每個英雄添加四個不相同的技能:

小明經過幾次寫代碼的經歷后,一天便寫出來了:

public abstract class GameRole {
    private int atk; // 攻擊力
    private int def; // 防禦力
    private int dex; // 敏捷度
    private Skills skill_One;
    private Skills skill_Two;
    
    // 省略 Getter and Setter method
    
    public abstract void Skill_One();
    public abstract void Skill_Two();
    public abstract void Skill_Three();
    public abstract void Skill_Four();
}

public class Role_Demo extends GameRole {

    @Override
    public void Skill_One() {
        // TODO Auto-generated method stub
    }

    @Override
    public void Skill_Two() {
        // TODO Auto-generated method stub    
    }

    @Override
    public void Skill_Three() {
        // TODO Auto-generated method stub    
    }

    @Override
    public void Skill_Four() {
        // TODO Auto-generated method stub    
    }
}

經過這幾次任務,小明感覺寫的代碼更漂亮,更優雅了,腰也不酸了,背也不疼了。小明上交任務后,項目經理也露出了滿意的笑容!

最後,小明成功通過了實習,然而項目經理給他分配了下一項任務……

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

一文搞懂:Adaboost及手推算法案例

boosting

Boosting 算法的特點在於:將表現一般的弱分類器通過組合變成更好的模型。代表自然就是我們的隨即森林了。

GBDT和Adaboost是boost算法中比較常見的兩種,這裏主要講解Adaboost。

Adaboost

Adaboost算法的核心就是兩個權重。對於數據有一個權重,權重大的數據計算的損失就大;然後對於每一個弱分類器有一個權重,這個權重就是每一個弱分類器最終投票的比重。

【先給出Adaboost關鍵的公式】
\(\alpha_1=\frac{1}{2}ln(\frac{1-\epsilon_1}{\epsilon_1})\) 分類器的投票權重
\(W_i=W_ie^{-\alpha_i y_i \hat{h}(x_i)}\) 更新樣本的權重

【隨即森林中最終投票每一個弱分類器的比重相同】

大概流程就是,現在有一個數據集,然後每個數據的比重都相同,然後訓練了好幾個不同的弱分類器。

  1. 挑選錯誤率最低的弱分類器,然後通過【某種算法】得到這個弱分類器最終投票的比重,然後通過【某種算法】更新每一個數據的比重;
  2. 因為每一個數據的比重更新了,所以再選擇一個錯誤率最低的弱分類器,然後通過【某種算法】得到這個弱分類器最終投票的比重,然後通過【某種算法】更新每一個數據的比重;
  3. 重複這個過程。

算法的流程:

這裏給一個具體計算的例子:
假設這裡有10個數據:

加號和減號分別代表不同的類別。然後每個類別有5個樣本。

下面會給出3個弱分類器:

這三個分類器分別是\(h_1(x),h_2(x),h_3(x)\)
圖中畫圈的數據就是分類錯誤的數據。可以發現每個弱分類器都分錯了3個。下面開始Adaboost的算法。

先計算三個弱分類器的錯誤率,因為一開始每個樣本的權重都是0.1,每個分類器又都錯了3個樣本,所以錯誤率都是0.3。這裏就隨機選取第一個分類器作為錯誤率最低的那個好了。
我們這裏通過第一個【某種算法】計算第一個弱分類器在最終的投票權重:
\(\alpha_1=\frac{1}{2}ln(\frac{1-\epsilon_1}{\epsilon_1})=0.5*ln(\frac{0.7}{0.3})=0.4236\)

然後通過這個\(\alpha_1=0.4236\)來更新每一個樣本的權重。這也就是上面提到的第二個【某種算法】:
\(W(i)=W(i)*e^{-\alpha y_i \hat {h}(x_i)}\)

這啥意思的,現在假設第一個樣本+1,這個樣本的權重是0.1(更新前),然後這個樣本在第一個分類器中是非類正確的,所以\(y_i \hat{h}(x_i)=1\),所以這個樣本更新后的權重就是\(0.1e^{-0.4236}=0.0655\)

當然,對於+3這個樣本,第一個分類器就分類錯誤,所以\(y_i \hat{h}(x_i)=-1\),所以呢這個樣本更新后的權重就是:\(0.1e^{0.4236}=0.1527\)

下面經過第一個分類器之後的樣本的權重:

然後再計算每一個分類器的基於更新之後樣本權重的錯誤率:

這一次選的是第二個分類器,然後計算它的\(\alpha_2\),然後再更新每一個樣本的權重值:

然後是再尋找錯誤率最低的分類器:

到這一步的時候,我們已經有了\(\alpha_1,\alpha_2,\alpha_3\),所以我們的adaboost已經得到了所有分類器的投票權重,所以最終的模型投票公式就是:

喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

  • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
  • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
  • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

微信搜索公眾號:你好世界煉丹師。期待您的關注。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

計算機網絡之應用層

應用層協議

應用層協議 (application-layer protocol) 定義了運行在不同端系統上的應用程序進程如何相互傳遞報文,特別是應用層協議定義了:

  • 交換的報文類型,例如請求報文和響應報文
  • 各種報文類型的語法,如報文中的各個字段及這些字段是如何描述的
  • 字段的語義,即這些字段中包含的信息的含義
  • 一個進程何時以及如何發送報文,對報文進行響應的規則。

  在本文中主要是講5種重要的應用: Web 、文件傳輸、电子郵件、目錄服務和P2P,我們首先討論 Web應用,不僅因為它是極為流行的應用,而且因為它的應用層協議 HTTP 相對比較簡單並且易於理解。討論完 Web ,我們簡要地討論FTP,因為它與 HTTP 形成了很好的對照 我們接下來討論电子郵件應用,這是因特網上最早的招人喜愛的應用程序。說电子郵件比 Web更複雜,是因為它使用了多個而不是一個應用層協議。在电子郵件之後,我們討論DNS它為因特網提供目錄服務,大多數用戶不直接與 DNS 打交道,而是通過其他的應用(包括Web 、文件傳輸和电子郵件)間接使用它,DNS 很好地說明了一種核心的網絡功能(網絡名字到網絡地址的轉換)是怎樣在因特網的應用層實現的。

HTTP概況

  Web 的應用層協議是超文本傳輸協議 (HyperText Transfer Protocol , HTTP) ,它是Web的核心,HTTP 由兩個程序實現:一個客戶程序和一個服務器程序。客戶程序和服務器程序運行在不同的端系統中,通過交換HTTP報文進行會話。 HTTP 定義了這些報文的結構以及客戶和服務器進行報文交換的方式。HTTP使用TCP作為它的支撐運輸協議(而不是在 UDP 上運行)。HTTP 客戶首先發起一個與服務器的TCP連接,一旦連接建立,該瀏覽器和服務器進程就可以通過套接字接口訪問TCP。

注意:服務器向客戶發送被請求的文件,而不存儲任何關於該客戶的狀態信息,假如某個特定的客戶在短短的幾秒鐘內兩次請求同一個對象,服務器並不會因為剛剛為該客戶提供了該對象就不再做出反應,而是重新發送該對象,就像服務器已經完全忘記不久之前所做過的事一樣,因為 HTTP 服務器並不保存關於客戶的任何信息,所以我們說HTTP是一個無狀態協議 (stateless protocol)

非持續連接和持續連接

  在許多因特網應用程序中,客戶和服務器在一個相當長的時間範圍內通信,其中客戶發出一系列請求並且服務器對每個請求進行響應 依據應用程序以及該應用程序的使用方式,這一系列請求可以以規則的間隔周期性地或者間斷性地一個接一個發出。當這種客戶-服務器的交互是經TCP進行的,應用程序的研製者就需要做一個重要決定,即每個請求/響應對是經一個單獨的 TCP 連接發送,還是所有的請求及其響應經相同的TCP連接發送呢?採用前一種方法,該應用程序被稱為使用非持續連接( non- persistent connection) ;採用后一種方法,該應用程序被稱為使用持續連接( persistent connection)。儘管 HTTP在其默認方式下使用持續連接, HTTP 客戶和服務器也能配置成使用非持續連接。

  非持續連接有一些缺點:首先,必須為每一個請求的對象建立和維護一個全新的連接。對於每個這樣的連接,在客戶和服務器巾都要分配 TCP 的緩衝區和保持 TCP 變量。這給 Web 服務器帶來了嚴重的負擔,因為一台 Web 服務器可能同時服務於數以百計不同的客戶的請求。第二,每一個對象經受兩倍 RTT 的交付時延,即一個RTT 用於創建 TCP ,另一個 RTT 用於請求和接收一個對象。在採用持續連接的情況下,服務器在發送響應后保持該 TCP 連接打開。在相同的客戶與服務器之間的後續請求和響應報文能夠通過相同的連接進行傳送

HTTP報文格式

  1. 請求報文
      GET /somedir/page.html HTTP/l.l
      Host: www.someschool.edu
      Connection: close
      User-agent: Mozilla/5.0
      Accept-language: fr

  我們看到該報文由5行組成,每行由一個回車和換行符結束。最後一行后再附加一個回車換行。雖然這個特定的報文僅有5行,但一個請求報文能夠具有更多的行或者至少為一行。HTTP 請求報文的第一行叫做請求行 (request line) ,其後繼的行叫做首部行( headerline)。請求行有3個字段:方法字段、URL字段和HTTP版本字段。方法宇段可以取幾種不同的值,包括 GET、POST、HEAD、PUT和DELETE。絕大部分的 HTTP 請求報文使用GET方法,當瀏覽器請求一個對象時,使用 GET 方法,在 URL 字段帶有請求對象的標識。在本例中,該瀏覽器正在請求對象/somedirl page. html 其版本字段是自解釋的;在本例中,瀏覽器實現的是 HTTP/ l. 版本。

  現在我們看看本例的首部行:首部行 Host: www. someschool. edu 指明了對象所在的主機。你也許認為該首部行是不必要的,因為在該主機中已經有一條 TCP 連接存在了,但是,該首部行提供的信息是 Web 代理高速緩存所要求的,通過包含 Connection: close 首部行,該瀏覽器告訴服務器不希望麻煩地使用持續連接,它要求服務器在發送完被請求的對象后就關閉這條連接。User- agent: 首部行用來指明用戶代理,即向服務器發送請求的瀏覽器的類型。這裏瀏覽器類型是 Mozilla/5. 0,即Firefox 瀏覽器,這個首部行是有用的,因為服務器可以有效地為不同類型的用戶代理,實際發送相同對象的不同版本 (每個版本都由相同的URL尋址)。最後Accept-language:首部行表示用戶想得到該對象的法語版本(如果服務器中有這樣的對象的話);否則,服務器應當發送它的默認版本 Accept -language:首部行僅是 HTTP 中可用的眾多內容協商首部之一。

  HEAD方法類似於GET方法,當服務器收到使用HEAD方法的請求時,將會用一個HTTP報文進行響應,但是並不返回請求對象,應用程序開發者常用HEAD方法進行調試,跟蹤PUT方法常與Web發行工具聯合使用,它允許用戶上傳對象到指定的 Web 服務器上指定的路徑(目錄)。PUT方法也被那些需要向 Web 服務器上傳對象的應用程序使用。DELETE 方法允許用戶或者應用程序刪除 Web 服務器上的對象

  1. HTTP響應報文
    下面我們提供了一條典型的HTTP響應報文 該響應報文可以是對剛剛討論的例子中請求報文的響應:
HTTP/ 1. 1 200 OK
Connection: close 
Date: Tue , 09 Aug 2011 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue , 09 Aug 2011 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html 

(data data data data data ...) 

  我們仔細看一下這個響應報文 它有三個部分:一個初始狀態行 (sLatus line) ,6個首部行 (header 1ine) ,然後是實體體 (enLity body) 實體體部分是報文的主要部分,即它包含了所請求的對象本身(表示為 dala dala data data …)。 狀態行有3個字段:協議版本字段、狀態碼和相應狀態信息。在這個例子中,狀態行指示服務器正在使用 HTTP/l.1,並且一切正常(即服務器已經找到並正在發送所請求的對象)。

  我們現在來看看首部行。服務器用 Connection: close 首部行告訴客戶,發送完報文後將關閉該 TCP 連接。Date: 首部行指示服務器產生併發送該響應報文的日期和時間。值得一提的是,這個時間不是指對象創建或者最後修改的時間;而是服務器從它的文件系統中檢索到該對象,插入到響應報文,併發送該響應報文的時間。Server: 首部行指示該報文是由一台 Apache Web 服務器產生的,它類似於 HTTP 請求報文中的 User- agent 首部行。Last- Modified: 首部行指示了對象創建或者最後修改的日期和時間。Lasl-Modified: 首部行對既可能在本地客戶也可能在網絡緩存服務器上的對象緩存來說非常重要。下面將會介紹緩存服務器(也叫代理服務器)。Content- Length: 首部行指示了被發送對象中的字節數;Conlent- Type: 首部行指示了實體體中的對象是 HTML 文本 (該對象類型應該正式地由 Conlent- Type: 首部行而不是用文件擴展名來指示。

Web 緩存

  Web緩存器 (Web cache)也叫代理服務器 (proxy server),它是能夠代表初始 Web 服務器來滿足 HTTP 請求的網絡實體。Web 緩存器有自己的磁盤存儲空間,並在存儲空間中保存最近請求過的對象的副本 如圖 2-11 所示,可以配置用戶的瀏覽器,使得用戶的所有 HTTP請求首先指向 Web 緩存器。一旦某瀏覽器被配置,每個對某對象的瀏覽器請求首先被定向到該 Web 緩存器。舉例來說,假設瀏覽器正在請求對象 http://www.someschool. edu/ campus.giI.將會發生如下情況:

  • 瀏覽器建立一個到Web緩存器的TCP連接,並向 Web 緩存器中的對象發送一個HTTP請求
  • Web 緩存器進行檢查,看看本地是否存儲了該對象副本 如果有, Web 緩存器就向客戶瀏覽器用 HTTP 響應報文返回該對象
  • 如果 Web 緩存器中沒有該對象,它就打開一個與該對象的初始服務器(如www. someschool. edu),TCP 連接 Web 緩存器則在這個緩存器到服務器的TCP連接上發送一個對該對象的 HTTP 請求,在收到該請求后,初始服務器向該 Web緩存器發送具有該對象的 HTTP響應
  • 當 Web 緩存器接收到該對象時,它在本地存儲空間存儲一份副本,並向客戶的瀏覽器用盯TP 響應報文發送該副本(通過現有的客戶瀏覽器和 Web 緩存器之間的TCP連接)

DHCP協議

  DHCP(Dynamic Host Configuration Protocol: 動態主機設置協議,DHCP是一個局域網協議,DHCP是應用UDP協議的應用層協議。使用UDP協議工作,主要有兩個用途:給內部網絡或網絡服務供應商自動分配IP地址,給用戶或者內部網絡管理員作為對所有計算機作中央管理的手段。
對於一個 臨時設備,是如何知道自己的IP地址的?
  DHCP服務器監聽默認端口:67,主機使用UDP協議廣播DHCP發現報文,DHCP服務器發出DHCP提供報文,主機向DHCP服務器發出DHCP請求報文,DHCP服務器回應並提供IP地址。

HTTPS

  由於HTTP是明文傳輸的,則HTTPS(Secure)是安全的HTTP協議,默認端口為443,http(s): //<主機>:<端口>/<路徑>

  • A、B是擁有一定數學關係的一組秘鑰
    • 私鑰:私鑰自己使用,不對外公開
    • 公鑰:公鑰給大家使用,對外公開

      使用公鑰加密,使用私鑰解密。

  • 数字證書是可信任組織頒發給特定對象的認證
證書格式、版本號
證書序列號
簽名算法
有效期
對象名稱
對象公開秘鑰
  • SSL(Secure Sockets Layer: 安全套接層)
    • 數據安全和數據完整
    • 對傳輸層數據進行加密後傳輸

HTTPS 原理

  1. 客戶端將它所支持的算法列表和一個用作產生密鑰的隨機數發送給服務器 ;
  2. 服務器從算法列表中選擇一種加密算法,並將它和一份包含服務器公用密鑰的證書發送給客戶端;該證書還包含了用於認證目的的服務器標識,服務器同時還提供了一個用作產生密鑰的隨機數;
  3. 客戶端對服務器的證書進行驗證(有關驗證證書,可以參考数字簽名),並抽取服務器的公用密鑰;然後,再產生一個稱作 pre_master_secret 的隨機密碼串,並使用服務器的公用密鑰對其進行加密(參考非對稱加 / 解密),並將加密后的信息發送給服務器 ;
  4. 客戶端與服務器端根據 pre_master_secret 以及客戶端與服務器的隨機數值獨立計算出加密和 MAC密鑰(參考 DH密鑰交換算法);
  5. 客戶端將所有握手消息的 MAC 值發送給服務器;
  6. 服務器將所有握手消息的 MAC 值發送給客戶端

文件傳輸協議:FTP

  在一個典型的FTP會話中,用戶坐在一台主機(本地主機)前面,向一台遠程主機傳輸(或接收來自遠程主機的)文件 為使用戶能訪問它的遠程賬戶,用戶必須提供一個用戶標識和口令 在提供了這種授權信息后,用戶就能從本地文件系統向遠程主機文件系統傳送文件,反之亦然 如圖 2-14 所示,用戶通過一個FTP用戶代理與FTP交互。該用戶首先提供遠程主機的主機名,使本地主機的FTP客戶進程建立一個到遠程主機FTP服務器進程的 TCP 連接。該用戶接着提供用戶標識和口令,作為 FTP 命令的一部分在該 TCP連接上傳送。一旦該服務器向該用戶授權,用戶可以將存放在本地文件系統中的一個或者多個文件複製到遠程文件系統(反之亦然)。

  HTTP和FTP 都是文件傳輸協議,並且有很多共同的特點,例如,它們都運行在 TCP上,然而,這兩個應用層協議也有一些重要的區別 其中最顯著的就是FTP 使用了兩個并行的 TCP 連接來傳輸文件,一個是控制連接 (control connection) ,一個是數據連接( data connection) 。控制連接用於在兩主機之間傳輸控制信息,如用戶標識、口令、改變遠程目錄的命令以及”存放 (put) “和”獲取 (get)”文件的命令。數據連接用於實際發送一個文件,因為FTP協議使用一個獨立的控制連接,所以我們也稱FTP的控制信息是帶外(out-of-band)傳送的。如你所知,HTTP協議是在傳輸文件的同一個 TCP 連接中發送請求和響應首部行的 因此,HTTP也可以說是帶內 (in-band) 發送控制信息的。FTP協議的控制連接和數據連接如圖二 15 所示:

  當用戶主機與遠程主機開始一個FTP會話時,FTP的客戶(用戶)端首先在服務器21號端口與服務器(遠程主機)端發起一個用於控制的 TCP 連接。FTP的客戶端也通過該控制連接發送用戶的標識和口令,發送改變遠程目錄的命令,當FTP的服務器端從該連接上收到一個文件傳輸的命令后(無論是向還是來自遠程主機) ,就發起一個到客戶端的 TCP 數據連接 FTP 在該數據連接上準確地傳送一個文件,然後關閉該連接。在同一個會話期間,如果用戶還需要傳輸另一個文件,FTP則打開另一個數據連接,因而對FTP傳輸而言,控制連接貫穿了整個用戶會話期間,但是對會話中的每一次文件傳輸都需要建立一個新的數據連接(即數據連接是非持續的)。

  FTP服務器必須在整個會話期間保留用戶的狀態(state) 特別是,服務器必須把特定的用戶賬戶與控制連接聯繫起來,隨着用戶在遠程目錄樹上徘徊,服務器必須追蹤用戶在遠程目錄樹上的當前位置,對每個進行中的用戶會話的狀態信息進行追蹤,大大限制了FTP同時維持的會話總數。而另一方面,前面講過 HTTP 是無狀態的,即它不必對任何用戶狀態進行追蹤。

因特網中的电子郵件

  圖2-16 給出了因特網电子郵件系統的總體情況,從該圖中我們可以看到它有3個主要組成部分: 用戶代理( user agenl) 、郵件服務器 (mail server) 簡單郵件傳輸協議(Simple Mai] Transfer Prolocol , SMTP) 。

SMTP 是因特網电子郵件中主要的應用層協議,它使用 TCP 可靠數據傳輸服務,從發送方的郵件服務器向接收方的郵件服務器發送郵件,像大多數應用層協議一樣, SMTP有兩個部分:運行在發送方郵件服務器的客戶端和運行在接收方郵件服務器的服務器端,每台郵件服務器上既運行 SMTP 的客戶端也運行 SMTP 的服務器端 。當一個郵件服務器向其他郵件服務器發送郵件時,它就表現為 SMTP 的客戶;當郵件服務器從其他郵件服務器上接收郵件時,它就表現為SMTP的服務器。目前有一些流行的郵件訪問協議,包括第三版的郵局協議 (POSl OfficeProtocol-Version 3 , POP3)、因特網郵件訪問協議 (Intemet Mail Access Protocol , IMAP)以及 HTTP。

總結:應用層為操作系統或網絡應用程序提供訪問網絡服務的接口。數據傳輸基本單位為報文;包含的主要協議:FTP(文件傳送協議)、Telnet(遠程登錄協議)、DNS(域名解析協議)、SMTP(郵件傳送協議),POP3協議(郵局協議),HTTP協議(Hyper Text Transfer Protocol)。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Newtonsoft 六個超簡單又實用的特性,值得一試 【上篇】

一:講故事

看完官方文檔,閱讀了一些 Newtonsoft 源碼,對它有了新的認識,先總結 六個超經典又實用的特性,同大家一起分享,廢話不多說,快來一起看看吧~~~

二:特性分析

1. 代碼格式化

如果你直接使用 JsonConvert.SerializeObject的話,默認情況下所有的json是擠壓在一塊的,特別不方便閱讀,如下所示:


        static void Main(string[] args)
        {
            var reportModel = new ReportModel()
            {
                ProductName = "法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙",
                TotalPayment = 100,
                TotalCustomerCount = 2,
                TotalProductCount = 333
            };

            var json = JsonConvert.SerializeObject(reportModel);

            System.Console.WriteLine(json);
        }
    }

    public class ReportModel
    {
        public string ProductName { get; set; }
        public int TotalCustomerCount { get; set; }
        public decimal TotalPayment { get; set; }
        public int TotalProductCount { get; set; }
    }

那怎麼辦呢? JsonConvert中提供了一個 Formatting.Indented 用來格式化json,這樣在 debug 的過程中就非常友好,改造如下:

2. 踢掉沒有被賦值的字段

如果你寫過給 App 提供數據的後端服務,我相信你對手機流量這個詞特別敏感,往往一個 Model 上有十幾個字段,但需要傳給 App 可能就 三四個字段,這就造成了巨大的流量浪費,如下圖:


        static void Main(string[] args)
        {
            var reportModel = new ReportModel()
            {
                ProductName = "法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙",
                TotalPayment = 100
            };

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented);

            System.Console.WriteLine(json);
        }

從圖中可以看到,TotalCustomerCountTotalProductCount 這兩個字段就沒必要了,Netnewsoft 中提供了 DefaultValueHandling.Ignore 剔除默認值的枚舉,太實用了,改造如下:


            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented,
                                                   new JsonSerializerSettings
                                                   {
                                                       DefaultValueHandling = DefaultValueHandling.Ignore
                                                   });

3. 兼容其他語言的 駝峰,蛇形命名法

每一套編程語言都有各自偏好的命名法,比如 js 中都喜歡採用 駝峰命名法,在 mysql 中我見過最多的 蛇形命名法,而我們在 C# 中序列化的屬性一般都是大寫字母開頭,比如你看到的 特性二 中的字段,那這裏就存在問題了,有沒有辦法兼容一下,給 js 就用 駝峰,給 mysql 就用 蛇形,這樣顯得對別人友好一些,不是嘛,接下來看看怎麼改造。

  • 駝峰命名 CamelCasePropertyNamesContractResolver

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented,
                                                   new JsonSerializerSettings
                                                   {
                                                       ContractResolver = new CamelCasePropertyNamesContractResolver()
                                                   });

  • 蛇形命名 SnakeCaseNamingStrategy

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented,
                                                   new JsonSerializerSettings
                                                   {
                                                       ContractResolver = new DefaultContractResolver()
                                                       {
                                                           NamingStrategy = new SnakeCaseNamingStrategy()
                                                       }
                                                   });

4. 自定義屬性的名字

如果你和第三方系統進行過對接開發,通常都會遇到這個問題,就拿 OpenTaobao 來說,我的Model總不能按照它文檔這樣定義吧,而且字段名稱也不可能做到完全一致,如下圖:

所以這裏面必然要存在一個 Mapping 的過程,這就可以用 JsonProperty -> propertyName 幫你搞定,為了方便演示,我還是用 reportModel 吧。


    static void Main(string[] args)
    {
        var json = "{'title':'法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙','customercount':1000,'totalpayment':100.0,'productcount':10000}";

        var reportModel = JsonConvert.DeserializeObject<ReportModel>(json);
    }

    public class ReportModel
    {
        [JsonProperty("title")] public string ProductName { get; set; }
        [JsonProperty("customercount")] public int TotalCustomerCount { get; set; }
        [JsonProperty("totalpayment")] public decimal TotalPayment { get; set; }
        [JsonProperty("productcount")] public int TotalProductCount { get; set; }
    }


5. 對字段的 正向剔除 和 反向剔除

可能有些朋友對這兩個概念不是特別了解,這裏我僅显示 Model 中的 ProductName 為例講解一下:

  • 正向剔除: 默認所有都显示,手工踢掉不显示的,使用 MemberSerialization.OptOut 配合 JsonIgnore

 		static void Main(string[] args)
        {
            var reportModel = new ReportModel()
            {
                ProductName = "法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙",
                TotalPayment = 100
            };

            var json = JsonConvert.SerializeObject(reportModel, Formatting.Indented);

            System.Console.WriteLine(json);
        }

    [JsonObject(MemberSerialization.OptOut)]
    public class ReportModel
    {
        public string ProductName { get; set; }
        [JsonIgnore] public int TotalCustomerCount { get; set; }
        [JsonIgnore] public decimal TotalPayment { get; set; }
        [JsonIgnore] public int TotalProductCount { get; set; }
    }

  • 反向剔除: 默認都不显示,手工指定要显示的,使用 MemberSerialization.OptIn 配合 JsonProperty
       
    [JsonObject(MemberSerialization.OptIn)]
    public class ReportModel
    {
        [JsonProperty] public string ProductName { get; set; }
        public int TotalCustomerCount { get; set; }
        public decimal TotalPayment { get; set; }
        public int TotalProductCount { get; set; }
    }

6. 多個json 合併到 一個Model

這個特性當初打破了我對 Newtonsoft 的認知觀,不知道您呢? 通常我們都會認為 一個 json 對應一個 model,一個 model 對應一個 json,居然還可以多個 json 對應一個 model 的情況,這就有意思了,場景大家可以自己想一想哈,這裏使用 PopulateObject 方法就可以輕鬆幫你搞定,接下來看看怎麼寫這個代碼:


        static void Main(string[] args)
        {
            var json1 = "{'ProductName':'法式小眾設計感長裙氣質顯瘦純白色仙女連衣裙'}";
            var json2 = "{'TotalCustomerCount':1000,'TotalPayment':100.0,'TotalProductCount':10000}";

            var reportModel = new ReportModel();

            JsonConvert.PopulateObject(json1, reportModel);
            JsonConvert.PopulateObject(json2, reportModel);
        }

是不是有點意思

三:總結

為了怕影響閱讀體驗,這一篇就先總結六個供大家欣賞,Newtonsoft 這玩意確實非常強大,太多的東西需要去挖掘,希望本篇對你有幫助,謝謝。

如您有更多問題與我互動,掃描下方進來吧~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

Nginx 如何自定義變量?

之前的兩篇文章 Nginx 變量介紹以及利用 Nginx 變量做防盜鏈 講的是 Nginx 有哪些變量以及一個常見的應用。那麼如此靈活的 Nginx 怎麼能不支持自定義變量呢,今天的文章就來說一下自定義變量的幾個模塊以及 Nginx 的 keepalive 特性。

通過映射新變量提供更多的可能性:map 模塊

  • 功能:基於已有變量,使用類似 switch {case: … default: …} 的語法創建新變量,為其他基於變量值實現功能的模塊提供更多的可能性
  • 模塊:ngx_http_map_module 默認編譯進 Nginx,通過 --without-http_map_module 禁用

指令

Syntax: map string $variable { ... }
Default: —
Context: http

Syntax: map_hash_bucket_size size;
Default: map_hash_bucket_size 32|64|128; 
Context: http

Syntax: map_hash_max_size size;
Default: map_hash_max_size 2048; 
Context: http

我們主要看一下 map string $variable { ... } 這個指令。所謂類似 switch case 的語法是指,string 的值可以有多個,可以根據 string 值的不同,來給 $variable 賦不同的值。

規則

  • 已有變量:string 需要是已有的變量,可以分為下面這三種情況
    • 字符串
    • 一個或者多個變量
    • 變量與字符串的組合
  • case 規則:{…} 內的匹配規則需要遵循以下規則,尤其是要注意當使用 hostnames 指令時,與 server name 的匹配規則是一致的,可以看之前的文章 Nginx 的配置指令
    • 字符串嚴格匹配
    • 使用 hostnames 指令,可以對域名使用前綴 * 泛域名匹配
    • ~ 和 ~* 正則表達式匹配,後者忽略大小寫
  • default 規則
    • 沒有匹配到任何規則時,使用 default
    • 確實 default 時,返回空字符串給新變量
  • 其他
    • 使用 include 語法提升可讀性
    • 使用 volatile 禁止變量值緩存

大家看到上面這些規則可能都有些暈,廢話不多說,直接來看一個實戰配置文件就懂了。

實戰

這裏我們有一個配置文件,在這個文件裏面我們定義了兩個 map 塊,分別配置了兩個變量,$name 和 $mobile,$name 中包含 hostnames 指令。

map $http_host $name {
    hostnames;

    default       0;

    ~map\.ziyang\w+\.org.cn 1;
    *.ziyang.org.cn   2;
    map.ziyang.com   3;
    map.ziyang.*    4;
}

map $http_user_agent $mobile {
    default       0;
    "~Opera Mini" 1;
}

server {
	listen 10001;
	default_type text/plain;
	location /{
		return 200 '$name:$mobile\n';
	}
}

下面看一下實際的請求:

  test_nginx curl -H "Host: map.ziyang.org.cn" 127.0.0.1:10001
2:0

為什麼會返回 2:0 呢?我們來看一下匹配順序。

map.ziyang.org.cn 有三個規則可以生效,分別是:

  • ~map.ziyang\w+.org.cn 1;
  • *.ziyang.org.cn 2;
  • map.ziyang.* 4;

而泛域名是優先於正則表達式的,* 在前的泛域名優先於在後面的泛域名,因此最終匹配到的就是:

  • *.ziyang.org.cn 2;

而第二個變量 $mobile 自然走的是 default 規則,不用多說。

這就是 map 模塊的作用,大家可以多嘗試一下。

下面再來看一個與 map 模塊有點類似的 split_clients 模塊,這個模塊也是通過生成新的變量來完成 AB 測試功能的,它可以按照變量的值,按照百分比的方式,生成新的變量。

實現 AB 測試:split_clients 模塊

  • 功能:基於已有變量創建新變量,為其他 AB 測試提供更多的可能性
    • 對已有變量的值執行 MurmurHash2 算法,得到 32 位整形哈希数字,記為 hash
    • 32 位無符號整形的最大数字 2^32-1,記為 max
    • 哈希数字與最大数字相除,hash/max,可以得到百分比 percent
    • 配置指令中指示了各個百分比構成的範圍,如 0-1%,1%-5% 等,及範圍對應的值
    • 當 percent 落在哪個範圍里,新變量的值就對應着其後的參數
  • 模塊:ngx_http_split_clients_module,默認編譯進 Nginx,通過 --without-http_split_clients_module 禁用

規則

  • 已有變量
    • 字符串
    • 一個或者多個變量
    • 變量與字符串的組合
  • case 規則:
    • xx.xx%,支持小數點后 2 位,所有項的百分比相加不能超過 100%
    • *,由它匹配剩餘的百分比(100% 減去以上所有項相加的百分比)

指令

Syntax: split_clients string $variable { ... }
Default: —
Context: http

split_clients 的指令與 map 是非常相似的,可以看一下前面的介紹,這裏不再贅述了。

下面這個配置,來看下有沒有啥問題:

split_clients "${http_testcli}" $variant {
    0.51% .one;
    20.0% .two;
    50.5% .three;
    40% .four;
    * "";
}

細心的同學可能已經發現了,所有的百分比相加已經超過了 100%,所以 Nginx 直接會拋出一個錯誤,禁止執行。

  test_nginx ./sbin/nginx -s reload
nginx: [emerg] percent total is greater than 100% in /Users/mtdp/myproject/nginx/test_nginx/conf/example/17.map.conf:31

然後將 40% .four; 這一行給屏蔽掉再試試看:

  test_nginx curl -H "testcli: split_clients.ziyang.com" --resolve "split_clients.ziyang.com:80:127.0.0.1" http://split_clients.ziyang.com
ABtestfile.three

正常執行。

geo 模塊

geo 模塊與前面兩個模塊也很相似,不同之處在於,這個模塊是基於 IP 地址或者子網掩碼這樣的變量值來生成新的變量的。

  • 功能:根據 IP 地址創建新變量

  • 模塊: ngx_http_geo_module,默認編譯進 Nginx,通過 --without-http_geo_module 禁用

  • 指令

Syntax: geo [$address] $variable { ... }
Default: —
Context: http

規則

  • 如果 geo 指令后不輸入 $address,那麼默認使用 $remote_addr 變量作為 IP 地址

  • {} 內的指令匹配:優先最長匹配

    • 通過 IP 地址及子網掩碼的方式,定義 IP 範圍,當 IP 地址在範圍內時新變量使用其後的參數值

    • default 指定了當以上範圍都未匹配上時,新變量的默認值

    • 通過 proxy 指令指定可信地址(參考 realip 模塊),此時 remote_addr 的值為 X-Forwarded-For 頭部值中最後一個 IP 地址

    • proxy_recursive 允許循環地址搜索

    • include,優化可讀性

    • delete 刪除指定網絡

geo $country {
default ZZ;
#include conf/geo.conf;
#proxy 172.18.144.211;
127.0.0.0/24 US;
127.0.0.1/32 RU;
10.1.0.0/16 RU;
192.168.1.0/24 UK;
}


問題:以下命令執行時,變量 country 的值各為多少?(proxy 實際上為客戶端地址,這裏設置為本機的局域網地址即可,我這裡是 172.18.144.211)

curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.2’ geo.ziyang.com
curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1’ geo.ziyang.com
curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1,1.2.3.4’ geo.ziyang.com


結果如下:

```shell
  test_nginx curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.2' geo.ziyang.com
US
  test_nginx curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.1' geo.ziyang.com
RU
  test_nginx curl -H 'X-Forwarded-For: 10.1.0.0,127.0.0.1,1.2.3.4' geo.ziyang.com
ZZ

這裏可以看出來,匹配規則實際上是遵循最長匹配的規則的。

geoip 模塊

geoip 模塊可以根據 IP 地址生成對應的地址變量,用法與前面的也都類似,Nginx 是基於 MaxMind 數據庫來生成對應的地址的。

  • 功能:根據 IP 地址創建新變量
  • 模塊: ngx_http_geoip_module,默認未編譯進 Nginx,通過 --with-http_geoip_module 禁用

使用這個模塊是需要安裝 MaxMind 庫的,安裝步驟如下:

  • 安裝 MaxMind 里 geoip 的 C 開發庫(https://dev.maxmind.com/geoip/legacy/downloadable/ )
  • 編譯 Nginx 時帶上 --with-http_geoip_module 參數
  • 下載 MaxMind 中的二進制地址庫,這個地址庫是需要在指令中指定對應的地址的
  • 使用 geoip_country 或者 geoip_city 指令配置好 nginx.conf
  • 運行或者升級 Nginx

geoip_country 指令提供的變量

指令

Syntax: geoip_country file; # 指定國家類的地址文件
Default: —
Context: http

Syntax: geoip_proxy address | CIDR;
Default: —
Context: http

變量

  • $geoip_country_code:兩個字母的國家代碼,比如 CN 或者 US
  • $geoip_country_code3:三個字母的國家代碼,比如 CHN 或者 USA
  • $geoip_country_name:國家名稱,例如 “China”, “United States”

geoip_city 指令提供的變量

指令

Syntax: geoip_city file;
Default: —
Context: http

變量

  • $geoip_latitude:緯度
  • $geoip_longitude:經度
  • $geoip_city_continent_code:位於全球哪個洲,例如 EU 或 AS
  • 與 $geoip_country 指令生成的變量重疊
    • $geoip_country_code:兩個字母的國家代碼,比如 CN 或者 US
    • $geoip_country_code3:三個字母的國家代碼,比如 CHN 或者 USA
    • $geoip_country_name:國家名稱,例如 “China”, “United States”
  • $geoip_region:洲或者省的編碼,例如 02
  • $geoip_region_name:洲或者省的名稱,例如 Zhejiang 或者 Saint Petersburg
  • $geoip_city:城市名
  • $geoip_postal_code:郵編號
  • $geoip_area_code:僅美國使用的郵編號,例如 408
  • $geoip_dma_code:僅美國使用的 DMA 編號,例如 807

keepalive 模塊

前面說的都是 Nginx 的變量相關的內容,其實 Nginx 還有一個很具有特色的模塊,那就是 keepalive 模塊,由於內容不是很多,所以我就直接寫到這篇文章裏面了,單寫一篇顯得內容不夠哈。

這裏指的是 HTTP 的 keepalive,TCP 也有 keepalive,後面會說。

而且是對客戶端的 keepalive,不是對上游服務器的。

  • 功能:多個 HTTP 請求通過復用 TCP 連接,可以實現以下功能:

    • 減少握手次數
    • 通過減少併發連接數減少了服務器資源消耗
    • 降低 TCP 擁塞控制的影響,保證滑動窗口維持在一個最優的大小
  • Connection 頭部

    • close:表示請求處理完就關閉連接
    • keepalive:表示復用連接處理下一條請求
  • Keepalive 頭部:timeout=n,單位是秒,表示連接至少保持 n 秒

指令

對客戶端行為控制的指令:

Syntax: keepalive_disable none | browser ...;
Default: keepalive_disable msie6; 
Context: http, server, location

Syntax: keepalive_requests number;
Default: keepalive_requests 100; 
Context: http, server, location

Syntax: keepalive_timeout timeout [header_timeout];
Default: keepalive_timeout 75s; 
Context: http, server, location
  • keepalive_disable 設置為 none 表示對所有瀏覽器啟用 keepalive,msie6 表示在老版本 MSIE 上禁用 keepalive
  • keepalive_requests 設置允許保持 keepalive 的請求的數量
  • keepalive_timeout 表示超時時間

好了,關於 Nginx 的模塊介紹就已經全部介紹完了,有興趣的同學可以去翻我前面的系列文章。當然還有一部分重要的內容還沒有介紹,那就是關於 Nginx 的反向代理和負載均衡部分,這塊咱們單獨抽出來說,別著急,馬上乾貨就出來。

本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

【案例演示】JVM之強引用、軟引用、弱引用、虛引用

1.背景

想要理解對象什麼時候回收,就要理解到對象引用這個概念,於是有了下文

2.java中引用對象結構圖

3.引用詳解

3.1.什麼是強引用

a.當內存不足,JVM開始垃圾回收,對於強引用的對象,就算是出現了00M也不會對該對象進行回收,死都不收。

b.強引用是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還“活着”,垃圾收集器不會碰這種對象。

在Java中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。

當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即使該對象以後永遠都不會被用到JVM也不會回收。

因此強引用是造成Java內存泄漏的主要原因之一

c.對於一個普通的對象,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為null,一般認為就是可以被垃圾收集的了〈當然具體回收時機還是要看垃圾收集策略)。

案例:

package com.wfd360.demo03GC.referDemo;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class StrongRefer {
    /**
     * 強引用的理解
     *
     * @param args
     */
    public static void main(String[] args) {
        Object obj1 = new Object();
        // 建立強引用
        Object obj2 = obj1;
        // 觀察obj1 和 obj2 的各種內存地址
        System.out.println("obj1=" + obj1);
        System.out.println("obj2=" + obj2);
        // obj1創建可以回收的條件
        obj1 = null;
        // gc回收
        System.gc();
        // 觀察各對象情況
        System.out.println("obj1=" + obj1);
        System.out.println("obj2=" + obj2);
    }
}

View Code

 從測試結果課程看出,obj1的實際對象別沒有回收;

3.2.什麼是軟引用

a.軟引用是用來描述一些還有用但並非必需的對象,需要用java.lang.ref.SoftReference類來實現。

b.對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。在JDK1.2之後,提供了Soft Reference類來實現軟引用。

c.軟引用通常用在對內存敏感的程序中,比如高速緩存就有用到軟引用,內存夠用的時候就保留,不夠用就回收!

案例:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.SoftReference;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class SoftRefer {

    /**
     * 軟引用的理解
     * 通過設置jvm參數,在不同的條件下觀察
     *
     * @param -Xms5m -Xmx5m -XX:+PrintGCDetails
     * @param args
     */
    public static void main(String[] args) {
        // 測試內存充足(不回收軟引用)
        //testSoftReferNOGc();
        // 測試內存不充足(回收軟引用)
        testSoftReferGc();
    }

    /**
     * 模擬內存充足的情況
     */
    public static void testSoftReferNOGc() {
        Object obj1 = new Object();
        // 建立軟引用
        SoftReference softRefer = new SoftReference<>(obj1);
        // 觀察內存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1創建可以回收的條件
        obj1 = null;
        // gc回收
        System.gc();
        // 再次觀察內存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
    }

    /**
     * 模擬內存不足
     * 1.設置較小的堆內存
     * 2.創建大對象
     * 3.jvm參
     * -Xms5m -Xmx5m -XX:+PrintGCDetails
     */
    public static void testSoftReferGc() {
        Object obj1 = new Object();
        // 建立軟引用
        SoftReference softRefer = new SoftReference<>(obj1);
        // 觀察內存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1創建可以回收的條件
        obj1 = null;
        try {
            byte[] bytes = new byte[6 * 1024 * 1024];
        } catch (Throwable e) {
            System.out.println("===============>error:" + e.getMessage());
        } finally {
            // 再次觀察內存地址
            System.out.println("obj1=" + obj1);
            System.out.println("softRefer=" + softRefer.get());
        }
    }
}

View Code

內存充足測試結果:

 內存不充足測試結果:

 實際案例

假如有一個應用需要讀取大量的本地數據(圖片、通訊率、臨時文件等):

如果每次讀取數據都從硬盤讀取則會嚴重影響性能,

如果一次性全部加載到內存中又可能造成內存溢出。

此時使用軟引用可以解決這個問題。

設計思路是:用一個HashMap來保存數據的路徑和相應數據對象關聯的軟引用之間的映射關係,在內存不足時,

JVM會自動回收這些緩存數據對象所佔用的空間,從而有效地避免了00M的問題。

Map<String,SoftReference>imageCache=new HashMap<String,SoftReference>();

 3.3.什麼是弱引用

a.弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。

b..當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在JDK1.2之後,提供廣Weak Reference類來實現弱引用。

c.弱引用需要用Java.lang.ref.WeakReference類來實現,它比軟引用的生存期更短.

案例:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.WeakReference;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class WeakRefer {

    /**
     * 弱引用的理解
     *
     * @param args
     */
    public static void main(String[] args) {
        Object obj1 = new Object();
        // 建立弱引用
        WeakReference softRefer = new WeakReference<>(obj1);
        // 觀察內存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1創建可以回收的條件
        obj1 = null;
        // gc回收
        System.gc();
        // 再次觀察內存地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
    }

}

View Code

 擴展知識-WeakHashMap

查看API介紹:

 測試代碼:

package com.wfd360.demo03GC.referDemo;

import java.util.HashMap;
import java.util.WeakHashMap;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 5:10
 * @description <p>
 * 弱引用引用之:WeakHashMap
 * 以弱鍵 實現的基於哈希表的 Map。在 WeakHashMap 中,當某個鍵不再正常使用時,將自動移除其條目。
 * 更精確地說,對於一個給定的鍵,其映射的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止,
 * 然後被回收。丟棄某個鍵時,其條目從映射中有效地移除,因此,該類的行為與其他的 Map 實現有所不同。
 * </p>
 */
public class WeakReferMap {
    /**
     * 測試 HashMap 與 WeakHashMap 區別
     * 測試邏輯:
     * 1.創建不同的map
     * 2.創建key  value值
     * 3.放入各自的map,並打印結果
     * 4.將key設置為null,並打印結果
     * 5.手動GC,並打印結果
     *
     * @param args
     */
    public static void main(String[] args) {
        hashMapMethod();
        System.out.println("--------華麗的分割線--------");
        weakHashMapMethod();
    }

    /**
     * HashMap測試(強引用)
     */
    private static void hashMapMethod() {
        HashMap<String, String> map = new HashMap<>();
        String key = "key1";
        String value = "HashMap-value";

        map.put(key, value);
        System.out.println(map);

        key = null;
        System.out.println(map);

        System.gc();
        System.out.println(map);
    }

    /**
     * 若引用(WeakHashMap測試)
     */
    private static void weakHashMapMethod() {
        WeakHashMap<String, String> map = new WeakHashMap<>();
        // 注意這裏的new一個字符串與直接寫key="key2"對測試結果是有區別的,詳細原因可以看之前講的內存分配
        String key = new String("key2");
        String value = "WeakHashMap-value";

        map.put(key, value);
        System.out.println(map);

        key = null;
        System.out.println(map);

        System.gc();
        System.out.println(map);

    }

}

View Code

測試結果:

 從測試結果可以看出:弱引用的map數據已經被回收。

 擴展知識-ReferenceQueue引用隊列

 代碼:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 7:23
 * @description
 */
public class QueueRefer {
    /**
     * 測試弱引用回收前,把數據放入隊列中
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue();
        // 當GC釋放對象內存的時候,會將引用加入到引用隊列
        WeakReference<Object> weakReference = new WeakReference<>(obj1, referenceQueue);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("--------華麗的分割線--------");
        obj1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());
    }

}

View Code

採用弱引用的方式測試結果:

從測試結果可以看出,需要回收的對象已經進入隊列。

 採用軟引用的方式測試結果:

 從測試結果可以看出,軟引用,沒有到達回收的條件,並沒有進行回收,也不會進入隊列;

3.4.什麼是虛引用

1.虛引用需要java.lang.ref.PhantomReference類來實現。

2.與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有

虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它訪

問對象,虛引用必須和引用隊列(ReferenceQueue)聯合使用。

3.虛引用的主要作用是跟蹤對象被垃圾回收的狀態。僅僅是提供了一種確保對象被finalize以後,做某些事情的

機制。PhantomReference的get方法總是返回null,因此無法訪問對應的引用對象。其意義在於說明一個對象己

經進入俑finalization階段,可以被gc回收,用來實現比finalization機制更靈活的回收操作。

4.設置虛引用關聯的唯一目的,就是在這個對象被收集器回收的時候收到一個系統通知或者後續添加

進一步的處理。Java技術允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。

代碼:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 7:44
 * @description
 */
public class PhantomRefer {
    /**
     * 虛引用測試
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj1,referenceQueue);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("--------華麗的分割線--------");

        obj1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
    }

}

View Code

測試結果:

4.重要總結

對象是否存活判斷流程:

1.可達性分析,看是否有GC Roots的引用鏈,如果沒有將做第一次標記;

2.檢查是否需要執行finalize()方法,

如果沒必要(之前執行過了),直接回收內存;

如果要執行finalize()方法,這個時候對象如果再次建立引用鏈(唯一自救機會),對象不會被回收,否則直接回收;

總結:

1.對象回收滿足兩個條件:

a.沒有引用鏈。

b.回收前會執行finalize()方法,如果執行finalize(),沒有再次建立連接(如果重新與引用鏈上的任意對象建立連接,例如給對象賦值,該對象都不會被回收)

2.在gc回收前會執行finalize()方法,只執行一次,並且是異步執行不保證執行成功,線程優先級低

代碼演示:

package com.wfd360.demo03GC.referDemo;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 8:34
 * @description
 */
public class FinalizeGC {
    public static FinalizeGC obj1 = null;

    /**
     * 重寫finalize方法
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("執行finalize方法");
        // 自救,在回收時建立引用鏈
        FinalizeGC.obj1 = this;
    }

    public static void main(String[] args) throws InterruptedException {
        obj1  = new FinalizeGC();

        obj1 = null;
        System.gc();
        Thread.sleep(600);
        System.out.println("第一次自救成功:"+obj1);

        obj1 = null;
        System.gc();
        Thread.sleep(600);
        System.out.println("第二次自救失敗,不會再次執行finalize方法:"+obj1);
    }
}

View Code

測試結果:

 完美!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

.NET Core Hangfire周期性作業調度問題

前言

四月中旬Hangfire團隊發布了1.7.11版本,在使用周期性作業調度過程中發現一個問題,這個問題應該一直未解決,故做此記錄,希望遇到的童鞋根據項目業務而避開這個問題。

周期性作業調度

我們依然是在控制台中進行測試,下載所需包請參考官方文檔,這裏不再敘述,首先我們在內存中存儲數據,如下:

var storageOpts = new MemoryStorageOptions();

GlobalConfiguration.Configuration.UseMemoryStorage(storageOpts);

using var server = new BackgroundJobServer();

RecurringJob.AddOrUpdate("job1", () => Print1(), "*/10 * * * * *", TimeZoneInfo.Local);

RecurringJob.AddOrUpdate("job2", () => Print2(), "*/10 * * * * *", TimeZoneInfo.Local);

RecurringJob.AddOrUpdate("job3", () => Print3(), "*/10 * * * * *", TimeZoneInfo.Local);
public static void Print1()
{
    Console.WriteLine("start1");
}

public static void Print2()
{
    Console.WriteLine("start2");
}

public static void Print3()
{
    Console.WriteLine("start3");
}

Hangfire已支持秒級(1.7+)周期作業調度,如上代碼,我們每隔10秒執行上述3個作業,打印如下:

 

基於內存存儲間隔10秒執行對應作業,根據上述打印結果來看沒有問題,接下來我們使用SQLite來存儲作業數據看看,首先下載Hangfire.SQLite包,針對控制台需進行如下配置

GlobalConfiguration.Configuration.UseSQLiteStorage("Data Source=./hangfire.db;");

當我們啟動控制台時一定會拋出如下異常,其異常旨在表明需要SQLite驅動

我們去下載微軟官方針對SQLite的驅動(Microsoft.Data.Sqlite)

 接下來我們將發現對於每一個作業都會重複執行多次,如下:

猜測只會在SQLite數據庫中才會存在問題吧,為了解決這個問題,做了一點點嘗試,但還是無法從根本上完全解決,我們知道Hangfire服務的默認工作數量為當前機器的處理器數量乘以5即(Environment.ProcessorCount * 5),那麼我們嘗試給1是不是可以規避這個問題

var options = new BackgroundJobServerOptions()
{
    WorkerCount = 1
};

using var server = new BackgroundJobServer(options);

 

上述設置后,我們可以看到貌似只執行了一次,但是這種情況還是是隨機的並不靠譜,比如多執行幾次看看,會出現如下可能情況

 

沒招了,找了下官方issue列表,發現此問題(https://github.com/mobydi/Hangfire.Sqlite/issues/2)一直處於打開狀態並未得到解決,所以要麼看看能否根據項目業務規避這個問題或者下載源碼自行調試解決

總結

本文是在使用Hangfire過程中發現SQLite數據庫出現的問題,因針對Hangfire的SQLite具體實現並不是官方團隊所提供,所以暫不能確定到底是Hangfire.SQLite包提供者的問題,根據issue描述大概率是Hangfire的一個bug,希望在SQLite存儲作業等數據存在的問題引起使用者注意。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

selenium自動化操作

在前面爬蟲的相關介紹中,我們介紹了如何抓取靜態頁面信息。但是,在實際的網頁瀏覽過程中,我們可能會經常碰到各種需要進行交互的操作,典型的如輸入信息、點擊按鈕之類。

對於這種場景,之前的靜態頁面操作方式已經不能滿足需求,這時我們需要藉助新的工具,比如selenium或者PhantomJS。由於後者已經停止維護,推薦使用前者。

1.selenium是什麼

如果大家有做過web的自動化測試,相信對於selenium一定不陌生,測試人員經常使用它來進行自動化測試。

selenium最初是一個自動化web測試工具,通過代碼模擬人使用瀏覽器自動訪問目標站點並操作,比如跳轉、輸入、點擊、下拉等。

由於開發者的不斷完善,目前的功能越來越強大,基本支持各種交互操作。同時,不止支持有界瀏覽,還支持無界瀏覽。

2.selenium有什麼用

正如我們前面講過的,爬蟲的本質過程就是模擬人對瀏覽器的操作過程。在爬蟲中使用,selenium主要是為了解決requests無法執行javaScript代碼的問題。

本質上是通過驅動瀏覽器,完全模擬瀏覽的操作,比如跳轉、輸入、點擊、下拉等…進而進行跳轉。

當然,它也有壞處,主要的壞處就是它的速度比較慢。原因是selenium在操作時,需要等瀏覽器對頁面的元素渲染好之後才能操作。而我們知道,由於頁面渲染過程需要加載各種資源,響應速度與網絡帶寬要求非常高。通常情況,它比靜態頁面的響應至少慢一個數量級。

3.如何使用selenium

在知道selenium是什麼以及有什麼用之後,我們來具體學習如何操作這個工具。

由於selenium本質是模擬人對瀏覽器進行輸入、選擇、點擊等操作,因此對於目標標籤的定位非常重要。

在前面的章節,我們對於如何定位目標標籤有過詳細的介紹,這裏就不再贅述。selenium對於目標標籤定位的方式本質與靜態的頁面一樣,只不過因為使用的包不同,因此在beautifulSoup中使用的是find和findAll,而在selenium中使用的接口有所變化。

下圖中已對各種定位方式進行了歸納總結:

在找到目標標籤之後,最重要的是對這些標籤進行模擬操作。Selenium庫下webdriver模塊常用方法主要分類兩類:一類是模擬瀏覽器、鍵盤操作,另一類是模擬鼠標操作。

3.1模擬瀏覽器、鍵盤操作

模擬瀏覽器、鍵盤操作的方法歸納如下:

3.2 模擬鼠標操作

模擬鼠標操作的方法歸納如下:

4.示例演示

在介紹了selenium相關的使用方法之後,我們來進行操作。這裏介紹兩個例子:第一個例子是模擬百度搜索,第二個例子是模擬自動登錄網易163郵箱發送郵件。

在開始示例之前我們需要安裝selinum插件包,同時還需要下載webdriver。在我們的示例中,需要是使用chrome瀏覽器進行操作,需要使用瀏覽器的驅動webdriver。

關於下載什麼版本的webdriver,可以在瀏覽器的屬性中查看,並在http://npm.taobao.org/mirrors/chromedriver/下載對應的版本就好,如果是其他的瀏覽器,則需要下載對應的瀏覽器驅動程序,這種不再做進一步介紹。

4.1模擬百度搜索

第一步還是需要打開目標的地址“w w w.baidu.com”,分析目標網頁中目標元素的特點,如下圖所示:

通過分析,我們很容易就找到搜索框的id為kw,點擊按鈕的id為su,餘下的就是使用方法進行模擬。

實現的代碼如下所示:

from selenium import webdriver

#get 方法 打開指定網址
driver=webdriver.Chrome()
driver.get('http://www.baidu.com')

#選擇網頁元素
element_keyword = driver.find_element_by_id('kw')

#輸入字符
element_keyword.send_keys('python 爬蟲')

#找到搜索按鈕
element_search_button = driver.find_element_by_id('su')
element_search_button.click()

driver.close()

4.2模擬自動登錄網易163郵箱發送郵件

操作過程跟上面相似,第一步也是分析目標網頁http://mail.163.com。

如下圖所示:

找到了目標標籤然後就是模擬登錄。

實現代碼如下:

# coding:UTF-8
import time
from selenium.webdriver.common.keys import Keys
from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(5)  
driver.get('http://mail.163.com/')
driver.switch_to_frame(driver.find_element_by_tag_name('iframe'))
# driver.switch_to_frame('x-URS-iframe')  
driver.find_element_by_name('email').clear()
driver.find_element_by_name('email').send_keys('郵箱地址')
driver.find_element_by_name('password').send_keys('郵箱密碼', Keys.ENTER)
# 跳轉頁面時,強制等待6s
time.sleep(6)   
# 點擊寫信按鈕
driver.find_element_by_xpath("//div[@id='dvNavTop']/ul/li[2]/span[2]").click()   
time.sleep(2)
# 收件人
driver.find_element_by_class_name('nui-editableAddr-ipt').send_keys('目標的郵箱')  
driver.find_element_by_xpath("//input[@class='nui-ipt-input' and @type='text' and @maxlength='256']").send_keys(
    u'測試')   # 主題
xpath = driver.find_element_by_xpath("//div[@class='APP-editor-edtr']/iframe")
# 文本內容在iframe中
driver.switch_to_frame(xpath)   
driver.find_element_by_xpath("//body[@class='nui-scroll' and @contenteditable='true']").send_keys(u'這是一個自動化測試郵件')
# 發送按鈕在iframe外,所以需要跳出
driver.switch_to_default_content()
# 發送
driver.find_element_by_xpath("//div[@class='nui-toolbar-item']/div/span[2]").click()  
driver.close()

當然,在實際過程中,可能往往還有驗證碼的驗證。因為現在的驗證碼難度越來越大,形式也多種多樣,使用常規的方法很難解決,必須藉助機器學習或者第三方接口進行實現,將在後續單獨列一個章節進行介紹如何破解驗證碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

11.DRF-權限

Django rest framework源碼分析(2)—-權限

添加權限

(1)API/utils文件夾下新建premission.py文件,代碼如下:

  • message是當沒有權限時,提示的信息
# utils/permission.py

class SVIPPremission(object):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(object):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

(2)settings.py全局配置權限

#全局
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',],
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

(3)views.py添加權限

  • 默認所有的業務都需要SVIP權限才能訪問
  • OrderView類裏面沒寫表示使用全局配置的SVIPPremission
  • UserInfoView類,因為是普通用戶和VIP用戶可以訪問,不使用全局的,要想局部使用的話,裏面就寫上自己的權限類
  • permission_classes = [MyPremission,] #局部使用權限方法
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from API.utils.permission import SVIPPremission,MyPremission

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()

class AuthView(APIView):
    '''用於用戶登錄驗證'''

    authentication_classes = []      #裏面為空,代表不需要認證
    permission_classes = []          #不裏面為空,代表不需要權限
    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


class OrderView(APIView):
    '''
    訂單相關業務(只有SVIP用戶才能看)
    '''

    def get(self,request,*args,**kwargs):
        self.dispatch
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)


class UserInfoView(APIView):
    '''
       訂單相關業務(普通用戶和VIP用戶可以看)
       '''
    permission_classes = [MyPremission,]    #不用全局的權限配置的話,這裏就要寫自己的局部權限
    def get(self,request,*args,**kwargs):

        print(request.user)
        return HttpResponse('用戶信息')
# urls.py
from django.contrib import admin
from django.urls import path
from API.views import AuthView,OrderView,UserInfoView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/',AuthView.as_view()),
    path('api/v1/order/',OrderView.as_view()),
    path('api/v1/info/',UserInfoView.as_view()),
]
# API/utils/auth/py
# auth.py

from rest_framework import exceptions
from API import models
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass

(4)測試

普通用戶訪問OrderView,提示沒有權限

普通用戶訪問UserInfoView,可以返回信息

權限源碼流程

(1)dispatch

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    #對原始request進行加工,豐富了一些功能
    #Request(
    #     request,
    #     parsers=self.get_parsers(),
    #     authenticators=self.get_authenticators(),
    #     negotiator=self.get_content_negotiator(),
    #     parser_context=parser_context
    # )
    #request(原始request,[BasicAuthentications對象,])
    #獲取原生request,request._request
    #獲取認證類的對象,request.authticators
    #1.封裝request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        #2.認證
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

(2)initial

def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    #4.實現認證
    self.perform_authentication(request)
    #5.權限判斷
    self.check_permissions(request)
    self.check_throttles(request)

(3)check_permissions

裏面有個has_permission這個就是我們自己寫的權限判斷

def check_permissions(self, request):
    """
    Check if the request should be permitted.
    Raises an appropriate exception if the request is not permitted.
    """
    #[權限類的對象列表]
    for permission in self.get_permissions():
        if not permission.has_permission(request, self):
            self.permission_denied(
                request, message=getattr(permission, 'message', None)
            )

(4)get_permissions

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    return [permission() for permission in self.permission_classes]

(5)permission_classes

所以settings全局配置就如下

#全局
REST_FRAMEWORK = {
   "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

內置權限

django-rest-framework內置權限BasePermission

默認是沒有限制權限

class BasePermission(object):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

我們自己寫的權限類,應該去繼承BasePermission,修改之前寫的permission.py文件

# utils/permission.py

from rest_framework.permissions import BasePermission

class SVIPPremission(BasePermission):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(BasePermission):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

總結:

(1)使用

  • 自己寫的權限類:1.必須繼承BasePermission類; 2.必須實現:has_permission方法

(2)返回值

  • True 有權訪問
  • False 無權訪問

(3)局部

  • permission_classes = [MyPremission,]

(4)全局

REST_FRAMEWORK = {
   #權限
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!