server端和前端的区别
nodejs和JavaScript的区别
node引入/导出语法
http模块
fs模块
path模块
url模块
querystring模块
nodejs处理http请求
express
express-router
express中间件
redis存储session
nginx反向代理
morgan日志
安全
server端和前端的区别
- 服务的稳定性
- 需要考虑内存和cpu的优化
- 客户端独占一个浏览器,内存和cpu都不是问题
- server端承载很多请求,内存和cpu都是稀缺资源
- stream写日志是比较节省内存的(优化),redis存储session(扩展)
- 日志记录
- server端要记录日志,存储日志,分析日志,前端不用关心
- 安全
- server端要随时准备接受各种恶意攻击:越权操作,数据库攻击
- 预防xss攻击和sql注入
- 集群和服务拆分
- 产品发展速度快,承载流量大
nodejs和JavaScript的区别
- javascript
- 使用ESMAScript语法规范,外加web API
- web API : BOM DOM,事件绑定,ajax等
- nodejs
- 使用ESMAScript语法规范,外加nodejs API
- nodejs API:处理http请求,处理文件等
node引入/导出语法
- 引入模块
require('模块名称')
- 带路径的: 一般我们自己封装的模块
require('../xxx/yyy')
,js文件后缀名可以省略不写。 路径还可以写到文件夹的那层,但文件夹下一定要有一个index.js
系统会默认找index.js
require('../xxx/文件夹名称') === > require('../xxx/文件夹名称/index.js')
- 不带路径的:
require('koa')
其实就是找node_modules
文件夹 系统node_modules
- 带路径的: 一般我们自己封装的模块
- 导出模块语法
module.exports /exports
- 每一个Nodejs的执行文件都会自动地创建一个
module
对象,同时module.exports
会创建一个叫exports
的属性,初始值为空对象{}
。exports
和module.exports
指向同一个内存,但require()
返回的是module.exports
而不是exports
module.exports
可以直接等于一个对象 但exports
只能给其赋值新属性 不能直接等于一个新对象
- 每一个Nodejs的执行文件都会自动地创建一个
//module.exports的形式
module.exports = {
str,
arr,
targetObj
}
//exports的形式
exports.str = str;
exports.arr = arr;
exports.targetObj = targetObj;
http模块
nodejs
的运用基础是起码要创建一个服务,在该服务的基础上处理业务操作,对于创建一个服务器最原始的莫过于是使用http
模块。
http模块主要用于创建http server服务,其中封装了高效的http服务器和http客户端
http.server是一个基于事件的HTTP服务器,内部是由c++实现的,接口由JavaScript封装
http.request是一个HTTP客户端工具。用户向服务器发送数据。
var http = require('http');
var server = http.createServer((req,res)=>{
//解决跨域问题
res.setHeader('Access-Control-Allow-Origin',"*")
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
//设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf-8
res.writeHead(200,{"Content-Type":"text/html;charset='utf-8'"});
res.write('aaa');
/*如果没有res.end(),服务器会一直等待,运转到一定的时间,
页面会返回一个错误。提示服务器运行时间超长。*/
res.end(); // 结束下你的请求
});
server.listen(8080)
var http = require('http');
引入模块http.createServer(callback)
request:获取客户端发送的请求数据的信息(请求的地址,请求的方式等),response:返给客户端的相应数据server.listen(post,host,callback)
host可以不写 默认为localhost/127.0.0.1
fs模块
异步和同步
Node.js 文件系统(fs 模块
)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile()
和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。
// 属于异步操作 不会耽误其他的执行
fs.writeFile(path,data,callback);
fs.readFile(path,callback);
// 同步形式也有 很少用 太慢了
fs.writeFileSync();
fs.readFileSync();
var fs = require('fs');
//异步获取
fs.readFile('input.txt',(err,data)=>{
if(err){
return console.error(err)
}
// data返回的是原始的二进制数据(Buffer) 浏览器是可以解析的 自己看的时候需要toString()
console.log('异步读取:'+data.toString());
})
var fs = require('fs');
//异步写入
fs.writeFile('./a.txt','xxasxssx',(err)=>{
if(err){
console.log('写入失败',err);
}else{
console.log('写入成功',err);
}
});
var fs = require('fs');
//同步获取
var data = fs.readFileSync('input.txt');
console.log("同步读取: " + data.toString());
var fs = require('fs');
//同步写入
var data = fs.writeFileSync('input.txt');
console.log("同步读取: " + data.toString());
fs.writeFile(path,data,callback)
; //写入文件 路径,要写入的数据,回调函数 (接受一个err参数 )
写入一旦成功 即使没有这个文件 也会在目录中创建这个文件 并把数据写入到文件中 回调中的 err 返回为 null
写入失败 回调中的err会返回一个错误对象 。
fs.readFile(path,callback)
; // 用来读文件 不需要data 路径,回调函数(接受err,data参数 data是读取到的数据)
data返回的是原始的二进制数据(Buffer) 浏览器是可以解析的 只是你自己看的时候看不懂。通常我们可以data.toString()转为字符串来查看数据。
path模块
path
模块提供用于处理文件路径和目录路径的实用工具。
const path = require('path');
let str = '/root/a/b/1.txt';
//path.dirname(str) 获取目录名称 /root/a/b
console.log(path.dirname(str));
//path.extname(str) 获取扩展名 .txt
console.log(path.extname(str));
//path.basename(str) 获取文件名 1.txt
console.log(path.basename(str));
//path.resolve(str) 对路径的解析 类似于命令行的操纵 \root\a\b\c\d\e
console.log(path.resolve('/root/a/b','c','d','e'));
// 常用的是解析当前的绝对路径
console.log(path.resolve(__dirname,'build'));
url模块
url 模块用于处理与解析 URL。
url.parse(url)
这个方法可以将一个url的字符串解析并返回一个url的对象
const url = require('url');
url.parse("http://user:pass@host.com:8080/p/a/t/h?query=string#hash");
/*
返回值:
{
protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
}
没有设置第二个参数为true时,query属性为一个字符串类型
*/
url.parse(url,true);
url.parse("http://user:pass@host.com:8080/p/a/t/h?query=string#hash",true);
/*
返回值:
{
protocol: 'http:',
slashes: true,
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: { query: 'string' }, // 第二个参数 为true时
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'
}
返回的url对象中,query属性为一个对象
*/
url.format(urlObj)
这个方法是将传入的url对象编程一个url字符串并返回。
url.format({
protocol:"http:",
host:"182.163.0:60",
port:"60"
});
/*
返回值:
'http://182.163.0:60'
*/
url.resolve(from,to)
resolve这个方法返回一个格式为"from/to"的字符串
url.resolve("http://whitemu.com","gulu");
/*
返回值:
'http://whitemu.com/gulu'
*/
querystring模块
从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析。
querystring.parse(str)
将字符串解析为一个对象。
querystring.stringify(strObj)
将字符串对象解析为一个字符串。
const querystring = require('querystring');
console.log(querystring.parse('a=12&b=11&c=11')); //{ a: '12', b: '11', c: '11' }
console.log(querystring.stringify({ a: '12', b: '11', c: '11' })) //a=12&b=11&c=11
querystring.escape(str)
escape可使传入的字符串进行编码
querystring.escape("name=慕白");
/*
return:
'name%3D%E6%85%95%E7%99%BD'
*/
querystring.unescape(str)
unescape方法可将含有%的字符串进行解码
querystring.unescape('name%3D%E6%85%95%E7%99%BD');
/*
return:
'name=慕白'
*/
nodejs处理http请求
http请求概述
- DNS解析,建立TCP连接,发送http请求。
- server接收到http请求,处理,并返回。
- 客户端接收到返回数据,处理数据(如渲染页面,js)
nodejs处理http请求
- get请求和
querystring
- post请求和
postdata
- 路由
nodejs处理get请求
- get请求,即客户端要向server端获取数据
- 通过
querystring
来传递数据,如a.html?a=100&b=200 - 浏览器直接访问,就发送get请求。
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req,res)=>{
console.log('method',req.method); //get
const url = req.url;
console.log('url',url);
req.query = querystring.parse(url.split('?')[1]);
console.log('query',req.query);
res.end(JSON.stringify(req.query));
})
server.listen(8080,()=>{
console.log(`server is running `)
});
打印如下:
method GET
url /api/list?author=111
query [Object: null prototype] { author: '111' }
method GET
url /favicon.ico
query [Object: null prototype] {}
nodejs处理post请求
- post请求,即客户端要向server端传递数据
- 通过
postdata
来传递数据 - 浏览器无法直接模拟,需要手写js,或者用postman
const http = require('http');
const server = http.createServer((req,res)=>{
if(req.method == 'POST'){
console.log('req content-type',req.headers['content-type']);
let postData = '';
req.on('data',chunk =>{
postData+=chunk.toString();
})
req.on('end',() =>{
console.log('postData:',postData);
console.log('postData:',typeof postData); //string
req.body = JSON.parse(postData); //将post获取到的参数 放到 req.body上
res.end('hello world')
})
}
});
server.listen(8000,()=>{
console.log('ok')
})
express
安装脚手架
npm i express-generator -g
创建项目
express blog_express
cd blog-express
npm i
npm start
配置package.json
npm i nodemon cross-env -D
"scripts": {
"start": "node ./bin/www",
"dev":"cross-env NODE_ENV=development nodemon ./bin/www"
},
介绍app.js
var createError = require('http-errors'); 对错误页面的处理
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser'); 对cookie的解析 通过req.cookies就可以直接访问cookie
var logger = require('morgan'); 记录日志
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev')); 使用logger日志
app.use(express.json()); 可以获取到post json的数据 req.body获取
app.use(express.urlencoded({ extended: false })); 获取post除了json之外格式的数据 都是绑定到req.body上
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
res相关
res.send() (*****) // 万能方法 可以响应JSON 字符串 HTML script
res.json() // 主要给前端响应 JSON 数据
res.jsonp() // 主要给前端响应 JSON 数据(针对跨域请求)
数据和模板在后端合并渲染 生成HTML 返回给前端
res.render('ejs模板文件', {JSON对象格式的数据})
res.download('要下载的文件的路径', '标题')
res.redirect("要跳转到的新的网址url")
res.status(状态码404).render("ejs模板名字", {JSON对象格式的数据})
req相关
接收 get 方式的请求的参数,req.query.参数的key值
接收 post 方式的请求的参数,req.body.参数的key值
req.ip 获取浏览器的ip地址
express-router
//app.js
var blogRouter= require('./routes/blog.js');
app.use('/api/blog',blogRouter);
//routes---->blog.js
var express = require('express');
var router = express.Router();
router.get('/list', function (req, res, next) {
const { username, password } = req.query;
res.json({
errno: 0,
data: {
username,
password
}
})
})
router.post('/detail', function (req, res, next) {
const { username, password } = req.body;
res.json({
errno: 0,
data: {
username,
password
}
})
})
module.exports = router;
post
请求的数据在req.body
上get
请求的数据在req.query
上
express-中间件
在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。
其实中间件就是请求req
和响应res
之间的一个应用,请求浏览器向服务器发送一个请求后,服务器直接通过request
定位属性的方式得到通过request
携带过去的数据,就是用户输入的数据和浏览器本身的数据信息,这中间就一定有一个函数将这些数据分类做了处理,最后让request
对象调用使用,这个或者多个的处理函数就是我们所说得中间件。
原理分析
- app.use用来注册中间件,先收集起来。
- 遇到http请求,根据path和method判断触发那些。
- 实现next机制,即上一个通过next触发下一个
实现
- 创建存放所有路由的对象
(all,get,post.。。。)
,后期对不同的请求处理都会放到这里 - 声明对应处理的函数
use(),get(),post()
,register
函数统一处理path
信息,返回{path:'xxx',stack:['每个path对应中间件函数数组']}
use(),get(),post()
,将自己的info
信息存储到this.routes
中listen
监听端口callback
是createServer
回调,获取当前url
的路由和method
match
返回当前url
所需要执行的所有中间件集合
handle
核心方法 实现next()
调用各个中间件
const http = require('http');
const slice = Array.prototype.slice;
class LikeExpress{
constructor(){
// 静态属性
this.routes = {
all:[],
get:[],
post:[]
}
}
// 原型上定义的函数
//中间件公用的地方 定义在register里面
register(path){
const info = {};
if(typeof path === 'string'){
//如果第一个参数是路由的话
info.path = path;
// stack 以数组的形式存放路由后面的中间件
info.stack = slice.call(arguments,1);
}else{
// 如果是app.use((req,res,next)=>{}) 没有第一个参数的形式
info.path = '/';
info.stack = slice.call(arguments,0);
}
console.log(`info is ${info}`);
return info;
}
use(){
const info = this.register.apply(this,arguments);
this.routes.all.push(info);
}
get(){
const info = this.register.apply(this,arguments);
this.routes.get.push(info);
}
post(){
const info = this.register.apply(this,arguments);
this.routes.post.push(info);
}
// 监听事件
listen(...args){
const server = http.createServer(this.callback());
server.listen(...args);
}
// 定义server的回调函数
callback(){
return (req,res)=>{
// 定义res.json的使用
res.json = (data)=>{
res.setHeader('Content-type','application/json');
res.end(
JSON.stringify(data)
)
}
const url = req.url;
const method = req.method.toLowerCase();
const resultList = this.match(method,url);
this.handle(req, res, resultList)
}
}
// match 当前url所需要执行的所以中间件集合
match(method,url){
let stack = [];
if(url === '/favicon.ico'){
return stack;
}
// 获取routes
let curRoutes =[];
// 当前路由匹配到的中间件:所有的 all 以及当前url的method对应所有中间件
curRoutes = curRoutes.concat(this.routes.all);
curRoutes = curRoutes.concat(this.routes[method]);
curRoutes.forEach(routeInfo =>{
if(url.indexOf(routeInfo.path) === 0){
// url === '/api/get-cookie' 且 routeInfo.path === '/'
// url === '/api/get-cookie' 且 routeInfo.path === '/api'
// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
// 当前路由在curRoutes 中匹配对应自己的所有中间件
stack = stack.concat(routeInfo.stack)
}
})
return stack
}
// 核心的 next机制
handle(req,res,stack){
const next = ()=>{
const middleware = stack.shift();
if(middleware){
middleware(req, res, next);
}
}
next();
}
}
module.exports = () => {
return new LikeExpress()
}
调用
const express = require('./like-express2');
const app = express();
app.use((req,res,next)=>{
console.log('请求开始....',req.method,req.url);
next();
})
app.use((req,res,next)=>{
console.log('处理 cookie');
req.cookie = {
userId:'abc123'
}
next();
})
app.use('/api',(req,res,next)=>{
console.log('处理 /api 路由');
next();
})
app.get('/api',(req,res,next)=>{
console.log('get 处理 /api 路由');
next();
})
// 模拟登录验证
function loginCheck(req,res,next){
setTimeout(() => {
console.log('模拟登录成功');
next();
});
}
app.get('/api/get-cookie',loginCheck,(req,res,next)=>{
console.log('get 处理 /api/get-cookie 路由');
res.json({
errno:0,
data:req.cookie
})
})
app.listen(8000,()=>{
console.log(`server listen running http://localhost:8000`);
})
//访问 http://localhost:8000/api/get-cookie
//请求开始.... GET /api/get-cookie
//处理 cookie
//处理 /api 路由
//get 处理 /api 路由
//模拟登录成功
//get 处理 /api/get-cookie 路由
redis存储session
为什么session适合用redis?
- session访问频繁,对性能要求极高
- session不用担心断电丢失数据的问题
- session数据量不会很大(相比于mysql中存储的数据)
为什么网站数据不适合raduis
- 操作频率不是太高(相比于session操作)
- 断电不能丢失,必须保留
- 数据量太大,内存成本太高
redis默认端口和地址
REDIS_CONF = {
port:6379,
host:'127.0.0.1'
}
demo
const redis = require('redis');
// 创建客户端
const redisClient = redis.createClient(6379,'127.0.0.1');
redisClient.on('error',err =>{
console.log('err',err);
})
// 测试
redisClient.set('myname','zhangsan2',redis.print);
redisClient.get('myname',(err,val) => {
if(err){
console.log('err2',err);
return;
}
console.log('val is',val);
// 退出
redisClient.quit();
});
封装demo
const redis = require('redis');
const { REDIS_CONF } = require('../conf/db');
// 创建客户端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);
redisClient.on('error', err => {
console.log('err', err);
})
function set(key, val) {
if (typeof val == 'object') {
val = JSON.stringify(val);
}
redisClient.set(key, val, redis.print);
}
function get(key) {
const promise = new Promise((resolve, reject) => {
return redisClient.get(key, (err, val) => {
if (err) {
reject(err)
return;
}
// resolve(val)
if(val == null){
resolve(null)
}
try{
resolve(
JSON.parse(val)
)
}catch(ex){
resolve(val)
}
// 退出
// redisClient.quit();
});
})
return promise
}
module.exports = {
set,
get
}
nginx反向代理
nginx的配置
C:\nginx\conf --> nginx.conf
server {
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
# location / {
#root html;
#index index.html index.htm;
#}
location /api/ {// nodejs 接口访问地址
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
}
location / { //项目访问地址
proxy_pass http://localhost:5500;
}
proxy_pass
设置代理地址
proxy_set_header
设置头部信息
网站输入localhost:8080
—代理到localhost:5500
,当访问接口/api
的时候 请求的是 http://localhost:8000;
上的接口.
morgan日志
' dev 开发模式 combined线上模式 '
const ENV = process.env.NODE_ENV
if(ENV !== 'production'){
// 开发环境
app.use(logger('dev',{
stream:process.stdout //默认值
}));
}else{
// 线上环境
const logFileName = path.join(__dirname,'logs','access.log');
const writeStream = fs.createWriteStream(logFileName,{
flags:'a'
})
app.use(logger('combined',{
stream:writeStream
}));
}
安全
防止sql注入
主要通过mysql.escape
,对代码中特殊字符等进行转义处理。
const mysql = require('mysql');
const escape = mysql.escape;
const login = (username,password)=>{
username = escape(username); 防止sql注入,sql语句中不需要加单引号了
password = escape(password);
const sql = `
select username,realname from users where username=${username} and password=${password}
`
return exec(sql).then((data)=>{
return data[0] || {}
})
}
防止xss攻击(脚本攻击)
require('xss')
,对<>标签等进行转义;比如<转为<,这样就不会形成闭合的标签了
const xss = require('xss');
const newBlog = (blogData = {}) => {
const title = xss(blogData.title);;
密码加密
密码加密后即使窃取到了用户信息,密码也是加密处理的同样无法登录,我这里是通过require('crypto')
;来对密码进行加密处理。
const crypto = require('crypto');
// 密匙
const SECRET_KEY = 'wjas_1234#';
// md5加密
function md5(content){
let md5 = crypto.createHash('md5');
return md5.update(content).digest('hex'); // digest('hex') 是转为16进制
}
// 加密函数
function genPassword(password){
const str = `password=${password}&key=${SECRET_KEY}`
return md5(str)
}
module.exports = {
genPassword
}
/
const {genPassword} = require('../utils/cryp');
const login = (username,password)=>{
username = escape(username);
password = genPassword(password);加密处理
password = escape(password);
const sql = `
select username,realname from users where username=${username} and password=${password}
`
return exec(sql).then((data)=>{
return data[0] || {}
})
}