随笔 - 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 银河使者 阅读(4640) 评论(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 | 银河使者

只有注册用户登录后才能发表评论。


网站导航: