Http网络协议

目录结构:

contents structure [-]

1.什么是Http协议

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是因特网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。

2.Http协议的发展历史

http协议到目前为止,已经经历过了http0.9、http1.0、http1.1和http2。

http 0.9于1991年发布,该版本非常简单,只有Get请求,而且服务器只能返回html格式的字符串,其它格式的不能解析。

http 1.0于1996年5月发布,该版本相比较于0.9增加了许多功能,首先服务器可以返回任何格式的数据,然后除了Get方法,还增加了Post方法和Head方法。

http 1.1于1997年1月发布,在传统的1.0版本中有一个缺点,就是每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接,随着网页资源加载越来越多,这个问题就显得越来越突出了。而http 1.1就解决了这个问题,它引入了持久化连接,及在一个TCP连接中,可以发送多个请求。而且客户端和服务端发现对方一段时间没有活动就会主动关闭连接。1.1任然是目前使用最多的版本。

htttp 2于2015年发布,HTTP/1.1 版的头信息只能是文本(ASCII编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧。使用二进制的好处是可以定义额外的帧,为将来的高级应用打下基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多。

3.Http的报文结构

http的报文结构是由状态行、头部、空行、主体组成。

3.1客户端请求

如果是客户端请求的话,那么就是请求行、请求头部、空行、请求主体。

在这个图中,可以看出请求行的结构是:请求方法 URL 协议版本

例如:

GET /hello.txt HTTP/1.1

如果是GET请求的话,是没有请求数据的。请求数据只有POST才有。

3.2服务端响应消息

如果是服务端响应的话,那么就是响应行、响应头部、空行、响应主体。

响应行的结构是:版本 状态码 状态码描述

例如上面的:

HTTP/1.1 200 OK

 4.Content-Type

content-type一般只存在于Post方法中,因为Get方法是不含“body”的,它的请求参数都会被编码到url后面,所以在Get方法中加Content-type是无用的。

下面是Content-Type的几种常见类型:

类型 格式
text/html HTML格式
text/plain 纯文本格式
text/xml XML格式
image/gif gif图片格式
image/jpeg jpg图片格式
image/png png图片格式
application/xhtml+xml XHTML格式
application/xml XML数据格式
application/atom+xml Atom XML聚合格式
application/json JSON数据格式
application/pdf pdf格式
application/msword Word文档格式
application/octet-stream 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded 表单提交中默认的encType
multipart/form-data 在表单中文件上传时,就需要使用该格式

在指定Content-Type的时候,也可以指定编码方式(Charset),如果不指定编码方式的话,那么用系统默认的编码方式。

下面讲解上面的几种:

4.1 application/x-www-form-urlencoded

这应该是最常见的 POST 提交数据的方式了。浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了):

POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

首先,Content-Type 被指定为 application/x-www-form-urlencoded;其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。大部分服务端语言都对这种方式有很好的支持。例如 PHP 中,$_POST['title'] 可以获取到 title 的值,$_POST['sub'] 可以得到 sub 数组。

很多时候,我们用 Ajax 提交数据时,也是使用这种方式。例如 JQuery 和 QWrap 的 Ajax,Content-Type 默认值都是「application/x-www-form-urlencoded;charset=utf-8」。

4.2 multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 form 的 enctyped 等于这个值。直接来看一个请求示例:

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

这个例子稍微复杂点。首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 mutipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。关于 mutipart/form-data 的详细定义,请前往 rfc1867 查看。

这种方式一般用来上传文件,各大服务端语言对它也有着良好的支持。

上面提到的这两种 POST 数据的方式,都是浏览器原生支持的,而且现阶段原生 form 表单也只支持这两种方式。但是随着越来越多的 Web 站点,尤其是 WebApp,全部使用 Ajax 进行数据交互之后,我们完全可以定义新的数据提交方式,给开发带来更多便利。

4.3 application/json

application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

JSON 格式支持比键值对复杂得多的结构化数据,这一点也很有用

Google 的 AngularJS 中的 Ajax 功能,默认就是提交 JSON 字符串。例如下面这段代码:


var data = {'title':'test', 'sub' : [1,2,3]};
$http.post(url, data).success(function(result) {
    ...
});
最终发送的请求是:

POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8

{"title":"test","sub":[1,2,3]}

4.4 text/xml

XML-RPC(XML Remote Procedure Call)是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。典型的 XML-RPC 请求是这样的:

POST http://www.example.com HTTP/1.1
Content-Type: text/xml

<!--?xml version="1.0"?-->
<methodcall>
    <methodname>examples.getStateName</methodname>
    <params>
        <param>
            <value><i4>41</i4></value>
        
    </params>
</methodcall>

如果使用的框架的对content-type的支持不是很友好的话,那么就应该从输入流中获取数据。比如上面提到的application/json,text/xml依然有许多Web框架不直接支持。

5.Http协议的头部Range介绍

Range字段是Http1.1开始新增加的,Http1.1和传统的Http1.0相比,最大的特点就是解决1.0中不能支持多请求的缺点。判断一个WEB服务器是否支持分段下载可以通过查看 返回头是否有Accept-Ranges:Byte 字段。分段下载分为两种,一种就是一次请求一个字段,一种就是一次请求多个字段。关于超文本传输的报文信息,读者可以通过filter、burp或是浏览器的控制台中查看报文的信息。

   (一)一次请求一个分段

    下面看一下Range字段常用表示的写法:
    Range: bytes=0-1024 获取最前面1025个字节
    Range: bytes=-500   获取最后500个字节
    Range: bytes=1025-  获取从1025开始到文件末尾所有的字节
    Range: 0-0          获取第一个字节
    Range: -1           获取最后一个字节
    例如,在一个请求头中有Range:byte=0-1024,那么表示的意思就是请求数据的前1025个字节。
     如果这个分段请求的返回码是206,并且指示的分段范围是0-1024,文件的总大小是7877,那么在响应头中的数据应该表示为:
    Content-Range: bytes 0-1024/7877

    (二)一次请求多个分段

    多个分段和单个分段相差无几,只需要在请求头的分段中添加多个分段区域就可以了。
    例如:在请求头出现Range:byte:0-1024,2000-3000,表示的含义就是请求前1025个字节信息,和从2000到3000字节的信息。
    如果分段请求的返回状态码是206,那么Content-Range的返回值和一次请求单个分段一样。

 

接下来笔者结合Java实现一个多线程分段下载的功能,倘若读者能用其他语言语言实现分段下载,还望读者在评论区指点一二。使用Java多线程实现实现这个功能的大致思想,使用多个线程负责文件的子模块的下载,每个线程都要记录好下载的结束位置,以便于作为下个线程下载的开始位置。直接上代码:

多线程的下载类:

MutiThreadDownLoad.java

import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CountDownLatch;

public class MutiThreadDownLoad {
    /**
     * 同时下载的线程数
     */
    private int threadCount;
    /**
     * 服务器请求路径
     */
    private String serverPath;
    /**
     * 本地路径
     */
    private String localPath;
    /**
     * 线程计数同步辅助
     */
    private CountDownLatch latch;
 
    public MutiThreadDownLoad(int threadCount, String serverPath, String localPath, CountDownLatch latch) {
        this.threadCount = threadCount;
        this.serverPath = serverPath;
        this.localPath = localPath;
        this.latch = latch;
    }
 
    public void executeDownLoad() {
 
        try {
            URL url = new URL(serverPath);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);//设置超时时间
            conn.setRequestMethod("GET");//设置请求方式
            int code = conn.getResponseCode();
            if (code == 200) {
                //服务器返回的数据的长度,实际上就是文件的长度,单位是字节
                int length = conn.getContentLength();
                System.out.println("文件总长度:" + length + "字节(B)");
                RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
                //指定创建的文件的长度
                raf.setLength(length);
                raf.close();
                //分割文件
                int blockSize = length / threadCount;
                for (int threadId = 1; threadId <= threadCount; threadId++) {
                    //第一个线程下载的开始位置
                    int startIndex = (threadId - 1) * blockSize;
                    int endIndex = startIndex + blockSize - 1;
                    if (threadId == threadCount) {
                        //最后一个线程下载的长度稍微长一点
                        endIndex = length;
                    }
                    System.out.println("线程" + threadId + "下载:" + startIndex + "字节~" + endIndex + "字节");
                    new DownLoadThread(threadId, startIndex, endIndex).start();
                }
 
            }
 
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
    /**
     * 内部类用于实现下载
     */
    public class DownLoadThread extends Thread {
        /**
         * 线程ID
         */
        private int threadId;
        /**
         * 下载起始位置
         */
        private int startIndex;
        /**
         * 下载结束位置
         */
        private int endIndex;
 
        public DownLoadThread(int threadId, int startIndex, int endIndex) {
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        } 
        @Override
        public void run() {
 
            try {
                System.out.println("线程" + threadId + "正在下载...");
                URL url = new URL(serverPath);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                //请求服务器下载部分的文件的指定位置
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
                conn.setConnectTimeout(5000);
                int code = conn.getResponseCode();
 
                System.out.println("线程" + threadId + "请求返回code=" + code);
 
                InputStream is = conn.getInputStream();//返回资源
                RandomAccessFile raf = new RandomAccessFile(localPath, "rwd");
                //随机写文件的时候从哪个位置开始写
                raf.seek(startIndex);//定位文件
 
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len = is.read(buffer)) != -1) {
                    raf.write(buffer, 0, len);
                }
                is.close();
                raf.close();
                System.out.println("线程" + threadId + "下载完毕");
                //计数值减一
                latch.countDown();
 
            } catch (Exception e) {
                e.printStackTrace();
            }
 
        }
    }
}

ClientTest.java

import java.util.concurrent.CountDownLatch;

public class ClientTest{
    public static void main(String[] args) {
 
        int threadSize = 4;
        String serverPath = "https://www.baidu.com";
        String localPath = "NewsReader.apk";
        CountDownLatch latch = new CountDownLatch(threadSize);
        MutiThreadDownLoad m = new MutiThreadDownLoad(threadSize, serverPath, localPath, latch);
        long startTime = System.currentTimeMillis();
        try {
            m.executeDownLoad();
            latch.await();//等待所有的线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("全部下载结束,共耗时" + (endTime - startTime) / 1000 + "s");
    }
}

 

6.请求方法都有那些

根据HTTP标准,HTTP请求可以使用多种请求方法。

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。

HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

1 GET 请求指定的页面信息,并返回实体主体。
2 HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。
5 DELETE 请求服务器删除指定的页面。
6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
7 OPTIONS 允许客户端查看服务器的性能。
8 TRACE 回显服务器收到的请求,主要用于测试或诊断。

7.状态码都有那些

状态码共有五种类型:

1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5**

服务器错误,服务器在处理请求的过程中发生了错误

下面是常见的HTTP状态码:

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误
posted @ 2017-09-24 18:24 HDWK 阅读(...) 评论(...) 编辑 收藏