热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何解析域名到html网页,从URL到页面

一个老生常谈的问题,从输入url到页面渲染完成之间发生了什么?在这个过程中包括以下2大部分:-1.http请求响应-2.渲染1.http请

一个老生常谈的问题,从输入url到页面渲染完成之间发生了什么?

在这个过程中包括以下2大部分:

- 1.http请求响应

- 2.渲染

1.http请求响应

先来提三个问题:

1.当输入url后,浏览器如何包装发起请求?

2.在发出请求--接到响应之间发生了什么?

3.当返回请求结果后,浏览器如何解析结果?

1.1 请求

1.1.1 GET请求包装

1.为了知道浏览器是如何包装http请求的,使用nodejs搭建服务器const http = require('http');const server = http.createServer((req,res) => { if(req.url === '/'){

res.end('hello')

}

});

server.listen(8005,() => { console.log('server listen on http://localhost:8005')

});

2.服务器搭建好了,需要知道浏览器到底包装了什么信息,直接看控制台:Request URL: http://localhost:8005/Request Method: GET

Status Code: 200 OK

Remote Address: [::1]:8005Referrer Policy: no-referrer-when-downgradeAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8Cache-Control: max-age=0Connection: keep-aliveHost: localhost:8005Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36

1.1.2 POST请求包装

这些是浏览器自动包装过后的请求,包括请求行,请求头和请求主体,浏览器默认发送的是GET请求,如果需要指定POST请求,可以写个表单来验证一下,大概意思是浏览器发起post请求,服务端接收到后返回success,浏览器端显示返回的内容//index.html

submit

这样写的时候,由于html文件的协议是file,所以为了解决跨域问题,需要服务端进行设置const http = require('http');const server = http.createServer((req,res) => { if(req.url === '/'){

res.setHeader("Access-Control-Allow-Origin", "*")

res.setHeader("Access-Control-Allow-methods", "GET, POST, OPTIONS, PUT, DELETE")

res.setHeader("Access-Control-Allow-Headers","*")

res.setHeader("Content-type","application/plain")

res.end('success!!!')

}

});

server.listen(8005,() => { console.log('server listen on http://localhost:8005')

});

1460000019115527

这样一次post请求就成功了,来看看浏览器默认包装了什么信息Request URL: http://localhost:8005/Request Method: POST

Status Code: 200 OK

Remote Address: [::1]:8005//自动使用https协议Referrer Policy: no-referrer-when-downgrade

Content-type: application/*

Origin: null

User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36

这些信息有的是我们自己在后端写的,有的是浏览器自动添加的

1.2 过程

1.2.1 整体流程

前面已经知道了浏览器在发起GET或者POST请求的时候会自动的添加的字段,那浏览器在发送请求后到接收到服务端传来的数据前这段时间发生了什么?

网上看到大家的回答大部分都是:1.接收 URL,并拆分成协议,网络地址,资源路径

2.与缓存进行比对,如果请求的对象在缓存中,则直接进行第9步

3.检查域名是否在本地的 host 的文件中,在则直接返回 IP 地址,不在则向 DNS 服务器请求,直到查询到 IP 地址

4.浏览器向服务器发起一个 TCP 连接

5.浏览器通过 TCP 连接向服务器发起 HTTP 请求,HTTP 三次握手,HTTPS 握手过程则复杂得多

6.浏览器接受 HTTP 响应,这时候它能关闭 TCP 连接也能为另一个连接保留。

7.检查 HTTP header 里的状态码,并做出不同的处理方式。比如:错误(4XX、5XX),重定向(3XX),授权请求(2XX)

8.如果是可以缓存的,这个响应则会被存储起来

9.浏览器进行解码响应,并决定如何处理该响应(比如HTML页面,图像,声音等等)

10.浏览器渲染响应,或者为不能识别的类型提供下载的提示框

1.2.2 域名解析流程

这样的回答确实把相关的流程说了一遍,但是DNS是如何把域名解析成IP的?这个过程可以被观察到么?三次握手又是什么意思?

为了看到域名解析的过程,我们可以使用Nslookup,它是由微软发布用于对DNS服务器进行检测和排错的命令行工具

比如可以看一下,https://www.baidu.com它的IP是什么,nslookup https://www.baidu.com

我在查看的时候一直报延时错误,只好从网上引用一张图来说明一下了

1460000019115528

其中server代表本地地址ip,下面那个address是百度的ip

通过这样的方式就能看到具体域名解析的过程

1.2.3 三次握手流程

接下来是三次握手,当域名转化成IP后,浏览器沿着ip找到服务器,进行三次握手:第一次握手:客户端的应用进程主动打开,并向客户端发出请求报文段。其首部中:SYN=1,seq=x。

第二次握手:服务器应用进程被动打开。若同意客户端的请求,则发回确认报文,其首部中:SYN=1,ACK=1,ack=x+1,seq=y

第三次握手:客户端收到确认报文之后,通知上层应用进程连接已建立,并向服务器发出确认报文,其首部:ACK=1,ack=y+1。当服务器收到客户端的确认报文之后,也通知其上层应用进程连接已建立

1460000019115529

看到这里,有个问题,前两次握手已经把客户端和服务端联系在一起了,那为什么还要第三次握手?如果是两次握手,当A想要建立连接时发送一个SYN,然后等待ACK,结果这个SYN因为网络问题没有及时到达B,所以A在一段时间内没收到ACK后,在发送一个SYN,B也成功收到,然后A也收到ACK,这时A发送的第一个SYN终于到了B,对于B来说这是一个新连接请求,然后B又为这个连接申请资源,返回ACK,然而这个SYN是个无效的请求,A收到这个SYN的ACK后也并不会理会它,而B却不知道,B会一直为这个连接维持着资源,造成资源的浪费,但如果是三次握手,如果第三次握手迟迟不来,服务器便会认为这个SYN是无效的,释放相关资源

1.3 响应

成功发起请求并完整走完了上述流程,浏览器能获得服务器发来的数据,那这些数据被放在哪里,它是如何被浏览器处理的?

其实这个问题很简单,在前面成功发起http请求后,服务端会有一个响应,这里面规定了各种文件格式Access-Control-Allow-Headers: *

Access-Control-Allow-methods: GET, POST, OPTIONS, PUT, DELETE

Access-Control-Allow-Origin: *Connection: keep-alive

Content-Length: 10Content-type: application/plainDate: Wed, 08 May 2019 07:12:14 GMT

2.渲染

2.1 整体流程

数据请求回来以后,浏览器是如何把数据转化成页面的呢?这个过程就涉及到了DOM树,CSSOM树,render树的生成和页面的绘制,先来贴图看看整体流程:

1460000019115530

在构建DOM树的时候,遇到 js 和 CSS元素,HTML解析器就换将控制权转让给JS解析器或者是CSS解析器。开始构建CSSOM,在构建CSSOM树的时候,解析是从右向左进行的,DOM树构建完之后和CSSOM合成一棵render tree

有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置

Layout后,浏览器已经知道了哪些节点要显示(which nodes are visible)、每个节点的CSS属性是什么(their computed styles)、每个节点在屏幕中的位置是哪里(geometry)。就进入了最后一步:Painting,按照算出来的规则,通过显卡,把内容画到屏幕上,HTML默认是流式布局的,CSS和js会打破这种布局,改变DOM的外观样式以及大小和位置,当尺寸改变时会reflow,也就是重新绘制,比如table布局整体尺寸改变,页面就需要重绘,但当非尺寸改变时,会进行replaint通过这个分析知道了DOM树的生成过程中可能会被CSS和JS的加载执行阻塞,所以平时写CSS时,尽量用id和class,千万不要过渡层叠,尽量减少会造成reflow的操作,把JS代码放到页面底部,且Javascript 应尽量少影响 DOM 的构建

2.2 底层源码

这样说一遍,还是在很表面的层次在说渲染这件事,那有没有更深层次的理解呢?可以通过看浏览器源码来进行分析:

大致分为三个步骤:1.HTMLDocumentParser负责解析html文本为tokens

2.HTMLTreeBuilder对这些tokens分类处理

3.HTMLConstructionSite调用不同的函数构建DOM树

1460000019115531

接下来使用这个html文档来说明DOM树的构建过程:

demo

2.2.1生成tokens

首先是>>>HTMLDocumentParser负责解析html文本为tokensvoid DocumentLoader::commitData(const char* bytes, size_t length) {

ensureWriter(m_response.mimeType()); if (length)

m_dataReceived = true;

m_writer->addData(bytes, length);//内部调用HTMLDocumentParser}

构建出来的token是包含页面元素的信息表:tagName: html |type: DOCTYPE |attr: |text: "tagName: |type: Character |attr: |text: \n"tagName: html |type: startTag |attr: |text: "tagName: |type: Character |attr: |text: \n"tagName: head |type: startTag |attr: |text: "tagName: |type: Character |attr: |text: \n "tagName: meta |type: startTag |attr:charset=utf-8 |text: "tagName: |type: Character |attr: |text: \n"tagName: head |type: EndTag |attr: |text: "tagName: |type: Character |attr: |text: \n"tagName: body |type: startTag |attr: |text: "tagName: |type: Character |attr: |text: \n "tagName: div |type: startTag |attr: |text: "tagName: |type: Character |attr: |text: \n "tagName: h1 |type: startTag |attr:class=title |text: "tagName: |type: Character |attr: |text: demo"tagName: h1 |type: EndTag |attr: |text: "tagName: |type: Character |attr: |text: \n "tagName: input |type: startTag |attr:value=hello |text: "tagName: |type: Character |attr: |text: \n "tagName: div |type: EndTag |attr: |text: "tagName: |type: Character |attr: |text: \n"tagName: body |type: EndTag |attr: |text: "tagName: |type: Character |attr: |text: \n"tagName: html |type: EndTag |attr: |text: "tagName: |type: Character |attr: |text: \n"tagName: |type: EndOfFile |attr: |text: "

2.2.2tokens分类

接着是>>>>>HTMLTreeBuilder对这些tokens分类处理void HTMLTreeBuilder::processToken(AtomicHTMLToken* token) { if (token->type() == HTMLToken::Character) {

processCharacter(token); return;

}

switch (token->type()) { case HTMLToken::DOCTYPE:

processDoctypeToken(token); break; case HTMLToken::StartTag:

processStartTag(token); break; case HTMLToken::EndTag:

processEndTag(token); break; //othercode

}

}

2.2.3 构建DOM树

最后,最关键的就是HTMLConstructionSite调用不同的函数构建DOM树,它根据不同的节点类型进行不同的处理

1.DOCTYPE的处理// tagName不是html,那么文档类型将会是怪异模式

if (name != "html" ) {

setCompatibilityMode(Document::QuirksMode); return;

}// html4写法,文档类型是有限怪异模式

if (!systemId.isEmpty() &&

publicId.startsWith("-//W3C//DTD HTML 4.01 Transitional//",

TextCaseASCIIInsensitive))) {

setCompatibilityMode(Document::LimitedQuirksMode); return;

}// h5的写法,标准模式

setCompatibilityMode(Document::NoQuirksMode);

不同的模式会造成什么影响?// There are three possible compatibility modes:

// Quirks - quirks mode emulates WinIE and NS4. CSS parsing is also relaxed in

// this mode, e.g., unit types can be omitted from numbers.

// Limited Quirks - This mode is identical to no-quirks mode except for its

// treatment of line-height in the inline box model.

// No Quirks - no quirks apply. Web pages will obey the specifications to the

// letter.

//怪异模式会模拟IE,同时CSS解析会比较宽松,例如数字单位可以省略,

//有限怪异模式和标准模式的唯一区别在于在于对inline元素的行高处理不一样

//标准模式将会让页面遵守文档规定

2.开标签的处理

首先是标签,处理这个标签的任务应该是实例化一个HTMLHtmlElement元素,然后把它的父元素指向documentHTMLConstructionSite::HTMLConstructionSite(

Document& document)

: m_document(&document),

m_attachmentRoot(document)) {

}void HTMLConstructionSite::insertHTMLHtmlStartTagBeforeHTML(AtomicHTMLToken* token) {

HTMLHtmlElement* element = HTMLHtmlElement::create(*m_document);//创建一个html结点

attachLater(m_attachmentRoot, element);//加到一个任务队列里面

m_openElements.pushHTMLHtmlElement(HTMLStackItem::create(element, token));//压到一个栈里面,这个栈存放了未遇到闭标签的所有开标签

executeQueuedTasks();//执行队列里面的任务}//建立一个taskvoid HTMLConstructionSite::attachLater(ContainerNode* parent,Node* child, bool selfClosing) {

HTMLConstructionSiteTask task(HTMLConstructionSiteTask::Insert);

task.parent = parent;

task.child = child;

task.selfClosing = selfClosing;

// Add as a sibling of the parent if we have reached the maximum depth

// allowed.

if (m_openElements.stackDepth() > maximumHTMLParserDOMTreeDepth &&

task.parent->parentNode())

task.parent = task.parent->parentNode();

queueTask(task);

}//executeQueuedTasks根据task的类型执行不同的操作void ContainerNode::parserAppendChild(Node* newChild) { if (!checkParserAcceptChild(*newChild)) return;

AdoptAndAppendChild()(*this, *newChild, nullptr);

}

notifyNodeInserted(*newChild, ChildrenChangeSourceParser);

}//建立起html结点的父子兄弟关系void ContainerNode::appendChildCommon(Node& child) {

child.setParentOrShadowHostNode(this);//设置子元素的父结点,也就是会把html结点的父结点指向document

if (m_lastChild) { //子元素的previousSibling指向老的lastChild,老的lastChild的nexSibling指向它

child.setPreviousSibling(m_lastChild);

m_lastChild->setNextSibling(&child);

} else { //如果没有lastChild,会将这个子元素作为firstChild

setFirstChild(&child);

} //子元素设置为当前ContainerNode(即document)的lastChild

setLastChild(&child);

}

每当遇到一个开标签时,就把它压起来,下一次再遇到一个开标签时,它的父元素就是上一个开标签,借助一个栈建立起了父子关系

3.闭标签的处理

第一个闭标签是head标签,它会把开的head标签pop出来,栈里面就剩下html元素了,所以当再遇到body时,html元素就是body的父元素了m_tree.openElements()->popUntilPopped(token->name());



推荐阅读
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 一、什么是闭包?有什么作用什么是闭包闭包是定义在一个函数内部的函数,它可以访问父级函数的内部变量。当一个闭包被创建时,会关联一个作用域—— ... [详细]
  • EPPlus绘制刻度线的方法及示例代码
    本文介绍了使用EPPlus绘制刻度线的方法,并提供了示例代码。通过ExcelPackage类和List对象,可以实现在Excel中绘制刻度线的功能。具体的方法和示例代码在文章中进行了详细的介绍和演示。 ... [详细]
author-avatar
心如止水向北飞2012_737
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有