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 - 内部服务器错误
  • 相关阅读:
    POJ 2240 Arbitrage spfa 判正环
    POJ 3259 Wormholes spfa 判负环
    POJ1680 Currency Exchange SPFA判正环
    HDU5649 DZY Loves Sorting 线段树
    HDU 5648 DZY Loves Math 暴力打表
    HDU5647 DZY Loves Connecting 树形DP
    CDOJ 1071 秋实大哥下棋 线段树
    HDU5046 Airport dancing links 重复覆盖+二分
    HDU 3335 Divisibility dancing links 重复覆盖
    FZU1686 神龙的难题 dancing links 重复覆盖
  • 原文地址:https://www.cnblogs.com/HDK2016/p/7588045.html
Copyright © 2011-2022 走看看