要说http就绕不开tcp,TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来讲,两者没有可比性。可是,http是基于tcp协议的。javascript
物理层 | 链路层 |
---|---|
将二进制的0和1和电压高低,光的闪灭和电波的强弱信号进行转换 | 驱动 |
网络层css
- 使用 IP 协议,IP 协议基于 IP 转发分包数据
- IP 协议是个不可靠协议,不会重发
- IP 协议发送失败会使用ICMP 协议通知失败
- ARP 解析 IP 中的 MAC 地址,MAC 地址由网卡出厂提供
- IP 还隐含链路层的功能,无论双方底层的链路层是啥,都能通讯
复制代码
传输层 通用的 TCP 和 UDP 协议(tcp比udp安全)html
TCP 协议面向有链接,能正确处理丢包,传输顺序错乱的问题,可是为了创建与断开链接,须要至少7次的发包收包,资源浪费
UDP 面向无链接,无论对方有没有收到,若是要获得通知,须要经过应用层
复制代码
会话层以上分层 TCP/IP 分层中,会话层,表示层,应用层集中在一块儿 网络管理经过 SNMP 协议前端
Http协议是创建在TCP协议基础之上的,当浏览器须要从服务器获取网页数据的时候,会发出一次Http请求。Http会经过TCP创建起一个到服务器的链接通道,当本次请求须要的数据完毕后,Http会当即将TCP链接断开,这个过程是很短的。因此Http链接是一种短链接,是一种无状态的链接。java
keep-alive http虽然没有状态,可是能够经过会话例如session保持链接 连接可复用,节约拆桥时间。node
1XX | 2XX | 3XX | 4XX | 5XX |
---|---|---|---|---|
信息性状态码 | 成功状态码 | 重定向 | 客户端错误状态码 | 服务端错误状态码 |
少见 | 200 OK | 301 永久性重定向 | 400 请求报文语法错误 | 500服务器请求错误 |
204 响应报文不含实体的主体部分 | 302 临时性重定向(负载均衡) | 401发送的请求须要有经过 HTTP 认证的认证信息 307 和302含义相同 | 503 服务器暂时处于超负载或正在停机维护,没法处理请求 | |
206 范围请求 | 303 资源存在着另外一个 URL,应使用 GET 方法定向获取资源 | 403 对请求资源的访问被服务器拒绝 | ||
304 客户端已经执行了GET,但文件未变化。 | 404 服务器上没有找到请求的资源 |
写一个静态服务(命令行工具)web
客户端–发送带有SYN标志的数据包–一次握手–服务端 服务端–发送带有SYN/ACK标志的数据包–二次握手–客户端 客户端–发送带有带有ACK标志的数据包–三次握手–服务端ajax
客户端-发送一个FIN,用来关闭客户端到服务器的数据传送 服务器-收到这个FIN,它发回一个ACK,确认序号为收到的序号加1 。和SYN同样,一个FIN将占用一个序号 服务器-关闭与客户端的链接,发送一个FIN给客户端 客户端-发回ACK报文确认,并将确认序号设置为收到序号加1跨域
**输入地址
> 浏览器查找域名的 IP 地址
> 这一步包括 DNS 具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存...
> 浏览器向 web 服务器发送一个 HTTP 请求
> 服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)
> 浏览器跟踪重定向地址
> 服务器处理请求
> 服务器返回一个 HTTP 响应
> 浏览器显示 HTML
> 浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)
> . 浏览器发送异步请求**
复制代码
能够处理并发promise
uri 统一资源表示符,url 统一资源定位符 location ,统一资源命名符
> http://(协议)name:password(登陆信息,认证)@www.fs.ip(服务器地址):8080(端口号)/dir/index.htm(文件路径)?a=a(查询字符串)#asd(片断标识符)
复制代码
咱们访问一个路径,路径回去dns域上找对应的ip地址,中间包括查找浏览器缓存,本地文件等等,最后将ip地址返回,http主要针对应用层,应用层会有一些报文,主要经过tcp传输,http基于tcp,TCP会将HTTP拆分红不少段,把每一个报文可靠的传给对方,udp是不可靠的会丢包,拼完以后返回服务器
let url = require('url');
let Obj = url.parse('http://user:passwrd@www.zdl.cn:80/1.html?a=1#aaa')
console.log(Obj);
=>Url {
protocol: 'http:',
slashes: true,
auth: 'user:passwrd',
host: 'www.zdl.cn:80',
port: '80',
hostname: 'www.zdl.cn',
hash: '#aaa',
search: '?a=1',
query: 'a=1',
pathname: '/1.html',
path: '/1.html?a=1',
href: 'http://user:passwrd@www.zdl.cn:80/1.html?a=1#aaa' }
复制代码
咱们一般要解析字符串
let url = require('url');
let {pathname ,query,path} = url.parse('http://user:passwrd@www.zdl.cn:80/1.html?a=1&b=2&c=3&d=4#aaa')
//解析
let str = query;
let obj = {};
str.replace(/([^=&]*)=([^=&])/g,function(){
obj[arguments[1]] = arguments[2];
})
console.log(obj);
//或者
let {pathname ,query,path} = url.parse('http://user:passwrd@www.zdl.cn:80/1.html?a=1&b=2&c=3&d=4#aaa',true) //添加true会直接解析
//解析
console.log(query);
复制代码
如上请求报文包括,请求行,请求首部,请求实体(带content的都是实体),,而后请求还会返回响应头,请求头和响应头都有的叫通用首部字段,请求头独有的叫请求首部字段。
get post put delete head(获取报文首) options(跨域试探性请求,节约流量) teace(调用盏,追踪路径)
curl -v --header "Range:bytes=1-200" https://www.baidu.com/img/bd_logo1.png?qua=high
=>subjectAltName: host "www.baidu.com" matched cert's "*.baidu.com"
* issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign Organization Validation CA - SHA256 - G2
* SSL certificate verify ok.
> GET /img/bd_logo1.png?qua=high HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.54.0
> Accept: */*
> Range:bytes=1-200
>
< HTTP/1.1 206 Partial Content
< Accept-Ranges: bytes
< Cache-Control: max-age=315360000
< Connection: Keep-Alive
< Content-Length: 200
< Content-Range: bytes 1-200/7877
< Content-Type: image/png
< Date: Sat, 07 Jul 2018 03:56:46 GMT
< Etag: "1ec5-502264e2ae4c0"
< Expires: Tue, 04 Jul 2028 03:56:46 GMT
< Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT
< P3p: CP=" OTI DSP COR IVA OUR IND COM "
< Server: Apache
< Set-Cookie: BAIDUID=B37F40A5E737D32A9475DC95E0925B45:FG=1; expires=Sun, 07-Jul-19 03:56:46 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1
<
PNG
复制代码
请求头中的Range来指定 资源的byte范围 响应会返回状态码206响应报文 对于多重范围的范围请求,响应会在首部字段Content-Type中标明multipart/byteranges
let http = require('http')
let server = http.createServer();
server.on('request',function(req,res){
console.log("请求到来了");
//req,res都是基于socket的 req可读流=> 客户端,res可泻流 => 服务端
//res.writeHead(200,{...}) 之能写一次,且和setHeader冲突
res.statusCode = 200;
res.setHeader('Content-Length',2);
res.setHeader('Content-Type','text/html;charset=utf8');
res.end('hello');
//能够经过服务器,curl ,postman等发送请求
})
//默认连接成功以后会把socket解析成两个东西,req,res 解析后触发事件叫request事件
server.on('connection',function(socket){
//net 中的socket和res一个效果
// socket.write(`
// HTTP/1.1 200 OK
// Content-Length :2
// Content-Type: text/html;charset=utf8
// ok
// `)
// socket.end()
console.log("连接成功");
})
server.listen(3000)
复制代码
//命令行输入测试
//curl -X POST -v -d "name=zdl" http://localhost:3000/a?a=1&c=4
let http = require('http')
let server = http.createServer();
server.on('request',function(req,res){
console.log(req.method);
console.log(req.url);
console.log(req.httpVersion);
console.log(req.headers);//请求头对象,要取里面的参数,能够经过key来取(小写)
let arr = [];
req.on('data',function(data){//只要是post须要经过监听事件获取数据,默认触发一次64k
arr.push(data)
})
req.on("end",function(){
let str = Buffer.concat(arr);
console.log(str.toString());
res.end('hello');
})
})
server.listen(3000,function(socket){
console.log("server start 3000");
})
复制代码
//测试命令行输入
let net = require('net');
let server = net.createServer();
function parser(socket,callback){
// socket.on("data",function(){
// })//接收
function parserHeader(head){
let obj = {};
let headers = head.split(/\r\n/);
let line = headers.shift();
let [method,path,version] = line.split(' ');
let heads = {};
headers.forEach(line => {
let [key,value] = line.split(': ');
heads[key] = value;
});
obj['method'] = method;
obj['path'] = path;
obj['version'] = version;
obj['headers'] = headers;
return obj;
}
function fn(){
let result = socket.read().toString();//若是read 不传参数会默认全读
let [head,content] = result.split(/\r\n\r\n/);
let obj = parserHeader(head);
console.log(obj);
//readble方法会触发屡次,触发一次后就移除掉
socket.removeListener('readable',fn)
}
socket.on("readable",fn)//默认把缓存区填满
}
server.on('connection',function(socket){
parser(socket,function(req,res){
server.emit('request',req,res);//将socket派发给request
})
})
server.on('request',function(req,res){
console.log(req.method);
console.log(req.url);
console.log(req.httpVersion);
console.log(req.headers);//请求头对象,要取里面的参数,能够经过key来取(小写)
let arr = [];
req.on('data',function(data){//只要是post须要经过监听事件获取数据,默认触发一次64k
arr.push(data)
})
req.on("end",function(){
let str = Buffer.concat(arr);
console.log(str.toString());
res.end('hello');
})
})
server.listen(3000)
复制代码
咱们常常用的断点续传,多语言还有防盗链等等都是基于咱们的http来实现的,包括缓存,
客户端 请求头 Range:bytes=1-200
服务端
响应头
Accept-Ranges: bytes < Content-Length: 200 < Content-Range: bytes 1-200/7877
模拟上述功能
range.js
//服务端
let http = require('http');
let path = require('path'); //路径
let fs = require('fs'); //读文件
let p = path.join(__dirname,'1.txt'); //获取读文件的路径
let {promisify} = require('util');
let stat = promisify(fs.stat); // 将stat方法转化成promise的方法 可能没有end默认所有读取
let server = http.createServer();
server.on('request',async function (req,res) {
//取请求头,取的到则分段,不然就总体获取
let range = req.headers['range'];
try{
let s = await stat(p);
let size = s.size;
if (range) {
let [, start, end] = range.match(/(\d*)-(\d*)/);//第一个参数是匹配字符串,第二个是第一项,第二个是第二项
start = start ? Number(start) : 0;
end = end ? Number(end)-1 : size-1;
res.statusCode = 206;
// 告诉客户端当前是范围请求
res.setHeader('Accept-Ranges','bytes');
// 返回的内容长度
res.setHeader('Content-Length',end-start+1);
res.setHeader('Content-Range', `bytes ${start}-${end}/${size}`);
fs.createReadStream(p,{start,end}).pipe(res); //把读取的结果传给res
} else {
// 边读边写,返回文件
fs.createReadStream(p).pipe(res);//res是可写流,在可读流和可写流之间加管道,至关于不停的读文件不一样的调res的write方法
}
}catch(e){
console.log(e);
}
})
server.listen(3000);
//测试curl -v --header "Range:bytes=3-5" http://localhost:3000
复制代码
client.js
//客户端,须要启动楼上的服务端,而后在cmd里node 当前文件夹的名字运行客户端
let http = require('http');
let fs = require('fs');
let pause = false; // 默认开启下载模式 true时暂停
let ws = fs.createWriteStream('./download.txt');//但愿下载到这个地方去
let options = {
hostname: 'localhost', //主机/路径
port: 3000, //端口号 还有个头0-3/3-5等等
}
// 实现下载功能
let start = 0;
//监控输入
process.stdin.on('data',function (data) {
data = data.toString();
if(data.match(/p/)){
pause = true;
}else{
pause = false;
download();
}
})
function download() {
// 请求以前加个请求头
options.headers = {
'Range': `bytes=${start}-${start + 9}`
}
start += 10;
// let socket = http.request(options);//每次调用时请求的文件位置累加
// socket.write();
// socket.end()//发送请求
//等同于
http.get(options, function (res) { //屡次发送请求 get 没有请求题
let buffers = [];
let total = res.headers['content-range'].split('/')[1];
total = parseInt(total);//58
res.on('data',function(data){
buffers.push(data);
})
res.on('end', function () {
let str = Buffer.concat(buffers).toString();
ws.write(str);//写到文件去
if (!pause && start < total) { // 没有完毕才继续请求
setTimeout(() => {
download()
}, 1000);
}
});
})
}
download();
复制代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="screen" href="main.css" />
<script src="main.js"></script>
</head>
<body>
<img src="http://d.hiphotos.baidu.com/video/pic/item/e850352ac65c10385fd7b21fbe119313b17e8945.jpg" alt="">
</body>
</html>
复制代码
用http-server启动服务器,结果图片出现裂图,这就是简单的防盗,原理很简单,Referer: 若是当前请求不容许访问,返回裂图
文件结构
index.html
<body>
<img src="http://localhost:3000/2.jpg" alt="">
</body>
复制代码
let http = require('http');
let path = require('path');
let url = require('url');
let fs = require('fs');
let {promisify } = require('util');
let stat = promisify(fs.stat);
let whiteList = ['www.zdl1.cn'];
let p = path.resolve(__dirname,'public');
let server = http.createServer(async function(req,res){
let {pathname} = url.parse(req.url); // index.html 2.jpg 1.jpg
let refer = req.headers['referer'] || req.headers['referred'];
try{
let rp = path.join(p,pathname); // 真实的路径
let s = await stat(rp); // 文件存在就读取相应给客户端
if(refer){
// 若是有refer要判断是否和法若是 不合法返回一张裂图
// 如今再哪里用这张图 www.zdl2.cn
let hostname = url.parse(refer).hostname;
// 表明当前文件的主机名 www.zdl1.cn
let host = req.headers['host'].split(':')[0];
if(host != hostname ){
if (whiteList.includes(hostname)){
return fs.createReadStream(path.join(p, '2.jpg')).pipe(res);
}
fs.createReadStream(path.join(p,'1.jpg')).pipe(res);
}else{
fs.createReadStream(path.join(p, '2.jpg')).pipe(res);
}
}else{
fs.createReadStream(rp).pipe(res);
}
}catch(e){
res.end(`NOT Found`);
}
})
server.listen(3000);
复制代码
用 http://localhost:8080访问index文件,和http://www.zdl1.cn:8080/访问是同样的,http://www.zdl2.cn:8080则返回裂图
域名须要自行配置host文件
多语言也是用的头
language.js
// 多语言
let pack = {
'zh-CN':'你好',
'zh':'nihao',
'en':'hello',
'fr':'Bonjour'
}
let defaultLanguage = 'en'; // 默认是英语
let http = require('http');
http.createServer(function (req,res) {
let lang = req.headers["accept-language"];
if(lang){ // 若是有多语言
let langs = lang.split(',');//拆分语言,每种语言逗号分割
// [{name:'zh-CN',q:1},{name:'en',q:0.8}]
langs = langs.map(l=>{
let [name,q] = l.split(';');
q = q?Number(q.split('=')[1]):1;
return {name,q}
}).sort((lan1,lan2)=>lan2.q-lan1.q);
for(var i = 0;i<langs.length;i++){ // 循环每一种 语言看看包里有没有,若是有返回对应的语言
if(pack[langs[i].name]){
res.setHeader('Content-Language', langs[i].name);
res.end(pack[langs[i].name]);
return;
}
}
// 没有默认语言
res.setHeader('Content-Language', 'en')
res.end(pack[defaultLanguage]);// 默认语言;
}else{
res.setHeader('Content-Language', 'en')
res.end(pack[defaultLanguage]);// 默认语言;
}
}).listen(3000);
//accept-language: zh-CN,zh;q=0.7,en;q=0.8,fr;q=0.1
复制代码
测试:curl -v --header "Accept-Language:zh;n;q=0.8,fr;q=1" http://localhost:3000
这个包后台通常是咱们本身写的,前端是有包好比i8n