这即将是一篇很长很长的文章...
从一个简单的服务器开始
// server.js
const http = require('http')
const server = http.createServer(() => {
console.log('原地转300圈并listen3000端口')
})
server.listen(3000)
首先在终端运行node server.js
使用浏览器在地址栏输入localhost:3000
会发现控制台打印如下:
但是此时会发现浏览器地址栏始终在加载中,并且什么也没有显示,接下来让它显示一些东西出来。
http.createServer()
的回调函数其实是有参数的,可以用来处理浏览器的请求,并进行响应。
const server = http.createServer((req, res) => {
// 处理请求和响应
console.log('原地转300圈并listen3000端口')
res.write('lalala')
res.end()
})
此时在终端运行node server.js
使用浏览器在地址栏输入localhost:3000
会发现浏览器显示如下:
每一次修改都要使用ctrl+c
停止服务器后再用node server.js
重启很麻烦呐...那就推荐个工具:使用npm install -g supervisor
安装supervisor,然后运行supervisor server.js
就可以实现代码更改之后页面的自动更新。
并且会发现不论地址栏如何修改,页面内容都会是这样的:
是因为服务器并没有对浏览器的请求做出响应。所以http.createServer()
回调的第一个参数就派上了用场:
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
console.log(req.url)
res.write('lalala')
res.end()
})
此时控制台输出:
这里的/a/b/c.html
和/favicon.ico
都是浏览器在访问http://localhost:3000/a/b/c.html
时请求的内容,/favicon.ico
是页面标题左侧的小图标,也就是使用<link rel="shortcut icon" href="/favicon.ico">
设置的那个图标。
所以就可以根据请求的url来进行相应的处理~
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
switch(req.url) {
case '/1.html':
res.write('111')
break
case '/2.html':
res.write('222')
break
default:
res.write('404')
break
}
res.end()
})
发现可以响应请求了是不是很棒棒!
但是每次都使用switch...case...
肯定不是我们的本意啊,所以接下来肯定会进行一些懒人操作!
来呀,继续响应请求
这次是另一个模块的使用:fs
。
fs
模块主要有两个方法:fs.readFile()
和fs.writeFile()
、
fs.readFile('文件名', (err, data) => {
if (err) {
// err的处理
console.log(err)
} else {
// data的处理
console.log(data.toString())
}
})
这里之所以要使用data.toString()
是因为readFile
的data
是buffer
类型,这个buffer
可以日后再说,当前知道使用toString()
可以转换成我们之前输入的内容就好了。
这里如果发现出现了乱码则需要把aaa.txt
的文件格式改成UTF-8,至于怎么改可以自行百度。
fs.writeFile('文件名', '内容', (err) => {
console.log(err)
})
学习了fs
的两个基本函数之后就可以来访问静态文件啦!
可以在当前目录下新建一个public
文件夹,在public
文件放一些静态文件,比如图片啊,html文件之类的。
// server.js
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
const file_name = '/public' + req.url
fs.readFile(file_name, (err, data) => {
if (err) {
res.write('404')
} else {
res.write(data)
}
res.end()
})
})
server.listen(3000)
此处注意res.end()
的位置,readFile是异步操作,需要在回调中进行下一步的服务响应。此处返回的data不需要进行toString操作,因为浏览器可以识别buffer格式。
例如在public下放两个html文件:
<!-- 1.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>Document</title>
<style>
div {
width: 100px;
height: 100px;
background-color: #f3f3f3;
border: #ccc 1px solid;
}
</style>
</head>
<body>
<div>11111</div>
</body>
</html>
<!-- 2.html,别的内容都不变,只修改body下面的 -->
<body>
<div>22222</div>
</body>
然后对相应的路径进行访问:
啦啦啦可以直接访问文件啦!
这是和前端联系很紧密的一节
前端请求后端数据的常用的两种方式:GET和POST。GET的数据是放在url中,POST的数据不在url中。所以对这两种方式,后端需要不同的处理方式。
修改一下上节的1.html:
<!-- 别的内容都不变,只修改body下面的 -->
<body>
<div>11111</div>
<form action="http://localhost:3000" method="get">
用户:<input type="text" name="user" value=""><br>
密码:<input type="password" name="pass" value=""><br>
<input type="submit" value="提交">
</form>
</body>
GET
对于GET方式,需要处理的是req.url
部分。所以可以先尝试输出:
// server.js
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
res.write(req.url)
res.end()
})
server.listen(3000)
点提交之后,来观察一下页面的变化:
emmmm...虽说这确实太不安全了,但是就先学习一下思路嘛...
对于/?user=user&pass=123
,问号前面的部分是路径/
,问号后面的就是添加在url后面的参数啦,所以可以用最基本的split()
方法处理数据啊...但是要考虑那个/favicon.ico
,它没有?
没有&
,所以是要进行排雷的。
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
var get = {}
var url = req.url
if (url.indexOf('?') !== -1) {
var arr = url.split('?')
var arr1 = arr[1].split('&')
for (let i = 0; i < arr1.length; i++) {
var arr2 = arr1[i].split('=')
get[arr2[0]] = arr2[1]
}
res.write(JSON.stringify(get))
}
res.end()
})
server.listen(3000)
res.write()
参数只能是string或buffer,所以不能直接res.write(get)
。
此时会看到已经成功的处理了req.url
:
但是不想这么麻烦啊...于是还真的有简单的模块可以用哦!
例如:
const queryString = require('querystring')
var query = queryString.parse('user=user&pass=123')
console.log(query) // { user: 'user', pass: '123' }
所以就可以使用这个模块来将处理方式变得简单点~
// server.js
const http = require('http')
const queryString = require('querystring')
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
var get = {}
var url = req.url
if (url.indexOf('?') !== -1) {
var arr = url.split('?')
get = queryString.parse(arr[1])
res.write(JSON.stringify(get))
}
res.end()
})
server.listen(3000)
但是这里还需要split()
一次,所以呢,还有个更简单的模块~
const urlLib = require('url')
var url = urlLib.parse('localhost:4000/?user=user&pass=123')
console.log(url)
// Url {
// protocol: 'localhost:',
// slashes: null,
// auth: null,
// host: '4000',
// port: null,
// hostname: '4000',
// hash: null,
// search: '?user=user&pass=123',
// query: 'user=user&pass=123',
// pathname: '/',
// path: '/?user=user&pass=123',
// href: 'localhost:4000/?user=user&pass=123' }
主要看的就是Url.query
,但是现在并没有根据&
切开怎么办呢,这时需要给urlLib.parse()
设置它的第二个参数为true
,说明要解析query,如下:
const urlLib = require('url')
var url = urlLib.parse('localhost:4000/?user=user&pass=123', true)
console.log(url.query)//{ user: 'user', pass: '123' }
// server.js
const http = require('http')
const urlLib = require('url')
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
var obj = urlLib.parse(url, true)
var get = obj.query
var url = obj.pathname
res.write(JSON.stringify(get))
res.end()
})
server.listen(3000)
POST
上面的一路看过来已经get到点了...所以来post一下。
先修改之前的1.html,将get方式改为post即可
<form action="http:localhost:3000" method="post">
用户:<input type="text" name="user" value=""><br>
密码:<input type="password" name="pass" value=""><br>
<input type="submit" value="提交">
</form>
因为post的数据是放在HTTP报文主体中的,数据获取就是一个很大的问题,所以首先看一下node怎么接收post数据。
node接收post数据主要是使用req
的事件处理:
req.on('data', (data) => {}) // 每次接收数据时触发
req.on('end', () => {}) // 接收完毕数据时触发
因为post数据可以很大,所以对较大的数据包会进行分块再处理。分块可以避免由于种种错误而产生的大块数据重新传输,会浪费资源。
const server = http.createServer((req, res) => {
console.log('原地转300圈并listen3000端口')
var post = ''
req.on('data', (data) => {
post += data
})
req.on('end', () => {
res.write(JSON.stringify(post))
res.end()
})
})
server.listen(3000)
可以看到显示的数据是这样的;
之所以要监听两个事件是因为POST数据较大时数据也是分块发送的,所以读者朋友可以试一下增加个<texearea>
然后多输入一些数据,比如设置个递增的变量来感受一下data
事件的触发情况。
这里使用字符串拼接的方式来处理数据确实有一些low了,比如传输的是文件格式那就不能在字符串里面放了啊,但是先这样,目的是了解POST传输的特点啦~
对上图中的数据的处理方式还记得不?当然是牛逼哄哄的queryString
啦!对数据进行以下方式的处理,就会得到json格式的数据:
const queryString = require('querystring')
//...
req.on('end', () => {
var POST = queryString.parse(post)
res.write(JSON.stringify(POST))
res.end()
})
一个简单的应用
呐呐...来应用一下噻,比如说制作一个注册登录的界面。目前因为没有应用到数据库,就可以直接使用存储几个账号密码的map来模拟一下~
具体代码可以看reg&login。
接下来是express的部分~
express
使用之前应该先安装npm install express
。
然后server.js
内容如下:
// server.js
const express = require('express')
const server = express()
server.listen(3000)
emmmm...这个时候使用supervisor server.js
然后打开localhost:3000
会发现出现了Cannot GET /
,意思就是没什么东西嘛...那接下来就让他显示一些东西出来。
在server.listen(3000)
前加这样的代码:
server.use('/', (req, res) => {
res.send('这是首页')
res.end()
})
此时就会发现首页上有东西啦!
express中的req
和res
其实是进行封装过的,和原生node回调中的req
和res
兼容,这里的res.send
完全可以改成res.write
,只是res.write
的参数必须是buffer
或string
类型,而res.send
则没有这个限制。
发现了吗,express使用的server.use('/', callback)
代替了原来对req.url
的复杂处理,不用进行parse
再switch...case
了,方便了不少~
接下来看express怎么处理用户请求~
html文件还是之前的1.html,先<form>
元素的method
设置为get,随后再设置为post,来观察提交后的情况。server.js中更改如下:
const server = express()
server.get('/', (req, res) => {
res.send('get到了')
res.end()
})
server.post('/', (req, res) => {
res.send('post到了')
res.end()
})
server.listen(3000)
而使用server.use
的时候则是无论哪种请求都会触发,大家可以自己试试。
在express中,读取静态文件的方法是使用express.static设置一个放置静态文件的目录。
server.use(express.static('public'))
有了上面一句之后就可以访问public
目录下的静态页面1.html
啦~如下:
在express中处理url中的参数也有简单的方法:req.query
,若代码如下:
server.get('/', (req, res) => {
res.send(req.query)
res.end()
})
就可以像下图一样:
所以具备了用express搞注册登录的基础知识,可以来一波实践哦~
express实现注册登录
代码可以见2-reg-login
express处理POST请求
上文学习了express使用req.query
可以获取到GET方式的请求内容,这次来学习怎么接收POST请求并处理数据。
这里大家可以先去看看express的中间件的概念,上文中出现的express.static()
其实就是一个托管静态文件的中间件。
这里就需要用到一个叫做body-parser
的中间件,但是这个中间件就不是express内置的了,使用前需要npm install body-parser
。
使用方法很简单,还是使用之前的1.html
,将form元素的method改为post:
<form action="http://localhost:3000/" method="post">
用户:<input type="text" name="user" value=""><br>
密码:<input type="password" name="pass" value=""><br>
<input type="submit" value="提交">
</form>
server.js
代码如下:
const bodyParser = require('body-parser')
server.use(bodyParser.urlencoded())
server.use('/', (req, res) => {
res.send(req.body) // 使用了bodyParser之后body属性上才会有值
res.end()
})
然后打开1.html
输入数据后,结果如下:
于是会发现控制台会有这样一段提示:
这里的extended如果值为true则是开启扩展模式,还有另一个参数是limit表示限制大小,默认100k。但是一般用不到扩展模式,可以直接设置为false,如下:
server.use(bodyParser.urlencoded({
extended: false,
limit: 2 * 1024 * 1024 // 限制2M
}))
这个时候就会发现上图中的提示没有了。
express的一些操作
链式操作
server.use('/', (req, res, next) => {
console.log('a')
// next()
})
server.use('/', (req, res) => {
console.log('b')
})
如上述代码,当没有next时控制台只打印一个a,但是有了next之后,控制权可以交给下一个server.use('/', () => {})
,所以可以看到控制台打印了a和b。
中间件
server.use
参数为函数时,是对所有的请求都作用到。如上面的body-parser
。
server.use((req, res, next) => {
req.on('data', )
})
server.use('/', (req, res, next) => {
res.send(req.body) // 下方可以获取到
})
运行后如图:
那么就可以模仿body-parser
来搞一个中间件~
server.use((req, res, next) => {
var str = ''
req.on('data', (data) => {
str += data
})
req.on('end', () => {
req.body = str
next() // 注意这里next的位置
})
})
server.use('/', (req, res, next) => {
res.send(req.body)
})
还用之前那个1.html,结果如下图:
这时候想处理成json的话就可以用之前的querystring的parse。
依然是跟前端联系很紧密的一节
呐呐这次要了解的是cookie和session~
cookie设置和读取
可以在chrome浏览器F12 -> Application -> Cookies看到网页的cookie,可以使用dcument.cookie
获取或修改当前页面的cookie。
server.use('/', (req, res) => {
res.cookie('user', 'oneday', {
maxAge: 24 * 1000 * 3600 // 设置时间
})
res.send()
})
此时查看浏览器控制台就会发现有了这样一个cookie:
不设置maxAge的话cookie会在浏览器关闭后失效。
也可以在res.cookie()
中为cookie设置路径如下:
server.use('/', (req, res) => {
res.cookie('user', 'oneday', {
path: '/one', // 设置路径
})
res.send()
})
如果使用path属性设置了/one
的话,那么当访问localhost:3000
时无法读取到cookie,只有在localhost:3000/one
下的路径都可以访问到该cookie,如localhost:3000/one/a.html
。
在express中读取cookie则需要一个中间件cookie-parser
。
const cookieParser = require('cookie-parser')
server.use(cookieParser())
server.use('/', (req, res) => {
res.cookie('user', 'oneday', {
path: '/one',
maxAge: 24 * 1000 * 3600
})
res.send(req.cookies) // 使用了cookie-parser中间件后才有的cookies属性
})
这时如果访问localhost:3000
是不会看到有输出的,原因同上,但是/one
下的路径就会有,如下图:
cookie加密
server.use(cookieParser())
server.use('/', (req, res) => {
req.secret = '愿所有的爱和付出都不会被辜负' // 签名密钥
res.cookie('user', 'oneday', {
maxAge: 24 * 1000 * 3600,
signed: true
})
res.send(req.cookies)
})
可以看到如下图的输出:
可以看到该签了名的cookie很明显的将原始的cookie包含在了内容中,emmmm内容还是可以看到的。但是一旦cookie被修改了就能看得出来啊,所以签名只是能做到防篡改,不能做到直接加密。
签过名的cookie以s
开头,签名后的cookie长度较长,因此经cookie-parser
处理过的带签名的cookie会放在req.signedCookies
中,未签过名的就放在req.cookies
中。
// 告诉cookieParser加密使用的字符串
server.use(cookieParser('愿所有的爱和付出都不会被辜负'))
server.use('/', (req, res) => {
req.secret = '愿所有的爱和付出都不会被辜负' // 签名密钥
res.cookie('user', 'oneday', {
maxAge: 24 * 1000 * 3600,
signed: true
})
res.send(req.signedCookies) // 就可以将签过名的cookie原样输出
})
删除cookie
删除cookie则是使用res.clearCookie(name)
就可以了。
session的设置和读取
想处理session则需要一个叫cookie-session的中间件。但是要记得在使用cookie-session中间件之前先使用cookie-parser中间件。因为不处理cookie哪来的session呢~
将server.js
代码改成如下所示:
const cookieParser = require('cookie-parser')
const cookieSession = require('cookie-session')
server.use(cookieParser())
server.use(cookieSession())
server.use('/', (req, res) => {
res.send(req.session)
})
会发现报错了~~
呐呐来介绍一下这个keys。它的存在是为了预防session劫持的发生,keys是一个密钥数组,可以用来加密,express会循环使用密钥来加密cookie。
server.use(cookieSession({
keys: ['one', 'day', 'oneday'] // keys加密数组,注意使用方法
}))
server.use('/', (req, res) => {
if (req.session['count'] == null) {
req.session['count'] = 1
} else {
req.session['count']++
}
console.log(req.session['count'])
res.send('OK')
})
会发现每次控制台的session都会刷出来两个数字,那是因为也访问到了/favicon.ico
,这个可以暂时不用管。
打开浏览器后可以看到cookie的地方有两个:
其中的session
代表的是访问次数,session.sig
代表的是签名后的session
,刷新会发现session
变化不大但是session.sig
则会发生很大的变化,这样就可以防止别人拿着上次的session假冒用户进行操作了。
session也可以进行一些自行的设置~
server.use(cookieSession({
name: 'ses',
keys: ['one', 'day', 'oneday'],
maxAge: 2 * 3600 * 1000 // 2小时
}))
session的删除
服务器端使用delete req.session[name]
删除session。
ejs模板引擎
ejs相对来说是温和性的,可以和html共存的一个模板引擎,个人比较喜欢ejs,所以这里不介绍jade的相关知识...大家可以自己找教程~
首先当然是要使用npm install ejs
下载一下~
<!-- index.ejs -->
<!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>Document</title>
</head>
<body>
我的名字叫:<%= name %>
</body>
</html>
// ciews下ejs.js
const ejs = require('ejs')
ejs.renderFile("./views/index.ejs", {name: "oneday"}, (err, data) => {
if(err) {
console.log("编译失败")
} else {
console.log(data)
}
})
使用node ejs.js
之后,可以得到控制台的如下输出:
ejs中的某些语法(空格不是必须的):
- 输出变量值:
<%= name %>
- js语句:
<% for (var i = 0; i < 3; i++) { %>
<div><%= arr[i].name %></div>
<% } %>
- 不转义的字符串:
<%- <div></div> %>
- 引用别的文件:
<% include filepath+filename %>
,这里的filepath+filename
不能是变量。
ejs与express配合
emmmm可以看express的文档模板引擎。主要就是两句代码:
server.set('views', './views')
server.set('view engine', 'ejs')
server.get('/index', (req, res) => {
res.render('index.ejs', {name: 'oneday'})
})
文件上传
这节主要讲怎么用express实现一个文件上传功能。
将1.html内容进行更改:
<form action="http://localhost:3000/" method="post">
文件:<input type="file" name="f1"><br>
<input type="submit" value="上传">
</form>
const express = require('express')
const bodyParser = require('body-parser')
const server = express()
server.use(bodyParser.urlencoded({extended: false}))
server.post('/', (req, res) => {
res.send(req.body)
})
server.listen(3000)
此时运行一下试试会发现只打印出来了图片的名称,意思是只上传了文件名。原因是body-parser
只能处理表单中enctype为application/x-www-form-urlencoded
的项,上传文件需要将enctype改为multipart/form-data
,且不能用body-parser
处理了。这时就需要引入一个新的中间件:multer
。
npm install multer
之后,可以按如下操作:
<form action="http://localhost:3000/" method="post" enctype="multipart/form-data">
文件:<input type="file" name="f1"><br>
<input type="submit" value="上传">
</form>
const multer = require('multer')
var objmulter = multer()
const server = express()
server.use(objmulter.any()) // 可以是single(指定的名称)
server.post('/', (req, res) => {
res.send(req.files)
})
这时再运行就会发现有很多buffers数据出来了...但是只要这样的数据肯定是没有什么用处的啊,毕竟我们要的是上传的文件嘛...那就可以像下面这样对数据进行一下处理:
var objmulter = multer({dest: './www/upload'}) // 只改这一行代码,dest指定目标文件夹
这是再运行会发现www/upload下面真的会有一个文件,但是是一个很长名字且没有后缀,也就是没有文件类型的一个文件,接下来我们要做的就是给它加一个文件扩展名,要用到一个叫path
的包。
const path = require('path')
var str = "c:\\www\\aaa\\nbb.png"
var obj = path.parse(str)
console.log(obj)
会发现输出如下:
所以通过以下代码可以完成一个文件上传的小应用~
const multer = require('multer')
const pathLib = require('path')
const fs = require('fs')
var objmulter = multer({dest: './www/upload'})
const server = express()
server.use(objmulter.any())
server.post('/', (req, res) => {
var newName = req.files[0].path + pathLib.parse(req.files[0].originalname).ext
// 重命名临时文件
fs.rename(req.files[0].path, newName, (err) => { // fs.rename
if (err) {
res.send('上传失败')
} else {
res.send('上传成功')
}
})
})
选择文件并上传后,会发现在www/upload下是真的有该文件的,而且可以正常打开~
数据库操作
这里用到的数据库是mysql,管理工具是Navicat Premium。在node中需要首先npm install mysql
,随后先进行数据库连接:
const mysql = require('mysql')
const db = mysql.createConnection({
host: 'localhost', // 目标
user: 'root', // 用户名
port: 3306, // 端口号
password: '123456', // 密码
database: 'user_table' // 数据库名
})
数据库操作语法统一格式为:db.query('操作语句', (err, data) => {// callback})
SQL语句标准写法要求:1、关键字要求大写;2、库名、表名、字段名需要加上``
记录一些简单操作语句:
- 增-INSERT
-- INSERT INTO 表(字段列表) VALUES(值列表)
INSERT INTO `user_table` (`username`, `password`) VALUES ('one', '123456')
- 删-
-- DELETE FROM 表名 (WHERE 条件)
DELETE FROM `user_table` WHERE `username`='one'
- 改-
-- UPDATE 表名 SET 字段=值, 字段=值, 字段=值...(WHERE 条件)
UPDATE `user_table` SET `username`='oneday', `password`='233333' WHERE `username`='one'
- 查-SELECT
-- SELELT (内容) FROM 表名 (WHERE 条件)
SELECT * FROM `user_table`
一些子句
- where
WHERE `age` >= 18
WHERE `age` >= 18 AND `score` < 60
WHERE `cash` > 100 OR `score` > 1000
- order
-- ASC -> 升序 | DESC -> 降序
ORDER BY `age` ASC/DESC
-- 按价格升序,随后再按销量降序,多条件排序就用逗号分隔
ORDER BY `price` ASC, `sales` DESC
- group
-- 按班级计数
SELECT `class`, COUNT(class) FROM `student_table` GROUP BY `class`
-- 每个班级的平均分
SELECT `class`, AVG(score) FROM `student_table` GROUP BY `class`
-- 每个班级的最高分和最低分
SELECT `class`, MAX(score), MIN(score) FROM `student_table` GROUP BY `class`
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。