网络
-
什么是服务器
-
如何访问服务器
-
http协议
-
请求消息格式
- 请求头
- 请求体
- GET和POST
-
响应消息格式
- 响应头
- 响应体
-
-
在浏览器中输入一个页面地址, 按下回车以后发生了什么
-
ajax
什么是服务器?
如果所有的程序都是单机的, 会导致什么后果呢?
-
数据难以共享
-
受计算机配置的影响, 运算速度差异巨大
-
个人计算机的安全性堪忧, 可能受到恶意程序的影响
而有了服务器之后这些问题都可以得到解决
服务器在不同的语境中表达了不同的含义
-
一台独立的计算机
比如我们说要去买一台服务器 配置一台服务器, 就是代表的是一台独立的计算器 -
一个应用程序
绝大部分开发者, 通常把服务器看做是一个应用程序
无论他是哪个概念, 他都具有以下的特点:
-
能够通过网络, 被其他应用程序所访问
-
能够提供一些服务
如果一个服务器(应用程序), 它仅仅为一个浏览器提供访问网站服务, 我们将它称之为web服务器
实际上目前的web服务器和游戏服务器界限已经非常模糊, 可以认为, 凡是在互联网中提供服务的服务器都是web服务器
通常我们把访问服务器的程序, 称之为客户端
实际上web服务器不仅仅为浏览器提供服务, 还可以为手机app, 小程序, 小游戏等常见互联网应用提供服务, 而我们今天讨论的是仅考虑为浏览器提供服务的服务器
常见的web服务器有: nginx, apache, iis
开发阶段服务器一般安装在本地计算机, 通常也称之为本地服务器
vscode有一个live Server插件, 他其实就是一个轻量级的服务器
如何访问服务器
服务器可能在本机, 也可能在远程, 他一定运行在一台计算机上, 要在茫茫互联网中访问到服务器程序, 就必须:
-
精确的定位到服务器所在的计算机
-
精确定位到计算机中的服务器程序
-
精确定位到程序中的某个功能
通常我们用url地址来准确的描述上面的三个条件
url(Uniform Resource Locator), 统一资源定位符, 是一个字符串, 他的格式如下
protocal://hostname:port/path?query#hash
-
protocal: 使用的协议, 选择不同的协议会导致和服务器之间消息交互格式, 连接方式的不同, 大部分服务器都支持http和https两种协议, 如果选择了服务器不支持的协议则会导致访问失败
-
hostname: 主机名, 主机名可以是ip或者域名
- ip: 每台计算机在网络中的唯一编号, 127.0.0.1表示本机
- 域名: 网络中容易记忆的唯一单词, 通过DNS服务器可以将域名解析成ip, localhost会被解析成127.0.0.1
-
port: 端口, 0 - 65535之间的数字, 相当于服务器计算机上的房号, 使用不同的端口相当于敲不同的房门, 计算机上的程序可以监听一个或者多个端口号, 如果访问的端口号有程序被监听, 则计算机会将到达的网络访问交给对应的程序来处理
- 端口号可以不写有默认值, http默认为80, https默认为443
-
path: 一个普通的字符串, 该字符串会交给web服务器处理, 主要用于定位服务
- 如果path为/, 则表示根路径, 例如https://www.baidu.com/的path就是/
-
query: 一种特殊格式的字符串, 该字符串会交给web服务器处理, 主要用于向服务器的某个程序传递一些信息
-
hash: 一个普通的字符串, 在浏览器的地址栏中, 如果url其他位置的信息保持不变, 仅变动hash, 浏览器则不会重新访问服务器因此通常用于不刷新页面的跳转
经过这些了解我们可以看出:
-
hostname是用来精准定位计算机的
-
port是用来精准定位服务器的
-
protocal是用于告诉服务器使用哪种协议进行传输数据
-
path是用于精准定位服务器上的服务的
-
query是在使用服务的时候传递的额外的信息, 具体看服务器要求
-
hash也是一些额外信息, 具体要不要也要看服务器要求
注意: url仅支持ASCII字符, 如果包含非ASCII字符, 会被现代浏览器自动进行编码
例如: https://www.baidu.com/s?wd=王思聪
会被编码为: https://www.baidu.com/s?wd=%E7%8E%8B%E6%80%9D%E8%81%AA
同时url地址不能过长, 因为很多浏览器对url地址长度是有限制的, Chrome对url的长度限制为8182个ASSCI字符
http协议
我们可以通过url地址访问服务器, 但是, 服务器和服务器之间的数据到底是怎么交互的, 数据的格式是什么, 这取决于用什么协议
最常见的协议就是http协议
http协议将和服务器的一次交互看做是两段简单的过程组成: 请求request 和 响应response
-
请求: 客户端通过url地址发送数据到服务器的过程
-
响应: 服务器收到请求之后反馈结果给客户端的过程
当请求和响应都完成以后, 本次交互结束, 如果需要得到额外的服务器, 则需要重新发送请求
同时 http请求约定了请求的消息格式和响应的消息格式
请求消息格式
请求消息格式由两部分组成, 请求头request headers和请求体request body
请求头
请求头是一个多行文本的字符串
比如我们请求http://www.baidu.com/s?wd=html, 得到的请求头如下:
GET /s?wd=html HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
...
我们可以看出, 该字符串由两部分组成
-
请求行: 请求方法path协议
-
请求方法, 一个普通的字符串, 会被服务器读取到, 常见的请求方法: GET, POST
-
path: 即url中的path + search + hash, 浏览器可能会用到path的信息
-
协议: 协议即版本号, 目前固定为HTTP/1.1
-
-
键值对: 大量的属性名和属性值组合, 可以自定义
-
url: url地址中的hostname
-
User-Agent: 客户端信息描述
我们看上方的Chrome的User-Agent在干嘛, 他说自己又是Mozilla, 又是webkit又是Safari, 又是Chrome, 他在干嘛, 他是不是在骗人, 他就是在骗人, 这个为什么要骗人还是跟浏览器大战有关系
-
其他键值对
-
请求头描述了请求的元数据信息, 这里的元数据是指跟业务无关的额外信息(跟业务相关的数据如用户账号密码称之为业务数据, 像告诉服务器自己是哪个浏览器 主机名是什么这些都属于元数据)
当我们在浏览器输入一个url按下回车以后, 浏览器会自动构建一个请求, 方法为GET 并向服务器发送请求
请求体
请求体是包含业务数据的字符串
理论上, 请求体可以是任意格式的字符串, 但习惯上, 服务器普遍能识别以下格式
-
application/x-www-form-unlencoded: 属性名=属性值&属性名=属性值
-
application/json: {“属性名”: “属性值”, “属性名”: “属性值”}
-
multipart/form-data: 使用某个随机字符串作为属性之间的分隔符, 通常用于文件上传
由于请求体样式的多样性, 服务器在分析请求体时可能无法知晓具体的格式, 从而不知道如何解析请求体, 因此浏览器通常要在请求头中附带一个属性Content-Type来描述请求体使用的格式
例如:
Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
Content-Type: multipart/form-data
GET和POST
虽然http协议没有规定请求方式必须是什么, 但随意的请求方法服务器可能无法识别, 服务器一般都能识别GET和POST请求, 并作出如下的差异化处理
-
如果是GET请求, 不读取请求体, 业务数据从path中的search或者hash中读取
-
如果是POST请求, 读取请求体, 业务数据从请求体中获取, 关于请求体的格式, 不同的服务器, 或者同一个服务器不同的服务对于请求体格式要求都不相同
在地址栏输入url是不可能产生POST请求的, 但是提交表单可以产生POST请求
<form action="请求的地址" method="POST"> <p> 账号: <input type="text" name="loginId"> </p> <p>密码: <input type="password" name="password"> </p> <p> <button type="submit">提交</button> </p> </form>
由于服务器对GET和POST请求处理上的差异, 导致了GET和POST请求上的差异
-
GET请求一般不设置请求体, POST有
-
GET请求的业务数据放在地址中安全性相较于POST较低, POST放在请求体中
-
GET请求传递的业务数据量是有限的, POST则不做限制(除非服务器做限制)
-
GET请求利于分享页面结果, POST不行
-
在浏览器刷新或者回退页面的时候, 会按照之前的请求方式重新请求数据, 如果是GET请求, 浏览器则会重新发送GET请求, 如果是POST请求 浏览器则会重新构建之前的消息体数据, 通常会弹出提示
响应消息格式
和请求类似, 响应消息也分响应头(response headers)和响应体(response body)
响应头
比如我们请求 https: //www.baidu.com/s?wd=html, 得到的响应头可能如下
HTTP/1.1 200 OK
Bdpagetype: 3
Bdqid: 0xae29d62d0002d4fb
Cache-Control: private
Ckpacknum: 2
Ckrndstr: d0002d4fb
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Fri, 14 Feb 2020 01:22:58 GMT
Server: BWS/1.1
Set-Cookie: delPer=0; path=/; domain=.baidu.com
Set-Cookie: BD_CK_SAM=1;path=/
Set-Cookie: PSINO=3; domain=.baidu.com; path=/
Set-Cookie: BDSVRTM=18; path=/
Set-Cookie: H_PS_PSSID=30744_1468_21085; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1581643378042092493812549797325406655739
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
该字符串由两部分组成
-
响应行: 协议 状态码 状态码文本
-
协议: 协议以及协议版本号, 目前固定为HTTP/1.1
-
状态码和状态码文本: 一个数字和该数字对应的单词, 来描述服务器的响应状态, 浏览器会根据不同的状态码做不同的处理
(常用的状态码如下)- 200 OK: 一切正常
- 301 Move Permanently: 资源已经被永久重定向, 你的请求我已经收到了, 但是呢你要的东西已经不在这个地址了, 我已经永久的把它移动到了一个新的地址, 麻烦你请求新的地址, 地址我已经 放到请求头的loacation中了
- 302 Found: 资源已经被临时重定向, 你的请求我已经收到了, 但是呢你要的东西已经不在这个地址了, 我已经临时的把它移动到了一个新的地址, 麻烦你请求新的地址, 地址我已经 放到请求头的loacation中了(永久的话会直接覆盖浏览器的历史记录, 临时不会)
- 304 Not Modified: 文档内容未修改, 你的请求我收到了, 但是你要的东西和之前没有任何修改, 我就不给你结果了, 你自己就用以前的吧, 啥? 你没有缓存以前的内容? 管我啥事?
- 400 Bad Request: 语义有误, 当前请求无法被服务器理解 你给我发的是个啥啊? 听都听不懂
- 403 Forbidden: 服务器拒绝执行, 你的请求我已经收到了 但是我就是不给你东西
- 404 Not Found: 资源不存在, 你的请求我已经收到了, 但是我没有你要的东西
- 500 Interval Server Error: 服务器内部错误, 你的请求我已经收到了, 但是这道题我不会, 解不出来 我先睡了
通常认为, 0~399之间的状态码都是正常的, 其他都是不正常的
-
-
键值对: 大量的属性名和属性值的结合, 可以在服务器响应的时候自定义
-
Content-Type: 响应体中的数据格式, 常见格式如下
- text/plain: 普通的纯文本, 浏览器通常会将响应体原封不动的展示在页面上
- text/html: html文档, 浏览器通常会将响应体作为html页面进行渲染
- text/javascript: js代码, 浏览器通常会使用js引擎将他解析并且执行
- image/jpeg: 浏览器会将他视为jpg图片
- text/css: css代码, 浏览器会将他视为样式
- attachment: 附件, 浏览器看到这个类型, 通常会直接触发下载功能
- 其他MIME类型
面试题: 1. 如果访问http://www.xxx.com/1.js 浏览器是否会按照js来执行 不会, 只看响应头中的Content-Type 2. 如果服务器返回的响应体是js, 浏览器是否会按照js执行 不会, 还是只看响应头中的Content-Type
-
Server: web服务器类型
-
响应体
响应体没什么好说的, 就是响应消息的正文
在浏览器地址栏中输入一个地址, 按下回车键以后都发生了什么?
-
浏览器将url地址补充完整: 没有书写协议, 则补上协议
-
找到这个url域名的服务器ip, 浏览器会首先从缓存中获取, 如果无则会去系统文件的hosts文件中寻找,如果无则会查询DNS服务器
-
浏览器将对url地址进行url编码: 如果url地址中出现了非ASCII码字符, 则浏览器会对其进行编码
-
浏览器会构造一个http请求, 将请求头请求体和附带的数据封装在一个tcp包中, 该tcp包会依次经过传输层, 网络层, 数据链路层, 物理层到达服务器
-
服务器接收到请求, 解析之后将一个html页面代码放进响应体中, 返回给浏览器
-
浏览器拿到服务器的响应以后, 丢弃当前页面, 开始渲染新的html页面, 根据页面结构生成dom树, 根据css生成css规则树, 然后合并成渲染树
-
浏览器在渲染页面的过程中, 发现有其他的嵌入资源, 如图片js等, 浏览器会使用不阻塞加载的方式重新向服务器发送请求获取资源
-
当上述事件处理完毕以后触发window.onload事件
Ajax
不仅仅是浏览器可以发出请求并且获得响应, 任何具有网络通信能力的程序均可以这样做
过去, 在浏览器中, 只有浏览器本身有发送请求的能力, 直到ajax的出现
ajax是一种技术, 让js语言在浏览器中获得了新的api, 通过该api, js代码拥有了和服务器通信的能力
同时,js代码发出的网络请求浏览器是不会刷新页面的
我们来看一篇已经封装好的ajax的方法, 根据代码和注释相信可以帮助你理解好Ajax是如何请求服务器资源的
// 笔者直接用es5书写了, 避免有些朋友还未接触到es6
function ajax(url, methods, data, callback, flag) {
var requestTypes = {
GET: function() {
var date = new Date().getTime; // 时间戳, 好让服务器区分请求的不同
var paramsStr = '';
// 因为get请求一般是不设置响应体的, 所以我们要将参数进行遍历拼接进url
for(var prop in data) {
paramsStr += (prop + '=' + data[prop] + '&');
}
url = url + '?' + paramsStr + date;
xhr.open(methods, url, flag); // send代表配置请求, 这个flag一般代表是否异步, 默认肯定是异步
xhr.send(); // 代表构建请求发送至服务器
},
POST: function() {
// 如果是POST请求
xhr.open(method, url, flag);
xhr.send(data); // post请求的data是要进请求体的
}
}
// 创建发送请求的对象, 因为兼容性问题IE独有为ActiveXObject来创建,所以我们做一下兼容性处理
var xhr = windows.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft XMLHttp');
// 设置请求的请求头
xhr.setRequestHeader("Content-Type", "application/json");
var methods = methods.toUpperCase(); // 调用方法的人可能会使用小写的get post 我们统一转化为大写
// 当请求状态发生改变的时候运行的函数, 这个方法最好写在send之前, 因为如果写在send之后由于网速原因某些时候可能会监听不到变化
xhr.onreadystatechange = function() {
// xhr.readyState: 一个数字, 用于判断请求到了哪个阶段
// 0: 刚刚创建好了这个对象, 但是还未配置请求(未调用open方法)
// 1: open方法已经被调用, 配置请求已经处理好
// 2: send方法已经被调用, 请求体构建好并发送到服务器
// 3: 正在接收服务器的响应消息体
// 4: 服务器响应的所有内容均已经接收完毕
// 根据这四个状态我们可以想到的是我们要等到状态为4的时候才可以取到完整的服务器响应数据
if(xhr.readyState === 4) {
// xhr.status 表示状态码和状态文本
if(xhr.status === 200) {
// xhr.responseText: 获取服务器响应的消息体
// xhr.getResponseHeader("Content-Type") 获取响应消息头中的Content-Type
// 这个时候我们直接将消息体传进callback, 让回调函数取做他该做的事
callback(xhr.responseText);
}
}
}
// 根据请求方法进行不同的操作
requestTypes[methods]();
}
注释写的相对来说比较详细应该也可以帮助大家更好的理解ajax请求了