记录成长的点滴

2019-08-06

根据指定的name获取浏览器中search中的值

function getParamsByName(name){
    name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
    var regex = new RegExp("[\\?&]"+name+"=([^&#]*)"),
        result = regex.exec(location.search);
        return results ==null?"":decodeURIComponent(results[1])
}

复制代码

对象数组根据其中某一个key进行去重

/** * 功能:1.对象数组根据某一个key进行去重 key为id, * 1:[{id:1,name:'2'},{id:1,name:'n1'},{id:2,name:'n2'}] =>[{id:1,name:'2'},{id:2,name:'n2'}] * * 2.对象数组按key进行去重,并合并为一个针对key的数据格式 key为id,isExtract =true * [{id:1,name:'2'},{id:1,name:'n1'},{id:2,name:'n2'}] =>[1,2] * 参数:soucreArray:数组 key :根据哪一个字段进行去重 isExtract * */
    function sweepDuplicatte(sourceArray,key,isExtract){
        let storeDesignationkey ={};
        const targetArray = sourceArray.reduce((initalTargetArray,currentItem)=>{
            if(!storeDesignationKey[currentItem[key]]){
                storeDesignationKey[currentItem[key]]=true;
                if(isExtract){
                    initalTargetArray.push(currentItem[key])
                }else{
                    initalTargetArray.push(currentItem);
                }
            }
            return initalTargetArray;
        },[]);
        return targetArray;
    }
复制代码

2019-08-19

正则表达式中的量词符号

? 问号表示某个模式出现0次或1次,等同于{0, 1}。
* 星号表示某个模式出现0次或屡次,等同于{0,}。
+ 加号表示某个模式出现1次或屡次,等同于{1,}。javascript

String.replace的第二个参数

replace方法的第二个参数能够使用美圆符号$,用来指代所替换的内容。css

$&:匹配的子字符串。
$`:匹配结果前面的文本。
$’:匹配结果后面的文本。
$n:匹配成功的第n组内容,n是从1开始的天然数。
$$:指代美圆符号$。html

//先后的字符替换位置
'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
'abc'.replace('b', '[$`-$&-$\']') // "a[a-b-c]c" 复制代码

2019-08-26

动态加载建立script并执行代码

function loadScript(url,callback){
    var script = doucument.createElement('script');
    script.type ='text/javascript';
    if(script.readyState){
        //IE
        script.onreadystatechange =function(){
            if(script.readyState =='loaded'||script.readyState=='complete'){
                script.onreadysatatechange=null;
                callback()
            }
        }
    }else{
        scripte.onload = function(){
            callback();
        }
    }
    script.src =url;
    document.getElementByTagName('head')[0].appendChild(script);
}
复制代码

2019-08-27

做用域和标识符解析

每个js函数都是一个对象(是Function对象的一个实例)。前端

内部属性[[Scope]]包含一个函数被建立的做用域中对象的集合(做用域链)。函数做用域中的每一个对象被称为一个可变对象,而且以"键值对"的形式存在。java

执行函数时会建立一个执行环境(execution context)的内部对象。一个执行环境定义了一个函数执行时的环境。每次执行都不同。函数执行完毕,执行环节会被销毁。node

每一个执行环境都有本身的做用域链,用于解析标识符。当执行环境被建立时,它的做用域链初始化为当前运行函数的[[Scope]]属性中的对象。这个复制过程完成后,一个呗称为"活动对象(activation object)"的新对象就为执行环境建立好了。活动对象做为函数运行时的变量对象,包含了全部局部变量,命名参数,参数集合以及this.而后此对象被推入做用域链的最前端nginx

闭包

容许函数访问局部做用域以外的数据。c++

2019-08-28

DOM

文档对象模型(DOM)是一个独立于语言的,用于操做XML和HTML文档的程序接口(API)。web

浏览器中一般会把DOM和JavaScript独立实现。正则表达式

浏览器 js引擎 渲染引擎
IE JScript(旧)
Chakra(新)
Trident
Safari JavaScriptCore(SquirreFish) WebCore
Chorme V8 WebCore
FireFox TraceMonkey Gecko

2019-09-05

HTTP(HyperText Transfer Protocol)

HTTP over TCP/IP

Protocol

HTTP 是一个用在计算机世界里的协议,它确立了一种计算机之间交流通讯的规范,以及相关的各类控制和错误处理方式。

Transfer

HTTP 专门用来在两点之间传输数据,不能用于广播、寻址或路由。

HyperText

HTTP 传输的是文字、图片、音频、视频等超文本数据。


TCP/IP协议

分层

  1. 应用层
  2. 传输层(TCP)
  3. 网际层(IP)
  4. 连接层

IP协议

IP 协议是“Internet Protocol”的缩写,主要目的是解决寻址和路由问题,以及如何在两点间传送数据包。IP 协议使用“IP 地址”的概念来定位互联网上的每一台计算机。

TCP协议

TCP 协议是“Transmission Control Protocol”的缩写,意思是“传输控制协议”,它位于 IP 协议之上,基于 IP 协议提供可靠的、字节流形式的通讯,是 HTTP 协议得以实现的基础。

“可靠”是指保证数据不丢失,“字节流”是指保证数据完整,因此在 TCP 协议的两端能够如同操做文件同样访问传输的数据,就像是读写在一个密闭的管道里“流动”的字节。


URI/URL

URI(Uniform Resource Identifier)

中文名称是 统一资源标识符,使用它就可以惟一地标记互联网上资源。

URL(Uniform Resource Locator)

URI 另外一个更经常使用的表现形式是 , 统一资源定位符,也就是咱们俗称的“网址”,它其实是 URI 的一个子集

http://nginx.org/en/download.html
复制代码
  1. 协议名:即访问该资源应当使用的协议,在这里是“http”;
  2. 主机名:即互联网上主机的标记,能够是域名或 IP 地址,在这里是“nginx.org”;
  3. 路径:即资源在主机上的位置,使用“/”分隔多级目录,在这里是“/en/download.html”。

HTTPS

HTTP over SSL/TLS也就是运行在 SSL/TLS 协议上的 HTTP.

为HTTP 套了一个安全的外壳

HTTP+SSL/TLS+TCP/IP

SSL (Secure Socket Layer)

SSL 使用了许多密码学最早进的研究成果,综合了对称加密、非对称加密、摘要算法、数字签名、数字证书等技术,可以在不安全的环境中为通讯的双方建立出一个秘密的、安全的传输通道,为 HTTP 套上一副坚固的盔甲。

2019-09-09

TCP/IP 网络分层模型 (四层协议)

从下而上的分层

连接层(MAC层) (link layer)

负责在以太网、WiFi 这样的底层网络上发送原始数据包,工做在网卡这个层次,使用 MAC 地址来标记网络上的设备,因此有时候也叫 MAC 层。

网际层”或者“网络互连层”(internet layer)

IP 协议就处在这一层。由于 IP 协议定义了IP 地址的概念,因此就能够在“连接层”的基础上,用 IP 地址取代 MAC 地址,把许许多多的局域网、广域网链接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再“翻译”成 MAC 地址就能够了

传输层(transport layer)

保证数据在 IP 地址标记的两点之间“可靠”地传输,是 TCP 协议工做的层次,另外还有它的一个“小伙伴”UDP。

  1. TCP 是一个有状态的协议,须要先与对方创建链接而后才能发送数据,并且保证数据不丢失不重复。
  2. UDP 则比较简单,它无状态,不用事先创建链接就能够任意发送数据,但不保证数据必定会发到对方。

两个协议的另外一个重要区别在于数据的形式。

  1. TCP 的数据是连续的“字节流”,有前后顺序,
  2. UDP 则是分散的小数据包,是顺序发,乱序收。

应用层(application layer)

Telnet、SSH、FTP、SMTP,HTTP。

OSI

开放式系统互联通讯参考模型(Open System Interconnection Reference Model)

  1. 第一层:物理层,网络的物理形式,例如电缆、光纤、网卡、集线器等等;
  2. 第二层:数据链路层,它基本至关于 TCP/IP 的连接层;
  3. 第三层:网络层,至关于 TCP/IP 里的网际层;
  4. 第四层:传输层,至关于 TCP/IP 里的传输层;
  5. 第五层:会话层,维护网络中的链接状态,即保持会话和同步;
  6. 第六层:表示层,把数据转换为合适、可理解的语法和语义;
  7. 第七层:应用层,面向具体的应用传输数据。

两个分层模型的映射关系

  1. 第一层:物理层,TCP/IP 里无对应;
  2. 第二层:数据链路层,对应 TCP/IP 的连接层;
  3. 第三层:网络层,对应 TCP/IP 的网际层;
  4. 第四层:传输层,对应 TCP/IP 的传输层;
  5. 第5、6、七层:统一对应到 TCP/IP 的应用层。

域名

使用字符串来代替 IP 地址,方便用户记忆,本质上一个名字空间系统

DNS 是一个树状的分布式查询系统,但为了提升查询效率,外围有多级的缓存

弱类型、强类型、动态类型、静态类型

强类型

偏向于不容忍隐式类型转换

弱类型

偏向于容忍隐式类型转换。譬如说C语言的int能够变成double

静态类型

编译的时候就知道每个变量的类型,由于类型错误而不能作的事情是语法错误

动态类型

编译的时候不知道每个变量的类型,由于类型错误而不能作的事情是运行时错误

2019-09-10

复杂度

渐进复杂度,包括时间复杂度空间复杂度,用来分析算法执行效率与数据规模之间的增加关系,能够粗略地表示,越高阶复杂度的算法,执行效率越低

从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )

数组

静态类型语言(java,c++)

一种线性表数据结构。它用一组连续的内存空间,来存储一组具备相同类型的数据。

线性表

动态类型语言(javaScript)

As JavaScript arrays are implemented as hash-maps(哈希表) or dictionaries(字典) and not contiguous.(非连续)

2019-09-17

从输入网址到页面渲染发生了哪些过程(仅限网络层)

HTTP 协议是运行在 TCP/IP 基础上的,依靠 TCP/IP 协议来实现数据的可靠传输。因此浏览器要用 HTTP 协议收发数据,首先要作的就是创建 TCP 链接

使用IP访问网页

  1. 浏览器从地址栏的输入中得到服务器的 IP 地址和端口号;
  2. 浏览器用 TCP 的三次握手与服务器创建链接;
  3. 浏览器向服务器发送拼好的报文;
  4. 服务器收到报文后处理请求,一样拼好报文再发给浏览器;
  5. 浏览器解析报文,渲染输出页面。

具体交互过程以下

域名访问 Web 服务器

在使用域名访问网页的时候,多一步操做就是DNS的解析。在拿到对应的IP地址以后,操做流程同IP访问是同样的。

HTTP报文

报文结构

拿TCP 报文来举例,它在实际要传输的数据以前附加了一个 20 字节的头部数据,存储 TCP 协议必须的额外信息,例如发送方的端口号、接收方的端口号、包序号、标志位等等

HTTP 协议也是与 TCP/UDP 相似,一样也须要在实际传输的数据前附加一些头数据,不过与 TCP/UDP 不一样的是,它是一个纯文本的协议,因此头数据都是 ASCII 码的文本。

HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  1. 起始行(start line):描述请求或响应的基本信息;
  2. 头部字段集合(header):使用 key-value 形式更详细地说明报文;
  3. 消息正文(entity):实际传输的数据,它不必定是纯文本,能够是图片、视频等二进制数据。

其中前两部分起始行和头部字段常常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,不少时候就直接称为“body”。

HTTP 协议规定报文必须有 header,但能够没有 body,并且在 header 以后必需要有一个空行,也就是“CRLF”,十六进制的“0D0A”。

HTTP 报文的格式以下:

如上的报文中:第一行“GET / HTTP/1.1”就是请求行,然后面的“Host”“Connection”等等都属于 header,报文的最后是一个空白行结束,没有 body

请求行

描述了客户端想要如何操做服务器端的资源

请求行由三部分构成:

  1. 请求方法:是一个动词,如 GET/POST,表示对资源的操做
  2. 请求目标:一般是一个 URI,标记了请求方法要操做的资源
  3. 版本号:表示报文使用的 HTTP 协议版本

状态行

描述了服务器响应的状态

状态行也是由三部分组成

  1. 版本号:表示报文使用的 HTTP 协议版本;
  2. 状态码:一个三位数,用代码的形式表示处理的结果,好比 200 是成功,500 是服务器错误;
  3. 缘由:做为数字状态码补充,是更详细的解释文字,帮助人理解缘由。

头部字段

请求行或状态行再加上头部字段集合就构成了 HTTP 报文里完整的请求头或响应头

请求行

状态行

2019-09-18

数据结构(链表)

它并不须要一块连续的内存空间,它经过“指针”将一组零散的内存块串联起来使用

内存分配

结点

链表经过指针将一组零散的内存块串联在一块儿。其中,咱们把内存块称为链表的“结点”

单链表

其中有两个结点是比较特殊的,它们分别是第一个结点和最后一个结点。咱们习惯性地把第一个结点叫做头结点,把最后一个结点叫做尾结点。其中,头结点用来记录链表的基地址。有了它,咱们就能够遍历获得整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。

循环链表

循环链表的尾结点指针是指向链表的头结点

双向链表

双向链表须要额外的两个空间来存储后继结点和前驱结点的地址。因此,若是存储一样多的数据,双向链表要比单链表占用更多的内存空间。

链表 VS 数组性能大比拼

数组中随机访问,指的是按下标的随机访问

js实现一个链表(知足CRUD)

大体的结构以下:

size和head为LinkedList构造函数私有属性,size记录链表中有多少个节点,head指向链表的头结点

单向链表的代码实现

/**
 * 自定义链表:对外公开的方法有
 * append(element) 在链表最后追加节点
 * insert(index, element) 根据索引index, 在索引位置插入节点
 * remove(element)  删除节点
 * removeAt(index)  删除指定索引节点
 * removeAll(element) 删除全部匹配的节点
 * set(index, element) 根据索引,修改对应索引的节点值
 * get(index)  根据索引获取节点信息
 * indexOf(element) 获取某个节点的索引位置
 * clear()  清空全部节点
 * length()   返回节点长度
 * print() 打印全部节点信息
 * toString() 打印全部节点信息,同print
 * */
const LinkedList = function(){
    let head = null;
    let size = 0;   //记录链表元素个数

    //Node模型
    function LinkNode(element, next){
        this.element = element;
        this.next = next;
    }

    //元素越界检查, 越界抛出异常
    function outOfBounds(index){
        if (index < 0 || index >= size){
            throw("抱歉,目标位置不存在!");
        }
    }

    //根据索引,获取目标对象
    function node(index){
        outOfBounds(index);

        let obj = head;
        for (let i = 0; i < index; i++){
            obj = obj.next;
        }

        return obj;
    }

    //新增一个元素
     function append(element){
        if (size == 0){
            head = new LinkNode(element, null);
        }
        else{
            let obj = node(size-1);
            obj.next = new LinkNode(element, null);
        }
         size++;
    }

    //插入一个元素
     function insert(index, element){
        if (index == 0){
            head = new LinkNode(element, head);
        }
        else{
            let obj = node(index-1);
            obj.next = new LinkNode(element, obj.next);
        }
         size++;
    }

    //修改元素
    function set(index, element){
        let obj = node(index);
        obj.element = element;
    }

    //根据值移除节点元素
    function remove(element){
        if (size < 1) return null;

        if (head.element == element){
            head = head.next;
            size--;
            return element;
        }
        else{
            let temp = head;
            while(temp.next){
                if (temp.next.element == element){
                    temp.next = temp.next.next;
                    size--;
                    return element;
                }
                else{
                    temp = temp.next;
                }
            }
        }
        return null;
    }

    //根据索引移除节点
     function removeAt(index){
         outOfBounds(index);
         let element = null;

         if (index == 0){
             element = head.element;
             head = head.next;
         }
         else{
             let prev = node(index-1);
             element = prev.next.element;
             prev.next = prev.next.next;
         }
         size--;
        return element;
    }

    //移除链表里面的全部匹配值element的元素
     function removeAll(element){

        let virHead = new LinkNode(null, head); //建立一个虚拟头结点,head为次节点
         let tempNode = virHead, ele = null;

         while(tempNode.next){
             if (tempNode.next.element == element){
                 tempNode.next = tempNode.next.next;
                 size--;
                 ele = element;
             }
             else{
                tempNode = tempNode.next;
             }
         }

         //从新赋值
         head = virHead.next;

        return ele;
    }

    //获取某个元素
    function get(index){
        return node(index).element;
    }

    //获取元素索引
    function indexOf(element){
        let obj = head, index = -1;

        for (let i = 0; i < size; i++){
            if (obj.element == element){
                index = i;
                break;
            }
            obj = obj.next;
        }
        return index;
    }

    //清除全部元素
    function clear(){
        head = null;
        size = 0;
    }

    //属性转字符串
    function getObjString(obj){

        let str = "";

        if (obj instanceof Array){
            str += "[";
            for (let i = 0; i < obj.length; i++){
                str += getObjString(obj[i]);
            }
            str = str.substring(0, str.length - 2);
            str += "], "
        }
        else if (obj instanceof Object){
            str += "{";
            for (var key in obj){
                let item = obj[key];
                str += "\"" + key + "\": " + getObjString(item);
            }
            str = str.substring(0, str.length-2);
            str += "}, "
        }
        else if (typeof obj == "string"){
            str += "\"" + obj + "\"" + ", ";
        }
        else{
            str += obj + ", ";
        }

        return str;
    }
    function toString(){
        let str = "", obj = head;
        for (let i = 0; i < size; i++){
            str += getObjString(obj.element);
            obj = obj.next;
        }
        if (str.length > 0) str = str.substring(0, str.length -2);
        return str;
    }
    //打印全部元素
    function print(){
        console.log(this.toString())
    }

    //对外公开方法
    this.append = append;
    this.insert = insert;
    this.remove = remove;
    this.removeAt = removeAt;
    this.removeAll = removeAll;
    this.set = set;
    this.get = get;
    this.indexOf = indexOf;
    this.length = function(){
        return size;
    }
    this.clear = clear;
    this.print = print;
    this.toString = toString;
}


////测试
// let obj = new LinkedList();
// let obj1 = { title: "全明星比赛", stores: [{name: "张飞vs岳飞", store: "2:3"}, { name: "关羽vs秦琼", store: "5:5"}]};
//
// obj.append(99);
// obj.append("hello")
// obj.append(true)
// obj.insert(3, obj1);
// obj.insert(0, [12, false, "Good", 81]);
// obj.print();
// console.log("obj1.index: ", obj.indexOf(obj1));
// obj.remove(0);
// obj.removeAll(obj1);
// obj.print();

////测试2
console.log("\n\n......test2.....")
var obj2 = new LinkedList();
obj2.append(8); obj2.insert(1,99); obj2.append('abc'); obj2.append(8); obj2.append(false);
obj2.append(12); obj2.append(8); obj2.append('123'); obj2.append(8);
obj2.print();
obj2.removeAll(8); //删除全部8
obj2.print();
复制代码

反转单向链表

/**
 *
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
 
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let prev = null;
    let curr = head;//定义"哨兵"
    while (curr != null) {
        let nextTemp = curr.next;//暂存剩余链表
        curr.next = prev;//与剩余链表断开链接,并将指向变动
        prev = curr;//两个节点交互位置
        curr = nextTemp;//指针下移
    }
    return prev;

};
复制代码

网址格式解析

统一资源标识符(Uniform Resource Identifier)。由于它常常出如今浏览器的地址栏里,因此俗称为“网络地址”,简称“网址”。

URI 的格式

URI 本质上是一个字符串,这个字符串的做用是惟一地标记资源的位置或者名字。

具体格式以下:

note: user:passwd@ 已经不推荐使用了

URI编码

URI 里只能使用 ASCII 码

URI 引入了编码机制,对于 ASCII 码之外的字符集和特殊字符作一个特殊的操做,把它们转换成与 URI 语义不冲突的形式。这在 RFC 规范里称为“escape”和“unescape”,俗称“转义”。

响应状态码的使用

响应状态行格式以下:

状态码

RFC 标准把状态码分红了五类

  • 1××:提示信息,表示目前是协议处理的中间状态,还须要后续的操做;
  • 2××:成功,报文已经收到并被正确处理;经常使用的有 200(OK)、204(同200,但响应头后没有 body 数据)、206(分块下载或断点续传的基础)
  • 3××:重定向,资源位置发生变更,须要客户端从新发送请求;经常使用的有301(这次请求的资源已经不存在了,须要改用改用新的 URI 再次访问)、302(请求的资源还在,但须要暂时用另外一个 URI 来访问)、304( If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制{缓存重定向})
  • 4××:客户端错误,请求报文有误,服务器没法处理;经常使用的400(通用的错误码,表示请求报文有错误)、403(服务器禁止访问资源)、404(资源在本服务器上未找到)
  • 5××:服务器错误,服务器在处理请求时内部发生了错误。经常使用的500(通用的错误码)、501(客户端请求的功能还不支持)、502(服务器做为网关或者代理时返回的错误码,表示服务器自身工做正常,访问后端服务器时发生了错误)、503(服务器当前很忙,暂时没法响应服务)

HTTP特色和优缺点

特定

  1. HTTP 是灵活可扩展的,能够任意添加头字段实现任意功能;
  2. HTTP 是可靠传输协议,基于 TCP/IP 协议“尽可能”保证数据的送达;
  3. HTTP 是应用层协议,比 FTP、SSH 等更通用功能更多,可以传输任意数据;
  4. HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
  5. HTTP 本质上是无状态的,每一个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息。

优缺点

  1. HTTP 最大的优势是简单、灵活和易于扩展;
  2. HTTP 拥有成熟的软硬件环境,应用的很是普遍,是互联网的基础设施;
  3. HTTP 是无状态的,能够轻松实现集群化,扩展性能,但有时也须要用 Cookie 技术来实现“有状态”;
  4. HTTP 是明文传输,数据彻底肉眼可见,可以方便地研究分析,但也容易被窃听;
  5. HTTP 是不安全的,没法验证通讯双方的身份,也不能判断报文是否被窜改;
  6. HTTP 的性能不算差,但不彻底适应如今的互联网,还有很大的提高空间

2019-09-19

HTTP的实体数据

数据类型(MIME type)

在 TCP/IP 协议栈里,传输数据基本上都是“header+body”的格式。

MIME:多用途互联网邮件扩展”(Multipurpose Internet Mail Extensions)用于发送 ASCII 码之外的任意数据

MIME 把数据分红了八大类,每一个大类下再细分出多个子类,形式是type/subtype的字符串。

经常使用的以下:

  1. text:即文本格式的可读数据,咱们最熟悉的应该就是 text/html 了,表示超文本文档,此外还有纯文本 text/plain、样式表 text/css 等。
  2. image:即图像文件,有 image/gif、image/jpeg、image/png 等。
  3. audio/video:音频和视频数据,例如 audio/mpeg、video/mp4 等。
  4. application:数据格式不固定,多是文本也多是二进制,必须由上层应用程序来解释。常见的有 application/json,application/javascript、application/pdf 等,另外,若是实在是不知道数据是什么类型,像刚才说的“黑盒”,就会是 application/octet-stream,即不透明的二进制数据

数据编码( Encoding type)

HTTP 在传输时为了节约带宽,有时候还会压缩数据,有一个“Encoding type”,告诉数据是用的什么编码格式,这样对方才能正确解压缩,还原出原始的数据。

  1. gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
  2. deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
  3. br:一种专门为 HTTP 优化的新压缩算法(Brotli)

数据类型使用的头字段

有了 MIME type 和 Encoding type,不管是浏览器仍是服务器就均可以轻松识别出 body 的类型,也就可以正确处理数据了。

HTTP 协议为此定义了两个 Accept 请求头字段和两个** Content** 实体头字段,用于客户端和服务器进行“内容协商”。也就是说,客户端用 Accept 头告诉服务器但愿接收什么样的数据,而服务器用 Content 头告诉客户端实际发送了什么样的数据。

Accept字段标记的是客户端可理解的 MIME type,能够用“,”作分隔符列出多个类型,让服务器有更多的选择余地。

相应的,服务器会在响应报文里用头字段Content-Type告诉实体数据的真实类型。

语言类型

MIME type 和 Encoding type 解决了计算机理解 body 数据的问题,但互联网遍及全球,不一样国家不一样地区的人使用了不少不一样的语言,虽然都是 text/html,但如何让浏览器显示出每一个人均可理解可阅读的语言文字呢?

这实际上就是“国际化”的问题。HTTP 采用了与数据类型类似的解决方案,又引入了两个概念:语言类型与字符集

语言类型:人类使用的天然语言,例如英语、汉语、日语等,而这些天然语言可能还有下属的地区性方言,因此在须要明确区分的时候也要使用“type-subtype”的形式,不过这里的格式与数据类型不一样,分隔符不是/,而是-

语言类型使用的头字段

HTTP 协议也使用 Accept 请求头字段和 Content 实体头字段,用于客户端和服务器就语言与编码进行“内容协商”。

Accept-Language字段标记了客户端可理解的天然语言,也容许用“,”作分隔符列出多个类型,例如:

Accept-Language: zh-CN, zh, en
复制代码

相应的,服务器应该在响应报文里用头字段Content-Language告诉客户端实体数据使用的实际语言类型:

Content-Language: zh-CN
复制代码

字符集在 HTTP 里使用的请求头字段是Accept-Charset响应头里却没有对应的 Content-Charset,而是在Content-Type字段的数据类型后面用charset=xxx来表示,这点须要特别注意。

要点汇总

  1. 数据类型表示实体数据的内容是什么,使用的是 MIME type,相关的头字段是 Accept 和 Content-Type;
  2. 数据编码表示实体数据压缩方式,相关的头字段是 Accept-Encoding 和 Content-Encoding;
  3. 语言类型表示实体数据的天然语言,相关的头字段是 Accept-Language 和 Content-Language;
  4. 字符集表示实体数据的编码方式,相关的头字段是 Accept-Charset 和 Content-Type;
  5. 客户端须要在请求头里使用 Accept 等头字段与服务器进行“内容协商”,要求服务器返回最合适的数据;
  6. Accept 等头字段能够用“,”顺序列出多个可能的选项,还能够用“;q=”参数来精确指定权重

HTTP传输大文件的方法

数据压缩

一般浏览器在发送请求时都会带着Accept-Encoding头字段,里面是浏览器支持的压缩格式列表,例如 gzip、deflate、br 等,这样服务器就能够从中选择一种压缩算法,放进Content-Encoding响应头里,再把原数据压缩后发给浏览器。

分块传输(服务端化整为零)

压缩是把大文件总体变小,若是大文件总体不能变小,那就把它拆开,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原

这种化整为零的思路在 HTTP 协议里就是chunked分块传输编码,在响应报文里用头字段Transfer-Encoding: chunked来表示,意思是报文里的 body 部分不是一次性发过来的,而是分红了许多的块(chunk)逐个发送。

分块传输也能够用于流式数据,例如由数据库动态生成的表单页面,这种状况下 body 数据的长度是未知的,没法在头字段Content-Length里给出确切的长度,因此也只能用 chunked 方式分块发送。

Transfer-Encoding: chunkedContent-Length这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)。

分块传输的编码规则

  1. 每一个分块包含两个部分,长度头数据块
  2. 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
  3. 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
  4. 最后用一个长度为 0 的块表示结束,即“0\r\n\r\n”。

范围请求(客户端化整为零)

有了分块传输编码,服务器就能够轻松地收发大文件了,可是若是想获取一个大文件其中的片断数据,而分块传输并无这个能力。

HTTP 协议为了知足这样的需求,提出了范围请求(range requests)的概念,容许客户端在请求头里使用专用字段来表示只获取文件的一部分,至关因而客户端的“化整为零”

范围请求不是 Web 服务器必备的功能,能够实现也能够不实现,因此服务器必须在响应头里使用字段Accept-Ranges: bytes明确告知客户端,支持范围请求。

请求头Range是 HTTP 范围请求的专用字段,格式是bytes=x-y,其中的 x 和 y 是以字节为单位的数据范围。

x、y 表示的是“偏移量”,范围必须从 0 计数。

服务器收到 Range 字段后,须要作四件事。

  • 第一,它必须检查范围是否合法,好比文件只有 100 个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码416,意思是“你的范围请求有误,我没法处理,请再检查一下”。

  • 第二,若是范围正确,服务器就能够根据 Range 头计算偏移量,读取文件的片断了,返回状态码206 Partial Content,和 200 的意思差很少,但表示 body 只是原数据的一部分。

  • 第三,服务器要添加一个响应头字段Content-Range,告诉片断的实际偏移量和资源的总大小,格式是bytes x-y/length,与 Range 头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请求,值就是“bytes 0-10/100”。

  • 最后剩下的就是发送数据了,直接把片断用** TCP** 发给客户端,一个范围请求就算是处理完了。

多段数据

请求报表

刚才说的范围请求一次只获取一个片断,其实它还支持在 Range 头里使用多个x-y,一次性获取多个片断数据。

响应报文

这种状况须要使用一种特殊的 MIME 类型:multipart/byteranges,表示报文的 body 是由多段字节序列组成的,而且还要用一个参数boundary=xxx给出段之间的分隔标记。

要点汇总

  1. **压缩 **HTML 等文本文件是传输大文件最基本的方法;
  2. 分块传输能够流式收发数据,节约内存和带宽,使用响应头字段Transfer-Encoding: chunked来表示,分块的格式是 16 进制长度头 + 数据块;
  3. 范围请求能够只获取部分数据,即“分块请求”,实现视频拖拽或者断点续传,使用请求头字段“Range”和响应头字段“Content-Range”,响应状态码必须是 206
  4. 也能够一次请求多个范围,这时候响应报文的数据类型是“multipart/byteranges”,body 里的多个部分会用 boundary 字符串分隔

数据结构(栈)

关于,有一个很是贴切的例子,就是一摞叠在一块儿的盘子。咱们平时放盘子的时候,都是从下往上一个一个放;取的时候,咱们也是从上往下一个一个地依次取,不能从中间任意抽出。后进者先出,先进者后出,这就是典型的“栈”结构

从栈的操做特性上来看

栈是一种“操做受限”的线性表,只容许在一端插入和删除数据。

实际上,栈既能够用数组来实现,也能够用链表来实现。用数组实现的栈,咱们叫做顺序栈,用链表实现的栈,咱们叫做链式栈。(这也仅仅针对的是C,C++,JAVA等静态类型语言)

函数调用栈

操做系统给每一个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量做为一个栈帧入栈,当被调用函数执行完成,返回以后,将这个函数对应的栈帧出栈。

function main() {
   let a = 1; 
   let ret = 0;
   let res = 0;
   ret = add(3, 5);
   res = a + ret;
   console.log(`处理结果为 ${res}`);
   reuturn 0;
}
 
function add( x, y) {
   let sum = 0;
   sum = x + y;
   return sum;
}
复制代码

图中显示的是,在执行到 add() 函数时,函数调用栈的状况。

栈在表达式求值中的应用

实际上,编译器就是经过两个栈来实现的。其中一个保存操做数的栈,另外一个是保存运算符的栈。咱们从左向右遍历表达式,当遇到数字,咱们就直接压入操做数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。

将 3+5*8-6 这个表达式的计算过程画成了一张图,以下:

###实现浏览器的前进、后退功能

使用两个栈,X 和 Y,咱们把首次浏览的页面依次压入栈 X,当点击后退按钮时,再依次从栈 X 中出栈,并将出栈的数据依次放入栈 Y。当咱们点击前进按钮时,咱们依次从栈 Y 中取出数据,放入栈 X 中。当栈 X 中没有数据时,那就说明没有页面能够继续后退浏览了。当栈 Y 中没有数据,那就说明没有页面能够点击前进按钮浏览了。

入栈操做

  1. 好比顺序查看了 a,b,c 三个页面,依次把 a,b,c 压入栈,这个时候,两个栈的数据就是这个样子:
    2.当你经过浏览器的后退按钮,从页面 c 后退到页面 a 以后,依次把 c 和 b 从栈 X 中弹出,而且依次放入到栈 Y。这个时候,两个栈的数据就是这个样子:

如此反复如上操做,就能够简单解释浏览器回退和前进的操做步骤。

内存中的堆栈vs数据结构堆栈

内存中的堆栈和数据结构堆栈不是一个概念,能够说内存中的堆栈是真实存在物理区数据结构中的堆栈是抽象的数据存储结构

内存空间

内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。

  1. 代码区:存储方法体的二进制代码。高级调度(做业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
  2. 静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
  3. 栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
  4. 堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。

2019-09-21

HTTP的链接管理

短链接

HTTP 协议最初(0.9/1.0)是个很是简单的协议,通讯过程也采用了简单的请求 - 应答方式。

它底层的数据传输基于 TCP/IP,每次发送请求须要先与服务器创建链接,收到响应报文后会当即关闭链接。

由于客户端与服务器的整个链接过程很短暂,不会与服务器保持长时间的链接状态,因此就被称为短链接(short-lived connections)。早期的 HTTP 协议也被称为是无链接的协议。

短链接的缺点

由于在 TCP 协议里,创建链接和关闭链接都是很是“昂贵”的操做。TCP 创建链接要有“三次握手”,发送 3 个数据包,须要 1 个 RTT;关闭链接是“四次挥手”,4 个数据包须要 2 个 RTT。

而 HTTP 的一次简单“请求 - 响应”一般只须要 4 个包,若是不算服务器内部的处理时间,最可能是 2 个 RTT。这么算下来,浪费的时间就是“3÷5=60%”,有三分之二的时间被浪费掉了,传输效率低得惊人。

RTT(Round-Trip Time):往返时延。是指数据从网络一端传到另外一端所需的时间。一般,时延由发送时延、传播时延、排队时延、处理时延四个部分组成。

长链接

针对短链接暴露出的缺点,HTTP 协议就提出了长链接的通讯方式,也叫持久链接(persistent connections)、链接保活(keep alive)、链接复用(connection reuse)。

其实解决办法也很简单,用的就是成本均摊的思路,既然 TCP 的链接和关闭很是耗时间,那么就把这个时间成本由原来的一个“请求 - 应答”均摊到多个“请求 - 应答”上。

一个短链接与长链接的对比示意图。

长链接相关的头字段

因为长链接对性能的改善效果很是显著,因此在 HTTP/1.1 中的链接都会默认启用长链接。不须要用什么特殊的头字段指定,只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的 TCP 链接,也就是长链接,在这个链接上收发数据。

固然,咱们也能够在请求头里明确地要求使用长链接机制,使用的字段是Connection,值是keep-alive

服务器支持长链接,它总会在响应报文里放一个Connection: keep-alive字段。

长连接的"缺点"

由于 TCP 链接长时间不关闭,服务器必须在内存里保存它的状态,这就占用了服务器的资源。若是有大量的空闲长链接只连不发,就会很快耗尽服务器的资源,致使服务器没法为真正有须要的用户提供服务。

长链接也须要在恰当的时间关闭,不能永远保持与服务器的链接,这在客户端或者服务器均可以作到。

客户端,能够在请求头里加上Connection: close字段,告诉服务器:“此次通讯后就关闭链接”。

服务器端一般不会主动关闭链接,但也能够使用一些策略。拿 Nginx 来举例,它有两种方式:

  1. 使用keepalive_timeout指令,设置长链接的超时时间,若是在一段时间内链接上没有任何数据收发就主动断开链接,避免空闲链接占用系统资源。
  2. 使用keepalive_requests指令,设置长链接上可发送的最大请求次数。好比设置成 1000,那么当 Nginx 在这个链接上处理了 1000 个请求后,也会主动断开链接。

队头阻塞

队头阻塞与短链接和长链接无关,而是由 HTTP 基本的请求 - 应答模型所致使的。

由于 HTTP 规定报文必须是一发一收,这就造成了一个先进先出的“串行”队列。队列里的请求没有轻重缓急的优先级,只有入队的前后顺序,排在最前面的请求被最优先处理。

要点汇总

  1. 早期的 HTTP 协议使用短链接,收到响应后就当即关闭链接,效率很低;
  2. HTTP/1.1 默认启用长链接,在一个链接上收发多个请求响应,提升了传输效率;
  3. 服务器会发送Connection: keep-alive字段表示启用了长链接;
  4. 报文头里若是有“Connection: close”就意味着长链接即将关闭;
  5. 过多的长链接会占用服务器资源,因此服务器会用一些策略有选择地关闭长链接;
  6. 队头阻塞问题会致使性能降低,能够用并发链接域名分片技术缓解。

HTTP的重定向和跳转

主动跳转:跳转动做是由浏览器的使用者主动发起(经过<a/>标签进行页面跳转)

被动跳转:服务器来发起的,浏览器使用者没法控制,这在 HTTP 协议里有个专门的名词,叫作重定向(Redirection)。

重定向的过程

一次重定向实际上发送了两次 HTTP 请求,第一个请求返回了 302,而后第二个请求就被重定向到了目标地址(URI)

Location字段属于响应字段,必须出如今响应报文里。但只有配合 301/302 状态码才有意义,它标记了服务器要求重定向的** URI**。

Location里的 URI 既能够使用绝对 URI,也能够使用相对 URI

绝对 URI:就是完整形式的 URI,包括 scheme、host:port、path 等。

相对 URI:就是省略了 scheme 和 host:port,只有 path 和 query 部分,是不完整的,但能够从请求上下文里计算获得。

重定向状态码

最多见的重定向状态码就是 301 和 302

  1. 301俗称“永久重定向”(Moved Permanently),意思是原 URI 已经“永久”性地不存在了,从此的全部请求都必须改用新的 URI。
  2. 302俗称“临时重定向”(“Moved Temporarily”),意思是原 URI 处于“临时维护”状态,新的 URI 是起“顶包”做用的“临时工”。

要点汇总

  1. 重定向是服务器发起的跳转,要求客户端改用新的 URI从新发送请求,一般会自动进行,用户是无感知的;
  2. 301/302 是最经常使用的重定向状态码,分别是“永久重定向”和“临时重定向”;
  3. 响应头字段 Location 指示了要跳转的 URI,能够用绝对或相对的形式;
  4. 重定向能够把一个 URI 指向另外一个 URI,也能够把多个 URI 指向同一个 URI,用途不少;
  5. 使用重定向时须要小心性能损耗,还要避免出现循环跳转。

HTTP的Cookie机制

HTTP 是无状态的,这既是优势也是缺点。优势是服务器没有状态差别,能够很容易地组成集群,而缺点就是没法支持须要记录状态的事务操做

Cookie 是服务器委托浏览器存储的一些数据,让服务器有了“记忆能力”;

Cookie 的工做过程

响应头字段Set-Cookie和请求头字段Cookie。

当用户经过浏览器第一次访问服务器的时候,服务器确定是不知道他的身份的。因此,就要建立一个独特的身份标识数据,格式是key=value,而后放进 Set-Cookie 字段里,随着响应报文一同发给浏览器。

浏览器收到响应报文,看到里面有 Set-Cookie,知道这是服务器给的身份标识,因而就保存起来,下次再请求的时候就自动把这个值放进 Cookie 字段里发给服务器

由于第二次请求里面有了 Cookie 字段,服务器就知道这个用户不是新人,以前来过,就能够拿出 Cookie 里的值,识别出用户的身份,而后提供个性化的服务。

不过由于服务器的“记忆能力”实在是太差,一张小纸条常常不够用。因此,服务器有时会在响应头里添加多个 Set-Cookie,存储多个“key=value”。但浏览器这边发送时不须要用多个 Cookie 字段,只要在一行里用“;”隔开就行。

从这张图中咱们也可以看到, Cookie 是由浏览器负责存储的,而不是操做系统。因此,它是 浏览器绑定的,只能在本浏览器内生效。

Cookie 的属性

Cookie 就是服务器委托浏览器存储在客户端里的一些数据,而这些数据一般都会记录用户的关键识别信息。因此,就须要在“key=value”外再用一些手段来保护,防止外泄或窃取,这些手段就是** Cookie 的属性**。

设置 Cookie 的生存周期

让它只能在一段时间内可用,就像是食品的“保鲜期”,一旦超过这个期限浏览器就认为是 Cookie 失效,在存储里删除,也不会发送给服务器

Cookie 的有效期能够使用 Expires 和** Max-Age **两个属性来设置。

Expires俗称过时时间,用的是绝对时间点,能够理解为“截止日期”(deadline)。

Max-Age用的是相对时间单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就能够获得失效的绝对时间。

Expires 和 Max-Age 能够同时出现,二者的失效时间能够一致,也能够不一致,但浏览器会优先采用 Max-Age 计算失效期

设置 Cookie 的做用域

让浏览器仅发送给特定的服务器和 URI,避免被其余网站盗用。

做用域的设置比较简单,DomainPath指定了 Cookie 所属的域名和路径,浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。若是不知足条件,就不会在请求头里发送 Cookie。

Cookie 的安全性

在 JS 脚本里能够用 document.cookie来读写 Cookie 数据,这就带来了安全隐患,有可能会致使跨站脚本(XSS)攻击窃取数据。

属性HttpOnly会告诉浏览器,此 Cookie 只能经过浏览器 HTTP 协议传输,禁止其余方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API,脚本攻击也就无从谈起了。

另外一个属性SameSite能够防范跨站请求伪造(XSRF)攻击,设置成SameSite=Strict能够严格限定 Cookie 不能随着跳转连接跨站发送.

Cookie 的应用

  1. Cookie 最基本的一个用途就是身份识别,保存用户的登陆信息,实现会话事务。
  2. Cookie 的另外一个常见用途是广告跟踪。

2019-09-23

HTTP的缓存控制

缓存(Cache)是计算机领域里的一个重要概念,是优化系统性能的利器。

基于请求 - 应答模式的特色,能够大体分为客户端缓存服务器端缓存

服务器的缓存控制

HTTP缓存流程 就是:

  1. 浏览器发现缓存无数据,因而发送请求,向服务器获取资源;
  2. 服务器响应请求,返回资源,同时标记资源的有效期
  3. 浏览器缓存资源,等待下次重用。

服务器标记资源有效期使用的头字段是Cache-Control,里面的值max-age=x就是资源的有效时间,至关于告诉浏览器,“这个页面只能缓存 x 秒,以后就算是过时,不能用。”

Cache-Control字段里的max-age和 Cookie 有点像,都是标记资源的有效期

这里的max-age是“生存时间”(又叫“新鲜度”“缓存寿命”,相似 TTL,Time-To-Live),时间的计算起点是响应报文的建立时刻(即 Date 字段,也就是离开服务器的时刻),而不是客户端收到报文的时刻,也就是说包含了在链路传输过程当中全部节点所停留的时间

max-age是 HTTP 缓存控制最经常使用的属性,此外在响应报文里还能够用其余的属性来更精确地指示浏览器应该如何使用缓存:

  • no_store:不容许缓存,用于某些变化很是频繁的数据,例如秒杀页面;
  • no_cache:它的字面含义容易与 no_store 搞混,实际的意思并非不容许缓存,而是能够缓存,但在使用以前必需要去服务器验证是否过时,是否有最新的版本;
  • must-revalidate:又是一个和 no_cache 类似的词,它的意思是若是缓存不过时就能够继续使用,但过时了若是还想用就必须去服务器验证。

服务器的缓存控制策略画了一个流程图,

客户端的缓存控制

不止服务器能够发Cache-Control头,浏览器也能够发Cache-Control,也就是说请求 - 应答的双方均可以用这个字段进行缓存控制,互相协商缓存的使用策略。

当你点刷新按钮的时候,浏览器会在请求头里加一个Cache-Control: max-age=0。由于 max-age 是“生存时间”,max-age=0 的意思就是我要最新的数据,而本地缓存里的数据至少保存了几秒钟,因此浏览器就不会使用缓存,而是向服务器发请求。服务器看到 max-age=0,也就会用一个最新生成的报文回应浏览器。

Ctrl+F5 的强制刷新,它实际上是发了一个Cache-Control: no-cache,含义和**max-age=**0基本同样,就看后台的服务器怎么理解,一般二者的效果是相同的。

条件请求

浏览器用Cache-Control作缓存控制只能是刷新数据,不能很好地利用缓存数据,又由于缓存会失效,使用前还必需要去服务器验证是不是最新版。

HTTP 协议就定义了一系列If开头的条件请求字段,专门用来检查验证资源是否过时

条件请求一共有 5 个头字段,咱们最经常使用的是if-Modified-Since和If-None-Match这两个。须要第一次的响应报文预先提供Last-modifiedETag,而后第二次请求时就能够带上缓存里的原值,验证资源是不是最新的。

若是资源没有变,服务器就回应一个304 Not Modified,表示缓存依然有效,浏览器就能够更新一下有效期,而后放心大胆地使用缓存了。

ETag 是实体标签(Entity Tag)的缩写,是资源的一个惟一标识,主要是用来解决修改时间没法准确区分文件变化的问题。

ETag 还有“强”“弱”之分。 强 ETag 要求资源在字节级别必须彻底相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如 HTML 里的标签顺序调整,或者多了几个空格)。

Cache vs Cookie

相同点

都会保存到浏览器中,并能够设置过时时间。

不一样点

  1. Cookie 会随请求报文发送到服务器,而 Cache 不会,但可能会携带 if-Modified-Since(保存资源的最后修改时间)和 If-None-Match(保存资源惟一标识) 字段来验证资源是否过时。
  2. Cookie 在浏览器能够经过脚本获取(若是 cookie 没有设置 HttpOnly),Cache 则没法在浏览器中获取(出于安全缘由)。
  3. Cookie 经过响应报文的 Set-Cookie 字段得到,Cache缓存的是完整的报文。
  4. 用途不一样。Cookie 经常使用于身份识别,Cache 则是由浏览器管理,用于节省带宽和加快响应速度。
  5. Cookie 的 max-age 是从浏览器拿到响应报文时开始计算的,而 Cache 的 max-age 是从响应报文的生成时间(Date 头字段)开始计算。

要点汇总

  1. 缓存是优化系统性能的重要手段,HTTP 传输的每个环节中均可以有缓存;
  2. 服务器使用Cache-Control设置缓存策略,经常使用的是max-age,表示资源的有效期;
  3. 浏览器收到数据就会存入缓存,若是没过时就能够直接使用,过时就要去服务器验证是否仍然可用;
  4. 验证资源是否失效须要使用条件请求,经常使用的是if-Modified-SinceIf-None-Match,收到 304 就能够复用缓存里的资源;
  5. 验证资源是否被修改的条件有两个:Last-modifiedETag,须要服务器预先在响应报文里设置,搭配条件请求使用;
  6. 浏览器也能够发送Cache-Control字段,使用max-age=0或no_cache刷新数据

HTTP的代理服务

引入 HTTP 代理后,原来简单的双方通讯就变复杂了一些,加入了一个或者多个中间人,但总体上来看,仍是一个有顺序关系的链条,并且链条里相邻的两个角色仍然是简单的一对一通讯,不会出现越级的状况。

链条的起点仍是客户端(也就是浏览器),中间的角色被称为代理服务器(proxy server),链条的终点被称为源服务器(origin server),意思是数据的“源头”“起源”。

代理服务

代理服务就是指服务自己不生产内容,而是处于中间位置转发上下游的请求和响应,具备双重身份:面向下游的用户时,表现为服务器,表明源服务器响应客户端的请求;而面向上游的源服务器时,又表现为客户端,表明客户端发送请求。

代理的做用

代理最基本的一个功能是负载均衡。由于在面向客户端时屏蔽了源服务器,客户端看到的只是代理服务器,源服务器究竟有多少台、是哪些 IP 地址都不知道。因而代理服务器就能够掌握请求分发的“大权”,决定由后面的哪台服务器来响应请求。

在负载均衡的同时,代理服务还能够执行更多的功能,好比:

  • 健康检查:使用“心跳”等机制监控后端服务器,发现有故障就及时“踢出”集群,保证服务高可用;
  • 安全防御:保护被代理的后端服务器,限制 IP 地址或流量,抵御网络攻击和过载;
  • 加密卸载:对外网使用SSL/TLS加密通讯认证,而在安全的内网不加密,消除加解密成本;
  • 数据过滤:拦截上下行的数据,任意指定策略修改请求或者响应;
  • 内容缓存:暂存、复用服务器响应。

代理相关头字段

代理服务器须要用字段Via标明代理的身份。

Via 是一个通用字段,请求头或响应头里均可以出现。每当报文通过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾,就像是经手人盖了一个章。

例以下图中有两个代理:proxy1 和 proxy2,客户端发送请求会通过这两个代理,依次添加就是“Via: proxy1, proxy2”,等到服务器返回响应报文的时候就要反过来走,头字段就是“Via: proxy2, proxy1”。

服务器的 IP 地址应该是保密的,关系到企业的内网安全,因此通常不会让客户端知道。不过反过来,一般服务器须要知道客户端的真实 IP 地址,方便作访问控制、用户画像、统计分析。

HTTP 标准里并无为此定义头字段,但已经出现了不少事实上的标准,最经常使用的两个头字段是X-Forwarded-ForX-Real-IP

X-Forwarded-For的字面意思是为谁而转发,形式上和Via差很少,也是每通过一个代理节点就会在字段里追加一个信息。但Via追加的是代理主机名(或者域名),而X-Forwarded-For追加的是请求方的 IP 地址。因此,在字段里最左边的 IP 地址就客户端的地址

X-Real-IP是另外一种获取客户端真实 IP 的手段,它的做用很简单,就是记录客户端 IP 地址,没有中间的代理信息,至关因而“X-Forwarded-For”的简化版若是客户端和源服务器之间只有一个代理,那么这两个字段的值就是相同的

要点汇总

  1. HTTP 代理就是客户端和服务器通讯链路中的一个中间环节,为两端提供代理服务
  2. 代理处于中间层,为 HTTP 处理增长了更多的灵活性,能够实现负载均衡、安全防御、数据过滤等功能;
  3. 代理服务器须要使用通用字段Via标记本身的身份,多个代理会造成一个列表;
  4. 若是想要知道客户端的真实 IP 地址,能够使用字段X-Forwarded-ForX-Real-IP
  5. 专门的“代理协议”能够在不改动原始报文的状况下传递客户端的真实 IP。

HTTP的缓存代理

在没有缓存的时候,代理服务器每次都是直接转发客户端和服务器的报文,中间不会存储任何数据,只有最简单的中转功能。

###加入了缓存后以后

代理服务收到源服务器发来的响应数据后须要作两件事。第一个固然是把报文转发给客户端,而第二个就是把报文存入本身的 Cache 里。

下一次再有相同的请求,代理服务器就能够直接发送 304 或者缓存数据,没必要再从源服务器那里获取。这样就下降了客户端的等待时间,同时节约了源服务器的网络带宽。

在 HTTP 的缓存体系中,缓存代理的身份十分特殊,它既是客户端,又是服务器,同时也既不是客户端,又不是服务器

缓存代理便是客户端又是服务器

是由于它面向源服务器时是客户端,在面向客户端时又是服务器,因此它便可以用客户端的缓存控制策略也能够用服务器端的缓存控制策略,也就是说它能够同时使用各类Cache-Control属性。

缓存代理即不是客户端又不是服务器

由于它只是一个数据的中转站,并非真正的数据消费者和生产者,因此还须要有一些新的Cache-Control属性来对它作特别的约束

服务器端的Cache-Control属性**:max-age、no_store、no_cache 和 must-revalidate这 4 种缓存属性能够约束客户端,也能够约束代理**。

但客户端和代理是不同的,客户端的缓存只是用户本身使用,而代理的缓存可能会为很是多的客户端提供服务。因此,须要对它的缓存再多一些限制条件

首先,要区分客户端上的缓存和代理上的缓存,能够使用两个新属性privatepublic

private表示缓存只能在客户端保存,是用户私有的,不能放在代理上与别人共享。而public的意思就是缓存彻底开放,谁均可以存,谁均可以用。

而后缓存失效后的从新验证也要区分开(即便用条件请求Last-modifiedETag),must-revalidate是只要过时就必须回源服务器验证,而新的proxy-revalidate只要求代理的缓存过时后必须验证,客户端没必要回源,只验证到代理这个环节就好了。

再次,缓存的生存时间能够使用新的s-maxage(s 是 share 的意思,注意 maxage 中间没有“-”),只限定在代理上可以存多久,而客户端仍然使用“max_age”。

还有一个代理专用的属性no-transform。代理有时候会对缓存下来的数据作一些优化,好比把图片生成 png、webp 等几种格式,方便从此的请求处理,而no-transform就会禁止这样作,不准“偷偷摸摸搞小动做”。

下面的流程图是完整的服务器端缓存控制策略,能够同时控制客户端和代理。

客户端的缓存控制

客户端在 HTTP 缓存体系里要面对的是代理源服务器

关于缓存的生存时间,多了两个新属性 max-stalemin-fresh

max-stale的意思是若是代理上的缓存过时了也能够接受,但不能过时太多,超过 x 秒也会不要。min-fresh的意思是缓存必须有效,并且必须在 x 秒后依然有效。

要点汇总

  1. 计算机领域里最经常使用的性能优化手段是时空转换,也就是时间换空间或者空间换时间,HTTP 缓存属于后者;
  2. 缓存代理是增长了缓存功能的代理服务,缓存源服务器的数据,分发给下游的客户端;
  3. Cache-Control字段也能够控制缓存代理,经常使用的有privates-maxageno-transform等,一样必须配合“Last-modified”“ETag”等字段才能使用;
  4. 缓存代理有时候也会带来负面影响,缓存不良数据,须要及时刷新或删除。

数据结构(队列)

队列跟栈同样,也是一种操做受限的线性表数据结构。

顺序队列和链式队列

队列跟栈同样,也是一种抽象的数据结构。它具有先进先出的特性,支持在队尾插入元素,在队头删除元素。

队列能够用数组来实现,也能够用链表来实现。用数组实现的栈叫做顺序栈,用链表实现的栈叫做链式栈。一样,用数组实现的队列叫做顺序队列,用链表实现的队列叫做链式队列

代码实现一个顺序队列

// 用数组实现的队列
const  ArrayQueue=function(){
  // 数组:items,数组大小:n
  let items =[];
  let n = 0;
  // head 表示队头下标,tail 表示队尾下标
  let head = 0;
  let tail = 0;
 
  // 申请一个大小为 capacity 的数组
  function createArrayQueue(capacity) {
    items = new Array(capacity);
    n = capacity;
  }
  
  // 入队操做,将 item 放入队尾
  function enqueue(item) {
    // tail == n 表示队列末尾没有空间了
    if (tail == n) {
      // tail ==n && head==0,表示整个队列都占满了
      if (head == 0) return false;
      // 数据搬移
      for (int i = head; i < tail; ++i) {
        items[i-head] = items[i];
      }
      // 搬移完以后从新更新 head 和 tail
      tail -= head;
      head = 0;
    }
    
    items[tail] = item;
    ++tail;
    return true;
  }
 
  // 出队
  function dequeue() {
    // 若是 head == tail 表示队列为空
    if (head == tail) return null;
    // 为了让其余语言的同窗看的更加明确,把 -- 操做放到单独一行来写了
    let ret = items[head];
    ++head;
    return ret;
  }
  
  this.createArrayQueue = createArrayQueue;
  this.enqueue = enqueue;
  this.dequeue = dequeue;
}

复制代码

循环队列

用数组实现的队列,在 tail==n 时,会有数据搬移操做,这样入队操做性能就会受到影响。

想要避免数据搬移,能够用循环队列来处理。

咱们能够看到,图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,咱们放入下标为 7 的位置。但这个时候,咱们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,咱们将 b 放入下标为 0 的位置,而后 tail 加 1 更新为 1。因此,在 a,b 依次入队以后,循环队列中的元素就变成了下面的样子:

经过这样的方法,咱们成功避免了数据搬移操做。

代码实现一个循环队列

const  CircularQueue = function {
  // 数组:items,数组大小:n
  let items;
  let n = 0;
  // head 表示队头下标,tail 表示队尾下标
  let head = 0;
  let tail = 0;
 
  // 申请一个大小为 capacity 的数组
  function createCircularQueuee(capacity) {
    items = new Array(capacity);
    n = capacity;
  }
 
  // 入队
  function enqueue(item) {
    // 队列满了
    if ((tail + 1) % n == head) return false;
    items[tail] = item;
    tail = (tail + 1) % n;
    return true;
  }
 
  // 出队
  function dequeue() {
    // 若是 head == tail 表示队列为空
    if (head == tail) return null;
    String ret = items[head];
    head = (head + 1) % n;
    return ret;
  }
  
  this.createCircularQueuee = createCircularQueuee;
  this.enqueue = enqueue;
  this.dequeue = dequeue;
}

复制代码

2019-09-24

HTTPS

HTTP 的一些缺点,其中的无状态在加入 Cookie 后获得了解决,而另两个缺点——明文不安全仅凭 HTTP 自身是无力解决的,须要引入新的 HTTPS 协议

为何要有 HTTPS?简单的回答是由于 HTTP 不安全。因为 HTTP 天生明文的特色,整个传输过程彻底透明,任何人都可以在链路中截获、修改或者伪造请求 / 响应报文,数据不具备可信性。

什么是安全

若是通讯过程具有了四个特性,就能够认为是安全的,这四个特性是:

  • 机密性(Secrecy/Confidentiality):指对数据的保密,只能由可信的人访问,对其余人是不可见的秘密,简单来讲就是不能让不相关的人看到不应看的东西。
  • 完整性 (Integrity,也叫一致性):指数据在传输过程当中没有被窜改,很少也很多,“完完整整”地保持着原状。
  • 身份认证(Authentication):是指确认对方的真实身份,也就是“证实你真的是你”,保证消息只能发送给可信的人。
  • 不能否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不可否认已经发生过的行为,不能“说话不算数”“耍赖皮”。

什么是 HTTPS

HTTPS 实际上是一个很是简单的协议,RFC 文档很小,只有短短的 7 页,里面规定了新的协议名https默认端口号 443,至于其余的什么请求 - 应答模式、报文结构、请求方法、URI、头字段、链接管理等等都彻底沿用 HTTP,没有任何新的东西。

HTTPS如何作到数据安全的

它把 HTTP 下层的传输协议由 TCP/IP 换成了** SSL/TLS**,由HTTP over TCP/IP变成了HTTP over SSL/TLS,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文再也不使用 Socket API,而是调用专门的安全接口。

SSL/TLS

SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层)。

互联网工程组 IETF 在 1999 年把它更名为 TLS(传输层安全,Transport Layer Security),正式标准化。

TLS 由记录协议、握手协议、警告协议、变动密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。

浏览器和服务器在使用 TLS 创建链接时须要选择一组恰当的加密算法来实现安全通讯,这些算法的组合被称为密码套件(cipher suite,也叫加密套件)。

TLS 的密码套件命名很是规范,格式很固定。基本的形式是密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法

要点汇总

  1. 由于 HTTP 是明文传输,因此不安全,容易被黑客窃听或窜改;
  2. 通讯安全必须同时具有机密性、完整性,身份认证和不能否认这四个特性;
  3. HTTPS 的语法、语义仍然是 HTTP,但把下层的协议由 TCP/IP 换成了 SSL/TLS
  4. SSL/TLS 是信息安全领域中的权威标准,采用多种先进的加密技术保证通讯安全;

2019-09-25

对称加密与非对称加密

HTTPS 的安全性是由 TLS来保证的。它为 HTTP 增长了机密性、完整性,身份认证和不能否认等特性。

机密性

机密性是信息安全的基础,缺少机密性 TLS 就会成为无水之源无根之木

实现机密性最经常使用的手段是加密(encrypt),就是把消息用某种方式转换成谁也看不懂的乱码,只有掌握特殊钥匙的人才能再转换出原始文本。

这里的钥匙就叫作密钥(key),加密前的消息叫明文(plain text/clear text),加密后的乱码叫密文(cipher text),使用密钥还原明文的过程叫解密(decrypt),是加密的反操做,加密解密的操做过程就是加密算法

因为 HTTPS、TLS 都运行在计算机上,因此密钥就是一长串的数字,但约定俗成的度量单位是(bit),而不是字节(byte)。

1字节 = 8位

按照密钥的使用方式,加密能够分为两大类:对称加密非对称加密

对称加密

对称加密很好理解,就是指加密和解密时使用的密钥都是同一个,是对称的。只要保证了密钥的安全,那整个通讯过程就能够说具备了机密性。

TLS 里有很是多的对称加密算法可供选择,好比 RC四、DES、3DES、AES、ChaCha20 等。目前经常使用的只有 AESChaCha20

加密分组模式

对称算法还有一个分组模式的概念,它可让算法用固定长度的密钥加密任意长度的明文,把小秘密(即密钥)转化为大秘密(即密文)。

非对称加密

对称加密看上去好像完美地实现了机密性,但其中有一个很大的问题:如何把密钥安全地传递给对方,术语叫密钥交换

出现了非对称加密(也叫公钥加密算法)

它有两个密钥,一个叫公钥(public key),一个叫私钥(private key)。两个密钥是不一样的,不对称,公钥能够公开给任何人使用,而私钥必须严格保密。

公钥和私钥有个特别的单向性,虽然均可以用来加密解密,但公钥加密后只能用私钥解密,反过来,私钥加密后也只能用公钥解密。

非对称加密能够解决“密钥交换”的问题。网站秘密保管私钥,在网上任意分发公钥,你想要登陆网站只要用公钥加密就好了,密文只能由私钥持有者才能解密.

混合加密

非对称加密没有密钥交换的问题,但由于它们都是基于复杂的数学难题,运算速度很慢,即便是 ECC 也要比 AES 差上好几个数量级。若是仅用非对称加密,虽然保证了安全,但通讯速度很慢,实用性就变成了零。

混合加密

  • 通讯刚开始的时候使用非对称算法,好比 RSA、ECDHE,首先解决密钥交换的问题。

  • 而后用随机数产生对称算法使用的会话密钥(session key),再用公钥加密。由于会话密钥很短,一般只有 16 字节或 32 字节,因此慢一点也无所谓。

  • 对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就再也不使用非对称加密,全都使用对称加密。

要点汇总

  1. 加密算法的核心思想是把一个小秘密(密钥)转化为一个大秘密(密文消息),守住了小秘密,也就守住了大秘密;
  2. 对称加密只使用一个密钥,运算速度快,密钥必须保密,没法作到安全的密钥交换,经常使用的有 AESChaCha20
  3. 非对称加密使用两个密钥:公钥和私钥,公钥能够任意分发而私钥保密,解决了密钥交换问题但速度慢,经常使用的有 RSA 和 ECC;
  4. 把对称加密和非对称加密结合起来就获得了“又好又快”的混合加密,也就是 TLS 里使用的加密方式。

2019-09-26

数字签名与证书

对称加密和非对称加密,以及二者结合起来的混合加密,实现了机密性。仅有机密性,离安全还差的很远。

因此,在机密性的基础上还必须加上完整性、身份认证等特性,才能实现真正的安全。

摘要算法

实现完整性的手段主要是摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。

摘要算法近似地理解成一种特殊的压缩算法,它可以把任意长度的数据压缩成固定长度、并且独一无二的摘要字符串,就好像是给这段数据生成了一个数字指纹

换一个角度,也能够把摘要算法理解成特殊的单向加密算法,它只有算法,没有密钥,加密后的数据没法解密,不能从摘要逆推出原文

摘要算法其实是把数据从一个大空间映射到了小空间,因此就存在冲突(collision,也叫碰撞)的可能性,可能会有两份不一样的原文对应相同的摘要。好的摘要算法必须可以“抵抗冲突”,让这种可能性尽可能地小。

由于摘要算法对输入具备单向性雪崩效应,输入的微小不一样会致使输出的剧烈变化,因此也被 TLS 用来生成伪随机数(PRF,pseudo random function)。

目前 TLS 推荐使用的是 SHA-1 的后继者:SHA-2。

完整性

摘要算法保证了数字摘要和原文是彻底等价的。因此,咱们只要在原文后附上它的摘要,就可以保证数据的完整性。

不过摘要算法不具备机密性,若是明文传输,那么黑客能够修改消息后把摘要也一块儿改了,网站仍是鉴别不出完整性。

因此,真正的完整性必需要创建在机密性之上,在混合加密系统里用会话密钥加密消息和摘要,这样黑客没法得知明文,也就没有办法动手脚了。

这有个术语,叫哈希消息认证码(HMAC)。

数字签名

加密算法结合摘要算法,咱们的通讯过程能够说是比较安全了。但这里还有漏洞,就是通讯的两个端点(endpoint)。

使用私钥再加上摘要算法,就可以实现数字签名,同时实现“身份认证”和“不能否认”。

数字签名的原理其实很简单,就是把公钥私钥的用法反过来,以前是公钥加密、私钥解密,如今是私钥加密、公钥解密。

要点汇总

  1. 摘要算法用来实现完整性,可以为数据生成独一无二的“指纹”,经常使用的算法是 SHA-2;
  2. 数字签名是私钥对摘要的加密,能够由公钥解密后验证,实现身份认证和不能否认;
  3. 公钥的分发须要使用数字证书,必须由 CA 的信任链来验证,不然就是不可信的;
相关文章
相关标签/搜索