zoukankan      html  css  js  c++  java
  • 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 - 内部服务器错误
  • 相关阅读:
    (一)js概述
    (八)js函数二
    (七)js函数一
    (十)js获取日期
    Java 定时器 Timer 的使用.
    多线程编程学习四(Lock 的使用)
    多线程编程学习三(线程间通信).
    wait/notify 实现多线程交叉备份
    多线程编程学习二(对象及变量的并发访问).
    浅析多线程的对象锁和Class锁
  • 原文地址:https://www.cnblogs.com/HDK2016/p/7588045.html
Copyright © 2011-2022 走看看