本文首发于 vivo互联网技术 微信公众号
连接:mp.weixin.qq.com/s/WlT8070Ll…
做者:吴越html
开坑这个系列的缘由,主要是在大前端学习的过程当中遇到了很多跟web协议有关的问题,以前对这一块的了解仅限于用charles抓个包,基本功欠缺。强迫症发做的我决定这一次完全将web协议搞懂搞透,若是你遇到了和我同样的问题,例如前端
对http的了解,仅限于charles抓个包。java
对https的了解仅限于大概知道tls,ssl,对称加密,非对称加密。真正一次完整的交互过程,没法作到心中有数。ios
对h5的各类缓存字段,了解的不够。git
移动端各类深度弱网优化的文章由于基本功不扎实的缘由各类看不懂。程序员
有时做为移动端开发在定技术方案的时候,由于对前端或者服务器缺少基本的了解,没法力排众议制定出更完美的方案。github
移动端中的网页加载出了问题只能求助于前端工程师,没法第一时间本身定位问题。web
每次想深度学习web协议的时候,由于不会写服务端程序致使只能泛泛而读,随意找几篇网上的博客就得过且过了,并无真正解决心中的疑惑。没有实际动手过。chrome
没有实际对照过浏览器和Android端使用okhttp在发送网络请求的时候有什么不一样。后端
实话说如今okhttp的文章百分之99都忽略了真正实现http协议的部分,基本上都是简要介绍了下okhttp的设计模式和上层的封装,这其实对移动端工程师理解web协议自己是一个debuff(我也是其中受害者。)
但愿这个系列的文章能够帮助到和我同样对web协议有困惑的工程师们。本系列文章中全部的服务端程序均使用 Go语言开发完成。抓包工具使用的是wireshark,没有使用charles是由于charles看不到传输层的东西,不利于我理解协议的本质。本系列文章没有复制粘贴网上太多概念性的东西,以代码和wireshark抓包为主。概念性的东西须要读者自行搜索。实战有助于真正理解协议自己。
本文主要分为四块,若是以为文章过长能够自行选择感兴趣的模块阅读:
chrome network面板的使用:主要以一个移动端工程师的视角来看chrome的network模块,主要列举了我认为可能会对定位h5问题有帮助的几个知识点。
Connection-keeplive:主要阐述了如今客户端/前端与服务端交互的方式,简略介绍了服务端大概的样子。
http 队头拥塞: 主要以若干个实验来理解http 队头拥塞的本质,并给出okhttp与浏览器在策略上的不一样。
http 包体传输:以若干个实验来理解http 包体传输的过程。
打开商城的页面,打开chrome控制台。
注意看红色标注部分,左边disable cache 表明关闭浏览器缓存,打开这个选项以后,每次访问页面都会从新拉取而不是使用缓存,右边的online能够下拉菜单选择弱网环境下访问此页面。在模拟弱网环境的时候此方法一般很是有效。例如咱们正常访问的时候,耗时仅仅2s。
打开弱网(fast 3g)
时间膨胀到了26s.
这里说一下这2个选项的做用,Preserve log主要就是保存前一个页面的请求日志,好比咱们在当前页面a点击了一个超连接访问了页面b,那么页面a的请求在控制台就看不到了,若是勾选此选项那么就能够看到这个前面一个页面的请求。另外这个Hide data Urls,选项额外说明一下,有些h5页面的某些资源会直接用base64转码之后嵌入到代码里面(好比截图中data: 开头的东西),这样作的好处是能够省略一些http请求,固然坏处就是开启此选项浏览器针对这个资源就没有缓存了,且base64转码之后的体积大小要比原大小大4/3。咱们能够勾选此选项过滤掉这种咱们不想看的东西。
再好比,咱们只想看看同一个页面下某一个域名的请求(这在作竞品分析时对竞品使用域名数量的分析颇有帮助),那咱们也能够以下操做:
再好比说咱们只想看一下这个页面的post请求,或者是get请求也能够。
再好比咱们能够用 is:from-cache 查看咱们当前页面哪些资源使用了缓存。large-than:(单位是字节)这个也颇有用,一般咱们利用这个过滤出超出大小的请求,看看有多少超大的图片资源(移动端排查h5页面速度慢的一个手段)。
咱们也能够点击其中一个请求,按住shift,注意看蓝色的就是咱们选定的请求,绿色的表明这个请求是蓝色请求的上游,也就是说只有当绿色的请求执行完毕之后才会发出蓝色的请求,而红色的请求就表明只有蓝色的请求执行完毕之后才会请求。这种看请求上下游关系的方法是不少时候h5优化的一个技巧。将用户最关心的资源请求前移,能够极大优化用户体验,虽然在某种程度上这种行为并不会在数据上有所提升(例如activity之间跳转用动画,application启动优化用特殊theme等等,本质上耗时都没有减小,但给用户的感受就是页面和app速度很快)。
这个timing 能够显示一个请求的详细分段时间,好比排队时间,发出请求到第一个请求响应的字节时间,以及整个response都传输完毕的时间等等。有兴趣的能够自行搜索下相关资料。
在现代服务器架构中,客户端的长链接大部分时候并非直接和源服务器打交道(所谓源服务器能够粗略理解为服务端开发兄弟实际代码部署的那台服务器),而是会通过不少代理服务器,这些代理服务器有的负责防火墙,有的负责负载均衡,还有的负责对消息进行路由分发(例如对客户端的请求根据客户端的版本号,ios仍是Android等等分别将请求映射到不一样的节点上)等等。
客户端的长链接仅仅意味着客户端发起的这条tcp链接是和第一层代理服务器保持链接关系。并不会直接命中到原始服务器。
再看一张图:
一般来说,咱们的请求客户端发出之后会通过若干个代理服务器才会到咱们的源服务器。若是咱们的源服务器想基于客户端的请求的ip地址来作一些操做,理论上就须要额外的http头部支持了。由于基于上述的架构图,咱们的源服务器拿到的地址是跟源服务器创建tcp链接的代理服务器的地址,压根拿不到咱们真正发起请求的客户端ip地址。
http RFC规范中,规定了X-Forwarded-For 用于传递真正的ip地址。固然了在实际应用中有些代理服务器并不遵循此规定,例如Nginx就是利用的X-Real-IP 这个头部来传递真正的ip地址(Nginx默认不开启此配置,须要手动更改配置项)。
在实际生产环境中,咱们是能够在http response中将上述通过的代理服务器信息一一返回给客户端的。
看这个reponse的返回里面的头部信息有一个X-via 里面的信息就是代理服务器的信息了。
再好比说 咱们打开淘宝的首页,找个请求。
这里的代理服务器信息就更多了,说明这条请求通过了多个代理服务器的转发。另外有时咱们在技术会议上会听到正向代理和反向代理,其实这2种代理都是指的代理服务器,做用都差很少,只不过应用的场景有一些区别。
正向代理:好比咱们kexue上网的时候,这种是咱们明确知道咱们想访问外网的网站好比facebook、谷歌等等,咱们能够主动将请求转发到一个代理服务器上,由代理服务器来转发请求给facebook,而后facebook将请求返回给代理服务器,服务器再转发给咱们。这种就叫正向代理了。
反向代理:这个其实咱们天天都在用,咱们访问的服务器,99%都是反向代理而来的,现代计算机系统中指的服务器每每都是指的服务器集群了,咱们在使用一个功能的时候,根本不知道到底要请求到哪一台服务器,一般这种状况都是由Nginx来完成,咱们访问一个网站,dns返回给咱们的地址,一般都是一台Nginx的地址,而后由Nginx本身来负责将这个请求转发给他以为应该转发的那台服务器。
这里咱们屡次提到了Nginx服务器和代理服务器的概念,考虑到不少前端开发可能不太了解后端开发的工做,暂且在这里简单介绍一下。一般而言咱们认为的服务器开发工程师天天大部分的工做都是在应用服务器上开发,所谓http的应用服务器就是指能够动态生成内容的http服务器。好比 java工程师写完代码之后打出包交给Tomcat,Tomcat自己就是一个应用服务器。再好比Go语言编译生成好的可执行文件,也是一个http的应用服务器,还有Python的simpleServer等等。而Nginx或者Apache更像是一个单纯的http server,这个单纯的http server 几乎没有动态生成http response的能力,他们只能返回静态的内容,或者作一次转发,是一个很单纯的http server。严格意义上说,无论是Tomcat仍是Go语言编译出来的可执行文件仍是Python等等,本质上他们也是http server,也能够拿来作代理服务器的,只是一般状况下没有人这么干,由于术业有专攻,这种工做一般而言都是交给Nginx来作。
下图是Nginx的简要介绍:用一个Master进程来管理n个worker进程,每一个worker进程仅有一个线程在执行。
在Nginx以前,多数服务器都是开启多线程或者多进程的工做模式,然而进程或者线程的切换是有成本的,若是访问量太高,那么cpu就会消耗大量的资源在建立进程或者建立线程,还有线程和进程以前的切换上,而Nginx则没有使用相似的方案,而是采用了“进程池单线程”的工做模式,Nginx服务器在启动的时候会建立好固定数量的进程,而后在以后的运行中不会再额外建立进程,并且能够将这些进程和cpu绑定起来,完美的使用现代cpu中的多核心能力。
此外,web服务器有io密集型的特色(注意是io密集不是cpu密集),大部分的耗时都在网络开销而非cpu计算上,因此Nginx使用了io多路复用的技术,Nginx会将到来的http请求一一打散成一个个碎片,将这些碎片安排到单一的线程上,这样只要发现这个线程上的某个碎片进入io等待了就当即切换出去处理其余请求,等肯定可读可写之后再切回来。这样就能够最大限度的将cpu的能力利用到极致。注意再次强调这里的切换不是线程切换,你能够把他理解为这个线程中要执行的程序里面有不少go to 的锚点,一旦发现某个执行碎片进入了io等待,就立刻利用go to能力跳转到其余碎片(这里的碎片就是指的http请求了)上继续执行。
其实这个地方Nginx的工做模式有一点点相似于Go语言的协程机制,只不过Go语言中的若干个协程下面并非只有一个线程,也可能有多个。可是思路都是同样的,就是下降线程切换的开销,尽可能用少的线程来执行业务上的“高并发”需求。
然而Nginx再优秀,也抵不过岁月的侵蚀,提及来距离今天也有15年的时间了。仍是有一些天生缺陷的,好比Nginx只要你修改了配置就必须手动将Nginx进程重启(master进程),若是你的业务很是庞大,一旦遇到要修改配置的状况,几百台甚至几千台Nginx手动修改配置重启不但容易出错并且重复劳动意义也不大。此外Nginx可扩展性通常,由于Nginx是c语言写的,咱们都知道c语言其实仍是挺难掌握的,尤为是想要掌握的好更加难。不是每一个人都有信心用C语言写出良好可维护的代码。尤为你的代码还要跑在Nginx这种天天都要用的基础服务上。
基于上述缺陷,阿里有一个绰号为“春哥”的程序员章亦春,在Nginx的基础上开发了更为优秀的OpenResty开源项目,也是老罗锤子发布会上说要赞助的那个开源项目。此项目能够对外暴露Lua脚本的接口,80后玩过魔兽世界的同窗必定对Lua语言不陌生,大名鼎鼎的魔兽世界的插件机制就是用Lua来完成的。OpenResty出现之后终于能够用Lua脚本语言来操做咱们的Nginx服务器了,这里Lua也是用“协程”的概念来完成并发能力,与Go语言也是保持一致的。此外OpenResty对服务器配置的修改也能够及时生效,不须要再重启服务器。大大提升运维的效率。等等等等。。。
前文咱们数次提到了服务器,高并发等关键字。咱们印象中的服务器都是与高并发这3个字强关联的。那么所谓 http中的“队头拥塞”到底指的是什么呢?咱们先来看一张图:
这张互联网中流传许久的图,到底应该怎么理解?有的同窗认为http所谓的拥塞是由于传输协议是tcp致使的,由于tcp天生有拥塞的缺点。其实这句话并不全对。考虑以下场景:
网络状况很好。
客户端先用socket发送一组数据a,2s之后发送数据b。
服务端收到数据a 而后开始处理数据a,而后收到数据b,开始处理数据b(这里固然是开线程作)
此时服务端处理数据b的线程将数据b处理完毕之后开始将b的 responseb发到客户端,过了一段时间之后数据a的线程终于把数据处理完毕也将responsea发给客户端。
在发送responseb的时候,客户端甚至还能够同时发送数据c,d,e。
上述的通讯场景就是完美诠释tcp做为全双工传输的能力了。至关于客户端和服务端是有2条传输信道在工做。因此从这个角度上来看,tcp不是致使http 协议 “队头拥塞”的根本缘由。由于你们都知道http使用的传输层协议是tcp. 只有在网络环境很差的状况下,tcp做为可靠性协议,确实会出现不停重复发送数据包和等待数据包确认的状况。可是这不是http “队头拥塞“”的根本缘由。
从这张图上看,彷佛http 1.x 协议是只有等前面的http request的 response回来之后 后面的http request 才会发出去。可是这个角度上理解的话,服务器的效率是否是过低了一点?若是是这样的话怎么解释咱们天天打开网页的速度都很快,打开app的速度也很快呢?通过一段时间的探索,我发现上述的图是针对单tcp链接来讲的,所谓的http 队头拥塞 是指单条tcp链接上 才会发生。而咱们与服务器的一个域名交互的时候每每不止一条tcp链接。好比说Chrome浏览器就默认了最大限度能够和一个域名有6条tcp链接,这样的话,即便有队头拥塞的现象,也能够保证我一台服务器最多能够同时处理你这个ip发出来的6条http请求了。
为何浏览器会限制6条?按照这个理论难道不是越多的tcp链接速度就越快吗?但若是这样作每一个浏览器都针对单域名开多条tcp链接来加快访问速度的话,服务器的tcp资源很快就会被耗尽,以后就是拒绝访问了。然而道高一尺魔高一丈,既然浏览器限制了单一域名最多只能使用6条tcp链接,那干脆咱们在一个页面访问多个域名不就好了?实际上单一页面访问多域名也是前端优化中的一个点,浏览器只能限制你单一域名6条tcp链接,可是可没限制你一个页面能够有多个域名,咱们多开几个域名不就至关于多开了几条tcp链接么?这样页面的访问速度就会大大增长了。
这里咱们有人可能会以为好奇,浏览器限制了单一域名的tcp链接数量,那么Android中咱们天天使用的okhttp限制了吗?限制了多少?来看下源码:
okhttp中默认对单一域名的tcp链接数量限制为5,且对外暴露了设置这个值的方法。可是问题到这里还没完,单一tcp链接上,http为何要作成前一个消息的response回来之后,后面的http request才能发出去?这样的设计是否是有问题?速度太慢了?仍是说咱们理解错了?是否是还有一种多是:
http消息能够在单一的tcp链接上 不停的发送,不须要等待前面一个http消息的返回之后再发送。
服务器接收了http消息之后先去处理这些消息,消息处理完毕准备发response的时候 再判断一下,必定等到前面到达的request的response先发出去之后 再发,就好像一个先进先出的队列那样。这样彷佛也能够符合“队头拥塞”的设计?
带着这个疑问,我作了一组实验,首先咱们写一段服务端的代码,提供fast和slow2个接口,其中slow接口 延迟10秒返回消息,fast接口延迟5秒返回消息。
package main
import (
"io"
"net/http"
"os"
"time"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/slow", slowRes)
e.GET("/fast", fastRes)
e.Logger.Fatal(e.Start(":1329"))
}
func fastRes(c echo.Context) error {
println("get fast request!!!!!")
time.Sleep(time.Duration(5) * time.Second)
return c.String(http.StatusOK, "fast reponse")
}
func slowRes(c echo.Context) error {
println("get slow request!!!!!")
time.Sleep(time.Duration(10) * time.Second)
return c.String(http.StatusOK, "slow reponse")
}复制代码
而后咱们将这个服务器程序部署在云上,另外再写一段Android程序,咱们让这个程序发http请求的时候单一域名只能使用一条tcp链接,而且设置超时时间为20s(不然默认的okhttp响应超时时间过短 等不到服务器的返回就断开链接了):
dispatcher = new Dispatcher();
dispatcher.setMaxRequestsPerHost(1);
client = new OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).dispatcher(dispatcher).build();
new Thread() {
@Override
public void run() {
Request request = new Request.Builder().get().url("http://www.dailyreport.ltd:1329/slow").build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.v("wuyue","slow e=="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.v("wuyue", "slow reponse==" + response.body().string());
}
});
}
}.start();
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Request request = new Request.Builder().get().url("http://www.dailyreport.ltd:1329/fast").build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.v("wuyue","fast e=="+e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.v("wuyue", "fast reponse==" + response.body().string());
}
});
}
}.start();复制代码
这里要注意必定要使用enqueue也就是异步的方法来发送http请求,不然你设置的域名tcp链接数量限制是失效的。而后咱们用wireshark来抓包看看:
这里能够清晰的看出来,首先这2个http request 都是使用的同一条tcp链接, 都是源端口号60465到服务器1329. 而后看下time的时间,差很少0s开始发送了slow的请求,10s左右收到了slow的http response,而后立刻 fast这个接口的request 就发出去了,过了5秒 fast的http response 也返回了。
若是将这个域名tcp数量限制为1改为5 那么再次抓包运行能够看到:
这个时候就能够清晰的看到,这一次fast大约在slow接口2秒之后就发出去了,并无等待slow回来之后再发,且注意看这2条http消息使用的源端口号是不一样的,一个是64683,一个是64684。也就是说这里使用了不一样的tcp链接来传输咱们的http消息。
综上所述,咱们能够对http 1.x 中的“队头拥塞” 来下结论了:
所谓http1.x中的“队头拥塞”,除了自己传输层协议tcp的缘由致使的tcp包拥塞机制之外,更多的是指的http 应用层上的限制。这种限制具体表如今,对于http协议来讲,单条tcp链接上客户端要保证前面一条的request的response返回之后,才能发送后续的request。
http 1.x 中的 “队头拥塞” 本质上来讲是由http的客户端来保证明现的。
若是你访问的网页里面的请求都指向着同一个域名,那么无论服务器有多么高的并发能力,他也最多只能同时处理你的6条http请求,由于大多数浏览器限制了针对单一域名只能开6条tcp链接。想翻过这个限制提升页面加载速度只能依靠开启多域名来实现。
虽然okhttp中对外暴露了这个单域名下的tcp链接数的设置,可是也没法经过将这个值调的特别高来增长你应用的请求响应速度,由于大多数服务器都会限制单一ip的tcp链接数,好比Nginx的默认设置就是10。因此你客户端单一将这个数值调的特别大也没用,除非你和服务器约定好。可是这样还不如使用多域名方便了。
通过上面的分析,咱们得知其实http 1.x协议并无彻底发挥tcp 全双工通道的潜能,(也有多是http协议出现的太早当时的设计者没有考虑如今的场景)因此从1.1协议开始,又有了一个Pipelining 也就是管道的约定。这个约定可让http的客户端不用等前面一个request的response回来就能够继续发后面的request。可是各类缘由下,现代浏览器都没有开启这个功能(相关资料感兴趣的能够自行查询Pipelining关键字,这里就不复制粘贴了)。我带着好奇搜索了一下okhttp的代码,想看看他们有没有相似的实现。最终咱们在这个类中找到了线索:
看样子貌似这个tunnel的命名和咱们http1.1中所谓的pipelining好似一个意思?那么okhttp中是可使用这个浏览器默认关闭的技术了吗?继续看代码:
咱们看到这个值使用的地方是来自于connectTunnel这个方法,咱们看看这个方法是在connect方法里调用的:
咱们看下这个方法的实现:
/**
* Returns true if this route tunnels HTTPS through an HTTP proxy. See <a
* href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section 5.2</a>.
*/
public boolean requiresTunnel() {
return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
}复制代码
从注释和rfc文档中能够看出来,要开启这个所谓的tunnel的功能,须要你的目标地址是https的,讲白了是tls来作报文的传输,此外还须要一个http代理服务器。同时知足这2个条件之后才会触发这部分代码。这部分因为涉及到tls协议的相关知识,咱们将这一块的内容放到后续的第三个章节中再来解释。这里你们只须要大概清楚tunnel主要用来直接转发传输层的tcp报文到目标服务器,而不须要通过http的代理服务器额外进行应用层报文的转发便可。
好比说Referer(我在谷歌中搜索github,而后点击github的连接,而后看请求信息)
这个字段一般一般被利用作防盗链,页面来源统计分析,缓存优化等等。可是要注意的是,这个Referer字段浏览器在自动帮咱们添加的时候有一个策略:要么来源是http 目标也是http,要么来源是https 目标也是https,一旦出现来源是http目标是https或者反着来的状况,浏览器就不会帮咱们添加这个字段了。
此外,在http包体传输的时候,定长包体与不定长包体使用的单位是不同的。
好比Content-Length这个字段后面的单位就是10进制。传输的就是这个“Hello, World!”。可是对于Chunk非定长包体来讲 这个单位倒是16进制的,且对于Chunk传输方式来讲,有一些response的header是等待body传输完毕之后才继续传的。咱们来简单写个server端的例子,返回一个叫hellowuyue的response,可是使用chunk的传输方式。这里我简单使用Go语言来完成对应的代码。
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
//采用chunk传输 不使用默认的定长包体
e.GET("/", func(c echo.Context) error {
c.Response().WriteHeader(http.StatusOK)
c.Response().Write([]byte("hello"))
c.Response().Flush()
c.Response().Write([]byte("wuyue"))
c.Response().Flush()
return nil
})
e.Logger.Fatal(e.Start(":1323"))
}复制代码
咱们在浏览器访问一下,看看network的展现信息:
而后咱们用wireshark 详细的看一下chunk的传输机制:这里要注意的是,我没有选择将咱们服务端的代码部署在外网服务器上,只是简单的在本地,因此咱们要选择环回地址,不要选择本地链接。同时监听1323端口.而且作 port 1323的过滤器。
而后咱们来看下wireshark完整的还原过程:
能够看一下这个chunk的结构,每个chunk的结束都会伴随着一个0d0a的16进制,这个咱们能够把他理解成就是/r/n 也就是crlf换行符。而后看一下 当chunk所有结束之后 还会有一个end chunked 这里面 也是包含了一个0d0a 。(这里篇幅所限就不放ABNF范式对chunk使用的规范了。有兴趣的同窗能够自行对照ABNF的规范语法和wireshark实际抓包的内容进行对比加深理解)
最后咱们看一下,浏览器和服务端在利用form表单上传文件时的交互过程以及okhttp完成相似功能时候的异同,加深对包体传输的理解。首先咱们定义一个很是简单的html,提供一个表单。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>上传文件</title>
</head>
<body>
<h1>上传文件</h1>
<form action="/uploadresult" method="post" enctype="multipart/form-data">
Name: <input type="text" name="name"><br>
Files: <input type="file" name="file"><br><br>
Files2: <input type="file" name="file2"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>复制代码
而后定义一下咱们的服务端:
package main
import (
"io"
"net/http"
"os"
"time"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
//直接返回一个预先定义好的html
e.GET("/uploadtest", func(c echo.Context) error {
return c.File("html/upload.html")
})
//html里预先定义好点击上传之后就跳转到这个uri
e.POST("/uploadresult", getFile)
e.Logger.Fatal(e.Start(":1329"))
}
func getFile(c echo.Context) error {
name := c.FormValue("name")
println("name==" + name)
file, _ := c.FormFile("file")
file2, _ := c.FormFile("file2")
src, _ := file.Open()
src2, _ := file2.Open()
dst, _ := os.Create(file.Filename)
dst2, _ := os.Create(file2.Filename)
io.Copy(dst, src)
io.Copy(dst2, src2)
return c.String(http.StatusOK, "上传成功")
}复制代码
而后咱们访问这个表单,上传一下文件之后用wireshark抓个包来体会一下浏览器在背后帮咱们作的事情。
关于这个Content-Disposition有兴趣的能够自行搜索其含义。
最后咱们用okhttp来完成这个操做,看看okhttp作这个操做的时候,wireshark显示的结果又是什么样子:
//注意看 contentType 是须要你手动去设置的,咱们这里故意将这个contentType值写错 看看能不能上传文件成功
RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/gifccc"), new File("/mnt/sdcard/ccc.txt"));
RequestBody requestBody2 = RequestBody.create(MediaType.parse("text/plain111"), new File("/mnt/sdcard/Gif.gif"));
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM).addFormDataPart("file2", "Gif.gif", requestBody1)
.addFormDataPart("file", "ccc.txt", requestBody2)
.addFormDataPart("name","吴越")
.build();
Request request = new Request.Builder().get().url("http://47.100.237.180:1329/uploadresult").post(requestBody).build();复制代码
本章节初步介绍了如何使用chrome的network面板和wireshark抓包工具进行http协议的分析,重点介绍了http1.x协议中的“队头拥塞”的概念,以及该问题的应对方式和浏览器的限制策略。在后续的第二个章节中,将会详细介绍http协议中缓存,dns以及websocket的相关知识。在第三个章节中,将会详细分析http2以及tls协议的每个细节。
更多内容敬请关注 vivo 互联网技术 微信公众号
注:转载文章请先与微信号:labs2020 联系。