HTTP報文詳解


HTTP報文詳解

一、    基本概念

GETPOSTHTTP中常用的請求方式,面試經常問到其區別?更深的會讓你手寫一個請求報文?---網易

HTTP是面向文本傳輸的,報文中每個字段都是一些ASCII碼流,各個字段的長度是不確定的;HTTP報文分為請求報文和響應報文。

 

 

二、       HTTP請求報文

HTTP請求報文分為4個部分:請求行、請求頭部、空行和請求數據.

 

2.1 請求行

請求行由請求方法、URL字段和HTTP版本號3個字段組成,使用空格隔開。比如常見的:GET /index.html HTTP/1.1。

注意HTTP請求方法:GET、POST、HEAD、PUT、DELETE、CONNECT、OPTIONS等。

 

2.1.1 GET請求實例講解

   GET是最常見的請求方式,當客戶端從服務器讀取文檔時,當點擊網頁上的鏈接時或者通過瀏覽器輸入網址瀏覽網頁時,一般使用的都是GET方式。GET方法要求服務端將URL定位的資源放在響應報文中的數據部分,回送給客戶端。

   使用GET方法時,請求參數和對應的值附加在URL后面,利用一個“?”代表URL結束和請求參數的開始,不同的參數用&隔開;不同的瀏覽器對字符限制有所不同,一般最多識別1024個字節。傳遞參數長度收到限制;

   下面以用google搜索domety為例說下GET請求:

 

可以看到:GET請求一般是沒有請求數據部分的,或者說請求數據直接附加在URL后面給服務器了,所以直接空行結束即可。

 

2.1.2 POST請求實例講解

GET方式請求由於URL長度的限制,一般不能傳送較多的數據。這時候考慮使用POST請求。POST方法將請求參數封裝在HTTP請求數據中,以鍵值對形式存在,可以傳輸大量數據。

 

 

2.1.3 HEAD請求

HEAD就像GET一樣,只是服務端收到HEAD請求后只返回響應頭,而不會發送響應內容;當我們只需要查看某個頁面的狀態時,HEAD是很高效的,因為在傳輸的過程中省去了頁面的內容。

 

2.2 請求頭部

請求頭部由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號“:”分隔。請求頭部通知服務器有關於客戶端請求的信息,典型的請求頭有:

User-Agent:產生請求的瀏覽器類型。

Accept:客戶端可識別的內容類型列表。

Host:請求的主機名,允許多個域名同處一個IP地址,即虛擬主機。

 

2.3 空行

最后一個請求頭之后是一個空行,發送回車符和換行符,通知服務器以下不再有請求頭。

2.4 請求內容

請求數據不在GET方法中使用,而是在POST方法中使用。POST方法適用於需要客戶填寫表單的場合。與請求數據相關的最常使用的請求頭是Content-TypeContent-Length

 

三、       HTTP響應報文

HTTP響應也由三個部分組成,分別是:狀態行、消息報頭、響應正文。在響應中唯一真正的區別在於第一行中用狀態信息代替了請求信息。狀態行(status line)通過提供一個狀態碼來說明所請求的資源情況。

狀態行格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF

其中,HTTP-Version表示服務器HTTP協議的版本;Status-Code表示服務器發回的響應狀態代碼;Reason-Phrase表示狀態代碼的文本描述。狀態代碼由三位數字組成,第一個數字定義了響應的類別,且有五種可能取值。

  • 1xx:指示信息--表示請求已接收,繼續處理。
  • 2xx:成功--表示請求已被成功接收、理解、接受。
  • 3xx:重定向--要完成請求必須進行更進一步的操作。
  • 4xx:客戶端錯誤--請求有語法錯誤或請求無法實現。
  • 5xx:服務器端錯誤--服務器未能實現合法的請求。

常見狀態代碼、狀態描述的說明如下。

  • 200 OK:客戶端請求成功。
  • 400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解。
  • 401 Unauthorized:請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用。
  • 403 Forbidden:服務器收到請求,但是拒絕提供服務。
  • 404 Not Found:請求資源不存在,舉個例子:輸入了錯誤的URL。
  • 500 Internal Server Error:服務器發生不可預期的錯誤。
  • 503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能恢復正常,舉個例子:HTTP/1.1 200 OK(CRLF)。

 

 

下面給出一個HTTP響應報文例子

 

HTTP/1.1 200 OK

Date: Sat, 31 Dec 2005 23:59:59 GMT

Content-Type: text/html;charset=ISO-8859-1

Content-Length: 122

 

<html>

<head>

<title>Wrox Homepage</title>

</head>

<body>

<!-- body goes here -->

</body>

</html>

 

四、       一個POST上傳文件的例子

參考:http://www.tuicool.com/articles/muYfquQ

在開發中,我們使用的比較多的HTTP請求方式基本上就是GET、POST。其中GET用於從服務器獲取數據,POST主要用於向服務器提交一些表單數據,例如文件上傳等。而我們在使用HTTP請求時中遇到的比較麻煩的事情就是構造文件上傳的HTTP報文格式,這個格式雖說也比較簡單,但也比較容易出錯。今天我們就一起來學習HTTP POST的報文格式以及通過Java來模擬文件上傳的請求。

首先我們來看一個POST的報文請求,然后我們再來詳細的分析它。

4.1 POST報文格式

POST /api/feed/ HTTP/1.1

Accept-Encoding: gzip

Content-Length: 225873

Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp

Host: www.myhost.com

Connection: Keep-Alive

 

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp

Content-Disposition: form-data; name="lng"

Content-Type: text/plain; charset=UTF-8

Content-Transfer-Encoding: 8bit

 

116.361545

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp

Content-Disposition: form-data; name="lat"

Content-Type: text/plain; charset=UTF-8

Content-Transfer-Encoding: 8bit

 

39.979006

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp

Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"

Content-Type: application/octet-stream

Content-Transfer-Encoding: binary

 

 

這里是圖片的二進制數據

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--

這里我們提交的是經度、緯度和一張圖片(圖片數據比較長,而且比較雜亂,這里省略掉了)。

4.2 格式分析

4.2.1請求頭分析

我們先看報文格式中的第一行:

POST /api/feed/ HTTP/1.1

這一行就說明了這個請求的請求方式,即為POST方式,要請求的子路徑為/api/feed/,例如我們的服務器地址為www.myhost.com,然后我們的這個請求的完整路徑就是 www.myhost.com/api/feed/,最后說明了HTTP協議的版本號為1.1。

Accept-Encoding: gzip
Content-Length: 225873
Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Host: www.myhost.com
Connection: Keep-Alive

 

這幾個header的意思分別為服務器返回的數據需要使用gzip壓縮、請求的內容長度為225873、內容的類型為"multipart/form-data"、請求參數分隔符(boundary)為OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp、請求的根域名為www.myhost.com、HTTP連接方式為持久連接( Keep-Alive)。

其中這里需要注意的一點是分隔符,即boundary。 boundary用於作為請求參數之間的界限標識,例如參數1和參數2之間需要有一個明確的界限,這樣服務器才能正確的解析到參數1和參數2。但是分隔符並不僅僅是boundary,而是下面這樣的格式:-- + boundary。例如這里的boundary為 OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,那么參數分隔符則為:

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp

不管boundary本身有沒有這個"--",這個"--"都是不能省略的。

我們知道HTTP協議采用“請求-應答”模式,當使用普通模式,即非KeepAlive模式時,每個請求/應答客戶和服務器都要新建一個連接,完成之后立即斷開連接(HTTP協議為無連接的協議);當使用Keep-Alive模式(又稱持久連接、連接重用)時,Keep-Alive功能使客戶端到服務器端的連接持續有效,當出現對服務器的后續請求時,Keep-Alive功能避免了建立或者重新建立連接。

 

如上圖中,左邊的是關閉Keep-Alive的情況,每次請求都需要建立連接,然后關閉連接;右邊的則是Keep-Alive,在第一次建立請求之后保持連接,然后后續的就不需要每次都建立、關閉連接了, 啟用Keep-Alive模式肯定更高效,性能更高,因為避免了建立/釋放連接的開銷 。

http 1.0中默認是關閉的,需要在http頭加入"Connection: Keep-Alive",才能啟用Keep-Alive;http 1.1中默認啟用Keep-Alive,如果加入"Connection: close ",才關閉。目前大部分瀏覽器都是用http1.1協議,也就是說默認都會發起Keep-Alive的連接請求了,所以是否能完成一個完整的Keep- Alive連接就看服務器設置情況。

4.2.2請求實體分析

請求實體其實就是HTTP POST請求的參數列表,每個參數以請求分隔符開始,即-- + boundary。例如下面這個參數。

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="lng"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
 
116.361545

上面第一行為--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,也就是--加上boundary內容, 最后加上一個換行 (這個換行不能省略),換行的字符串表示為"\r\n" 。第二行為Content-Disposition和參數名,這里的參數名為lng,即經度。 Content-Disposition就是當用戶想把請求所得的內容存為一個文件的時候提供一個默認的文件名,這里我們不過多關注。第三行為 Content-Type,即 WEB 服務器告訴瀏覽器自己響應的對象的類型 ,還有指定字符編碼為UTF-8。 第四行是 描述的是消息請求(request)和響應(response)所附帶的實體對象(entity)的傳輸形式, 簡單文本數據我們設置為8bit,文件參數我們設置為binary就行 。然后添加兩個換行之后才是參數的具體內容。例如這里的參數內容為116.361545。

注意這里的每行之間都是使用“\r\n”來換行的,最后一行和參數內容之間是兩個換行。文件參數也是一樣的格式,只是文件參數的內容是字節流。

這里要注意一下,普通文本參數和文件參數有如下兩個地方的不同,因為其內容本身的格式是不一樣的。

普通參數:

Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

文件參數:

Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

參數實體的最后一行是: --加上boundary加上--,最后換行,這里的 格式即為: --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--。

4.3 模擬文件上傳請求

public static void uploadFile(String fileName) {
        try {
  // 換行符
  final String newLine = "\r\n";
  final String boundaryPrefix = "--";
  // 定義數據分隔線
  String BOUNDARY = "========7d4a6d158c9";
  // 服務器的域名
  URL url = new URL("www.myhost.com");
  HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  // 設置為POST情
  conn.setRequestMethod("POST");
  // 發送POST請求必須設置如下兩行
  conn.setDoOutput(true);
  conn.setDoInput(true);
  conn.setUseCaches(false);
  // 設置請求頭參數
  conn.setRequestProperty("connection", "Keep-Alive");
  conn.setRequestProperty("Charsert", "UTF-8");
  conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
  OutputStream out = new DataOutputStream(conn.getOutputStream());
  // 上傳文件
  File file = new File(fileName);
  StringBuilder sb = new StringBuilder();
  sb.append(boundaryPrefix);
  sb.append(BOUNDARY);
  sb.append(newLine);
  // 文件參數,photo參數名可以隨意修改
  sb.append("Content-Disposition: form-data;name=\"photo\";filename=\"" + fileName
          + "\"" + newLine);
  sb.append("Content-Type:application/octet-stream");
  // 參數頭設置完以后需要兩個換行,然后才是參數內容
  sb.append(newLine);
  sb.append(newLine);
  // 將參數頭的數據寫入到輸出流中
  out.write(sb.toString().getBytes());
  // 數據輸入流,用於讀取文件數據
  DataInputStream in = new DataInputStream(new FileInputStream(
          file));
  byte[] bufferOut = new byte[1024];
  int bytes = 0;
  // 每次讀1KB數據,並且將文件數據寫入到輸出流中
  while ((bytes = in.read(bufferOut)) != -1) {
      out.write(bufferOut, 0, bytes);
  }
  // 最后添加換行
  out.write(newLine.getBytes());
  in.close();
  // 定義最后數據分隔線,即--加上BOUNDARY再加上--。
  byte[] end_data = (newLine + boundaryPrefix + BOUNDARY + boundaryPrefix + newLine)
          .getBytes();
  // 寫上結尾標識
  out.write(end_data);
  out.flush();
  out.close();
  // 定義BufferedReader輸入流來讀取URL的響應
//       BufferedReader reader = new BufferedReader(new InputStreamReader(
//               conn.getInputStream()));
//       String line = null;
//       while ((line = reader.readLine()) != null) {
//           System.out.println(line);
//       }
        } catch (Exception e) {
  System.out.println("發送POST請求出現異常!" + e);
  e.printStackTrace();
        }
    }

 

4.4 使用Apache Httpmime上傳文件

/**
     * @param fileName 圖片路徑
     */
    public static void uploadFileWithHttpMime(String fileName) {
        // 定義請求url
        String uri = "www.myhost.com";
        // 實例化http客戶端
        HttpClient httpClient = new DefaultHttpClient();
        // 實例化post提交方式
        HttpPost post = new HttpPost(uri);
        // 添加json參數
        try {
  // 實例化參數對象
  MultipartEntity params = new MultipartEntity();
  // 圖片文本參數
  params.addPart("textParams", new StringBody(
          "{'user_name':'我的用戶名','channel_name':'卻道明','channel_address':'(123.4,30.6)'}",
          Charset.forName("UTF-8")));
  // 設置上傳文件
  File file = new File(fileName);
  // 文件參數內容
  FileBody fileBody = new FileBody(file);
  // 添加文件參數
  params.addPart("photo", fileBody);
  params.addPart("photoName", new StringBody(file.getName()));
  // 將參數加入post請求體中
  post.setEntity(params);
  // 執行post請求並得到返回對象 [ 到這一步我們的請求就開始了 ]
  HttpResponse resp = httpClient.execute(post);
  // 解析返回請求結果
  HttpEntity entity = resp.getEntity();
  InputStream is = entity.getContent();
  BufferedReader reader = new BufferedReader(new InputStreamReader(is));
  StringBuffer buffer = new StringBuffer();
  String temp;
  while ((temp = reader.readLine()) != null) {
      buffer.append(temp);
  }
  System.out.println(buffer);
        } catch (UnsupportedEncodingException e) {
  e.printStackTrace();
        } catch (ClientProtocolException e) {
  e.printStackTrace();
        } catch (IOException e) {
  e.printStackTrace();
        } catch (IllegalStateException e) {
  e.printStackTrace();
        }
    }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM