在讨论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(
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方法,要求用户继续输入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文件。