一次完整的HTTP请求过程

当咱们在浏览器的地址栏键入www.linux178.com,而后回车,从回车这一刻到看到页面到底发生了什么呢?php

  • 域名解析
  • 发起TCP3次握手
  • 创建TCP链接后发起http请求
  • 服务器响应请求,返回结果
  • 浏览器获得html标签代码
  • 浏览器解析html代码中的资源,例如js,css,img等
  • 浏览器对页面进行渲染并呈现给用户

一下咱们已chrome浏览器为例,对上面的过程一一分析。css

一. 域名解析html

首先chrome浏览器会解析www.linux178.com这个域名(准确的说法是主机名)对应的IP地址。怎么解析获得对应的IP地址呢?node

(1)浏览器首先搜索自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条数据),看自身的缓存中是否有www.linux178.com对应的条目,并且没有过时,若是有且没有过时,解析就到此结束。咱们能够经过在浏览器地址栏中输入:chrome://net-internals/#dns来查看。linux

(2)若是浏览器自身的缓存中没有找到对应的条目,那么chrome浏览器会搜索操做系统自身的DNS缓存,若是找到且没有过时则中止搜索,解析到此结束。如何查看操做系统自身的DNS缓存,以Windows系统为例,能够在cmd命令行中输入ipconfig/displaydns来查看。nginx

(3)若是在操做系统的DNS缓存中也没有找到,那么尝试读取hosts文件(位于c:\Windows\System32\direvers\etc),看看这里有没有该域名对应的IP地址,若是有则解析成功,解析到此结束。web

(4)若是在hosts文件中也没有找到对应的条目,浏览器会发起一个DNS系统调用,就会向本地配置的首选DNS服务器(通常是电信运营商提供的,也可使用像Google提供的DSN服务器)发起域名解析请求(经过UDP协议向DNS的53号端口发起请求,这个请求是递归请求,也就是这个运营商的DSN服务器必须给咱们提供该域名的IP地址)。请求过程以下:正则表达式

  • 运营商的DNS服务器,首先查找自身的缓存,若是能找到对应的条目,且没有过时则解析成功。若是没有找到对应的条目,则运营商的DNS代咱们的浏览器发起迭代DNS解析请求。
  • 运营商首先会查找根域DNS的IP地址(这个DNS服务器内置13台根DNS域服务器的IP地址),找到根域的DNS地址,就会向其发起请求(问一下www.linux178.com这个域名的ip地址是多少啊?)。根域发现这是一个com域(顶级域)的域名,因而告诉运营商的DNS我不知道这个域名的IP地址,可是我知道com域的IP地址,你去找它,因而运营商的DNS就获得com域的IP地址。
  • 运营商的DNS获得com域的IP地址以后又向com域的IP地址发起请求(问一下www.linux178.com这个域名的IP地址是多少啊?)。com域这台服务器告诉运营商的DNS我不知道www.linux178.com这个域名的IP地址,可是我知道linux178.com这个域名的DNS地址,你去找它吧。
  • 因而运营商的DNS又向linux178.com这个域名的DNS地址(这个通常是由域名注册商提供,像万网,新网)发起请求(问一下www.linux178.com这个域名的IP地址是多少?)这个时候linux178.com域的DNS服务器在本地查找,唉,果真在我这里,因而酒吧找到的结果发给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了www.linux178.com对应的IP地址,并返回给Windows系统内核,内核就把这个结果返回给浏览器,最终浏览器获得这个IP地址,进行下一步动做。

注:通常状况下是不会进行一下步骤的chrome

若是通过以上4个步骤,仍是没有解析成功,那么会进行以下步骤(针对Windows操做系统):后端

(5)操做系统就会查找NetBIOS name Cache(NetBIOS名称缓存,就在客户端电脑中),这个缓存是什么东西呢?它是最近一段时间内我和咱们成功通信的计算机名和IP地址,都会存在这个缓存里面。什么状况下该不住能解析成功呢?就是这个计算机名称正好是几分钟前咱们成功通信过的,那么这一步就能够解析成功。

(6)若是第5步也没有成功,那会查询WINS服务器(NETBIOS名称和IP地址对应的服务器)。

(7)若是第6步也没有查询成功,那么客户端就要进行广播查找。

(8)若是第7步也没有查询成功,那么客户端就读取LMHOSTS文件(和hosts文件在同一个目录下,写法也同样)。

若是第8步仍是没有解析成功,那么就宣告此次解析失败,没法和目标计算机进行通讯。只要这8步中任意一个解析成功,就能够成功和目标计算进进行通讯。

看下图的抓包截图:

Linux虚拟机测试,使用命令wget www.linux178.com来请求,发现直接使用chrome浏览器请求时,干扰请求比较多,因此使用wget命令来请求,不过wget命令只能把index.html请求回来,并不会对index.html中包含的静态资源(js,css等文件)进行请求。

 

 

 抓包分析:

①号包,这个是那台虚拟机在广播,要获取192.168.100.254(也就是网关)的MAC地址,由于局域网的通讯靠的是MAC地址,它为何须要和网关进行通讯是由于咱们的DNS服务器IP是外围IP,要出去必须靠网关帮助才行。

② 号包,这个是网关接收到虚拟机的广播以后,回应给虚拟机,告诉虚拟机本身的MAC地址,因而客户端找到了路由出口。

③号包,这个包是wget命令向系统配置的DNS服务器提出的域名解析请求(准确的说应该是wget发起了一个DNS解析的系统调用),请求的域名www.linux178.com指望获得的是IP6的地址(AAAA表明IPV6地址)。

④号包,这个DNS服务器给系统的响应,很显然目前使用IPv6的仍是极少数,所得得不到AAAA记录。

⑤号包,这个仍是请求解析IPv6地址,可是www.linux178.com.leo.com这个主机名是不存在的,因此获得的结果是no such name。

⑥号包,这个才是请求的域名对应的IPv4地址(A记录)。

⑦号包,DNS服务器无论是从缓存里,仍是进行迭代检查最终获得的域名IP地址,响应给了系统,系统给了wget命令,wegt因而获得了www.linux178.com的IP地址,这里能够看出客户端和本地DNS服务器是递归的查询(也就是服务器必须给客户端一个结果)就能够开始下一步了,进行TCP的三次握手。

二. 发起TCP的3次握手

拿到域名对应的IP地以后,User-Agent(通常是指浏览器)会以一个随机端口(1024<端口<65535)向服务器的web程序(经常使用的有httpd,nginx等)80端口发起TCP的链接请求。这个链接请求(原始的http请求通过TCP/IP层模型层层包装)到达服务器后(这中间经过各类路由设备,局域网除外),进入到网卡,而后进入到内核的TCP/IP协议栈(用于识别这个链接请求,解封包,一层层剥开),还有可能要通过Netfilter防火墙(属于内核的模块)的过滤,最终到达web程序(本文就以Nginx为例)最终创建TCP/IP链接,以下图:

 

 

  1.  Client首先发送一个链接试探,ACK=0表示确认号无效,SYN=1表示这是一个链接请求或者接受报文,同时表示这个数据报不能携带数据,seq=x表示Client本身的初始序号(seq=0就表明这是第0号包),这时候Client进入syn_sent状态,表示客户端等待服务器的回复。
  2. Server监听到链接请求报文后,若是赞成创建链接,则向Client发送确认。TCP报文首部中的SYN和ACK都置1,ack=x+1表示指望收到对方下一个报文段的第一个数据字节序号是x+1,同时代表到x为止的全部数据都已经正确收到(ack=1实际上是ack=0+1,也就是指望客户端的第一个包),seq=y表示Server本身的初始序号(seq=0就表明这是服务器这边发出的第0号包),这时服务器进入syn_rcvd,表示服务器已经接收到Client的链接请求,等待Client的确认。
  3. Client收到确认后还须要再次发送确认,同时携带要发送给Server的数据,ACK置1表示确认号ack=y+1有效(表明指望收到服务器的第1个包),Client本身的须要seq=x+1(表示这就是个人第一个包,相对于第0个包来讲的),一旦收到Client的确认以后,这个TCP链接就进入Established装填,就能够发送http请求了。

     

    ⑨号包,这个对应上面的步骤1)
    ⑩号包,这个对应上面的步骤2)
    ⑪号包,这个对应上面的步骤3)

TCP为何要进行三次握手?举一个列子,假如一个老外在故宫迷了路,看到小明,因而就有了下面的对话:

老外:Excuse me,Can you speck English?
小明:Yes
老外:OK, I want to...

在问路以前老外先问小明是否会说英语,小明回答是的,这时老外才开始问路。

两台计算机之间的通讯是靠协议(目前流行TCP/IP协议)来实现的,若是两台计算机使用的通讯协议不同,那是不能进行通讯的,因此这个3次握手就至关于试探一下对方是否遵循TCP/IP协议,协议完成后就能够进行通讯了,固然这个说法不是那么准确。

为何HTTP协议要基于TCP来实现?

目前在Internet中全部的传输都是经过TCP/IP进行的,HTTP协议做为TCP/IP模型中应用层的协议也不例外,TCP是一个端到端的可靠的面向链接的协议因此HTTP基于传输层TCP协议不用担忧数据传输中的各类问题

三.创建TCP链接后发起http请求

通过TCP3次握手以后,浏览器发起了http请求,使用的http的GET方法,请求的URL是/,协议是HTTP/1.0

 

 

下面是第⑫号包的详细内容:

 

 

以上的报文是HTTP请求的报文。

那么HTTP请求报文和响应报文是什么格式呢?

起始行:如GET/HTTP/1.0(请求的方法,请求的URL,请求所使用的协议)
头部信息:User-Agent Host等成对出现的值
主体:xxx

无论是请求报文仍是响应报文,都遵循以上的格式。

那么起始行中的请求方法有那些呢?

GET:完整请求一个资源(经常使用)
HEAD:仅请求响应的首部
POST:提交表单(经常使用)
PUT:上传文件(部分浏览器不支持这个方法)
DELETE:删除
OPTIONS:范秋请求的资源所支持的方法
TRACE:跟踪一个资源其你去中间所通过的代理(该方法不是由浏览器发出)

那什么是URL,URI,URN呢?

URL:统一资源标识符
URI:统一资源定位符,格式如:scheme://[username:password@]HOST:port/path/to/source,http://www.magedu.com/downloads/nginx-1.5.tar.gz
URN:统一资源名称
URL和URN都属于URI,为了方便就把URL和URI暂时都通指一个东西

请求的协议有那些呢?

http/0.9:stateless
http/1.0:MIME,keep-alive(保持链接),缓存
http/1.1:更多的请求方法,更精细的缓存控制,持久链接(persistent content)比较经常使用,下面是chrome发起的http请求报文头部信息:

 

其中

Accept就是告诉服务器,我接受那些MIME类型
Accept-Encoding:接受那些压缩方式的文件
Accept-Language:告诉服务器可以发送那些语言
Connection:告诉服务器支持keep-alive特性
Cookie:每次请求都会携带上Cookie以方便服务器识别是不是同一个客户端
Host:用来标识请求服务器的那个虚拟机,好比Nginx里面能够定义不少虚拟主机,这里就是来标识要访问是哪个
User-Agent:用户代理,通常状况是浏览器,也有其余类型,如wget, curl搜索引擎的蜘蛛等

条件请求首部:

If-Modified-Since是浏览器向服务器端询问某个资源文件若是自从什么时间修改过,那么从新发送给我,这样保证服务器资源文件更新时,浏览器再次请求,而不是使用缓存中的文件。

安全请求首部:

Anthorization:客户端提供给服务器的认证信息

什么是MIME?

MIME:多用途互联网邮件扩展,是一个互联网标准,它扩展了电子邮件的标准,使其可以支持非ASCII标准字符,二进制格式附件等多种格式的邮件消息,这个标准被定义在RFC 2045,RFC2046,RFC2047,RFC2048,RFC2049等RFC中。因为RFC 882转化而来的RFC2882规定电子邮件标准不容许在邮件消息中使用7位ASCII字符集之外的字符。所以,一些非英语消息和二进制文件,图像,声音,等非文字消息不能在电子邮件中传输。MIME规定了用于各类各样的数据类型的符号化方法。此外在万维网中使用HTTP协议也是用了MIME协议中的框架,标准被扩展为互联网媒体类型。

MIME遵循如下格式:major/minor主要类型/次要类型,例如:image/jpg,image/gif,text/html,video/quicktime,application/x-httpd-php。

四. 服务器响应http请求,浏览器获得html代码

看下图中第⑫号包是http请求包,第32号包是http响应包,服务器端web程序接收到http请求之后,就开始处理改请求,处理以后就返回给浏览器html文件。

第32号包是服务器返回给客户端的http响应包(200 ok响应的MIME类型是text/html),表明这一次客户端发起的http请求已经成功响应。200表明的是响应成功的状态码,还有其余的状态码以下:

1xx: 信息性状态码
  100, 101
2xx: 成功状态码
  200:OK
3xx: 重定向状态码
  301: 永久重定向, Location响应首部的值仍为当前URL,所以为隐藏重定向;
  302: 临时重定向,显式重定向, Location响应首部的值为新的URL
  304:Not Modified 未修改,好比本地缓存的资源文件和服务器上比较时,发现并无修改,服务器返回一个          304状态码,告诉浏览器,你不用请求该资源,直接使用本地的资源便可。

4xx: 客户端错误状态码
  404: Not Found 请求的URL资源并不存在
5xx: 服务器端错误状态码
  500: Internal Server Error 服务器内部错误
  502: Bad Gateway 前面代理服务器联系不到后端的服务器时出现
  504:Gateway Timeout 这个是代理能联系到后端的服务器,可是后端的服务器在规定的时间内没有给代理服务器响应

使用chrom浏览器能够看到响应头消息

 

 

Connection:使用keep-alive特性
Content-Encoding:使用gizp方式对资源压缩
Content-Type:MIME类型为html类型,字符集是UTF-8
Date:响应的日期
Server:使用的Web服务器
Transfer-Encoding:chunked分块传输码,是http中的一种数据传输基址,容许HTTP由网页服务器发送给客户端应用(一般是网页浏览器)的数据能够分红多部分
Vary:这个能够参考(http://blog.csdn.NET/tenfyguo/article/details/5939000)
X-Pingback:参考(http://blog.sina.com.cn/s/blog_bb80041c0101fmfz.html)

到底服务器端接收到http请求后怎样生成html文件?

假设服务器使用的是nginx+php(fastcgi)架构提供服务。

  1. nginx读取配置文件。咱们在浏览器的地址栏里输入的是www.linux178.com,其完整的地址应该是http:www.linux178.com./,com后面还有个点(这个点表明的就是根域通常状况咱们不用输入,也不显示),后面的斜杠/也不用添加,浏览器会自动添加,那么实际请求的URL是http://www.linux178.com/,在Nginx接受到浏览器GET/请求时,会读取http请求中的头部信息,根据Host来匹配,本身的所欲虚拟主机的配置文件的server_name,看看有没有匹配的,有匹配的就读取该虚拟主机的配置,返现有以下配置:
    root /web/echo

    经过这个配置就知道全部的网页文件放在这个目录下,就是当咱们访问http:www.linux178.com/时就是访问这个目录下面的文件,录入访问http://www.linux178.com/index.html,那么表明web/echo下面有个文件叫index.html。

    index index.html index.htm index.php

    经过这个就能够获得网站的首页文件是那个文件,也就是咱们在输入http://www.linux178.com/的时候,nginx就会自动帮咱们把index.html(假设首页是index.php固然是会尝试去找到这个,若是没有找到该文件就以此往下找,若是3个文件都没有找到,那么就会抛出一个404错误)加到后偶棉,那么添加后的URI若是是/index.php,而后根据配置进行处理:

    location ~ .*\.php(\/.*)*$ {
       root /web/echo;
       fastcgi_pass   127.0.0.1:9000;
       fastcgi_index  index.php;
       astcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
       include        fastcgi_params;
    }

    这一段配置指明范市请求的URL中配置(这里启用了正则表达式进行配置),*.php后缀的(后面跟参数)都交给后端的fastcgi进程进行处理。

  2. 把php文件交给fastcgi进程处理
    因而nginx把index.php这个URL交给后端的fastcgi处理,等待fastcgi处理完成后(结合数据查询出数据,填充模板生成html文件)返回给fastcgi一个index.html文档,Nginz再把这个index.html返回给浏览器,因而与浏览器拿到了首页的html代码,同时nginx会在日志文件中写一条记录。

    注1:nginx是怎么找到index.php文件的?
    当nginx发现须要/web/echo/index.php文件时,会向内核发起IO系统调用(由于要跟硬件打交道,这里的硬件是指硬盘,一般须要内核来操做,而内核提供的这些功能是经过系统调用来实现的),告诉内核,我须要这个文件,内核从/开始找到web目录,再在web目录下找到echo目录,最后在echo目录下找到index.php文件,因而把这个index.php从硬盘上读取到内核自身的内存空间,而后再把这个文件复制到nginx进程所在的内存空间,因而乎nginx就获得了本身想要的文件了。

    注2:寻找文件在文件系统层面是怎么操做的?
    好比nginx须要获得/web/echo/index.php这个文件,每一个分区(像linux中的ext3等文件系统,block块是文件存储的最小单元,默认是4096字节)包含元数据区和数据区,每个文件在元数据区都有元数据条目(通常是128字节大小),每个条目都有一个编号,咱们称之为inode(index node索引节点),这个inode里面包含文件类型,权限,链接次数,属主和数组的ID,时间戳,这个文件占据了那些磁盘块,也就是块的编号(block每一个文件能够占用多个block,而且block不必定是连续的,每一个block都有编号),以下图所示:

     

     还有一个要点:目录其实也是普通文件,也要占用磁盘块,目录不是一个容器。默认建立的目录都是4096字节,也就是说只须要占用一个磁盘块,可是这个是不肯定的。因此要找到目录也须要到元数据区里面找到对应的条目,只有找到对应inode就能够找到目录锁占用的磁盘块。
    那到底目录里面存放着什么,难道不是文件或者其余目录吗?
    其实目录里面存着这么一张表,里面放着目录或者文件的名称和对应的inode号(暂时称之为映射表),以下图:

     

     假设

    /          在数据区占据 一、2号block ,/其实也是一个目录 里面有3个目录 web 111
    web    占据 5号block 是目录 里面有2个目录 echo data
    echo   占据 11号 block 是目录 里面有1个文件 index.php
    index.  php 占据 15 16号 block 是文件

    其在文件系统中分布以下图

     

     

     

name内核到底是怎么找到index.php这个文件的呢?

内核拿到nginx的IO系统调用要获取/web/echo/index.php这个文件请求以后:
1.内核读取元数据 / 的inode,从inode里读取/所对应的数据块的编号,而后再数据区找到对应的(1,2号块),读取一号快上的映射表找到web这个名称在元数据区对应的inode号
2.内核读取web对应的node(3号),从中得知web在数据区对应的块是5号块,因而到数据区找到5号块,从中读取映射表,知道echo对应的inode是5号,因而元数据找到5号inode
3.内核读取5号的inode,获得echo在数据区对应的是11号块,因而到数据区读取11号块获得映射表,获得index.php对应的inode是9号
4.内核到元数据读取9号inode,获得index.php对应的是15和16号数据块,因而在数据区域找到15,16号块,读取其中的内容,获得index.php的完整内容。

五. 浏览器解析html代码,并请求html代码中的资源

浏览器拿到index.html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载(会使用多线程下载,每一个浏览器的线程数不同),这个时候就用上keep-alive特性了,创建一次HTTP链接,能够请求多个资源,下载资源的顺序就是按照代码里的顺序,可是因为每一个资源大小不同,而浏览器是多线程请求资源,因此从下图能够看出,这里的顺序不必定是代码中的顺序。

浏览器在请求静态资源时(在未过时的状况下),向服务器发起一个http请求(询问自从上一次修改时间到如今有没有对资源进行修改),若是服务器端返回304状态码,(告诉服务器端没有修改),那么浏览器会直接读取本地的该资源文件。

 

 

 

 详细的浏览器工做原理参考:http://kb.cnblogs.com/page/129756/

 最后浏览器使用本身内部的工做机制,把请求到的静态资源和html代码进行渲染,以后呈现给用户。

至此,一次完整的http请求事务宣告完成。

 

参考:http://www.360doc.com/content/17/0427/09/35497398_649008108.shtml

相关文章
相关标签/搜索