从输入URL到页面的渲染完成经历了什么过程


  1. 浏览器地址栏输入URL并回车
  2. 浏览器查找当前URL是否存在缓存,并比较缓存是否过期
  3. DNS解析URL对应的IP
  4. 根据IP建立TCP连接(三次握手)
  5. 发送http请求
  6. 服务器处理请求,浏览器接受HTTP响应
  7. 浏览器解析并渲染页面
  8. 关闭TCP连接(四次握手)

其中涉及的知识点

多进程的浏览器

  • 浏览器主进程:负责协调、主控,只有一个
  • 浏览器渲染进程(内核)(Render进程):每个标签页面有一个互不影响的进程,负责页面渲染、脚本执行、事件处理等
  • 第三方插件进程:每种类型的插件对应一个进程,仅当该插件被使用时才创建
  • GPU进程:最多一个,用于3D绘制

多线程浏览器内核

每个标签页的浏览器内核进程是多线程的,包括:

  • GUI渲染线程:负责解析HTML/CSS,构建DOM树/CSS规则树和render树,布局渲染,处理Reflow(回流)和Repaint(重绘)
  • JS引擎线程:负责解释执行js脚本,js引擎是浏览器内核进程中的一个单线程
  • 事件触发线程:该事件不属于JS引擎线程,用来控制事件循环,当对应的事件(如鼠标事件)符合条件被触发时,该线程会把事件添加到待处理事件队列的尾部,等待JS引擎线程的处理
  • 定时触发器线程:setInterval和setTimeout所在线程,当计时完毕时将要处理的逻辑置于事件队列中,等待JS引擎线程的处理
  • 异步HTTP请求线程:用于AJAX请求,当检测到状态(readyState、status)发生变化时,如果设有回调函数,该线程就会产生状态变化事件,将这个回调函数放入事件队列中,等待JS引擎处理

  注意:GUI渲染线程与js引擎线程互斥,当js引擎线程执行时,GUI线程会被挂起,GUI更新会被保存到事件队列中等待JS引擎线程空闲时执行,如果JS引擎线程执行时间过长,会造成页面渲染不连贯,导致页面渲染加载阻塞

URL解析:解析URL的协议、域名、端口号、路径、查询参数、哈希值等

http:不安全

https:=http+加密+认证+完整性保护

HTTPS是身披SSL外壳的HTTP:通常情况下HTTP是直接和TCP层进行通信的。当使用SSL(安全套接字)时,则演变成HTTP先和SSL通信,SSL再和TCP通信

https的好处:

  • SEO,在搜索引擎上排名更高
  • 安全性,数据的完整性,现行架构下最安全的解决方案,虽然不是绝对安全,但是大幅度增加了中间人攻击的成本
  • 缺点:费时,费资源

缓存解析:流程是先找浏览器缓存和本地缓存,如果存在则直接使用该缓存;如果不存在,则进行DNS缓存寻找,如果还是不存在,则进行DNS解析,查询出域名对应的IP地址

浏览器缓存:

  缓存位置:

  1. Service Worker:传输协议必须是https,Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。
  2. Memory Cache:内存中的缓存,会随着进程的释放而释放,即一旦关闭页面,内存中的缓存也就被释放了。当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验
  3. Disk Cache:硬盘缓存,在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache
  4. Push Cache:推送缓存,只有在上述三种缓存都没有命中时才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

  缓存种类:

  cookie:为了解决HTTP协议无状态特性的方法,从而使服务器可以跟踪会话。cookie由服务端生成,发送给User-Agent,浏览器会将cookie以key/value形式保存,下次请求同一网站时自动发送该cookie给服务器,即添加在请求头部,一般大小为4KB

    具有保质期:可以通过Expires、max-age来指定

    需要满足同源策略才能调用cookie

指定可访问cookie的主机名 和路径类似,主机名是指同一个域下的不同主机,例如:www.google.com和gmail.google.com就

是两个不同的主机名。默认情况下,一个主机中创建的cookie在另一个主机下是不能被访问的,但可以通过domain参数来实现对其的控制,其语法格式为:

document.cookie="name=value;//操作cookie

domain=cookieDomain";

以google为例,要实现跨主机访问,可以写为:

document.cookie="name=value;

domain=.google.com";

这样,所有google.com下的主机都可以访问该cookie。

  localstorage:html5的一种新的本地缓存方案,目前用的比较多,一般用来存储ajax返回的数据,加快下次页面打开时的渲染速度。但是loaclstorage大小有限制,5MB。不适合存放过多的数据,如果数据存放超过最大值会报错,并移除最先保存的数据

  

  var name = localStorage.key(i)​;

     var value = localStorage.getItem(name);​

  localStorage.removeItem("key");//删除名称为“key”的信息。

  localStorage.clear();​//清空localStorage中所有信息

  sessionstorage:和localstorage类似,但是在浏览器关闭时会被删除

  不同浏览器无法共享localstorage或sessionstorage中的信息,但相同浏览器的不同页面间可以共享相同的localstorage,但是不同标签页之间无法共享sessionstorage的信息。这里需要注意的是,页面及标签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。

  缓存策略:都是通过设置HTTP Header来实现的

  浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的,即:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中                    

 根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存

  1.强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种HTTP Header来实现:ExpiresCache-Control

  Expires:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。位于Response Header中,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存获取数据,而无需再次请求。如expires:Tue, 31 Mar 2020 14:49:57 GMT。Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

  Cache-Control:主要控制网页缓存,比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。

    参数:public:所有内容都将被缓存(客户端和代理服务器都可以缓存)

         private:所有内容只有客户端可以缓存,默认

         no-cache:不是说浏览器不再缓存数据,而是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致

         no-store:所有内容都不会被缓存,即不使用强缓存,也不使用协商缓存

         max-age:max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效

         s-maxage:同max-age作用一样,只在代理服务器中生效(比如CDN缓存)。比如当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。

         max-stale:能容忍的最大过期时间。

         min-fresh:能够容忍的最小新鲜度。

  Expires和Cache-Control对比:其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。

  2.协商缓存:强缓存判断是否缓存的依据是来自于是否超出某个时间或某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端的最新内容。想要获知服务器端内容是否已经发生更新,这时就要用到协商缓存策略

  协商缓存就是强缓存失效后,浏览器携带缓存标识向服务器发送请求,由服务器根据缓存标识决定是否使用标识的过程。主要有两种情况:

  • 协商缓存生效,返回304和Not Modified,即服务器端没有发生该资源的更新,可以使用该缓存
  • 协商缓存失效,返回200和请求结果,即从服务器端重新获取更新之后的资源,再将其缓存到浏览器缓存中,并使用

  协商缓存可以通过设置两种HTTP Header实现:Last-Modified和ETag

  Last-Modified:浏览器在第一次访问资源时,服务器返回资源的同时,会在response header中添加Last-Modified的header,值为这个资源在服务器上最后修改的时间,如Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT

        浏览器下一次请求这个资源时,这个值被放进资源请求发给服务器端,如果这个值没有改变,则返回304和Modified,即协商缓存生效,否则协商缓存失效,重新请求该资源

        但是根据文件修改时间来判断是否修改这个做法有缺陷,所以引出了根据文件内容是否修改来决定的缓存策略,即ETag

  ETag:服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

  两者对比:

  • 首先在精确度上,Etag要优于Last-Modified。

Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。

  • 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
  • 第三在优先级上,服务器校验优先考虑Etag

  缓存机制:强缓存优先于协商缓存,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

   如果什么缓存策略都没设置,那么浏览器会怎么处理:浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。

实际场景应用缓存策略

1.频繁变动的资源

Cache-Control: no-cache

对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

2.不常变化的资源

Cache-Control: max-age=31536000

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

八、用户行为对浏览器缓存的影响

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:

  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。
DNS缓存:递归查找:查找的顺序是(根域名服务器,一级域名服务器,二级域名服务器,三级域名服务器)

HTTP请求

  • HTTP请求报文是由三部分组成: 请求行, 请求头和请求正文。

请求行

Method Request-URL HTTP-Version CRLF eg: GET index.html HTTP/1.1 
  • 常用的方法有: GET, POST, PUT, DELETE, OPTIONS, HEAD。

请求头

  • 请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。
  • 常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。
  • Accept用于指定客户端用于接受哪些类型的信息,Accept-Encoding与Accept类似,它用于指定接受的编码方式。Connection设置为Keep-alive用于告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP连接建立的时间。

请求正文

当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中。在请求包头中有一些与请求正文相关的信息,例如: 现在的Web应用通常采用Rest架构,请求的数据格式一般为json。这时就需要设置Content-Type: application/json。

 

浏览器获取HTML,解析,渲染

  1. 加载解析HTML,开始构建DOM树。
  2. 遇到CSS外链,异步加载解析CSS,构建CSS规则树。
  3. 遇到script标签,如果是普通JS标签则同步加载并执行,阻塞页面渲染,如果标签上有defer/async属性则异步加载JS资源。设置defer的JS资源会在DOMContentLoaded事件之前执行;设置了async的JS资源加载完就执行。
  4. 合并DOM树和CSS规则树生成render树。
  5. 布局render树,计算各元素的尺寸、位置等,在内存上生成Bitmap。
  6. 渲染render树,将内存上的Bitmap绘制到屏幕上。
  7. 浏览器将各层信息发送给GPU,GPU将各层合成,显示在屏幕上 

  回流和重绘:

  回流:当render tree中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建的过程,成为回流(reflow),页面渲染的时候必然发生一次

  重绘:当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

  区别:回流必然引起重绘,但是重绘不一定引起回流,比如:只有颜色改变的时候就只会发生重绘而不会引起回流。

     当页面布局和几何属性改变时就需要回流,比如:添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变——边距、填充、边框、宽度和高度,内容改变

  尽量减少回流和重绘,提高性能(通过浏览器或代码优化的方式):因为,回流花销很大,所以大部分浏览器对于回流都会进行优化,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush(清空)队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

优化方案

  • 减少逐项更改样式,最好一次性更改style,或者将样式定义为class并一次性更新
  • 避免循环操作dom,创建一个documentFragment或div,在它上面应用所有DOM操作,最后再把它添加到window.document
    • (1)DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。不过它有一种特殊的行为,该行为使得它非常有用,即当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。其实他就是一个游离在DOM树外面的容器,所以你在把它插入文档节点之前,随便给他增删节点都不会引起回流
    • (2)使用display:none,只引发两次回流和重绘。道理跟上面的一样。因为display:none的元素不会出现在render树
    • (3)使用cloneNode和replaceChild技术,引发一次回流和重绘(这条其实没太明白)
  • 避免多次读取offset等属性。无法避免则将它们缓存到变量
  • 将复杂的元素绝对定位或固定定位,使得它脱离文档流,否则回流代价会很高
  • 尽量不要使用表格布局,如果没有定宽表格一列的宽度由最宽的一列决定,那么很可能在最后一行的宽度超出之前的列宽,引起整体回流造成table可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。

资源外链的下载

遇到外链时的处理

  • 遇到外链时,会单独开启一个下载线程去下载资源(http1.1中是每一个资源的下载都要开启一个http请求,对应一个tcp/ip链接)

遇到CSS样式资源

CSS资源的处理有几个特点:

  • CSS下载时异步,不会阻塞浏览器构建DOM树
  • 会阻塞渲染,也就是在构建render时,会等到css下载解析完毕后才进行(这点与浏览器优化有关,防止css规则不断改变,避免了重复的构建)
  • media query声明的CSS是不会阻塞渲染的

遇到JS脚本资源

JS脚本资源的处理有几个特点:

  • 阻塞浏览器的解析,也就是说发现一个外链脚本时,需等待脚本下载完成并执行后才会继续解析HTML
  • 浏览器的优化,一般现代浏览器有优化,在脚本阻塞时,也会继续下载其它资源(当然有并发上限),但是虽然脚本可以并行下载,解析过程仍然是阻塞的,也就是说必须这个脚本执行完毕后才会接下来的解析,并行下载只是一种优化而已
  • defer与async,普通的脚本是会阻塞浏览器解析的,但是可以加上defer或async属性,这样脚本就变成异步了,可以等到解析完毕后再执行 详见异步加载JS

遇到img图片类资源

  • 遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有src的地方

为什么要先引入CSS文件,再引入js文件?

(1)js的下载是阻塞下载,不可以和其他代码并行下载和解析。但是CSS的加载不会阻塞DOM树的解析(会阻塞其渲染,也会阻塞后面js的执行)

(2)页面加载时,是按照从上到下,从左到右的顺序加载的,如果将js放在前面,会立即执行,阻塞后面的资源下载和执行。如果外部脚本加载时间过长,就会造成网页长时间失去响应,浏览器会呈现假死状态

(3)部分js的执行依赖于前面的CSS样式

(4)js一般是处理功能,所以不需要提前加载。先跟用户观感,再给用户上手体验

loaded和domcontentloaded

简单的对比:

  • DOMContentLoaded: 事件触发时,仅当DOM加载完成,不包括样式表,图片(譬如如果有async加载的脚本就不一定完成)
  • load: 事件触发时,页面上所有的DOM,样式表,脚本,图片都已经加载完成了

为了避免用户的白屏时间,应尽可能提高CSS加载速度,方法?

  • 使用CDN(Content Delivery Network,内容分发网络),CDN会根据你的网络状况,替你挑选最近的一个具有缓存内容的节点为你提供资源,减少加载时间
  • 将CSS压缩
  • 合理使用缓存(设置cache-control,expires以及E-tag)
  • 减少HTTP请求数,多个CSS合并,或者干脆直接写成内联样式(但是内联样式的缺点是不能缓存)

JS引擎解析过程

JS的解释阶段

  • JS是解释型语言,所以它无需提前编译,而是由解释器实时运行
  • 核心的JIT编译器将源码编译成机器码运行

JS的预处理阶段

  • 分号补全
  • 变量提升

JS的执行阶段

  • 执行上下文,执行堆栈概念(如全局上下文,当前活动上下文)
  • VO(变量对象)和AO(活动对象)
  • 作用域链
  • this机制等

  

 

HTTP和HTTPS的区别

 

HTTP(Hyper Text Transfer Protocol,超文本传输协议)被用于在web浏览器和网站服务器之间传递信息,HTTP协议以明文的方式发送内容,不提供任何方式的数据加密,如果攻击者截取了web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号,密码等支付信息。

 

HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer,安全套接字超文本传输协议),为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL/TLS,依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。其中SSL(Secure Socket Layer,安全套接层)TLS(Transport Layer Securit,传输层安全协议),SSL 3.0和TLS 1.0差别很小,在HTTPS通信中具体使用哪一个还要看客户端和服务端的支持程度,二者在网络模型中位于哪一层?

 

 

 

 

 

区别:

 

(1)HTTPS协议需要CA申请证书,一般免费证书比较少,所以需要一定费用

 

(2)HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议

 

(3)HTTP和HTTPS使用的是完全不同的连接方式,使用的端口号也不一样,前者是80,后者是443

 

(4)HTTP连接很简单,是无状态的;HTTPS协议是由HTTP+SSL协议构建的可进行加密传输、身份认证的网络协议,比较安全。

 

(5)谷歌搜索引擎算法中,比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中排名会更高

 

15.客户端使用HTTPS方式与web服务器通信的步骤:

 

(1)客户使用HTTPS的URL访问web服务器,要求与web服务器建立SSL连接

 

(2)web服务器收到客户端请求后,将网站的证书信息(证书中包含公钥)传送一份给客户端;这个证书其实是一套公钥和私钥,这里把公钥给客户端,相当于锁头,私钥(唯一)服务器端保留,相当于钥匙

 

(3)客户端的浏览器与web服务器开始协商SSL连接的安全等级,也就是信息的加密等级

 

4、客户端解析证书

这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随机值,然后用证书对该随机值进行加密,就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。

5、传送加密信息

这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。

6、服务段解密信息

服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行非对称加密,所谓非对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。

对称加密:使用同样的算法和密钥对密文进行解密。加密-解密的过程完全对称,因此被称为对称密钥加密。

7、传输加密后的信息

这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。

8、客户端解密信息

客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容,整个过程第三方即使监听到了数据,也束手无策。

 

16.如何从HTTP切换到HTTPS?

 

(1)需要将页面中所有的链接(例如js,css,图片等链接)都由http改为https

 

(2)一般情况下会建议保留HTTP,所以在切换的时候可以做HTTP和HTTPS的兼容,具体实现方式是:去掉页面连接中的http头部,这样可以自动匹配HTTP头和HTTPS头

 



 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM