引言
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很快就搞定了它,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * 工具類 * * @author 小B * <a href="http://www.jobbole.com/members/chchxinxinjun">@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工具類的方式,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * 身份證號驗證工具類 * * @author 小A * <a href="http://www.jobbole.com/members/chchxinxinjun">@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又寫了一個簡單的測試類測試了一下,測試類如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * 客戶端 * * <a href="http://www.jobbole.com/members/chchxinxinjun">@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 + "是一個無效的身份證號!"); } } } |
執行之後,程式執行的結果為正確的,如下。
1 |
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的一般格式如下。
1 |
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請求到底都有哪些資料,以及這些資料是什麼格式。
1 2 3 4 5 6 7 8 9 10 |
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請求時所傳送的表單資料。
同樣的,伺服器返回的資料格式也是相似的。一個比較實際的例子如下。
1 2 3 4 5 6 7 8 9 |
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介面都有哪些方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
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(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
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毀一時,重複毀一生,重構要趁早。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式