1. 程式人生 > >淺談HTTP中GET、POST用法以及它們的區別

淺談HTTP中GET、POST用法以及它們的區別

HTTP定義了與伺服器互動的不同方法,最基本的方法有4種,分別是GET,POST,PUT,DELETE。URL全稱是資源描述符。我們可以這樣認為: 一個URL地址,它用於描述一個網路上的資源,而HTTP中的GET,POST,PUT,DELETE就對應著對這個資源的 查,改,增,刪 4個操作。到這裡,大家應該有個大概的瞭解了,GET一般用於獲取/查詢資源資訊,而POST一般用於更新資源資訊。那麼,除了上面說的四種方法,HTTP還有其它方法麼?其實HTTP中定義了以下幾種請求方法:

  • GET方法;
  • POST方法;
  • PUT方法;
  • DELETE方法。
  • HEAD方法;
  • TRACE方法;
  • OPTIONS方法;
  1. Get是最常用的方法,通常用於請求伺服器傳送某個資源,而且應該是安全的和冪等的。

    (1). 所謂安全是指該操作用於獲取資訊而非修改資訊。換句話說,GET 請求一般不應產生副作用。就是說,它僅僅是獲取資源資訊,就像資料庫查詢一樣,不會修改和增加資料,不會影響資源的狀態。

      注意:這裡安全的含義僅僅是指是非修改資訊。

    (2). 冪等是指對同一個URL的多個請求應該返回同樣的結果。這裡我再解釋一下冪等這個概念:

    冪等(idempotent、idempotence)是一個數學或計算機學概念,常見於抽象代數中。
    冪等有以下幾種定義:
      對於單目運算,如果一個運算對於在範圍內的所有的一個數多次進行該運算所得的結果和進行一次該運算所得的結果是一樣的,那麼我們就稱該運算是冪等的。比如絕對值運算就是一個例子,在實數集中,有abs(a)=abs(abs(a))。
      對於雙目運算,則要求當參與運算的兩個值是等值的情況下,如果滿足運算結果與參與運算的兩個值相等,則稱該運算冪等,如求兩個數的最大值的函式,在實數集中便是冪等的 ,即max(x,x) = x。

  2. POST方法向伺服器提交資料,比如完成表單資料的提交,將資料提交給伺服器處理。

  3. PUT方法是讓伺服器用請求的主體部分來建立一個由所請求的URL命名的新文件;如果那個文件存在的話,就用這個主體來代替它。

  4. DELETE方法就是請求伺服器刪除指定URL所對應的資源。但是,客戶端無法保證刪除操作一定會被執行,因為HTTP規範允許伺服器在不通知客戶端的情況下撤銷請求。

  5. HEAD方法與GET方法的行為很類似,但伺服器在響應中只返回實體的主體部分。這就允許客戶端在未獲取實際資源的情況下,對資源的首部進行檢查,

    使用HEAD,我們可以更高效的完成以下工作:
    ①. 在不獲取資源的情況下,瞭解資源的一些資訊,比如資源型別;
    ②. 通過檢視響應中的狀態碼,可以確定資源是否存在;
    ③. 通過檢視首部,測試資源是否被修改。

  6. TRACE方法會在目的伺服器端發起一個“迴環”診斷,我們都知道,客戶端在發起一個請求時,這個請求可能要穿過防火牆、代理、閘道器、或者其它的一些應用程式。這中間的每個節點都可能會修改原始的HTTP請求,TRACE方法允許客戶端在最終將請求傳送伺服器時,它變成了什麼樣子。由於有一個“迴環”診斷,在請求最終到達伺服器時,伺服器會彈回一條TRACE響應,並在響應主體中攜帶它收到的原始請求報文的最終模樣。這樣客戶端就可以檢視HTTP請求報文在傳送的途中,是否被修改過了。

  7. OPTIONS方法用於獲取當前URL所支援的方法。若請求成功,則它會在HTTP頭中包含一個名為“Allow”的頭,值是所支援的方法,如“GET, POST”。

GET和POST的區別:

  1. GET請求的資料會附在URL之後(就是把資料放置在HTTP協議頭中),以?分割URL和傳輸資料,引數之間以&相連,如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。如果資料是英文字母或數字,則原樣傳送;如果是空格,轉換為+;如果是中文或其他字元,則直接把字串用BASE64加密,得出如:%E4%BD%A0%E5%A5%BD,其中%XX中的XX為該符號以16進製表示的ASCII碼值。而與之對應的,POST把提交的資料放置在HTTP包的包體中,文章最下面將會有程式碼示例。

  2. POST的安全性要比GET的安全性高。注意:這裡所說的安全性和上面GET提到的“安全”不是同個概念。上面“安全”的含義僅僅是不作資料修改,而這裡安全的含義是真正的Security的含義。比如:通過GET提交資料,使用者名稱和密碼將明文出現在URL上,因為:(1)登入頁面有可能被瀏覽器快取,(2)其他人檢視瀏覽器的歷史紀錄,那麼別人就可以拿到你的賬號和密碼了,除此之外,使用GET提交資料還可能會造成Cross-site request forgery攻擊(CSRF,跨站請求偽造,也被稱為:one click attack/session riding)。

首先,你登入了銀行網站A,然後訪問危險網站B,噢,這時你會發現你的銀行賬戶少了1000塊……
為什麼會這樣呢?原因是銀行網站A違反了HTTP規範,使用GET請求更新資源。在訪問危險網站B之前,你已經登入了銀行網站A,而B中的< img …/>以GET的方式請求第三方資源(這裡的第三方就是指銀行網站A了。原本這是一個合法的請求,但這裡被不法分子利用了),所以你的瀏覽器會帶上你的銀行網站A的Cookie發出Get請求,去獲取資源“http://www.mybank.com/Transfer.php?toBankId=11&money=1000”,結果銀行網站伺服器收到請求後,認為這是一個更新資源操作(轉賬操作),所以就立刻進行轉賬操作……
為了杜絕上面的問題,銀行改用POST請求完成轉賬操作。

GET和POST的誤區:
誤區一:POST可以比GET提交更多更長的資料?

由於使用GET方法提交資料時,資料會以&符號作為分隔符的形式,在URL後面新增需要提交的引數,有人會說,瀏覽器位址列輸入的引數是有限的,而POST不用再位址列輸入,所以POST就比GET可以提交更多的資料。難道真的是這樣的麼?

而實際上,URL不存在引數上限的問題,HTTP協議規範沒有對URL長度進行限制。這個限制是特定的瀏覽器及伺服器對它的限制。IE對URL長度的限制是2083位元組(2K+35)。對於其他瀏覽器,如Netscape、FireFox等,理論上沒有長度限制,其限制取決於作業系統的支援。所以POST也是沒有大小長度限制的,HTTP協議規範也沒有進行大小限制。起限制作用的是伺服器的處理能力。總歸一句話,這個限制是針對所有HTTP請求的,與GET、POST沒有多少關係

注意:
上面大概說了一下HTTP規範中GET和POST的一些原理性的問題。但在實際的做的時候,很多人卻沒有按照HTTP規範去做,導致這個問題的原因有很多,比如說:

  1. 很多人貪方便,更新資源時用了GET,因為用POST必須要用到FORM(表單),這樣會麻煩一點。

  2. 對資源的增,刪,改,查操作,其實都可以通過GET/POST完成,不需要用到PUT和DELETE。

  3. 早期的Web MVC框架設計者們並沒有有意識地將URL當作抽象的資源來看待和設計,所以導致一個比較嚴重的問題是傳統的Web MVC框架基本上都只支援GET和POST兩種HTTP方法,而不支援PUT和DELETE方法。

  4. 大家都覺得使用GET很方便,畢竟使用POST要用到Form。但是使用GET方法時,瀏覽器會快取你的地址等資訊,留下歷史記錄和Cookie。而對於POST方法,則不會進行快取。以後在開發中,一定要分清楚GET和POST的使用場合

建立和更新某個URL代表的資源的時候,是用HTTP的PUT還是POST:

其實,用PUT還是POST,不是看這是建立還是更新資源的動作,這不是風格的問題,而是語義的問題。REST是一種風格,但是還是依賴於HTTP協議。在HTTP中,PUT被定義為idempotent的方法,POST則不是,這是一個很重要的區別。

“Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.”

上面的話就是說,如果一個方法重複執行多次,產生的效果是一樣的,那就是idempotent的。

舉一個簡單的例子,假如由一個部落格系統提供一個Web API,模式是 http://superblogging/blogs/post/{blog-name} 。很簡單,將{blog-name}替換為我們的blog名字,往這個URI傳送一個HTTP PUT或者POST請求,HTTP的body部分就是博文,這是一個很簡單的REST API例子。我們應該用PUT方法還是POST方法?取決於這個REST服務的行為是否是idempotent的,假如我們傳送兩個http://superblogging/blogs/post/Sample請求,伺服器端是什麼樣的行為?如果產生了兩個部落格帖子,那就說明這個服務不是idempotent的,因為多次使用產生了副作用;如果後一個請求把第一個請求覆蓋掉了,那這個服務就是idempotent的。前一種情況,應該使用POST方法,後一種情況,應該使用PUT方法。

也許你會覺得這兩個方法的差別沒什麼大不了的,用錯了也不會有什麼問題,但是你的服務一放到internet上,如果不遵從HTTP協議的規範,就可能給自己帶來麻煩。比如,沒準Google Crawler也會訪問你的服務,如果讓一個不是indempotent的服務可以用indempotent的方法訪問,那麼你伺服器的狀態可能就會被Crawler修改,這是不應該發生的。

使用GET:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   new AsyncTask<String, Void, Void>() {  //網路載入是耗時操作,放在非同步任務中
       @Override
       protected Void doInBackground(String... params) {  //相當於執行緒中的run方法,執行後臺操作
           try {
               URL u = new URL(params[0]);
               URLConnection conn = u.openConnection();  //獲取URL的網際網路連線
               InputStream is = conn.getInputStream();
               InputStreamReader isr = new InputStreamReader(is);
               BufferedReader br = new BufferedReader(isr);  //包裹到BufferedReader中
               String line;
               while ((line = br.readLine()) != null) {
                   System.out.println(line);
               }
               br.close();
               isr.close();
               is.close();     
             } catch (IOException e) {
                  e.printStackTrace();
             }
           return null;
        }
    }.execute("http://apis.juhe.cn/oil/region?key=3f73d0ea9d7c33d288fdc16f5257c1a5&format=2&city=%E5%8C%97%E4%BA%AC%E5%B8%82");  

    //execte(URL)使用GET方法時,填入的URL是帶有資訊的,例子中使用的是聚合資料提供的加油站資料
}

使用POST:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   new AsyncTask<String, Void, Void>() {
      @Override
      protected Void doInBackground(String... params) {  //相當於執行緒中的run方法,執行後臺操作
        try {
          URL u = new URL(params[0]);
          HttpURLConnection conn = (HttpURLConnection) u.openConnection(); 
          //使用POST,需將URLConnection轉換成HttpURLConnection

          //使用之前,需要先對conn進行配置     
          conn.setDoInput(true);
          conn.setDoOutput(true);  //設定成true,conn才能向伺服器輸出資料
          conn.setRequestMethod("POST");  //請求方式設定為POST

          OutputStreamWriter osw = new OutputStreamWriter(conn.getOutputStream()); //把資料傳到伺服器
          BufferedWriter bw = new BufferedWriter(osw);                             
          bw.write("key=3f73d0ea9d7c33d288fdc16f5257c1a5&format=2&city=%E5%8C%97%E4%BA%AC%E5%B8%82");  //要傳遞的資料
          bw.flush();


          InputStream is = conn.getInputStream();
          InputStreamReader isr = new InputStreamReader(is);
          BufferedReader br = new BufferedReader(isr);
          String line;
          while ((line = br.readLine()) != null) {
                System.out.println(line);
          }
          br.close();
          isr.close();
          is.close();     
        } catch (IOException e) {
              e.printStackTrace();
        }
              return null;
     }
   }.execute("http://apis.juhe.cn/oil/region");
 }

上述兩段程式碼獲取到的資料結果一樣,如下圖:
這裡寫圖片描述

從圖片可以看出,我們已經成功獲取到資料