隨筆 - 312, 文章 - 14, 評論 - 1393, 引用 - 0
          數據加載中……

          Java網絡編程從入門到精通(22):實現HTTP模擬器

          本文為原創,如需轉載,請注明作者和出處,謝謝!

          上一篇:Java網絡編程從入門到精通(21):HTTP消息的格式

          源代碼和.class文件下載

              在討論HTTP協議的具體請求和響應頭字段之前,讓我們先來利用以前所學的知識來實現一個HTTP模擬器。所謂HTTP模擬器就是可以在用戶輸入HTTP的請求消息后,由這個模擬器將HTTP請求發送給相應的服務器,再接收服務器的響應消息。這個HTTP模擬器有幾下特點:

          1.  可以手工輸入HTTP請求,并向服務器發送。

          2.  接收服務器的響應消息。

          3.  消息頭和實體內容分段顯示,也就是說,并不是象Telnet等客戶端一樣將HTTP響

          應消息全部顯示,而是先顯示消息頭,然后由用戶決定是否顯示實體內容。

          4.  集中發送請求。這個HTTP模擬器和Telnet不同的是,并不是一開始就連接服務器,

          而是將域名、端口以及HTTP請求消息都輸完后,才連接服務器,并將這些請求發送給服務器。這樣做的可以預防服務器提前關閉網絡連接的現象。

              5. 可以循環做上述的操作。

          從以上的描述看,要實現這個HTTP模擬器需要以下五步:

          1.  建立一個大循環,在循環內部是一個請求/響應對。這樣就可以向服務器發送多次請求/響應以了。下面的四步都是被包括在循環內部的。

          2.  從控制臺讀取域名和端口,這個功能可以由readHostAndPort(...)來完成。

          3.  從控制臺讀取HTTP請求消息,這個功能由readHttpRequest(...)來完成。

          4.  向服務器發送HTTP請求消息,這個功能由sendHttpRequest()來完成。

          5.  讀取服務器回送的HTTP響應消息,這個功能由readHttpResponse(...)來完成。

          下面我們就來逐步實現這五步:

          一、建立一個大循環

          在建立這個循環之前,先建立一個中叫HttpSimulator的類,并在這個類中定義一個run方法用來運行這個程序。實現代碼如下:

            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)  // 開始大循環
            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分別調用了上述的四個方法。這些方法的具體實現將在后面討論。上面的代碼除了調用這四個核心方法外,還做了一些準備工作。在008至012行定義了一些以后要用到的變量。在016和017行使用控制臺的輸入流建立了BufferedReader對象,通過這個對象,可以直接從控制臺讀取字符串,而不是一個個地字節。

          二、readHostAndPort(...)
          方法的實現

              這個方法的主要功能是從控制臺讀取域名和端口。域名和端口通過":"隔開,":"和域名以及端口之間不能有空格。當從控制臺讀取一個"q"時,這個函數返回false,表示程序可以退出了,否則返回true,表示輸入的域名和端口是正確的。這個方法的實現代碼如下:

            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行:這個方法有一個BufferedReader類型的參數,這個參數的值就是在HttpSimulator.java中的第016和017行根據控制臺輸入流建立的BufferedReader對象。

          第 004 行:這輸出HTTP模擬器的控制符,就象Windows的控制臺的"C:">"一樣。

          第 006 行:從控制臺讀取一行字符串。

          第 011 行:通過字符串的split方法和響應的正則表示式("[:]")將域名和端口分開。域名的默認值是localhost,端口的默認值是80。

          三、readHttpRequest(...)
          方法的實現

              這個方法的主要功能是從控制臺讀取HTTP請求消息,如果輸入一個空行,表示請求消息頭已經輸完;如果使用的是POST方法,還要輸入POST請求的實體內容。這個方法的實現代碼如下:

            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(04).equals("POST");
            
          008      boolean isHead = s.substring(04).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方法,要求用戶繼續輸入HTTP請求的實體內容。

          四、sendHttpRequest()
          方法的實現

              這個方法的功能是將request變量中的HTTP請求消息發送到服務器。下面是這個方法的實現代碼:

            001      private void sendHttpRequest() throws Exception
            
          002      {
            
          003          socket = new Socket();
            
          004          socket.setSoTimeout(10 * 1000);
            
          005          System.out.println("正在連接服務器");
            
          006          socket.connect(new InetSocketAddress(host, port), 10 * 1000);
            
          007          System.out.println("服務器連接成功!");
            
          008          OutputStream out = socket.getOutputStream();
            
          009          OutputStreamWriter writer = new OutputStreamWriter(out);
            
          010          writer.write(request);
            
          011          writer.flush();
            
          012      }

          第004行:設置讀取數據超時為10秒。

          第006行:連接服務器,并設置連接超時為10秒。

          五、readHttpResponse(...)
          方法的實現

          這個方法的主要功能是從服務器讀取返回的響應消息。首先讀取了響應消息頭,然后要求用戶輸入Y或N以確定是否顯示響應消息的實體內容。這個程序之所以這樣做,主要有兩個原因:

          (1) 為了研究HTTP協議。

          (2) 由于本程序是以字符串形式顯示響應消息的,因此,如果用戶請求了一個二進制Web資源,如一個rar文件,那么實體內容將會顯示亂碼。所以在顯示完響應消息頭后由用戶決定是否顯示實體內容。

          這個方法的實現代碼如下:

            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: 已經讀取消息頭
            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("")表示讀入一個空行(表明消息頭已經結束);由于在實體內容中也可以存在空行,因此,b == true來標記消息頭是否已經被讀過,當讀完消息頭后,將b設為false,如果以后再遇到空行,就不會當成消息頭來處理了。當HTTP請求使用HEAD方法時,服務器只返回響應消息頭;因此,使用!isHead來保證使用HEAD發送請求時不顯示響應消息的內容實體。

          現在我們已經實現了這個HTTP模擬器,下面讓我們來運行并測試它。

           運行

          運行如下的命令

          java http.HttpSimulator   

              運行以上的命令后,將顯示如圖1所示的界面。

          圖1

          測試

          在HTTP模擬器中輸入如下的域名:

          www.csdn.net

          在HTTP模擬器中輸入如下的HTTP請求消息:

          GET / HTTP/1.1
          Host: www.csdn.net

          運行的結果如圖2所示。


          圖2

          本文實現的Http模擬器在后面的文章中會經常使用,讀者可以從本文的開始部分下載Http模擬器的源代碼和.class文件。

          下一篇:Java網絡編程從入門到精通(23):HTTP消息頭字段



          Android開發完全講義(第2版)(本書版權已輸出到臺灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱:李寧_Lining

          posted on 2009-06-09 12:16 銀河使者 閱讀(4675) 評論(11)  編輯  收藏 所屬分類: java 原創網絡編程

          評論

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          http是建立在scoket通信之上的?
          2009-06-09 12:58 | 銀河猛男

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          http是建立在TCP基礎上的,socket只是通信框架,并不是協議。
          2009-06-09 13:04 | 銀河使者

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          比如ie的一個表單提交,產生get/post請求傳給服務器。
          這中間數據的傳輸的過程有誰來完成,是通過socket么,
          ie調用winsocket,服務端tomcat通過java socket接受?
          2009-06-09 15:28 | 銀河猛男

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          是的,ie調用了windows的socket api,java的socket在windows下也是調用了windows的api。
          2009-06-09 15:34 | 銀河使者

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          比如request.getparmater()在servlet容器會被翻譯成socket通信?
          2009-06-09 16:05 | 銀河猛男

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          到request.getParameter已經和socket沒關系了,servlet容器會分析http請求消息,并將分析的結果保存在HttpServletRequest對象中。而getParameter方法就是從HttpServletRequest對象中獲得相應的值。
          2009-06-09 16:14 | 銀河使者

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          servlet容器的監聽器通過jvm調用win32 api監聽http請求,通信是用的是socket
          2009-06-09 16:48 | 銀河猛男

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          還自己寫啊, 不能用WEB容器嗎?
          我的博客搬到新家了 http://www.javaly.cn, 順便給你推薦一個導航網站 http://www.510gougou.com
          2009-06-09 17:16 | 找個美女做老婆

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          如果是我寫程序的話我會把
          private String request = ""; // HTTP請求消息
          定義為StringBuffer類型,不知博主覺得怎樣?

          我的目的是不要創建那么多的String類
          2009-09-23 20:16 | 學生

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          如果只是幾個變量,定義成什么都可以,如果是很多String變量,例如在循環中產生的,這就需要定義成StringBuffer或StringBuilder。
          2009-09-23 21:53 | 銀河使者

          # re: Java網絡編程從入門到精通(22):實現HTTP模擬器  回復  更多評論   

          本例只是為了演示Socket,并未考慮性能和資源問題。還有就是如果在循環中追加字符串,不是產生多個String類,而是產生多個StringBuffer或StringBuilder類
          2009-09-23 21:57 | 銀河使者
          主站蜘蛛池模板: 平塘县| 宁海县| 鄂伦春自治旗| 海兴县| 芜湖市| 澎湖县| 英山县| 玉环县| 东平县| 富川| 济宁市| 汨罗市| 绥棱县| 建瓯市| 台州市| 荆州市| 安康市| 金秀| 湛江市| 古蔺县| 祁阳县| 通州区| 昌吉市| 江达县| 衡南县| 安徽省| 高阳县| 龙岩市| 富平县| 华亭县| 江安县| 钟山县| 四平市| 绥德县| 宁津县| 华亭县| 策勒县| 友谊县| 平武县| 高州市| 确山县|