实现进程间的通信有多种方法,文件读写机制或者使用IPC( Inter-Process Communication ),但是这些方法都只能在一台计算机上进行进程通信,进程也可以通过网络进行通信,每台计算机的能提供的资源是有限的(亚马逊的AWS云服务器就是通过暴露API接口,来访问服务器资源)。
协议
协议是一种给通信双方提供的标准格式,一但,在没有协议的情况下通信双方会出现信息无法正确理解,或者不能完全理解信息。
谈到协议那肯定离不开OSI(Open Systems Interconnection)七层模型,这里主要用的是应用层HTTP协议,图放到下面,就不赘述了。
使用HTTP协议进行请求和响应
HTTP协议是基于文本的协议,位于TCP协议之上,当传输需要保证时是首选协议,这个协议基于请求,由客户端生成一个HTTP会话,和响应响应一样,由服务器返回给客户端。
HTTP压缩
可以通过压缩HTTP响应体,减少在网络上传输的数据,当客户端支持了压缩,可以设置Accept-Encoding 头,服务器一但遇到这个头就会选择压缩算法对响应体进行压缩,其中gzip是最常用的压缩算法。
基本使用
创建一个index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>哈哈哈哈哈哈哈哈哈哈</h1>
</body>
</html>
创建server-gzip.js文件,引入http、zlib和fs
const http = require('http')
const zlib = require('zlib')
const fs = require('fs')
http.createServer((request, response) => {
//读取文件流
const raw = fs.createReadStream(__dirname + '/index.html');
// 设置accept-encoding
const acceptEncoding = request.headers['accept-encoding'] || '';
response.setHeader('Content-Type', 'text/plain');
console.log(acceptEncoding);
//接收的头是否有gzip
if (acceptEncoding.includes('gzip')) {
console.log('使用了 gzip');
// 设置gzip响应头
response.setHeader('Content-Type', 'gzip');
raw.pipe(zlib.createGzip()).pipe(response)
} else {
console.log('没有压缩');
raw.pipe(response);
}
}).listen(process.env.PORT || 8866)
压缩显示二进制信息
curl -H 'Accept-Encoding: gzip' http://localhost:8866/ | xxd
解压缩查看内容
curl -H 'Accept-Encoding: gzip' http://localhost:8866/ | gunzip
成功!
给自己补充的扩展:curl命令的行为类似客户端在网络上使用服务,这个服务打印请求是否使用压缩解释正在发生的行为
比较压缩前后的大小
之前是236之后是183,还是有可见的缩小的
HTTP压缩只压缩body内容,不压缩请求头
HTTP/TLS
加密,TLS(Transport Layer Security)是传输层安全,用于HTTP传输,也就是HTTPS。与gzip不同TLS对请求头也有效,TLS是CPU密集型操作,应该由外部进程执行,TLS取代了过时的SSL(Secure Sockets Layer )。
TLS使用证书进行验证,证书有两种
包含公钥public key的证书,可以被任何人持有
包含私钥private key的证书,只可以被少数特定人持有,且要保密
对于HTTP,服务器会提供它的公钥,客户端请求加密会使用这个公钥,当客户端与服务器首次通信时会产生大量的随机数和会话的密码,用公钥加密信息后发送到服务器,临时密码加密TLS会话。
通过openssl创建私钥和证书
创建私钥
openssl req -nodes -new -x509 -keyout basic-private-key.key
创建证书
openssl req -nodes -new -x509 -out basic-certificate.cert
(友情提醒:和我一样是macOS系统的小伙伴注意了,如果是新手且制作过Mac的证书的,千万不要自作聪明,如果使用的不是-node是创建的你的服务一定跑不起来,别问我咋知道的,这是个悲伤的故事,以下是错误示范)
openssl genrsa -out basic-private-key.key
openssl req -new -key basic-private-key.key -out basic-certificate.crt
(友情提醒2:创建私钥和证书一定要用一条命令一起创建出来,否则就会得到不匹配的错误)
Error: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch
以下是正确示范
openssl req -nodes -new -x509 -keyout basic-private-key.key -out basic-certificate.cert
-------------------------------分割线------------------------------------------
在之前的食谱API进行补充,将生成的私钥和证书放入项目中
对producer_http_basci.js进行简单的修改,引入fs模块对私钥和证书进行同步读取创建HTTPS服务,其余部分基本不变
const fs = require('fs')
//fastify创建服务
const server = require('fastify')({
https: {
// 同步读取私钥和证书
key: fs.readdirSync(__dirname+'路径'),
cert: fs.readFileSync(__dirname+'路径')
}
})
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 4000
server.get('/recipes/:id', async(req, reply) => {
console.log(`work pid =${process.pid}`);
const id = Number(req.params.id)
if (id !== 42) {
reply.statusCode = 404
return {error: 'not found'}
}
return {
producer_id:process.pid,
recipe: {
id, name: "Chicken Tikka Masala",
steps: "Throw it in a pot...",
ingredients: [
{ id: 1, name: "Chicken", quantity: "1 lb", },
{ id: 2, name: "Sauce", quantity: "2 cups", }
]
}
}
})
server.listen(PORT, HOST, () => {
console.log(`running at https:${HOST}:${PORT}`);
})
成功启动
通过浏览器访问
这个原因很简单就是,因为签名是我们自己的,可以通过命令行加 --insecure进行测试
curl --insecure https://127.0.0.1:4000/recipes/42
成功获取数据
之前的web-api也需要更新一下
const server = require('fastify')()
const fetch = require('node-fetch')
const https = require('https')
const fs = require('fs')
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 3000
const TARGET = process.env.TARGET || 'localhost:4000'
const options = {
agent: new https.Agent({
ca:fs.readFileSync('/Users/guofengrui/JS/distribute_node/shared/tls/basic-certificate.cert')
})
}
server.get('/', async () => {
const req = await fetch(`https://${TARGET}/recipes/42`, options)
const payload = await req.json()
return {
consumer_pid: process.pid,
producer_data: payload
}
})
server.listen(PORT, HOST, () => {
console.log(`Consumer running at https://${HOST}:${PORT}/`);
})
由于证书是自签名的,实际请求会有错误,但路子没问题。
GraphQL
GraphQL是Facebook设计的一种查询协议,非常适用于facade(外观) service(服务)这种开发模式,是位于多个其它服务和数据源服务之前,GraphQL尤其擅长给客户端返回最小量的数据。
注意:GraphQL没有规定底层需要使用的协议,通常HTTP协议即可
编写一个基本的gql文件
type Query {
recipe(id: ID): Recipe
pid: Int
}
type Recipe {
id: ID!
name: String!
step: String
ingredients: [Ingredient]!
}
type Ingredient {
id: ID!
name: String!
quantity: String
}
第一个实体Query,代表根查询,由consumer提供,这段代码有两个必须设置的信息,pid和recipe。
接下来使用gql数据实体的方式改造之前的项目
producer部分
第一步:安装fastify-gql
第二步:设置实体解析器
第三步:注册解析器
consumer部分
第四步:设置查询参数
第五步:把参数包装为JSON字符串
producer-graphql.js
const server = require('fastify')()
const graphql = require('fastify-gql')
const fs = require('fs')
const schema = fs.readFileSync('/Users/guofengrui/JS/distribute_node/shared/graphql-schema.gql').toString()
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 4000
// resolvers对象告诉graphql对象如何响应
const resolvers = {
//查询实体的顶级
Query: {
pid: () => process.id,
recipe: async (_obj, { id }) => {
if (id != 42) throw new Error(`recipe ${id} 找不到`)
return {
id, name: 'Chicken Tikka Masala',
steps: 'Throw it in a pot...'
}
}
},
// 当Recipe取到值,运行Recipe解析器
Recipe: {
ingredients: async (obj) => {
return (obj.id != 42) ? [] : [
{ id: 1, name: "Chicken", quantity: "1 lb", },
{ id: 2, name: "Sauce", quantity: "2 cups", }
]
}
}
}
// 注册解析器、gql
server.register(graphql, {schema, resolvers, graphql:true}).listen(PORT, HOST, () => {
console.log(`Producer running at http://${HOST}:${PORT}/graphql`);
})
consumer-graphql.js
const server = require('fastify')();
const fetch = require('node-fetch'); //2.6版本
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 3000
const TARGET = process.env.TARGET || 'localhost:4000'
// gql查询参数
const complex_query = `query kitchenSink ($id:ID){
recipe(id: $id) {
id name
ingredients {
name quantity
}
}
pid
}`
server.get('/', async () => {
const req = await fetch(`http://${TARGET}/graphql`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
//参数通过JSON包裹起来,发送JSON字符串
body: JSON.stringify({
query: complex_query,
variables: {id: "42"}
})
})
return {
consumer_pid: process.pid,
producer_data: await req.json()
}
})
运行,分布式方式3000访问4000,gql数据实体,成功获取数据