從日常開發說起,淺談HTTP協議是做什么的

AntwanGula 8年前發布 | 13K 次閱讀 HTTP Web服務器

來自: http://blog.jobbole.com/97857/

引言

HTTP協議作為Web開發的基礎一直被大多數人所熟知,不過相信有很多人只知其一不知其二。比如咱們經常用到的session會話機制是如何實現的,可能很多人都說不出來吧。其實session會話就是HTTP協議中的一個header屬性cookie所支持的,在你了解了HTTP協議之后,其實這些都非常容易理解。

本文會嘗試從各位的日常開發去解釋一下HTTP到底是做什么的,文章篇幅有限,如果有什么本文沒有提到的,各位請自行百度或者看書補腦。接下來,咱們先來看一個小A和小B的故事。

小故事:兩個人的任務

小A和小B是中國著名的互聯網公司BAT的兩個員工,時間走到2015年4月15日下午6點半,北京風沙漫天,仿佛是妖怪來臨,小A和小B興奮的正準備收拾回家,順便享受一下免費的風沙晚餐。

就在這時,項目經理大S的聲音不合時宜的響了起來,“小A,小B,你們倆先別走,給你們倆一個任務”。

兩人聽到這個聲音,苦逼的相視一笑,之后便異口同聲的說道,“頭兒,有事兒您說話”。

“你倆也別不樂意,自從來到BAT,你倆單身問題解決了,房子也買了,存款也有了,加會班也是應該的”。大S注意到二人苦逼的眼神,淡定的說道。看到二人心悅誠服的點了點頭,大S也就不再多說,吩咐道:“咱們項目里有不少地方需要判斷String是否為空,是否為空串等等,小B你寫一個工具類,小A你把調用的地方改一下。”

兩人一聽,這還不簡單,趕緊點頭哈腰的說,“包在我倆身上”。

看到二人的反應,大S滿意的點了點頭,正色說道:“記得我一直教導你們的,bug毀一時,重復毀一生,重構要趁早。”說完這句話,大S便頭也不回的離開二人,數十秒后便鉆進了風沙之中。

小A和小B簡單商量了一下,由小B來編寫String的工具類,小A來調用小B的工具類方法。工具類名叫StringUtils,里面共有兩個方法,一個叫isNull,一個叫isEmpty,參數都是一個String,返回值都是一個boolean。

由于這個類非常簡單,小B很快就搞定了它,代碼如下。

/**
 * 工具類
 *
 * @author 小B
 * <a >@since</a> 4/17/2015 2:47 PM
 */
public abstract class StringUtils {

    public static boolean isNull(String s) {
        return s == null;
    }

    public static boolean isEmpty(String s) {
        return isNull(s) || s.length() == 0;
    }

}

小B把工具類寫好以后,小A也很快把其中一個用于驗證身份證號的類改成了調用小B工具類的方式,代碼如下。

/**
 * 身份證號驗證工具類
 *
 * @author 小A
 * <a >@since</a> 4/17/2015 2:50 PM
 */
public class CardNumberValidator {

    /**
     * 判斷身份證號是否有效 (忽略身份證號復雜的判斷規則)
     */
    public boolean valid(String cardNumber) {
        if (StringUtils.isNull(cardNumber)) {
            throw new NullPointerException("cardNumber is null!");
        } else if (StringUtils.isEmpty(cardNumber)) {
            throw new IllegalArgumentException("cardNumber is empty!");
        } else if (cardNumber.length() == 18) {
            return true;
        } else {
            throw new IllegalArgumentException("the length of cardNumber must be 18!");
        }
    }

}

最后小A又寫了一個簡單的測試類測試了一下,測試類如下。

/**
 * 客戶端
 *
 * <a >@since</a> 4/17/2015 3:01 PM
 */
public class Client {

    /**
     * 主函數
     *
     * @param args
     */
    public static void main(String[] args) {
        CardNumberValidator validator = new CardNumberValidator();
        String cardNumber = "433182198803211232";
        if (validator.valid(cardNumber)) {
            System.out.println(cardNumber + "是一個有效的身份證號!");
        } else {
            System.out.println(cardNumber + "是一個無效的身份證號!");
        }
    }

}

運行之后,程序運行的結果為正確的,如下。

433182198803211232是一個有效的身份證號!

看到程序很快就正確運行,小A和小B都欣喜不已,感覺自己的技術水平又得到了質的提升。由于兩人住的比較近,于是干完活之后,二人便一同鉆進了風沙之中。

花絮與故事分析

一個小小的京城故事,兩個快樂的2B程序員。沒有復雜的故事情節,沒有高深的技術含量,但卻蘊含著一個像極了WEB資源請求的過程。回想一下剛才小A的程序調用小B程序的過程,其實包含了以下三個步驟。

1,小A的程序通過【類名.方法名(參數列表)】的形式找到了小B程序當中的方法。

2,小A的程序傳給小B的程序一個參數,小B的程序對這個請求進行相應的處理,比如判斷是否為空等等。

3,小B的程序根據處理結果返回給小A的程序,小A的程序根據返回結果進行后續的處理。

這整個過程與一個簡單的WEB資源請求如出一撤,具體的內容咱們在接下來的過程中再去討論。這里,咱們先簡單的回顧一下,小A和小B都商量了哪些東西以后,開始了各自的編程。

1,首先小A和小B定義了類名,方法名和參數類型。根據這三個內容,小A就可以通過命名找到小B的兩個方法(比如StringUtils.isNull)。那么簡單點說,類名,方法名以及參數類型就可以解決“小A怎么找到小B的方法”。

2,其次,由于規定了小A需要給小B傳送String類型的數據,那么小B就可以按照String類型進行相應的處理。因此,小A和小B對于方法參數類型的約定,就可以解決“小A傳什么數據和小B接到以后按照什么類型去處理”。

3,最后,小B和小A約定返回的類型為boolean,那么小A這邊收到結果以后就可以按照boolean類型去處理。返回結果的約定,就可以解決“小B返回什么數據和小A接收到以后按照什么類型去處理結果”。

HTTP與小故事

一次WEB資源請求的過程,其實就和一次方法調用特別相似,上面小A的程序其實就相當于瀏覽器,小B的程序就相當于服務器,而小B提供的方法就相當于服務器上的資源。上面咱們分析了方法調用的過程,咱們來看看一次WEB資源請求大致分為哪三步。

1,瀏覽器需要根據某種字符串格式(類似于故事當中的【類名.方法(參數列表)】的方式)找到服務器當中的資源。

2,瀏覽器傳給服務器一個請求,服務器對這個請求進行相應的處理(比如增刪改查)。

3,服務器根據處理結果返回給瀏覽器,瀏覽器根據返回結果進行相應的處理(比如顯示網頁,顯示圖片等)。

可以看出,這三個步驟是非常相似的。既然是相似的步驟,那么就會存在相似的問題。接下來,咱們簡單的分析一下都有哪些問題,以及這些問題如何處理。

【1】 如何找到服務器當中的資源

故事當中,小A根據【類名.方法(參數列表)】的方式找到小B的方法,那么在WEB資源請求當中,瀏覽器如何找到服務器的資源呢?

相信大部分人腦子里已經浮現出了那三個字母。

是的,就是URL。URL就是專門用來定位資源的。URL的一般格式如下。

protocol :// hostname[:port] / path / [;parameters][?query]#fragment

其中各個部分的含義相信大部分人都知道,這里咱們就不過多解釋了。最重要的就是protocol,hostname和port,分別代表著應用層的協議(比如http,https,ftp等等),主機名或IP以及服務端口。

【2】 瀏覽器和服務器互相傳輸的數據如何解析

這個問題其實就是第二和第三步所面臨的問題,瀏覽器要給服務器發請求,但是服務器哪知道你發的是什么玩意。同理,如果服務器給瀏覽器返回數據,瀏覽器同樣也不知道服務器返回的是什么東西。

在故事當中,小A和小B商量好了,小A給小B傳String,小B給小A返回boolean,這就很好的解決了程序之間數據交換的解析工作。當然,由于上面的程序非常簡單,所以解析的工作還不是體現的特別明顯。

假設小B的方法是一個save(Map user)的形式,返回值也是一個Map。這時候,如果小B和小A不商量好Map里面都需要put點啥東西的話,估計這程序也沒法寫下去了。

所以問題就出現在這里,如果不給瀏覽器和服務器制定好一個規則的話,不管是開發瀏覽器的程序員,還是開發服務的程序員,都會出現程序不知道怎么寫的情況。最可怕的是,開發瀏覽器的程序員和開發服務的程序員可不一定是同事,他們無法面對面或者通過通訊工具去商量你給我傳什么,我給你傳什么這種問題。

所以HTTP協議就應運而生了,這是一群外國人勾搭以后產生的(聽說這群外國人叫World Wide Web Consortium和Internet Engineering Task Force)。HTTP協議自出現以來,主要解決的就是瀏覽器和服務器數據交換的格式問題。

既然是解決數據交換的格式問題,所以不用去想,也知道HTTP其實是定義了一套數據格式。咱們來看一個實際的例子,一個HTTP請求到底都有哪些數據,以及這些數據是什么格式。

GET http://www.cnblogs.com/mvc/Follow/GetFollowStatus.aspx HTTP/1.1
Host: www.cnblogs.com
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0
Accept: text/plain, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Referer: http://www.cnblogs.com
Cookie: _ga=GA1.2.1892107667.1429258898; __gads=ID=ab3bc12f51ce0821:T=1429258914:S=ALNI_MYk8_dptVuXGkQkQbTMuiNN4DdPcA; .CNBlogsCookie=D84CF73E03B7891789045C601A78AAF52AB67268B93E15C1138FE9A9CE858E98FE769CE0249E5C5C68D6E84DB2CBC9D9BA77ACA3993260AA8671E17F2C3AC7B3267298C6160C09A737AF94A353F58D0B7FF5C0AD6287EFC4DBE122883CBDD2CB42C3052C; _gat=1; CNZZDATA1684828=cnzz_eid%3D1208659555-1429265886-%26ntime%3D1429265886
Connection: keep-alive

可以看到,最上面的那一行其實是協議當中定義的首行。第一個GET代表的是,這是一個get請求。后面緊跟著的是訪問的URL,最后是HTTP協議版本。

再往下就是HTTP當中定義的header了,具體每個header都代表什么意思這里就不一一解釋了,這不是本文的重點,可以去參考網絡上其它的文章。這些Header其實就相當于HTTP協議提供的一些方便的功能,你設置相應的header,可以讓服務器產生相應的行為。

舉個例子,比如Cookie這個header,大家應該再熟悉不過。它的作用就是告訴服務器當前請求者的身份,而大部分的服務器也都會自動去管理Cookie。

除了上面出現的首行和header以外,對于一些特定的請求,HTTP還有特定的數據格式。比如post請求的時候,在【Connection: keep-alive】下面會多出來一個json格式的字符串,這個字符串就是post請求時所發送的表單數據。

同樣的,服務器返回的數據格式也是相似的。一個比較實際的例子如下。

HTTP/1.1 200 OK
Date: Fri, 17 Apr 2015 10:18:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 128
Connection: keep-alive
Cache-Control: private
X-UA-Compatible: IE=10

<a href="javascript:void(0);" onclick="cnblogs.UserManager.FollowBlogger('83f9460b-63cf-dd11-9e4d-001cf0cd104b')">+加關注</a>

響應當中依然有首行,而首行就是協議版本,加上狀態碼和狀態描述。接下來就是一堆header,這點與請求相同。不同的是,請求和響應所支持的header并不一樣。比如請求的時候,瀏覽器給服務器傳送Cookie時使用的header是Cookie。但是當服務器返回響應給瀏覽器時,如果要更新Cookie的話,對應的header是Set-Cookie。最后一行則是服務器所返回的內容,格式是由Content-Type所指定的,類型為html,字符編碼為UTF-8。對于其它的header這里就不一一解釋了,請各位自行補腦。

HTTP與WEB開發的聯系

說到這里,需要簡單提一下HTTP與WEB開發的聯系。比如大家在做J2EE開發時所熟知的request和response對象,咱們來看一下request和response接口都有哪些方法。

public interface HttpServletRequest extends ServletRequest {
    String BASIC_AUTH = "BASIC";
    String FORM_AUTH = "FORM";
    String CLIENT_CERT_AUTH = "CLIENT_CERT";
    String DIGEST_AUTH = "DIGEST";

    String getAuthType();

    Cookie[] getCookies();

    long getDateHeader(String var1);

    String getHeader(String var1);

    Enumeration getHeaders(String var1);

    Enumeration getHeaderNames();

    int getIntHeader(String var1);

    String getMethod();

    String getPathInfo();

    String getPathTranslated();

    String getContextPath();

    String getQueryString();

    String getRemoteUser();

    boolean isUserInRole(String var1);

    Principal getUserPrincipal();

    String getRequestedSessionId();

    String getRequestURI();

    StringBuffer getRequestURL();

    String getServletPath();

    HttpSession getSession(boolean var1);

    HttpSession getSession();

    boolean isRequestedSessionIdValid();

    boolean isRequestedSessionIdFromCookie();

    boolean isRequestedSessionIdFromURL();

    /** @deprecated */
    boolean isRequestedSessionIdFromUrl();
}
public interface HttpServletResponse extends ServletResponse {
    int SC_CONTINUE = 100;
    int SC_SWITCHING_PROTOCOLS = 101;
    int SC_OK = 200;
    int SC_CREATED = 201;
    int SC_ACCEPTED = 202;
    int SC_NON_AUTHORITATIVE_INFORMATION = 203;
    int SC_NO_CONTENT = 204;
    int SC_RESET_CONTENT = 205;
    int SC_PARTIAL_CONTENT = 206;
    int SC_MULTIPLE_CHOICES = 300;
    int SC_MOVED_PERMANENTLY = 301;
    int SC_MOVED_TEMPORARILY = 302;
    int SC_FOUND = 302;
    int SC_SEE_OTHER = 303;
    int SC_NOT_MODIFIED = 304;
    int SC_USE_PROXY = 305;
    int SC_TEMPORARY_REDIRECT = 307;
    int SC_BAD_REQUEST = 400;
    int SC_UNAUTHORIZED = 401;
    int SC_PAYMENT_REQUIRED = 402;
    int SC_FORBIDDEN = 403;
    int SC_NOT_FOUND = 404;
    int SC_METHOD_NOT_ALLOWED = 405;
    int SC_NOT_ACCEPTABLE = 406;
    int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
    int SC_REQUEST_TIMEOUT = 408;
    int SC_CONFLICT = 409;
    int SC_GONE = 410;
    int SC_LENGTH_REQUIRED = 411;
    int SC_PRECONDITION_FAILED = 412;
    int SC_REQUEST_ENTITY_TOO_LARGE = 413;
    int SC_REQUEST_URI_TOO_LONG = 414;
    int SC_UNSUPPORTED_MEDIA_TYPE = 415;
    int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
    int SC_EXPECTATION_FAILED = 417;
    int SC_INTERNAL_SERVER_ERROR = 500;
    int SC_NOT_IMPLEMENTED = 501;
    int SC_BAD_GATEWAY = 502;
    int SC_SERVICE_UNAVAILABLE = 503;
    int SC_GATEWAY_TIMEOUT = 504;
    int SC_HTTP_VERSION_NOT_SUPPORTED = 505;

    void addCookie(Cookie var1);

    boolean containsHeader(String var1);

    String encodeURL(String var1);

    String encodeRedirectURL(String var1);

    /** @deprecated */
    String encodeUrl(String var1);

    /** @deprecated */
    String encodeRedirectUrl(String var1);

    void sendError(int var1, String var2) throws IOException;

    void sendError(int var1) throws IOException;

    void sendRedirect(String var1) throws IOException;

    void setDateHeader(String var1, long var2);

    void addDateHeader(String var1, long var2);

    void setHeader(String var1, String var2);

    void addHeader(String var1, String var2);

    void setIntHeader(String var1, int var2);

    void addIntHeader(String var1, int var2);

    void setStatus(int var1);

    /** @deprecated */
    void setStatus(int var1, String var2);
}

可以看到,request和response里面有好幾個方法都和header有關,使用這些方法就可以取到相應的HTTP請求當中的header內容,也可以返回相應的header內容給瀏覽器。還有一點,就是response接口當中定義了一大把狀態碼和狀態描述,比如200對應OK,404對應NOT_FOUND,500對應內部錯誤等等。

可以預見的是,在編寫HttpServletRequest和HttpServletResponse這兩個接口的時候,一定是參照HTTP協議去定義的,而且每當HTTP協議進行一次大的變更,這兩個接口都要跟著進行相應的變化。

總的來說,把對HTTP的了解和日常的開發聯系起來,更加有助于你理解HTTP協議,而且有時候也可以利用HTTP協議擴展一些功能,比如授權服務,自定義的狀態保持等等。

小結

到此,大家應該基本上了解HTTP主要是用來做什么的了,具體HTTP協議當中都規定了哪些內容,大家可以去找各種資料翻閱。個人覺得,只要深刻理解HTTP協議是做什么的,了解一些常用的協議內容就行了,你并不需要把HTTP所有的header都給背下來并記住它們的作用。

最后,重復一下那句與HTTP無關的話:bug毀一時,重復毀一生,重構要趁早。

 本文由用戶 AntwanGula 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!