learn.lianglianglee.com/专栏/透视HTTP协议/08 键入网址再按下回车,后面究竟发生了什么?.md.html
2022-05-11 19:04:14 +08:00

677 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>08 键入网址再按下回车,后面究竟发生了什么?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/透视HTTP协议/00 开篇词To Be a HTTP Hero.md.html">00 开篇词To Be a HTTP Hero.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/01 时势与英雄HTTP的前世今生.md.html">01 时势与英雄HTTP的前世今生.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/02 HTTP是什么HTTP又不是什么.md.html">02 HTTP是什么HTTP又不是什么.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/03 HTTP世界全览与HTTP相关的各种概念.md.html">03 HTTP世界全览与HTTP相关的各种概念.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/04 HTTP世界全览与HTTP相关的各种协议.md.html">04 HTTP世界全览与HTTP相关的各种协议.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/05 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?.md.html">05 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/06 域名里有哪些门道?.md.html">06 域名里有哪些门道?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/07 自己动手搭建HTTP实验环境.md.html">07 自己动手搭建HTTP实验环境.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/透视HTTP协议/08 键入网址再按下回车,后面究竟发生了什么?.md.html">08 键入网址再按下回车,后面究竟发生了什么?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/09 HTTP报文是什么样子的.md.html">09 HTTP报文是什么样子的.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/10 应该如何理解请求方法?.md.html">10 应该如何理解请求方法?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/11 你能写出正确的网址吗?.md.html">11 你能写出正确的网址吗?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/12 响应状态码该怎么用?.md.html">12 响应状态码该怎么用?.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/13 HTTP有哪些特点.md.html">13 HTTP有哪些特点.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/14 HTTP有哪些优点又有哪些缺点.md.html">14 HTTP有哪些优点又有哪些缺点.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/15 海纳百川HTTP的实体数据.md.html">15 海纳百川HTTP的实体数据.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/16 把大象装进冰箱HTTP传输大文件的方法.md.html">16 把大象装进冰箱HTTP传输大文件的方法.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/17 排队也要讲效率HTTP的连接管理.md.html">17 排队也要讲效率HTTP的连接管理.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/18 四通八达HTTP的重定向和跳转.md.html">18 四通八达HTTP的重定向和跳转.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/19 让我知道你是谁HTTP的Cookie机制.md.html">19 让我知道你是谁HTTP的Cookie机制.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/20 生鲜速递HTTP的缓存控制.md.html">20 生鲜速递HTTP的缓存控制.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/21 良心中间商HTTP的代理服务.md.html">21 良心中间商HTTP的代理服务.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/22 冷链周转HTTP的缓存代理.md.html">22 冷链周转HTTP的缓存代理.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/23 HTTPS是什么SSLTLS又是什么.md.html">23 HTTPS是什么SSLTLS又是什么.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/24 固若金汤的根本(上):对称加密与非对称加密.md.html">24 固若金汤的根本(上):对称加密与非对称加密.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/25 固若金汤的根本(下):数字签名与证书.md.html">25 固若金汤的根本(下):数字签名与证书.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/26 信任始于握手TLS1.2连接过程解析.md.html">26 信任始于握手TLS1.2连接过程解析.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/27 更好更快的握手TLS1.3特性解析.md.html">27 更好更快的握手TLS1.3特性解析.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/28 连接太慢该怎么办HTTPS的优化.md.html">28 连接太慢该怎么办HTTPS的优化.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/29 我应该迁移到HTTPS吗.md.html">29 我应该迁移到HTTPS吗.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/30 时代之风HTTP2特性概览.md.html">30 时代之风HTTP2特性概览.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/31 时代之风HTTP2内核剖析.md.html">31 时代之风HTTP2内核剖析.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/32 未来之路HTTP3展望.md.html">32 未来之路HTTP3展望.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/33 我应该迁移到HTTP2吗.md.html">33 我应该迁移到HTTP2吗.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/34 Nginx高性能的Web服务器.md.html">34 Nginx高性能的Web服务器.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/35 OpenResty更灵活的Web服务器.md.html">35 OpenResty更灵活的Web服务器.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/36 WAF保护我们的网络服务.md.html">36 WAF保护我们的网络服务.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/37 CDN加速我们的网络服务.md.html">37 CDN加速我们的网络服务.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/38 WebSocket沙盒里的TCP.md.html">38 WebSocket沙盒里的TCP.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/39 HTTP性能优化面面观.md.html">39 HTTP性能优化面面观.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/40 HTTP性能优化面面观.md.html">40 HTTP性能优化面面观.md.html</a>
</li>
<li>
<a href="/专栏/透视HTTP协议/结束语 做兴趣使然的Hero.md.html">结束语 做兴趣使然的Hero.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>08 键入网址再按下回车,后面究竟发生了什么?</h1>
<p>经过上一讲的学习,你是否已经在自己的电脑上搭建好了“最小化”的 HTTP 实验环境呢?</p>
<p>我相信你的答案一定是“Yes”那么让我们立刻开始“螺蛳壳里做道场”在这个实验环境里看一下 HTTP 协议工作的全过程。</p>
<h2>使用 IP 地址访问 Web 服务器</h2>
<p>首先我们运行 www 目录下的“start”批处理程序启动本机的 OpenResty 服务器启动后可以用“list”批处理确认服务是否正常运行。</p>
<p>然后我们打开 Wireshark选择“HTTP TCP port(80)”过滤器再鼠标双击“Npcap loopback Adapter”开始抓取本机 127.0.0.1 地址上的网络数据。</p>
<p>第三步,在 Chrome 浏览器的地址栏里输入<code>http://127.0.0.1/</code>,再按下回车键,等欢迎页面显示出来后 Wireshark 里就会有捕获的数据包,如下图所示。</p>
<p><img src="assets/86e3c635e9a9ab0abd523c01fc181cb0.png" alt="img" /></p>
<p>如果你还没有搭好实验环境,或者捕获与本文里的不一致也没关系。我把这次捕获的数据存成了 pcap 包文件名是“08-1”放到了 GitHub 上,你可以下载到本地后再用 Wireshark 打开,完全精确“重放”刚才的 HTTP 传输过程。</p>
<h2>抓包分析</h2>
<p>在 Wireshark 里你可以看到,这次一共抓到了 11 个包(这里用了滤包功能,滤掉了 3 个包,原本是 14 个包),耗时 0.65 秒,下面我们就来一起分析一下&quot;键入网址按下回车&quot;后数据传输的全过程。</p>
<p>通过前面“破冰篇”的讲解,你应该知道 HTTP 协议是运行在 TCP/IP 基础上的,依靠 TCP/IP 协议来实现数据的可靠传输。所以浏览器要用 HTTP 协议收发数据,首先要做的就是建立 TCP 连接。</p>
<p>因为我们在地址栏里直接输入了 IP 地址“127.0.0.1”,而 Web 服务器的默认端口是 80所以浏览器就要依照 TCP 协议的规范,使用“三次握手”建立与 Web 服务器的连接。</p>
<p>对应到 Wireshark 里,就是最开始的三个抓包,浏览器使用的端口是 52085服务器使用的端口是 80经过 SYN、SYN/ACK、ACK 的三个包之后,浏览器与服务器的 TCP 连接就建立起来了。</p>
<p>有了可靠的 TCP 连接通道后HTTP 协议就可以开始工作了。于是,浏览器按照 HTTP 协议规定的格式,通过 TCP 发送了一个“GET / HTTP/1.1”请求报文,也就是 Wireshark 里的第四个包。至于包的内容具体是什么现在先不用管,我们下一讲再说。</p>
<p>随后Web 服务器回复了第五个包,在 TCP 协议层面确认:“刚才的报文我已经收到了”,不过这个 TCP 包 HTTP 协议是看不见的。</p>
<p>Web 服务器收到报文后在内部就要处理这个请求。同样也是依据 HTTP 协议的规定,解析报文,看看浏览器发送这个请求想要干什么。</p>
<p>它一看,原来是要求获取根目录下的默认文件,好吧,那我就从磁盘上把那个文件全读出来,再拼成符合 HTTP 格式的报文,发回去吧。这就是 Wireshark 里的第六个包“HTTP/1.1 200 OK”底层走的还是 TCP 协议。</p>
<p>同样的,浏览器也要给服务器回复一个 TCP 的 ACK 确认,“你的响应报文收到了,多谢。”,即第七个包。</p>
<p>这时浏览器就收到了响应数据,但里面是什么呢?所以也要解析报文。一看,服务器给我的是个 HTML 文件那我就调用排版引擎、JavaScript 引擎等等处理一下,然后在浏览器窗口里展现出了欢迎页面。</p>
<p>这之后还有两个来回共四个包重复了相同的步骤。这是浏览器自动请求了作为网站图标的“favicon.ico”文件与我们输入的网址无关。但因为我们的实验环境没有这个文件所以服务器在硬盘上找不到返回了一个“404 Not Found”。</p>
<p>至此,“键入网址再按下回车”的全过程就结束了。</p>
<p>我为这个过程画了一个交互图,你可以对照着看一下。不过要提醒你,图里 TCP 关闭连接的“四次挥手”在抓包里没有出现,这是因为 HTTP/1.1 长连接特性,默认不会立即关闭连接。</p>
<p><img src="assets/8a5bddd3d8046daf7032c7d60a3d1a19.png" alt="img" /></p>
<p>再简要叙述一下这次最简单的浏览器 HTTP 请求过程:</p>
<ol>
<li>浏览器从地址栏的输入中获得服务器的 IP 地址和端口号;</li>
<li>浏览器用 TCP 的三次握手与服务器建立连接;</li>
<li>浏览器向服务器发送拼好的报文;</li>
<li>服务器收到报文后处理请求,同样拼好报文再发给浏览器;</li>
<li>浏览器解析报文,渲染输出页面。</li>
</ol>
<h2>使用域名访问 Web 服务器</h2>
<p>刚才我们是在浏览器地址栏里直接输入 IP 地址,但绝大多数情况下,我们是不知道服务器 IP 地址的,使用的是域名,那么改用域名后这个过程会有什么不同吗?</p>
<p>还是实际动手试一下吧把地址栏的输入改成“http://www.Chrono.com”重复 Wireshark 抓包过程,你会发现,好像没有什么不同,浏览器上同样显示出了欢迎界面,抓到的包也同样是 11 个:先是三次握手,然后是两次 HTTP 传输。</p>
<p>这里就出现了一个问题浏览器是如何从网址里知道“www.Chrono加微信642945106 发送“赠送”领取赠送精品课程 发数字“2”获取众筹列表。.com”的 IP 地址就是“127.0.0.1”的呢?</p>
<p>还记得我们之前讲过的 DNS 知识吗浏览器看到了网址里的“www.Chrono加微信642945106 发送“赠送”领取赠送精品课程 发数字“2”获取众筹列表。.com”发现它不是数字形式的 IP 地址,那就肯定是域名了,于是就会发起域名解析动作,通过访问一系列的域名解析服务器,试图把这个域名翻译成 TCP/IP 协议里的 IP 地址。</p>
<p>不过因为域名解析的全过程实在是太复杂了,如果每一个域名都要大费周折地去网上查一下,那我们上网肯定会慢得受不了。</p>
<p>所以,在域名解析的过程中会有多级的缓存,浏览器首先看一下自己的缓存里有没有,如果没有就向操作系统的缓存要,还没有就检查本机域名解析文件 hosts也就是上一讲中我们修改的“C:\WINDOWS\system32\drivers\etc\hosts”。</p>
<p>刚好里面有一行映射关系“127.0.0.1 www.Chrono加微信642945106 发送“赠送”领取赠送精品课程 发数字“2”获取众筹列表。.com”于是浏览器就知道了域名对应的 IP 地址,就可以愉快地建立 TCP 连接发送 HTTP 请求了。</p>
<p>我把这个过程也画出了一张图,但省略了 TCP/IP 协议的交互部分,里面的浏览器多出了一个访问 hosts 文件的动作,也就是本机的 DNS 解析。</p>
<p><img src="assets/5717c967b8d46e5ba438e1d8ed605a1b.png" alt="img" /></p>
<h2>真实的网络世界</h2>
<p>通过上面两个在“最小化”环境里的实验,你是否已经对 HTTP 协议的工作流程有了基本的认识呢?</p>
<p>第一个实验是最简单的场景,只有两个角色:浏览器和服务器,浏览器可以直接用 IP 地址找到服务器,两者直接建立 TCP 连接后发送 HTTP 报文通信。</p>
<p>第二个实验在浏览器和服务器之外增加了一个 DNS 的角色,浏览器不知道服务器的 IP 地址,所以必须要借助 DNS 的域名解析功能得到服务器的 IP 地址,然后才能与服务器通信。</p>
<p>真实的互联网世界要比这两个场景要复杂的多,我利用下面的这张图来做一个详细的说明。</p>
<p><img src="assets/df4696154fc8837e33117d8d6ab1776d.png" alt="img" /></p>
<p>如果你用的是电脑台式机那么你可能会使用带水晶头的双绞线连上网口由交换机接入固定网络。如果你用的是手机、平板电脑那么你可能会通过蜂窝网络、WiFi由电信基站、无线热点接入移动网络。</p>
<p>接入网络的同时,网络运行商会给你的设备分配一个 IP 地址,这个地址可能是静态分配的,也可能是动态分配的。静态 IP 就始终不变,而动态 IP 可能你下次上网就变了。</p>
<p>假设你要访问的是 Apple 网站,显然你是不知道它的真实 IP 地址的在浏览器里只能使用域名“www.apple.com”访问那么接下来要做的必然是域名解析。这就要用 DNS 协议开始从操作系统、本地 DNS、根 DNS、顶级 DNS、权威 DNS 的层层解析,当然这中间有缓存,可能不会费太多时间就能拿到结果。</p>
<p>别忘了互联网上还有另外一个重要的角色 CDN它也会在 DNS 的解析过程中“插上一脚”。DNS 解析可能会给出 CDN 服务器的 IP 地址,这样你拿到的就会是 CDN 服务器而不是目标网站的实际地址。</p>
<p>因为 CDN 会缓存网站的大部分资源比如图片、CSS 样式表,所以有的 HTTP 请求就不需要再发到 AppleCDN 就可以直接响应你的请求,把数据发给你。</p>
<p>由 PHP、Java 等后台服务动态生成的页面属于“动态资源”CDN 无法缓存,只能从目标网站获取。于是你发出的 HTTP 请求就要开始在互联网上的“漫长跋涉”,经过无数的路由器、网关、代理,最后到达目的地。</p>
<p>目标网站的服务器对外表现的是一个 IP 地址,但为了能够扛住高并发,在内部也是一套复杂的架构。通常在入口是负载均衡设备,例如四层的 LVS 或者七层的 Nginx在后面是许多的服务器构成一个更强更稳定的集群。</p>
<p>负载均衡设备会先访问系统里的缓存服务器,通常有 memory 级缓存 Redis 和 disk 级缓存 Varnish它们的作用与 CDN 类似,不过是工作在内部网络里,把最频繁访问的数据缓存几秒钟或几分钟,减轻后端应用服务器的压力。</p>
<p>如果缓存服务器里也没有,那么负载均衡设备就要把请求转发给应用服务器了。这里就是各种开发框架大显神通的地方了,例如 Java 的 Tomcat/Netty/JettyPython 的 Django还有 PHP、Node.js、Golang 等等。它们又会再访问后面的 MySQL、PostgreSQL、MongoDB 等数据库服务,实现用户登录、商品查询、购物下单、扣款支付等业务操作,然后把执行的结果返回给负载均衡设备,同时也可能给缓存服务器里也放一份。</p>
<p>应用服务器的输出到了负载均衡设备这里,请求的处理就算是完成了,就要按照原路再走回去,还是要经过许多的路由器、网关、代理。如果这个资源允许缓存,那么经过 CDN 的时候它也会做缓存,这样下次同样的请求就不会到达源站了。</p>
<p>最后网站的响应数据回到了你的设备,它可能是 HTML、JSON、图片或者其他格式的数据需要由浏览器解析处理才能显示出来如果数据里面还有超链接指向别的资源那么就又要重走一遍整个流程直到所有的资源都下载完。</p>
<h2>小结</h2>
<p>今天我们在本机的环境里做了两个简单的实验,学习了 HTTP 协议请求 - 应答的全过程,在这里做一个小结。</p>
<ol>
<li>HTTP 协议基于底层的 TCP/IP 协议,所以必须要用 IP 地址建立连接;</li>
<li>如果不知道 IP 地址,就要用 DNS 协议去解析得到 IP 地址,否则就会连接失败;</li>
<li>建立 TCP 连接后会顺序收发数据,请求方和应答方都必须依据 HTTP 规范构建和解析报文;</li>
<li>为了减少响应时间,整个过程中的每一个环节都会有缓存,能够实现“短路”操作;</li>
<li>虽然现实中的 HTTP 传输过程非常复杂,但理论上仍然可以简化成实验里的“两点”模型。</li>
</ol>
<h2>课下作业</h2>
<ol>
<li>你能试着解释一下在浏览器里点击页面链接后发生了哪些事情吗?</li>
<li>这一节课里讲的都是正常的请求处理流程,如果是一个不存在的域名,那么浏览器的工作流程会是怎么样的呢?</li>
</ol>
<p>欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。</p>
<p><img src="assets/8ef903c86d3ef548a9536bd4345f0156.png" alt="unpreview" /></p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/透视HTTP协议/07 自己动手搭建HTTP实验环境.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/透视HTTP协议/09 HTTP报文是什么样子的.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997cee1c743cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>