Java網(wǎng)絡(luò)編程從入門(mén)到精通(22):實(shí)現(xiàn)HTTP模擬器
本文為原創(chuàng),如需轉(zhuǎn)載,請(qǐng)注明作者和出處,謝謝!
上一篇:Java網(wǎng)絡(luò)編程從入門(mén)到精通(21):HTTP消息的格式
源代碼和.class文件下載
在討論HTTP協(xié)議的具體請(qǐng)求和響應(yīng)頭字段之前,讓我們先來(lái)利用以前所學(xué)的知識(shí)來(lái)實(shí)現(xiàn)一個(gè)HTTP模擬器。所謂HTTP模擬器就是可以在用戶輸入HTTP的請(qǐng)求消息后,由這個(gè)模擬器將HTTP請(qǐng)求發(fā)送給相應(yīng)的服務(wù)器,再接收服務(wù)器的響應(yīng)消息。這個(gè)HTTP模擬器有幾下特點(diǎn):
1. 可以手工輸入HTTP請(qǐng)求,并向服務(wù)器發(fā)送。
2. 接收服務(wù)器的響應(yīng)消息。
3. 消息頭和實(shí)體內(nèi)容分段顯示,也就是說(shuō),并不是象Telnet等客戶端一樣將HTTP響
應(yīng)消息全部顯示,而是先顯示消息頭,然后由用戶決定是否顯示實(shí)體內(nèi)容。
4. 集中發(fā)送請(qǐng)求。這個(gè)HTTP模擬器和Telnet不同的是,并不是一開(kāi)始就連接服務(wù)器,
而是將域名、端口以及HTTP請(qǐng)求消息都輸完后,才連接服務(wù)器,并將這些請(qǐng)求發(fā)送給服務(wù)器。這樣做的可以預(yù)防服務(wù)器提前關(guān)閉網(wǎng)絡(luò)連接的現(xiàn)象。
5. 可以循環(huán)做上述的操作。
從以上的描述看,要實(shí)現(xiàn)這個(gè)HTTP模擬器需要以下五步:
1. 建立一個(gè)大循環(huán),在循環(huán)內(nèi)部是一個(gè)請(qǐng)求/響應(yīng)對(duì)。這樣就可以向服務(wù)器發(fā)送多次請(qǐng)求/響應(yīng)以了。下面的四步都是被包括在循環(huán)內(nèi)部的。
2. 從控制臺(tái)讀取域名和端口,這個(gè)功能可以由readHostAndPort(...)來(lái)完成。
3. 從控制臺(tái)讀取HTTP請(qǐng)求消息,這個(gè)功能由readHttpRequest(...)來(lái)完成。
4. 向服務(wù)器發(fā)送HTTP請(qǐng)求消息,這個(gè)功能由sendHttpRequest()來(lái)完成。
5. 讀取服務(wù)器回送的HTTP響應(yīng)消息,這個(gè)功能由readHttpResponse(...)來(lái)完成。
下面我們就來(lái)逐步實(shí)現(xiàn)這五步:
一、建立一個(gè)大循環(huán)在建立這個(gè)循環(huán)之前,先建立一個(gè)中叫HttpSimulator的類(lèi),并在這個(gè)類(lèi)中定義一個(gè)run方法用來(lái)運(yùn)行這個(gè)程序。實(shí)現(xiàn)代碼如下:
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請(qǐng)求消息
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分別調(diào)用了上述的四個(gè)方法。這些方法的具體實(shí)現(xiàn)將在后面討論。上面的代碼除了調(diào)用這四個(gè)核心方法外,還做了一些準(zhǔn)備工作。在008至012行定義了一些以后要用到的變量。在016和017行使用控制臺(tái)的輸入流建立了BufferedReader對(duì)象,通過(guò)這個(gè)對(duì)象,可以直接從控制臺(tái)讀取字符串,而不是一個(gè)個(gè)地字節(jié)。
二、readHostAndPort(...)方法的實(shí)現(xiàn)
這個(gè)方法的主要功能是從控制臺(tái)讀取域名和端口。域名和端口通過(guò)":"隔開(kāi),":"和域名以及端口之間不能有空格。當(dāng)從控制臺(tái)讀取一個(gè)"q"時(shí),這個(gè)函數(shù)返回false,表示程序可以退出了,否則返回true,表示輸入的域名和端口是正確的。這個(gè)方法的實(shí)現(xiàn)代碼如下:
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)型的參數(shù),這個(gè)參數(shù)的值就是在HttpSimulator.java中的第016和017行根據(jù)控制臺(tái)輸入流建立的BufferedReader對(duì)象。
第 004 行:這輸出HTTP模擬器的控制符,就象Windows的控制臺(tái)的"C:">"一樣。
第 006 行:從控制臺(tái)讀取一行字符串。
第 011 行:通過(guò)字符串的split方法和響應(yīng)的正則表示式("[:]")將域名和端口分開(kāi)。域名的默認(rèn)值是localhost,端口的默認(rèn)值是80。
三、readHttpRequest(...)方法的實(shí)現(xiàn)
這個(gè)方法的主要功能是從控制臺(tái)讀取HTTP請(qǐng)求消息,如果輸入一個(gè)空行,表示請(qǐng)求消息頭已經(jīng)輸完;如果使用的是POST方法,還要輸入POST請(qǐng)求的實(shí)體內(nèi)容。這個(gè)方法的實(shí)現(xiàn)代碼如下:
002 throws Exception
003 {
004 System.out.println("請(qǐng)輸入HTTP請(qǐng)求:");
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("請(qǐng)輸入POST方法的內(nèi)容:");
015 s = consoleReader.readLine();
016 request = request + s;
017 }
018 }
第 005 行:讀入HTTP請(qǐng)求消息的第一行。
第 007、008行:確定所輸入的請(qǐng)求方法是不是POST和HEAD。
第 009、010行:讀入HTTP請(qǐng)求消息的其余行。
第012 ? 017行:如果HTTP請(qǐng)求使用的是POST方法,要求用戶繼續(xù)輸入HTTP請(qǐng)求的實(shí)體內(nèi)容。
四、sendHttpRequest()方法的實(shí)現(xiàn)
這個(gè)方法的功能是將request變量中的HTTP請(qǐng)求消息發(fā)送到服務(wù)器。下面是這個(gè)方法的實(shí)現(xiàn)代碼:
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è)置讀取數(shù)據(jù)超時(shí)為10秒。
第006行:連接服務(wù)器,并設(shè)置連接超時(shí)為10秒。
五、readHttpResponse(...)方法的實(shí)現(xiàn)
這個(gè)方法的主要功能是從服務(wù)器讀取返回的響應(yīng)消息。首先讀取了響應(yīng)消息頭,然后要求用戶輸入Y或N以確定是否顯示響應(yīng)消息的實(shí)體內(nèi)容。這個(gè)程序之所以這樣做,主要有兩個(gè)原因:
(1) 為了研究HTTP協(xié)議。
(2) 由于本程序是以字符串形式顯示響應(yīng)消息的,因此,如果用戶請(qǐng)求了一個(gè)二進(jìn)制Web資源,如一個(gè)rar文件,那么實(shí)體內(nèi)容將會(huì)顯示亂碼。所以在顯示完響應(yīng)消息頭后由用戶決定是否顯示實(shí)體內(nèi)容。
這個(gè)方法的實(shí)現(xiàn)代碼如下:
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的內(nèi)容(Y/N):");
018 String choice = consoleReader.readLine();
019 if (choice.equals("Y") || choice.equals("y"))
020 {
021 System.out.println("---------HTTP內(nèi)容---------");
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)結(jié)束);由于在實(shí)體內(nèi)容中也可以存在空行,因此,b == true來(lái)標(biāo)記消息頭是否已經(jīng)被讀過(guò),當(dāng)讀完消息頭后,將b設(shè)為false,如果以后再遇到空行,就不會(huì)當(dāng)成消息頭來(lái)處理了。當(dāng)HTTP請(qǐng)求使用HEAD方法時(shí),服務(wù)器只返回響應(yīng)消息頭;因此,使用!isHead來(lái)保證使用HEAD發(fā)送請(qǐng)求時(shí)不顯示響應(yīng)消息的內(nèi)容實(shí)體。
現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了這個(gè)HTTP模擬器,下面讓我們來(lái)運(yùn)行并測(cè)試它。
運(yùn)行
運(yùn)行如下的命令
運(yùn)行以上的命令后,將顯示如圖1所示的界面。
測(cè)試
在HTTP模擬器中輸入如下的域名:
在HTTP模擬器中輸入如下的HTTP請(qǐng)求消息:
Host: www.csdn.net
運(yùn)行的結(jié)果如圖2所示。
本文實(shí)現(xiàn)的Http模擬器在后面的文章中會(huì)經(jīng)常使用,讀者可以從本文的開(kāi)始部分下載Http模擬器的源代碼和.class文件。
下一篇:Java網(wǎng)絡(luò)編程從入門(mén)到精通(23):HTTP消息頭字段
《Android開(kāi)發(fā)完全講義(第2版)》(本書(shū)版權(quán)已輸出到臺(tái)灣)
http://product.dangdang.com/product.aspx?product_id=22741502
《Android高薪之路:Android程序員面試寶典 》http://book.360buy.com/10970314.html
新浪微博:http://t.sina.com.cn/androidguy 昵稱(chēng):李寧_Lining
posted on 2009-06-09 12:16 銀河使者 閱讀(4679) 評(píng)論(11) 編輯 收藏 所屬分類(lèi): java 、 原創(chuàng) 、網(wǎng)絡(luò)編程