理解Http协议(一)

  本文对Http协议进行了简要的描述,说明了其用途的广泛性;通过代码对Http连接和Http请求消息的发送进行实现,希望能将这些抽象的过程直观的显示出来;最后对HttpURL和Http协议中“资源”这些相对抽象概念进行描述和举例。

  网络协议是指为计算机网络中进行数据交换而建立的规则、标准或约定的集合。Http协议是一种应用层协议,能用于构建分布式协作的超媒体信息系统。作为一种通用的无状态的协议,Http不仅能用于超文本的传输,也能用于名称服务器,分布式对象管理系统。HTTP的其中一个特征就是能对数据的表示形式进行分类和协商,因此能够建立独立于数据传输的各种系统。除了用于html网页,javascript脚本等文件的传输及其它文件的上传下载;也能通过HTTP协议开放Web服务的api,诸如亚马逊,FACEBOOK等公司提供基于Http开放自身的服务api;还有像Solr这样的全文检索服务器,通过运行于Web服务器,通过Http传输数据。HTTP的目的是提供多样的可扩展的多种配置选项,达到能Web应用提供高可靠性,至少在服务失败的情况下提供可靠地报错的目的,这样的Web应用可以是运行于高带宽的企业内部网的或者是手持设备接入的低带宽传输链路的环境(协议上的一段翻译,不明觉厉,他们说是这样就是吧,我们可以理解为了达到这样的效果,协议内部定义了不少细节)。Http协议定义的细节,包括了诸如:客户端请求消息格式、服务端应答消息格式、服务应答消息状态码以及像GET或POST的多种请求方法等等细节。更具体的内容见:http://en.wikipedia.org/wiki/List_of_HTTP_headers 和 http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol。

 

一 、Http请求消息格式及Http连接

  Http是基于请求/响应的协议,即客户端向服务端发送请求消息,服务端给与应答。
  Http请求消息依次包括几个部分:请求行(以CRLF结尾,CR为回车符,LF为换行符,两者使用US-ASCII编码,分别是'\r','\n'),请求头部(以CRLF结尾),空行(CRLF),消息主体(其他请求方法可选,在POST方法必须用到)。请求头部以及请求行是由字符串组成,这一部分的字符串编码要求使用US-ASCII编码。
  下图使用Chrome浏览器自带的开发者工具捕捉到的一个HTTP请求消息:

  

  请求消息的请求行:

  请求行由以下几部分依次组成,请求方法(如:GET、POST、HEAD等);Request-URI,标志请求资源的字符串,可以有如http://www.w3.org/pub/WWW/TheProject.html 或 /pub/WWW/TheProject.html两种表示方法;协议版本:HTTP/1.1 或HTTP/1.0;最后以CRLF(使用US-ASCII编码的'\r','\n'两个字符)结尾。

  请求头部:

  请求头部能够传递额外的客户端信息,是由请求头部字段及字段对应值组成的键值对,并且是可扩展的,字段名称和字段值使用冒号分开,并且使用CRLF作为结尾。
  

  最后请求消息可以带有消息主体,消息主体的解析由自身应用逻辑进行处理。服务端接收解析请求消息,将回应Http应答信息,应答信息由状态行、应答头部、空行及消息主体组成。

  Http连接

  Http通常建立在TCP连接上,但是协议中并没有规定必须使用TCP,HTTP建立在下层提供可靠的传输协议之上。因此,任何能够提供这种保证的协议都可以被其使用。服务端通常开放端口80,提供Http服务。

  具备了对Http请求消息以及Http连接的基本知识,我们可以通过Socket编程简单实现这一协议;实现步骤如下:向服务主机建立起TCP连接,端口为80;发送请求消息;读取服务端输出,向控制台输出服务端的输出。

  以下是Java代码的实现:

package http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;

public class HttpTest {

    public static void main(String[] args) {
        // Http请求头部的字符串表示;
        String requestMessage="GET / HTTP/1.1\r\n"+
                              "Host: www.w3.org\r\n"+
                              "Connection: keep-alive\r\n"+
                              "\r\n";
        //服务器主机名
        String hostName="www.w3.org";
        Socket tcpSocket=new Socket();
        //指定所要连接的服务端端口为80
        InetSocketAddress hostAddr=new InetSocketAddress(hostName,80);
        try {
            tcpSocket.connect(hostAddr);
            //获取向该连接写入的输出流
            OutputStream outputStream=tcpSocket.getOutputStream();
             //Http请求消息的编码为US-ASCII;由于不同字符编码中可能在一些字符二进制表示重叠,因此
            //不指定编码也可能产生正确的结果但不够准确的。
            byte[] bytes=requestMessage.getBytes(Charset.forName("US-ASCII"));
            outputStream.write(bytes);
            //获取该连接的输入流,可以读取流中的数据
            InputStream inputStream=tcpSocket.getInputStream();
            byte[] buffer=new byte[128];
            int n=inputStream.read(buffer);
            while(n>0)
            {
                //这里将读取的二进制流转化成字符串,这里的实现没有考虑到二进制流的边界是否能够
                //刚好转化为字符串的问题,但是由于都是英文所以这里也不会出现乱码问题,UTF-8为该网页选择的编码
                String str=new String(buffer,0,n,Charset.forName("UTF-8"));
                System.out.print(str);
                n=inputStream.read(buffer);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
/*程序的部分输出:
 HTTP/1.1 200 OK
Date: Sat, 15 Feb 2014 07:08:30 GMT
Server: Apache/2
Content-Location: Home.html
Vary: negotiate,accept
TCN: choice
Last-Modified: Sat, 15 Feb 2014 05:00:19 GMT
ETag: "967f-4f26acc8b9ec0;89-3f26bd17a2f00"
Accept-Ranges: bytes
Content-Length: 38527
Cache-Control: max-age=600
Expires: Sat, 15 Feb 2014 07:18:30 GMT
P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml"
Content-Type: text/html; charset=utf-8

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!-- Generated from data/head-home.php, ../../smarty/{head.tpl} -->
<head>
 */

 

  我们也可以使用telnet工具模拟这一过程,Telnet是Internet远端登录服务的一个协议;建立在TCP连接之上,这里我们可以借助它建立TCP连接,并发送HTTP请求消息。

 

二、什么是HttpURL和Http协议中的资源?

  URL(Uniform Resource Locator,统一资源定位符),用于标志网络上某个资源的地址,而URI(Uniform Resource Identifier,统一资源标识符)是一个用于标识某一互联网资源名称的字符串,其内涵包括URL和URN(Uniform Resource Name,统一资源名称)。URN定义某事物的身份,而URL提供查找该事物的方法。可以认为统一资源名(URN)如同一个人的名称,而统一资源定位符(URL)代表一个人的住址。这里的资源可以理解为某个数据对象,如:某个html文档;js脚本文件;或者是某种服务,如查询服务,然后可以以json或xml格式返回结果。协议指出通过HTTP协议请求的资源由URI来标识。笔者没有接触过URN和HTTP协议相结合的东西,因此本文只讨论HttpURL的东西。

  一个URL由两个部分组成:

(1)协议标志符:对于URL http://example.com ,http便是该URL的协议标志符;意味着使用该协议所规定的内容访问资源。

(2)资源名称: 对于 URL http://example.com ,资源名称为 “example.com”。

  HttpURL的格式如下:

        http_URL = "http:""//" host [ ":" port ] [ abs_path [ "?" query ]]
其中http: 和"//"为文字常量,host为服务端的主机名也可以是服务端的IP(协议建议尽量不用IP地址),port为可选的选项,可以指定值,或者采用默认的端口80,即访问服务端的80端口。
abs_path指定主机上的某个资源路径。如:http://www.telnet.org/htm/dev.htm中的/htm/dev,如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,底层代码实现必须指定为“/";通常这个工作浏览器自动帮我们完成。
例如:
  输入:www.guet.edu.cn此时浏览器自动转换成:http://www.guet.edu.cn/ (例子来源于:http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html );也就是在请求行是    GET / HTTP/1.1。
  对于HttpURL来说,"Http",意味着以Http协议来访问资源,从实现的角度来说也就是,底层建立起TCP连接,连接服务器端口port,服务器名为HttpURL字符串中的host,然后发送连接的消息;也就是HttpURL将建立TCP连接和发送请求消息,还有可以对接收服务端返回的应答消息进行一定解析这些过程进行抽象,Java API提供了抽象类java.net.HttpURLConnection和类java.net.URL中的openConnection()支持这写过程,以下是使用这两个类进行http连接的代码,最后将服务端的应答消息输出至控制台:

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;

public class HttpURLTest {

    public static void main(String[] args) throws MalformedURLException {
        // 指定HttpURL字符串构建URL对象;格式错误的话抛出MalformedURLException
        URL httpURL=new URL("http://www.w3c.org/");
        try {
            //打开连接,将返回的URLConnection向下转化为HttpConnection
            //由于我们指定了http协议访问,因而这样的转型是安全的
            HttpURLConnection httpConn=(HttpURLConnection)httpURL.openConnection();
            //HttpURLConnection将Http协议的一些细节进行抽象,因此我们可以很方便的得到应答消息的
            //状态码;
            System.out.println("response code="+httpConn.getResponseCode());
            InputStream inputStream=httpConn.getInputStream();
            byte[] buffer=new byte[128];
            int n=inputStream.read(buffer);
            while(n>0)
            {
                //这里将读取的二进制流转化成字符串,这里的实现没有考虑到二进制流的边界是否能够
                //刚好转化为字符串的问题,但是由于都是英文所以这里也不会出现乱码问题,UTF-8为该网页选择的编码
                String str=new String(buffer,0,n,Charset.forName("UTF-8"));
                System.out.print(str);
                n=inputStream.read(buffer);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
/*程序的部分输出:
response code=200
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!-- Generated from data/head-home.php, ../../smarty/{head.tpl} -->
<head>
<title>World Wide Web Consortium (W3C)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="Help" href="/Help/" />
<link rel="stylesheet" href="/2008/site/css/minimum" type="text/css" media="handheld, all" />
<style type="text/css" media="print, screen and (min-width: 481px)">
 */

 

 

posted @ 2014-02-15 16:00  NotOnlyAnAnswer  阅读(875)  评论(0编辑  收藏  举报