——以下內容如有各種問題,煩請指出,謝謝各位^_^——
最基本的Java程序員面試題都有這個題
——http get和post的區別?
不少人大學還沒畢業就知道,就算不知道也會去搜,我記得我快畢業那會,簡單搜出來,排在前面的大概就這么幾個區別:
1、get用於獲取數據,post用於提交數據
2、get提交參數追加在url后面,post參數可以通過http body提交
3、get的url會有長度上的限制,則post的數據則可以非常大
4、get提交信息明文顯示在url上,不夠安全,post提交的信息不會在url上顯示
5、get提交可以被瀏覽器緩存,post不會被瀏覽器緩存
現在回頭總結下,發現自己快畢業哪會自己真是什么都不知道啊,當時網上搜出來的這份東西就是有誤的啊,國內也是各種傳來傳去,錯誤的到處看得到,都快成標准答案了。今年5月用netty http 些服務端程序時,調接口無意發現了原來get也可以使用http body提交數據,抽空弄了下tomcat,發現也可以啊。今天整理筆記看到了這里,覺得有必要在博客上記錄一下,避免后來人繼續犯錯。
一點一點的說 第1點:rfc2616說get方法用於獲取指定uri所代表的資源,應該設計成冪等的(其他情況不變時,多次請求返回同樣的結果,差不多算是只讀),不過在很長一段時間內,get方法都有“寫”功能,最簡單的例子就是/delete?id=1,然后很常見的就是jsonp形式的請求。 post方法該做什么rfc2616說是叫服務器自己決定,現實中用post進行只讀操作的很多啊,一些提供http接口的數據庫都有post json進行只讀查詢的功能,post提交表單數據進行寫操作到處都是。 所以第一點這個,不怎么好評價,現在的多數用途下第一點就是廢話,什么意思都沒表達。不過現在RESTful炒得火熱,在RESTful的理念下,第一點差不多算是對的,不過RESTful任重道遠啊,比起現在只用get/post的http,畢竟實質性的功能沒有多大變化。 應該是今后很長一段時間內,get和post在第一點上基本沒區別,能用post實現的操作,基本上也能用get實現。
第2點:這一點是最坑的。 http沒明確規定什么get/post方法要用什么樣的方式傳輸數據,之所以出現第2點所說的情況,原因主要有兩點:瀏覽器設計、服務器設計,瀏覽器不支持get+httpbody是比較常見的,fiddler模擬請求時,如果是get,你填下面的body部分會紅色顯示,告訴你這樣不好,僅僅是不好,因為fiddler並沒有禁止get+body這種請求,但看得出它不建議你用這種方式的請求。
常見的servlet服務器,比如tomcat,默認不解析get的body部分,造成了get不能用body傳遞參數的現象。我寫了個簡單的例子,對比看下就知道是tomcat沒解析get的body,不是get本身不能使用body傳參。
1 import java.io.IOException; 2 3 import javax.servlet.http.HttpServletRequest; 4 5 import org.apache.commons.lang3.StringUtils; 6 import org.springframework.stereotype.Controller; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.ResponseBody; 9 10 @Controller 11 public class TestController { 12 private static final String NEW_LINE = StringUtils.CR + StringUtils.LF; 13 14 @ResponseBody 15 @RequestMapping("/testLogin") 16 public String testLogin(HttpServletRequest req, String username, String pwd) throws IOException { 17 StringBuilder builder = new StringBuilder(); 18 builder.append("req.method: ").append(req.getMethod()).append(NEW_LINE); 19 builder.append("req.queryString: ").append(req.getQueryString()).append(NEW_LINE); 20 int contentLength = req.getContentLength(); 21 String body = null; 22 if (contentLength > 0) { 23 byte[] bytes = new byte[contentLength]; 24 req.getInputStream().read(bytes); 25 body = new String(bytes, "UTF-8"); 26 } 27 builder.append("req.body: ").append(body).append(NEW_LINE); 28 builder.append("req.params.username: ").append(req.getParameter("username")).append(NEW_LINE); 29 builder.append("req.params.pwd: ").append(req.getParameter("pwd")).append(NEW_LINE); 30 builder.append("username: ").append(username).append(NEW_LINE); 31 builder.append("pwd: ").append(pwd); 32 System.err.println(builder); 33 return builder.toString(); 34 } 35 }
上面這個controller功能很簡單,就是打印請求參數,並且原樣返回,結果如下
上面張圖看出來,body部分可以獲取得到,但是tomcat沒有讀取了ServletInputStream中的body並解析body,造成req.getParam獲取不到對應的參數。
這張圖就是把方法換成POST,Request的其余一個字符都沒有變,可以看到tomcat讀取了ServletInputStream中的body,並進行解析,造成了手動讀取ServletInputStream時流中沒有內容了,打印出來的req.body顯示無內容,req.getParam能夠獲取到解析完成后對應的參數。
這張圖是普通的get+queryString,效果和post+body一樣。
對比上面三個結果就知道,get不是不能使用body傳參,只是瀏覽器和服務器進行了限制。瀏覽器的限制我不知道,這個研究得不多,tomcat的限制倒是可以根據配置解除。
官方配置:http://tomcat.apache.org/tomcat-8.0-doc/config/http.html
具體就是這一項
默認是POST,也就是當Content-Type=application/x-www-form-urlencoded(提交Web表單時的標准數據傳輸格式,跟url傳參格式一樣,使用鍵值對,用&區分)時,只對post方法的body部分進行解析。把tomcat的server.xml中Http1.1的Connector配置上這項,就能夠讓tomcat能夠解析get的body部分,也就能夠在tomcat上使用get+body的方式了。
上面這圖是修改配置后的結果,改完配置后get+body跟post+body功能一樣了。
第3點:這個跟第2點一樣,rfc2616中說了不對uri長度做限制,要求http能夠實現無限長度的uri,無限長度的body。
原話是下面這個:
The HTTP protocol does not place any a priori limit on the length of a URI. Servers MUST be able to handle the URI of any resource they serve, and SHOULD be able to handle URIs of unbounded length if they provide GET-based forms that could generate such URIs. A server SHOULD return 414 (Request-URI Too Long) status if a URI is longer than the server can handle (see section 10.4.15).
Note: Servers ought to be cautious about depending on URI lengths above 255 bytes, because some older client or proxy implementations might not properly support these lengths.
rfc2616:http://www.ietf.org/rfc/rfc2616.txt
現實中就是太長的url沒什么用,太長的body部分也不是很好,所以各種客戶端服務端的實現默認都有限制的這http種各個部分的長度。瀏覽器的我不清楚,服務端的http實現一般這個長度都是可以配置的,netty http的HttpServerCodec默認是4K長度(整個請求行,uri是請求行里面大頭),tomcat是用maxHttpHeaderSize來配置請求行和header部分的總長度;post長度,netty http默認是8K,tomcat默認是maxPostSize=2M,tomcat的配置設置成-1就是無限長度,但是這么做在實際中一點意義沒有。
第4點:安全,這個扯得有點遠,也隨便扯扯。 不是別人一眼看不到的就是安全,不是別人一眼看得到就不安全。http抓包很容易的,對路由器做點手腳就能夠抓一堆移動設備的http請求包,移動設備的http請求你自己都一眼看不到,更不用說別人了,想要安全,還是用https吧,大多數人看到了也沒用。
另外扯一句,Base64不是加密,用這個加密就是掩耳盜鈴啊。
第5點:關於http response的緩存
get被建議做成冪等的,緩存是很有必要的,一般靜態資源的get請求都是會設置有效緩存時間的,這一點很容易想明白。很多時候緩存get在服務器和瀏覽器中是默認行為,這對大量使用get請求的ajax不利,所以ajax請求一般會在請求url后加上一個隨機數,瀏覽器和服務器就認為它是不同的get請求,不會緩存這個get請求。
關於post的緩存,現在的實際情況是絕大多數瀏覽器都不支持post緩存。
看了下rfc2616中關於post的response緩存的說明,發覺說得很混亂,它主要有兩個地方說了post的緩存。
第一個是關於post方法的說明中(9.5小節):
Responses to this method are not cacheable, unless the response includes appropriate Cache-Control or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource.
這段話也就是說,是否緩存post的response,是根據http請求頭中的Cache-Control、Expires決定的。
還有一個位置,13.10小節(13節是專門說緩存的):
In this section, the phrase “invalidate an entity” means that the cache will either remove all instances of that entity from its storage, or will mark these as “invalid” and in need of a mandatory revalidation before they can be returned in response to a subsequent request.
Some HTTP methods MUST cause a cache to invalidate an entity. This is either the entity referred to by the Request-URI, or by the Location or Content-Location headers (if present). These methods are:
- PUT
- DELETE
- POST
這段話是說 PUT DELETE POST 應該讓緩存無效uri代表的實體——刪除該實體的所有實例存儲,或將這些標記為“無效”並需要強制性重新驗證,然后才能返回以響應后續操作請求。
我覺得rfc2616中的兩處規定有沖突啊,都說叫POST無效緩存,那還緩存毛線啊!
stackoverflow上有個問題討論這個,感覺也沒說明白:http://stackoverflow.com/questions/626057/is-it-possible-to-cache-post-methods-in-http
后來看了下rfc7231,它是對rfc2616的補充說明,其中關於POST的緩存說明如下:
Responses to POST requests are only cacheable when they include explicit freshness information (see Section 4.2.1 of [RFC7234]). However, POST caching is not widely implemented. For cases where an origin server wishes the client to be able to cache the result of a POST in a way that can be reused by a later GET, the origin server MAY send a 200 (OK) response containing the result and a Content-Location header field that has the same value as the POST’s effective request URI (Section 3.1.4.2).
這段話說POST的resp在某些條件下可以被緩存,但是客戶端很少實現這個功能,具體是什么條件,在rfc7234這篇專門說http緩存中說了,不過我看得不是很明白。
總之根據上面幾個rfc的意思,POST的resp實際上是允許被緩存的,但是一般都不實現這個。
瀏覽器不實現是有道理的,現實中POST大多執行的是寫操作,緩存寫操作結果無意義,還有POST可以說是登錄/支付這類功能的標准方法了,雙方交流的是敏感數據,緩存這類數據的安全隱患很大。
總結:工作也有些時間了,我也不是一個純小白了,了解了一些方法和途徑,該多靠自己的力量去弄懂些東西。還有,這個問題一路走來也是深有感觸啊,網上搜到的東西不一定正確,排名靠前的也不一定是最真的,但確是那些初學者最能依賴的。像我這種工作的時候自己搗鼓無意間發現的還算幸運啊,畢竟這個問題,對於很多人,畢業的時候就定型了,可能會一輩子錯下去。
——以上內容如有各種問題,煩請指出,謝謝各位^_^——