当用户打开浏览器,输入一个URL地址,就能接收到远程HTTP服务器发送过来的网页。浏览器就是常见的HTTP客户程序。如图1所示,HTTP客户程序必须先发出一个HTTP请求,然后才能接收到来自HTTP服务器的响应。

图1 HTTP客户程序与HTTP服务器的通信过程
HTTP客户程序和HTTP服务器分别由不同的软件开发商提供,它们都可以用任意的编程语言编写。用VC编写的HTTP客户程序能否与用Java编写的HTTP服务器顺利通信呢?答案是肯定的。HTTP协议严格规定了HTTP请求和HTTP响应的数据格式,只要HTTP服务器与客户程序都遵守HTTP协议,就能彼此看得懂对方发送的消息。
1.HTTP请求
HTTP协议规定,HTTP请求由三部分构成:请求方法、URI、HTTP协议的版本;请求头(Request Header);请求正文(Request Content)。
下面是一个HTTP请求的例子:
POST /hello.htm HTTP/1.1
Accept: image/gif, image/jpeg, */*
Referer: http://localhost/login.htm
Accept-Language: en,zh-cn;q=0.5
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: localhost
Content-Length: 43
Connection: Keep-Alive
Cache-Control: no-cache
username=weiqin&password=1234&submit=submit
HTTP请求的第一行包括请求方式、URI和协议版本这三项内容,以空格分开:
POST /hello.htm HTTP/1.1
在以上代码中,“POST” 表示请求方式,“/hello.htm”表示URI,“HTTP/1.1” 表示HTTP协议的版本。
根据HTTP协议,HTTP请求可以使用多种请求方式,主要包括:
1) GET
这种请求方式最为常见,客户程序通过这种请求方式访问服务器上的一个文档,服务器把文档发送给客户程序。
2) POST
客户程序可通过这种方式发送大量信息给服务器。在HTTP请求中除了包含要访问的文档的URI,还包括大量的请求正文,这些请求正文中通常会包含大量HTML表单数据。
3) HEAD
客户程序和服务器之间交流一些内部数据,服务器不会返回具体的文档。当使用GET和POST方法时,服务器最后都将特定的文档返回给客户程序。而HEAD请求方式则不同,它仅仅交流一些内部数据,这些数据不会影响用户浏览网页的过程,可以说对用户是透明的。HEAD请求方式通常不单独使用,而是为其他请求方式起辅助作用。一些搜索引擎使用HEAD请求方式来获得网页的标志信息,还有一些HTTP服务器进行安全认证时,用这个方式来传递认证信息。
4) PUT
客户程序通过这种方式把文档上传给服务器。
5) DELETE
客户程序通过这种方式来删除远程服务器上的某个文档。客户程序可以利用PUT和DELETE请求方式来管理远程服务器上的文档。
GET和POST请求方式最常用,而PUT和DELETE请求方式并不常用,因而不少HTTP服务器并不支持PUT和DELETE请求方式。
URI(Universal Resource Identifier,统一资源定位符)用于标识要访问的网络资源。在HTTP请求中,通常只要给出相对于服务器的根目录的相对目录即可,因此以“/”开头。
HTTP请求的第一行的最后一部分内容为客户程序使用的HTTP协议的版本。
请求头包含许多有关客户端环境和请求正文的有用信息。例如,请求头可以声明浏览器的类型、所用的语言、请求正文的类型,以及请求正文的长度等:
Accept: image/gif, image/jpeg, */*
Referer: http://localhost/login.htm
Accept-Language: en,zh-cn;q=0.5 //浏览器所用的语言
Content-Type: application/x-www-form-urlencoded //正文类型
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0) //浏览器类型
Host: localhost //远程主机
Content-Length: 43 //正文长度
Connection: Keep-Alive
Cache-Control: no-cache
HTTP协议规定,请求头和请求正文之间必须以空行分割,这个空行非常重要,它表示请求头已经结束,接下来是请求正文。请求正文中可以包含客户以POST方式提交的表单数据:
username=weiqin&password=1234
在以上HTTP请求例子中,请求正文只有一行内容。在实际应用中,HTTP请求的正文可以包含更多的内容。
2.HTTP响应
和HTTP请求相似,HTTP响应也由三部分构成:HTTP协议的版本、状态代码、描述;响应头(Response Header);响应正文(Response Content)。
下面是一个HTTP响应的例子:
HTTP/1.1 200 OK
Server: nio/1.1
Content-type: text/html; charset=GBK
Content-length: 102
<html>
<head>
<title>helloapp</title>
</head>
<body >
<h1>hello</h1>
</body>
</html>
HTTP响应的第一行包括服务器使用的HTTP协议的版本、状态代码,以及对状态代码的描述,这三项内容之间以空格分割。在本例中,使用HTTP1.1协议,状态代码为200,该状态代码表示服务器已经成功地处理了客户端发出的请求:
HTTP/1.1 200 OK
状态代码是一个3位整数,以1、2、3、4或5开头:
l 1xx:信息提示,表示临时的响应。
l 2xx:响应成功,表明服务器成功地接收了客户端请求。
l 3xx:重定向。
l 4xx:客户端错误,表明客户端可能有问题。
l 5xx:服务器错误,表明服务器由于遇到某种错误而不能响应客户请求。
以下是一些常见的状态代码:
l 200:响应成功。
l 400:错误的请求。客户发送的HTTP请求不正确。
l 404:文件不存在。在服务器上没有客户要求访问的文档。
l 405:服务器不支持客户的请求方式。
l 500:服务器内部错误。
响应头也和请求头一样包含许多有用的信息,例如服务器类型、正文类型和正文长度等:
Server: nio/1.1 //服务器类型
Content-type: text/html; charset=GBK //正文类型
Content-length: 102 //正文长度
响应正文就是服务器返回的具体文档,最常见的是HTML网页:
<html>
<head>
<title>helloapp</title>
</head>
<body >
<h1>hello</h1>
</body>
</html>
HTTP请求头与请求正文之间必须用空行分割,同样,HTTP响应头与响应正文之间也必须用空行分隔。
3.测试HTTP请求
当用户在浏览器中输入一个URL,浏览器就会生成一个HTTP请求,建立与远程HTTP服务器的连结,然后把HTTP请求发送给远程HTTP服务器,HTTP服务器再返回相应的网页,浏览器最后把这个网页显示出来。当浏览器与服务器之间的数据交换完毕,就会断开连结。如果用户希望访问新的网页,浏览器必须再次建立与服务器的连结。
例程1(SimpleHttpServer)创建了一个非常简单的HTTP服务器,它接收客户程序的HTTP请求,把它打印到控制台。然后对HTTP请求做简单的解析,如果客户程序请求访问login.htm,就返回该网页,否则一律返回hello.htm网页。login.htm和hello.htm文件位于classpath下的root目录下。
SimpleHttpServer监听80端口,按照阻塞模式工作,采用线程池来处理每个客户请求。
//例程1 SimpleHttpServer.java(阻塞模式)
//此处省略import语句
public class SimpleHttpServer {
private int port=80;
private ServerSocketChannel serverSocketChannel = null;
private ExecutorService executorService;
private static final int POOL_MULTIPLE = 4;
public SimpleHttpServer() throws IOException {
executorService= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * POOL_MULTIPLE);
serverSocketChannel= ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("服务器启动");
}
public void service() {
while (true) {
SocketChannel socketChannel=null;
try {
socketChannel = serverSocketChannel.accept();
executorService.execute(new Handler(socketChannel));
}catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String args[])throws IOException {
new SimpleHttpServer().service();
}
class Handler implements Runnable{ //Handler是内部类,负责处理HTTP请求
private SocketChannel socketChannel;
public Handler(SocketChannel socketChannel){
this.socketChannel=socketChannel;
}
public void run(){
handle(socketChannel);
}
public void handle(SocketChannel socketChannel){
try {
Socket socket=socketChannel.socket();
System.out.println("接收到客户连接,来自: " +
socket.getInetAddress() + ":" +socket.getPort());
ByteBuffer buffer=ByteBuffer.allocate(1024);
socketChannel.read(buffer); //接收HTTP请求,假定其长度不超过1024个字节
buffer.flip();
String request=decode(buffer);
System.out.print(request); //打印HTTP请求
//生成HTTP响应结果
StringBuffer sb=new StringBuffer("HTTP/1.1 200 OK\r\n");
sb.append("Content-Type:text/html\r\n\r\n");
socketChannel.write(encode(sb.toString())); //发送HTTP响应的第一行和响应头
FileInputStream in;
//获得HTTP请求的第一行
String firstLineOfRequest=request.substring(0,request.indexOf("\r\n"));
if(firstLineOfRequest.indexOf("login.htm")!=-1)
in=new FileInputStream("login.htm");
else
in=new FileInputStream("hello.htm");
FileChannel fileChannel=in.getChannel();
fileChannel.transferTo(0,fileChannel.size(),socketChannel);//发送响应正文
}catch (Exception e) {
e.printStackTrace();
}finally {
try{
if(socketChannel!=null)socketChannel.close(); //关闭连结
}catch (IOException e) {e.printStackTrace();}
}
}
private Charset charset=Charset.forName("GBK");
public String decode(ByteBuffer buffer){ … } //解码
public ByteBuffer encode(String str){ … } //编码
} //#Handler内部类
}//#SimpleHttpServer类
运行“java SimpleHttpServer”命令,就启动了HTTP服务器,然后打开一个IE浏览器,按照如下步骤访问HTTP服务器。根据服务器端控制台的打印结果,可以了解IE浏览器发送给服务器的HTTP请求信息。
在IE浏览器中输入URL:http://localhost:80/login.htm或者http://localhost/login.htm。默认情况下,IE浏览器总是与远程HTTP服务器的80端口建立连接,因此在URL中可以不指定80端口。图2显示了IE浏览器接收到的网页,以及服务器接收到的HTTP请求。

图2 浏览器按照GET方式访问login.htm
|