Web全栈架构师(三)——NodeJS+持久化学习笔记(1)

NodeJS基础

NodeJS是什么?

node.js 是一个异步的事件驱动的 JavaScript 运行时

node.js 特性:

  • 非阻塞I/O:https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/
  • 事件驱动:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

node.js的应用场景

  • web 应用
  • api编写
  • 中间层
  • 微服务

准备工作

安装运行

  • 下载安装 node.js:http://nodejs.cn/download/
  • 运行node程序node 01-runnode.js
console.log('run me use: node 01-runnode.js!');
console.log('hello, node.js');
  • 注意:每次修改 js 文件需要重新执行node 01-runnode.js才能生效,可以安装nodemon对文件改动进行监视,自动重启:npm i -g nodemon,之后用nodemon运行js文件即可。
    在这里插入图片描述

调试

  • 点debug图标,增加配置
    在这里插入图片描述
  • 点击Add Configuration,选择Node.js:Nodemon Setup
    在这里插入图片描述
    在这里插入图片描述
  • 保存该配置即可进行断点调试:
    在这里插入图片描述

使用模块(module)

  • node 内建模块:
require('os')
  • 第三方模块:
// 需要先安装 npm i cpu-stat -S
require('cpu-stat')

示例:

// 内建模块
const os = require('os')
// 第三方模块
const cpuStat = require('cpu-stat')

function showStatistics(){
    const memory = os.freemem() / os.totalmem() * 100
    console.log(`内存占用率:${memory}%`);
    
    cpuStat.usagePercent((err, percent) => {
        console.log(`CPU占用率:${percent}%`);
    })
}
setInterval(showStatistics, 5000);
  • 自定义模块:
// 导入
module.exports = {}
// 导出
require('./conf')

示例:

// 自定义配置导出
module.exports = {
  url: 'mongodb://localhost:27017',
  dbName: 'test',
  user: 'admin',
  password: '12345'
}
// 导入自定义模块
const conf = require('./conf')
console.log(conf);
// 导出
exports.rmb2dollar = function (rmb){
  return rmb/6;
}
// 导入
const {rmb2dollar} = require('./currency')
console.log(rmb2dollar(100))
// 导出
let rate;
function rmb2dollar(rmb){
  return rmb/rate
}
module.exports = function(r){
  rate = r
  return {
    rmb2dollar
  }
}
// 导入时传递配置参数
const {rmb2dollar} = require('./currency')(7)
console.log(rmb2dollar(100))

了解:
ES6导入语法:Node处于试验阶段,需要 js 变为 mjs node9.0以上,node --experimental-modules ./server.mjs
import http from ‘http’

核心API

global

setTimeout、setInterval、module、require、Buffer、等等

fs

  • 同步阻塞读取异步非阻塞读取
// fs
const fs = require('fs')
// 读取一个文件
const data = fs.readFileSync('./conf.js')  // 阻塞操作
console.log('readFileSync', data)
// 异步回调方式读取文件
fs.readFile('./conf.js', (err, data) => {
  console.log('readFile', data)
})
console.log('其他操作')

输出结果:

readFileSync <Buffer 6d 6f 64 75 6c 65 2e 65 78 70 6f 72 74 73 20 3d 20 7b 0d 0a 20 20 75 72 6c 3a 20 27 6d 6f 6e 67 6f 64 62 3a 2f 2f 6c 6f 63 61 6c 68 6f 73 74 3a 32 37 ... >
其他操作
readFile <Buffer 6d 6f 64 75 6c 65 2e 65 78 70 6f 72 74 73 20 3d 20 7b 0d 0a 20 20 75 72 6c 3a 20 27 6d 6f 6e 67 6f 64 62 3a 2f
2f 6c 6f 63 61 6c 68 6f 73 74 3a 32 37 ... >
  • Promise异步化读取
// 解决异步回调效率低的解决方案
// Promise
const {promisify} = require('util')
const readFile = promisify(fs.readFile)
readFile('./conf.js').then(data => {
  console.log('Promise readFile', data)
})

// v10.0
// fs Promises API
const {promises} = require('fs')
promises.readFile('./conf.js').then(data => {
  console.log('Promises API readFile', data)
})

输出结果:

(node:22992) ExperimentalWarning: The fs.promises API is experimental   // 试验阶段API
Promise readFile <Buffer 6d 6f 64 75 6c 65 2e 65 78 70 6f 72 74 73 20 3d
20 7b 0d 0a 20 20 75 72 6c 3a 20 27 6d 6f 6e 67 6f 64 62 3a 2f 2f 6c 6f 63 61 6c 68 6f 73 74 3a 32 37 ... >
Promises API readFile <Buffer 6d 6f 64 75 6c 65 2e 65 78 70 6f 72 74 73 20 3d 20 7b 0d 0a 20 20 75 72 6c 3a 20 27 6d 6f 6e 67 6f 64 62 3a 2f 2f 6c 6f 63 61 6c 68 6f 73 74 3a 32 37 ... >
  • Generator迭代器读取
// Generator
function* ascReadFile (){
  yield readFile('./conf.js')
  yield readFile('./currency.js')
}
const gen = ascReadFile()
gen.next().value.then(data => {
  console.log('Generator readFile', data);
  return gen.next().value;    
}).then(data => {
  console.log('Generator readFile', data);
  return gen.next().value;   
});
  • async await异步阻塞读取
// async await
async function asyncReadFile(){
  let data1 = await readFile('./conf.js');
  console.log('async await readFile', data1);
  let data2 = await readFile('./currency.js');
  console.log('async await readFile', data2);
}
asyncReadFile();

buffer

八位字节组成数组,可以有效的在js中存储二进制数据

  • buffer的基本使用:
// 创建
const buf1 = Buffer.alloc(10); // 内存分配,一旦分配长度不可变,超出部分截断
console.log(buf1)
// <Buffer 00 00 00 00 00 00 00 00 00 00>

// 从数据创建
let buf2 = Buffer.from([1, 2, 10])
console.log(buf2)
// <Buffer 01 02 0a>
buf2 = Buffer.from([1, 'hello', 10])
console.log(buf2)
// <Buffer 01 00 0a>  // 存放不下,则是00

const buf3 = Buffer.from('hell, 开课吧')
// const buf3 = Buffer.from('hell, 开课吧', 'utf8') // 默认utf8
console.log(buf3);
// <Buffer 68 65 6c 6c 2c 20 e5 bc 80 e8 af be e5 90 a7>

// 写入
buf1.write('hello')
console.log(buf1) 
// <Buffer 68 65 6c 6c 6f 00 00 00 00 00>
buf1.write('hello, kaikeba lalalalalalala')
console.log(buf1) 
// <Buffer 68 65 6c 6c 6f 2c 20 6b 61 69> 

// 读取
console.log(buf1.toString()) 
// hello, kai
console.log(buf3.toString('ascii')) 
// 乱码 hell, e<h/>e'

// 合并
const buf4 = Buffer.concat([buf1, buf3])
console.log(buf4.toString());
// hello, kaihell, 开课吧

http

const http = require('http')
const fs = require('fs')
const path = require('path')

http.createServer((req, res) => {
  console.log('来了一个请求!')
  const {url, method, headers} = req
  if(url === '/' && method === 'GET'){
    // 读取首页
    console.log(path.resolve('../index.html')) // 绝对路径 
    fs.readFile(path.resolve('../index.html'), (err, data) => {
      if(err){
        res.statusCode = 500 // 服务器内部错误
        res.end('500 - Internal Server Error')
        return
      }
      res.statusCode = 200  // 请求成功
      res.setHeader('Content-Type', 'text/html')
      // res.setHeader('Content-Type', 'text/plain')
      res.end(data)
    })
    // res.end('服务器响应啦!')
  } else if (url === '/users' && method === 'GET'){
    // 接口编写
    res.setHeader('Content-Type', 'application/json')
    // res.end(字符串 | Buffer)
    res.end(JSON.stringify([{name: 'zhangsan', age: 20}]))
  } else if(headers.accept.indexOf('image/*') !== -1 && method === 'GET'){
    console.log(path.resolve(url))
    fs.createReadStream(path.resolve('.' + url)).pipe(res)
  }
}).listen(3000);

stream

用于 node 中数据流的交互接口

// stream:用于 node 中数据流的交互接口
const fs = require('fs')
const rs = fs.createReadStream('./conf.js')  // 读取流
const ws = fs.createWriteStream('./info.txt') // 写入流

rs.pipe(ws)

// 二进制操作友好
const rs2 = fs.createReadStream('./01.png')  // 读取流
const ws2 = fs.createWriteStream('./02.pg') // 写入流
rs2.pipe(ws2)

仿写一个简版的express

试用express

const express = require('express')
const fs = require('fs')
const path = require('path')

const app = express()
app.get('/', (req, res) => {
  // 读取首页
  console.log(path.resolve('../index.html')) // 绝对路径 
  fs.readFile(path.resolve('../index.html'), (err, data) => {
    if(err){
      res.statusCode = 500 // 服务器内部错误
      res.end('500 - Internal Server Error')
      return
    }
    res.statusCode = 200  // 请求成功
    res.setHeader('Content-Type', 'text/html')
    // res.setHeader('Content-Type', 'text/plain')
    res.end(data)
  })
  // res.end('服务器响应啦!')
})
app.get('/users', (req, res) => {
  // 接口编写
  res.setHeader('Content-Type', 'application/json')
  // res.end(字符串 | Buffer)
  res.end(JSON.stringify([{name: 'zhangsan', age: 20}]))
})

app.listen(3000)

实现kexpress

  • 实现:
const http = require('http')
const url = require('url')

// 实现一个路由器
let routes = []

class Applicatin {
  get(path, handler) {
    routes.push({
      path,
      method: 'get',
      handler
    })
  }
  listen(){
    // 创建一个Server
    http.createServer((req, res) => {
      let {pathname} = url.parse(req.url, true)
      for(const route of routes){
        if(route.path === pathname){
          route.handler(req, res)
          return;
        }
      }
    }).listen(...arguments)
  }
}

module.exports = function(config){
  return new Applicatin()
}
  • 测试:
const kexpress = require('./kexpress')
const fs = require('fs')
const path = require('path')

const app = kexpress()
app.get('/', (req, res) => {
  // 读取首页
  console.log(path.resolve('../index.html')) // 绝对路径 
  fs.readFile(path.resolve('../index.html'), (err, data) => {
    if(err){
      res.statusCode = 500 // 服务器内部错误
      res.end('500 - Internal Server Error')
      return
    }
    res.statusCode = 200  // 请求成功
    res.setHeader('Content-Type', 'text/html')
    // res.setHeader('Content-Type', 'text/plain')
    res.end(data)
  })
  // res.end('服务器响应啦!')
})
app.get('/users', (req, res) => {
  // 接口编写
  res.setHeader('Content-Type', 'application/json')
  // res.end(字符串 | Buffer)
  res.end(JSON.stringify([{name: 'zhangsan', age: 20}]))
})

app.listen(3000)

NodeJS网络编程

HTTP协议详解

概述

HTTP 是一个属于应用层的面向对象的协议,由于其简洁、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前 www 中使用的是 HTTP/1.0 的第六版,HTTP/1.1 的规范化工作正在进行中,而且 HTTP-NG(Next Generation of HTTP)的建议已经提出。
HTTP 协议的主要特点可概括如下:

  • 支持客户端/服务端模式。
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有 GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP 允许传输任意类型的数据对象,正在传输的类型由 Content-Type 加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。
  • 无状态:HTTP 协议是无状态协议,无状态是指协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器端不需要先前信息时,它的应答就较快。

URL

HTTP URL(URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息),格式如下:
http://host:port/source_path

请求

HTTP 请求由三部分组成,分别是:请求行、消息报文、请求正文

  • 请求行:以一个方法符号开头,以空格分开,后面跟着请求的 URI 和协议版本,格式如下:
    Method Request-URI HTTP-Version CRLF
  • Method请求方法有多种:
    在这里插入图片描述

响应

HTTP 响应也是由三部分组成,分别是:状态行、消息报文、响应正文

  • 状态行格式如下:
    HTTP-Version Status-Code Reason-Phrase CRLF
  • 状态码:
    在这里插入图片描述
  • HTTP状态码列表:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

消息报头

普通报头

在这里插入图片描述

请求报头

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

响应报头

在这里插入图片描述
在这里插入图片描述

实体报头

在这里插入图片描述
在这里插入图片描述

跨域问题

  • 创建接口——http-server.js:
const http = require("http");
const fs = require("fs");

http.createServer((req, res) => {
  const {url, method} = req
  if(url == '/' && method == 'GET'){
    fs.readFile('../index.html', (err, data) => {
      res.setHeader('Content-Type', 'text/html')
      res.end(data)
    })
  }else if(url == '/users' && method == 'GET'){
    res.setHeader('Content-Type', 'application/json')
    res.end(JSON.stringify([{name: 'zhangsan', age: 20}]))
  }
}).listen(3000);
  • 请求接口,index.html:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- <script src="axios.min.js"></script> -->
<script>
  axios
    .get("http://localhost:3000/users")
    .then(res => res.data)
    .then(users => console.log(users));
</script>
  • 跨域:浏览器同源策略引起的接口调用问题
// 创建 http-server-2.js 使用端口 3002
http.createServer((req, res) => {
  ...
}).listen(3002);

打开http://localhost:3002访问,浏览器抛出跨域错误:
在这里插入图片描述

跨域解决方案

JSONP

前端+后端,绕过跨域

前端构造 script 标签请求指定的 URL(由于 script 标签发出的 GET 请求不受同源策略的限制),服务器返回一个函数执行语句,该函数名称通常由查询参 callback 的值决定,函数的参数为服务器返回的json数据,该函数在前端执行后即可获取数据。

代理服务器

请求同源服务器,通过该服务器转发请求至目标服务器,得到结果载转发给前端。
前端开发中测试服务器的代理功能就是采用的该解决方案,但是最终发布上线时如果 web 应用和接口服务器不在一起仍会跨域

CORS

跨域资源共享:后端方案,解决跨域
原理:CORS是W3C规范,真正意义上解决跨域问题,他需要服务器端对请求进行检查并对响应头做相应的处理,从而允许跨域请求。
具体实现:

  • 响应简单请求:动词为【GET、POST、HEAD】,没有自定义请求头,Content-Type是 application/www-form-urlencoded、multipart/form-data 或 text/plain 之一,通过添加以下响应头解决
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3002')
  • 响应preflight请求,需要响应浏览器发出的 options 请求(预检请求),并根据实际情况设置响应头:
else if(method == 'OPTIONS' && url == '/users'){
	res.writeHead(200, {
		'Access-Control-Allow-Origin': 'http://localhost:3002',
		'Access-Control-Allow-Headers': 'X-Token, Content-Type',
		'Access-Control-Allow-Methods': 'PUT'
	});
	res.end();
}
  • 该示例中可以通过添加自定义的 X-Token 请求头使请求变为preflight请求:
// index.html
axios.get('http://localhost:3000/users', {headers: {'X-Token': 'abcdef12345'}})
  • 则服务器端需要允许 X-Token,若请求为 POST 还传递了参数,则服务器还需要允许 Content-Type 请求头
// index.html
axios.get('http://localhost:3000/users', {foo: 'bar'}, {headers: {'X-Token': 'abcdef12345'}})

// http-server.js
else if((method == 'GET' || method == 'POST') && url == '/users'){}
  • 如果要携带 Cookie 信息,则请求变为 Credential 请求
// 预检 Options 中和 /users 接口中均需添加
res.setHeander('Access-Control-Allow-Credentials', 'true')

实战一个爬虫

原理:服务端模拟客户端发送请求到目标服务器获取页面内容并解析,获取其中关注部分的数据。

// 用来发送 https 请求
const originRequest = require('request')
// 类似服务器端的jquery
const cheerio = require('cheerio')
// 解码
const iconv = require('iconv-lite')

function request(url, callback){
  const options = {
    url: url, encoding: null   // 返回Buffer,不进行解码
  }
  originRequest(url, options, callback)
}
for(let i = 100570; i<100580; i++){
  const url = `https://www.dy2018.com/i/${i}.html`
  request(url, function(err, res, body) {
    const html = iconv.decode(body, 'gb2312')
    console.log(html)

    const $ = cheerio.load(html)
    console.log($('.title_all h1').text())
  })
}

实现一个实时聊天程序

socket实现

  • 原理:Net模块提供了一个异步 API 能够创建基于流的 TCP 服务器,客户端与服务器建立连接后,服务器可以获得全双工 Socket 对象,服务器可以保存 Socket 对象列表,在接收某客户端消息时,推送给其他客户端。
const net = require('net')
const chatServer = net.createServer()
const clientList = []
chatServer.on('connection', function(client) {
  client.write('Hi!\n')
  clientList.push(client)
  client.on('data', function(data) {
    clientList.forEach(v => {
      v.write(data)
    })
  })
})
chatServer.listen(9000)
  • 测试:通过 Telnet 连接服务器
telnet localhost 9000

http实现

  • 原理:客户端通过 ajax 方式发送数据给 http 服务器,服务器缓存消息,其他客户端通过轮询方式查询最新数据并更新列表
  • chat.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Chat-HTTP</title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
  <div id="app">
    <input v-model="message">
    <button @click="send">发送</button>
    <button @click="clear">清空</button>
    <div v-for="item in list">{{item}}</div>
  </div>
  <script>
    const host = 'http://localhost:3000'
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello, Vue!',
        list: []
      },
      mounted(){
        setInterval(async () => {
          const res = await axios.get(host + '/list')
          this.list = res.data
        }, 1000)
      },
      methods: {
        async send() {
          let res = await axios.post(host + '/send', {
            message: this.message
          })
          this.message = ''
          this.list = res.data
        },
        async clear(){
          let res = await axios.post(host + '/clear')
          this.list = res.data
        }
      }
    })
  </script>
</body>
</html>
  • chat-http.js
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const path = require('path')

app.use(bodyParser.json())

const list = ['ccc', 'ddd']

app.get('/chat', (req, res) => {
  res.sendFile(path.resolve('./chat.html'))
})

app.get('/list', (req, res) => {
  res.end(JSON.stringify(list))
})

app.post('/send', (req, res) => {
  list.push(req.body.message)
  res.end(JSON.stringify(list))
})

app.post('/clear', (req, res) => {
  list.length = 0
  res.end(JSON.stringify(list))
})

app.listen(3000)

socket.io 实现

  • 安装:npm i socket.io
  • 两部分:nodejs模块、客户端js
  • 特点:
* 源于 HTML5 标准
* 支持优雅降级
	- WebSocket
	- WebSocket over Flash
	- XHR Polling
	- XHR Multipart Streaming
	- Forever Iframe
	- JSONP Polling
  • 客户端:
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
var app = new Vue({
	el: '#app',
	data: {
	  message: 'Hello, Vue!',
	  list: [],
	  socket: null
	},
	mounted(){
	  this.socket = io()
	  this.socket.on('chat-msg', list => {
	    this.list = data
	  })
	},
	methods: {
	  async send() {
	    this.socket.emit('send-chat-msg', this.message)
	    this.message = ''
	  },
	  async clear(){
	    this.socket.emit('clear-chat-msg')
	  }
	}
})
  • 服务器端:
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http)
const path = require('path')

app.get('/chat', (req, res) => {
  res.sendFile(path.resolve('./chat2.html'))
})
const list = ['ccc', 'ddd']

io.on('connection', (socket) => {
  console.log('a user connected')
  io.emit('chat-msg', list)
  // 广播给除了发送者外的所有人
  // socket.broadcast.emit('chat-msg', list)
  // 自定义事件
  socket.on('send-chat-msg', (msg) => {
    if(!msg){
      return;
    }
    console.log(msg)
    list.push(msg)
    io.emit('chat-msg', list)
  })
  socket.on('clear-chat-msg', () => {
    list.length = 0
    io.emit('chat-msg', list)
  })
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
})
http.listen(3000, function(){
  console.log('listening on *:3000');
});

https

  • 创建证书
# 创建私钥
openssl genrsa -out privatekey.pem 1024
# 创建证书签名请求
openssl req -new -key privatekey.pem -out certrequest.csr
# 获取证书,线上证书需要经过证书授证中心签名的文件,下面只创建一个学习使用的证书
openssl x509 -req -in certrequest.csr -signkey privatekey.pem -out certificate.pem
# 创建pfx文件
openssl pkcs12 -export -in certificate.pem -inkey privatekey.pem -out certificate.pfx

在这里插入图片描述
在这里插入图片描述

http2

  • 多路复用-雪碧图、多域名CDN、接口合并
* 官方演示:https://http2.akamai.com/demo
* 多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息;而 HTTP/1.1 协议中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制,超过限制数目的请求会被阻塞。
  • 首部压缩
* HTTP/1.x 的 header 由于 cookie 和 user agent 很容易膨胀,而且每次都要重新发送。HTTP/2 使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩 header,减少发送包的数量,从而降低延迟。
  • 服务端推送
HTTP/2 中,服务器可以对客户端的一个请求发送多个响应。举个例子,如果一个请求请求的是 index.html,服务器很可能会同时响应index.html、logo.jpg、css文件和js文件,因为它知道客户端会用到这些东西。这相当于在一个HTML文档内集合了所有的资源。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值