在討論HTTP協(xié)議的具體請求和響應頭字段之前,讓我們先來(lái)利用以前所學(xué)的知識來(lái)實(shí)現一個(gè)HTTP模擬器。所謂HTTP模擬器就是可以在用戶(hù)輸入HTTP的請求消息后,由這個(gè)模擬器將HTTP請求發(fā)送給相應的服務(wù)器,再接收服務(wù)器的響應消息。這個(gè)HTTP模擬器有幾下特點(diǎn): 1. 可以手工輸入HTTP請求,并向服務(wù)器發(fā)送。 2. 接收服務(wù)器的響應消息。 3. 消息頭和實(shí)體內容分段顯示,也就是說(shuō),并不是象Telnet等客戶(hù)端一樣將HTTP響 應消息全部顯示,而是先顯示消息頭,然后由用戶(hù)決定是否顯示實(shí)體內容。 4. 集中發(fā)送請求。這個(gè)HTTP模擬器和Telnet不同的是,并不是一開(kāi)始就連接服務(wù)器, 而是將域名、端口以及HTTP請求消息都輸完后,才連接服務(wù)器,并將這些請求發(fā)送給服務(wù)器。這樣做的可以預防服務(wù)器提前關(guān)閉網(wǎng)絡(luò )連接的現象。 5. 可以循環(huán)做上述的操作。 從以上的描述看,要實(shí)現這個(gè)HTTP模擬器需要以下五步: 1. 建立一個(gè)大循環(huán),在循環(huán)內部是一個(gè)請求/響應對。這樣就可以向服務(wù)器發(fā)送多次請求/響應以了。下面的四步都是被包括在循環(huán)內部的。 2. 從控制臺讀取域名和端口,這個(gè)功能可以由readHostAndPort(……)來(lái)完成。 3. 從控制臺讀取HTTP請求消息,這個(gè)功能由readHttpRequest(……)來(lái)完成。 4. 向服務(wù)器發(fā)送HTTP請求消息,這個(gè)功能由sendHttpRequest()來(lái)完成。 5. 讀取服務(wù)器回送的HTTP響應消息,這個(gè)功能由readHttpResponse(……)來(lái)完成。 下面我們就來(lái)逐步實(shí)現這五步: 一、建立一個(gè)大循環(huán) 在建立這個(gè)循環(huán)之前,先建立一個(gè)中叫HttpSimulator的類(lèi),并在這個(gè)類(lèi)中定義一個(gè)run方法用來(lái)運行這個(gè)程序。實(shí)現代碼如下: 001 package http; 002 003 import java.net.*; 004 import java.io.*; 005 006 public class HttpSimulator 007 { 008 private Socket socket; 009 private int port = 80; 010 private String host = "localhost"; 011 private String request = ""; // HTTP請求消息 012 private boolean isPost, isHead; 013 014 public void run() throws Exception 015 { 016 BufferedReader reader = new BufferedReader(new InputStreamReader( 017 System.in)); 018 while (true) // 開(kāi)始大循環(huán) 019 { 020 try 021 { 022 if (!readHostAndPort(reader)) 023 break; 024 readHttpRequest(reader); 025 sendHttpRequest(); 026 readHttpResponse(reader); 027 } 028 catch (Exception e) 029 { 030 System.out.println("err:" + e.getMessage()); 031 } 032 } 033 } 034 public static void main(String[] args) throws Exception 035 { 036 new HttpSimulator().run(); 037 } 038 } 從上面的代碼可以看出,第022、024、025和026分別調用了上述的四個(gè)方法。這些方法的具體實(shí)現將在后面討論。上面的代碼除了調用這四個(gè)核心方法外,還做了一些準備工作。在008至012行定義了一些以后要用到的變量。在016和017行使用控制臺的輸入流建立了BufferedReader對象,通過(guò)這個(gè)對象,可以直接從控制臺讀取字符串,而不是一個(gè)個(gè)地字節。 二、readHostAndPort(……)方法的實(shí)現 這個(gè)方法的主要功能是從控制臺讀取域名和端口。域名和端口通過(guò)":"隔開(kāi),":"和域名以及端口之間不能有空格。當從控制臺讀取一個(gè)"q"時(shí),這個(gè)函數返回false,表示程序可以退出了,否則返回true,表示輸入的域名和端口是正確的。這個(gè)方法的實(shí)現代碼如下: 001 private boolean readHostAndPort(BufferedReader consoleReader) 002 throws Exception 003 { 004 System.out.print("host:port>"); 005 String[] ss = null; 006 String s = consoleReader.readLine(); 007 if (s.equals("q")) 008 return false; 009 else 010 { 011 ss = s.split("[:]"); 012 if (!ss[0].equals("")) 013 host = ss[0]; 014 if (ss.length > 1) 015 port = Integer.parseInt(ss[1]); 016 System.out.println(host + ":" + String.valueOf(port)); 017 return true; 018 } 019 } 第001行:這個(gè)方法有一個(gè)BufferedReader類(lèi)型的參數,這個(gè)參數的值就是在HttpSimulator.java中的第016和017行根據控制臺輸入流建立的BufferedReader對象。 第 004 行:這輸出HTTP模擬器的控制符,就象Windows的控制臺的"C:">"一樣。 第 006 行:從控制臺讀取一行字符串。 第 011 行:通過(guò)字符串的split方法和響應的正則表示式("[:]")將域名和端口分開(kāi)。域名的默認值是localhost,端口的默認值是80. 三、readHttpRequest(……)方法的實(shí)現 這個(gè)方法的主要功能是從控制臺讀取HTTP請求消息,如果輸入一個(gè)空行,表示請求消息頭已經(jīng)輸完;如果使用的是POST方法,還要輸入POST請求的實(shí)體內容。這個(gè)方法的實(shí)現代碼如下: 001 private void readHttpRequest(BufferedReader consoleReader) 002 throws Exception 003 { 004 System.out.println("請輸入HTTP請求:"); 005 String s = consoleReader.readLine(); 006 request = s + "\r\n"; 007 boolean isPost = s.substring(0, 4).equals("POST"); 008 boolean isHead = s.substring(0, 4).equals("HEAD"); 009 while (!(s = consoleReader.readLine()).equals("")) 010 request = request + s + "\r\n"; 011 request = request + "\r\n"; 012 if (isPost) 013 { 014 System.out.println("請輸入POST方法的內容:"); 015 s = consoleReader.readLine(); 016 request = request + s; 017 } 018 } 第 005 行:讀入HTTP請求消息的第一行。 第 007、008行:確定所輸入的請求方法是不是POST和HEAD. 第 009、010行:讀入HTTP請求消息的其余行。 第012 -017行:如果HTTP請求使用的是POST方法,要求用戶(hù)繼續輸入HTTP請求的實(shí)體內容。 四、sendHttpRequest()方法的實(shí)現 這個(gè)方法的功能是將request變量中的HTTP請求消息發(fā)送到服務(wù)器。下面是這個(gè)方法的實(shí)現代碼: 001 private void sendHttpRequest() throws Exception 002 { 003 socket = new Socket(); 004 socket.setSoTimeout(10 * 1000); 005 System.out.println("正在連接服務(wù)器"); 006 socket.connect(new InetSocketAddress(host, port), 10 * 1000); 007 System.out.println("服務(wù)器連接成功!"); 008 OutputStream out = socket.getOutputStream(); 009 OutputStreamWriter writer = new OutputStreamWriter(out); 010 writer.write(request); 011 writer.flush(); 012 } 第004行:設置讀取數據超時(shí)為10秒。 第006行:連接服務(wù)器,并設置連接超時(shí)為10秒。 五、readHttpResponse(……)方法的實(shí)現 這個(gè)方法的主要功能是從服務(wù)器讀取返回的響應消息。首先讀取了響應消息頭,然后要求用戶(hù)輸入Y或N以確定是否顯示響應消息的實(shí)體內容。這個(gè)程序之所以這樣做,主要有兩個(gè)原因: (1) 為了研究HTTP協(xié)議。 (2) 由于本程序是以字符串形式顯示響應消息的,因此,如果用戶(hù)請求了一個(gè)二進(jìn)制Web資源,如一個(gè)rar文件,那么實(shí)體內容將會(huì )顯示亂碼。所以在顯示完響應消息頭后由用戶(hù)決定是否顯示實(shí)體內容。 這個(gè)方法的實(shí)現代碼如下: 001 private void readHttpResponse(BufferedReader consoleReader) 002 { 003 String s = ""; 004 try 005 { 006 InputStream in = socket.getInputStream(); 007 InputStreamReader inReader = new InputStreamReader(in); 008 BufferedReader socketReader = new BufferedReader(inReader); 009 System.out.println("---------HTTP頭---------"); 010 boolean b = true; // true: 未讀取消息頭 false: 已經(jīng)讀取消息頭 011 while ((s = socketReader.readLine()) != null) 012 { 013 if (s.equals("") && b == true && !isHead) 014 { 015 System.out.println("------------------------"); 016 b = false; 017 System.out.print("是否顯示HTTP的內容(Y/N):"); 018 String choice = consoleReader.readLine(); 019 if (choice.equals("Y") || choice.equals("y")) 020 { 021 System.out.println("---------HTTP內容---------"); 022 continue; 023 } 024 else 025 break; 026 } 027 else 028 System.out.println(s); 029 } 030 } 031 catch (Exception e) 032 { 033 System.out.println("err:" + e.getMessage()); 034 } 035 finally 036 { 037 try 038 { 039 socket.close(); 040 } 041 catch (Exception e) 042 { 043 } 044 } 045 System.out.println("------------------------"); 046 } 在上面的代碼中013行是最值得注意的。其中s.equals("")表示讀入一個(gè)空行(表明消息頭已經(jīng)結束);由于在實(shí)體內容中也可以存在空行,因此,b == true來(lái)標記消息頭是否已經(jīng)被讀過(guò),當讀完消息頭后,將b設為false,如果以后再遇到空行,就不會(huì )當成消息頭來(lái)處理了。當HTTP請求使用HEAD方法時(shí),服務(wù)器只返回響應消息頭;因此,使用!isHead來(lái)保證使用HEAD發(fā)送請求時(shí)不顯示響應消息的內容實(shí)體。 現在我們已經(jīng)實(shí)現了這個(gè)HTTP模擬器,下面讓我們來(lái)運行并測試它。 運行 運行如下的命令 java http.HttpSimulator 運行以上的命令后,將顯示如圖1所示的界面。 ![]() 圖1 測試 在HTTP模擬器中輸入如下的域名: www.csdn.net 在HTTP模擬器中輸入如下的HTTP請求消息: GET / HTTP/1.1 Host: www.csdn.net 運行的結果如圖2所示。 ![]() 本文實(shí)現的Http模擬器在后面的文章中會(huì )經(jīng)常使用,讀者可以從本文的開(kāi)始部分下載Http模擬器的源代碼和。class文件。 |