您好,登錄后才能下訂單哦!
這篇“Http組裝報文怎么實現”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Http組裝報文怎么實現”文章吧。
這里主要涉及的知識點就是解析報文和組裝報文。 解析報文就是指解析HTTP請求報文,你需要知道報文請求的資源是什么。 組裝報文就是指組裝HTTP響應報文,你需要返回客戶請求的相應資源。
解析報文,需要獲取完整的報文,利用報文的特定結構,獲取報文里面的信息。然后依據這些信息,先客戶端返回響應報文。這里涉及到自己解析報文,比較有難度,因為需要報文的結構特定。上一篇博客已經簡單介紹過了(這里只是處理一些簡單的請求和響應報文,不是那種特別復雜的,畢竟只是學習,沒有必要自己和自己過不去!)
組裝報文,需要將客戶需要的信息組裝好,然后發送給客戶端。報文會由客戶端(通常是瀏覽器)自動解析,這里就不需要解析了,只是把報文發送給客戶端。對于編程來說,只是涉及到IO流的處理而已。所以,組裝報文比較簡單一些。
我們來回顧一下前面介紹的知識,通常一個完整的報文包括報文頭和報文體。(當然了,GET請求方式是沒有請求體的。)
主要的代碼就是下面三行了。
out.write(header); //寫入Http報文頭部部分 out.write(content); //寫入Http報文數據部分 out.flush(); //刷新輸出流,確保緩沖區內數據已經發送完成
是不是感覺很神奇,所謂的HTTP報文,在TCP這個層次來看,不過就是一個字節流。(這里 header 和 content 在網絡上是可以看成串行的流。)
這個類和平時使用的 ServerSocket 類用法沒有什么區別,就是使用多線程來處理每一個客戶端的連接。啟動一個ServerSocket,監聽10000端口。
package com.dragon; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class HttpServer { private static ServerSocket server; /** * 啟動服務 * */ public void start() { try { server = new ServerSocket(10000); System.out.println("服務啟動成功..."); this.receiveRequest(); } catch (IOException e) { e.printStackTrace(); System.out.println("服務啟動失敗!"); } } /** * 接收請求 * */ public void receiveRequest() { ExecutorService pool = Executors.newFixedThreadPool(10); while (true) { try { Socket client = server.accept(); System.out.println("用戶"+client.getInetAddress().toString()+"建立連接" + client.toString()); pool.submit(new Connection(client)); //使用線程處理每一個請求。 } catch (IOException e) { e.printStackTrace(); } } } /** * 停止服務 * 注:一般是不需要關閉服務的。 * */ public void stop() { try { if (server != null) { server.close(); } } catch (IOException e) { e.printStackTrace(); System.out.println("服務器關閉失敗!"); } } }
對于每一個客戶端的連接,獲取用戶的請求,并返回響應。因為只是一個簡單的模擬,這里其實獲取用戶的請求也不進行處理(因為處理需要解析請求報文),對于任何的請求返回的響應都是同一個。所以,它實際上還具有一個非常有趣的特點–消滅了404。 相信經常使用瀏覽器的人應該都知道404這個錯誤吧,404的意思是對于當前的請求沒有找到請求的資源。所以,通常可以看到 Not Found 這兩個英文單詞,當然了也可以自定義成其它的形式。因為這個程序,只具有接收請求,返回響應的基本功能,所以,我間接消滅了404,哈哈!
注:頭部字段中,我只是返回了幾個必要的頭部。因為HTTP頭部還是比較多的,有些也不是必要的。具體的信息,可以參考一些專業的書籍來了解更多的知識或者直接閱讀這方面的權威–RFC文檔,哈哈(不過我也沒有看,就是瞅了一眼。)。
package com.dragon; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.Charset; import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; public class Connection implements Runnable { private static final String BLANK = " "; private static final String CRLF = "\r\n"; private byte[] content; private byte[] header; private Socket client; public Connection(Socket client) { this.client = client; } @Override public void run() { //這里不對請求進行處理,只是收到請求之后會進行響應,而不管是什么請求 //這里模擬服務器最原始的功能:請求、響應。 this.response(); System.out.println("線程執行結束了!"); } /** * 接收請求信息,這里只是一個簡單的模擬,這里只能接收get請求。 * @throws IOException * */ public String getRequestInfo(InputStream in) throws IOException { //只讀取第一行,這是我們需要的全部內容 StringBuilder requestLine = new StringBuilder(80); while (true) { int c = in.read(); if (c == '\r' || c == '\n' || c == -1) break; requestLine.append((char)c); } return requestLine.toString(); } /** * 響應信息 * */ public void response() { InputStream in = null; OutputStream out = null; try { in = new BufferedInputStream(client.getInputStream()); out = new BufferedOutputStream(client.getOutputStream()); //獲取輸出流 String requestInfo = this.getRequestInfo(in); //如果不讀取客戶端發來的數據,服務器就會出錯。 System.out.println(requestInfo); } catch (IOException e1) { e1.printStackTrace(); } //獲取輸入流 //響應體數據 File file = new File("D:/DragonFile/target/attitude.jpg"); String contentType = null; //文件的 MIME 類型 try { content = Files.readAllBytes(file.toPath()); //使用 Files 工具類,一次性讀取文件 contentType = Files.probeContentType(file.toPath()); //獲取文件的 MIME 類型 long length = file.length(); //獲取文件字節長度 header = this.getHeader(contentType, length); // 填充響應頭 } catch (IOException e) { e.printStackTrace(); } try { out.write(header); //寫入Http報文頭部部分 out.write(content); //寫入Http報文數據部分 out.flush(); //刷新輸出流,確保緩沖區內數據已經發送完成 System.out.println("報文總大小(字節):" + (header.length + content.length)); } catch (IOException e) { e.printStackTrace(); System.out.println("客戶斷開連接或者發送失敗!"); } finally { //此處關閉 client 會導致程序出現問題,但是原因不清楚。 try { if (client != null) { client.close(); } System.out.println("請求結束了"); } catch (IOException e) { e.printStackTrace(); } } } //響應頭 private byte[] getHeader(String contentType, long length) { return new StringBuilder() .append("HTTP/1.1").append(BLANK).append(200).append(BLANK).append("OK").append(CRLF) // 響應頭部 .append("Server:"+"CrazyDragon").append(CRLF) .append("Date:").append(BLANK).append(this.getDate()).append(CRLF) .append("Content-Type:").append(BLANK).append(contentType).append(CRLF) //文件的 Content-Type 可通過Java獲取。 .append("Content-Length:").append(BLANK).append(length).append(CRLF).append(CRLF) .toString() .getBytes(Charset.forName("UTF-8")); } //獲取時間 private String getDate() { Date date = new Date(); SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); // 設置時區為GMT return format.format(date); } }
package com.dragon; public class Test { public static void main(String[] args) { HttpServer httpServer = new HttpServer(); httpServer.start(); } }
打印輸出:
瀏覽器訪問:
注:上面可以看到會有兩個請求,這是因為瀏覽器訪問的時候,會請求網站的圖標,通常的路徑為 /favicon.ico,這里似乎是因為緩存,沒有看到它的請求,如果存在請求行應該為:GET /favicon.ico HTTP/1.1。
隨便訪問一個地址,看一看沒有404的網站!
訪問結果
注意:這里可能會遇到異常。 但是這個對于程序的運行結果沒有影響。報錯的原因處在這句話:out.wirte(content);
可能的原因有一下幾點:
①:服務器的并發連接數超過了其承載量,服務器會將其中一些連接Down掉;
②:客戶關掉了瀏覽器,而服務器還在給客戶端發送數據;
③:瀏覽器端按了Stop 按鈕。
④:用servlet的outputstream輸出流下載圖片時,當用戶點擊取消也會報這個錯誤。
以上就是關于“Http組裝報文怎么實現”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。