使用 Node.js 搭建一个 HTTP Server
const http = require (’ http ’ );
Koa将node.js的request(请求)和Response(响应)对象封装到
Context对象中,所以也可以把Context对象称为一次对话的上下文,通过加工Context对象,就可以控制返回给用户的内容。
const koa = require ( ' koa
const app = new koa();
app.use(async (ctx) => {
let postdata = '';
ctx.req.on('data', (data) => {
postdata += data ;
});
ctx.req.on ('end', ()=>{
console.log(postdata);
});
ctx.body='hello koa2'
})
app.listen(3000);
ctx.request.method === 'POST') { //判断是否为POST请求
ctx.request.path !== '/' 判断请求路径
ctx.state是推荐命名空间,用于通过中间件传递信息和前端视图。类koa-views些渲染View层的中间件也会默认把ctx.state面的属性作为View的上下文传入ctx.state .user = yield User .find(id) 上述代码是把user属性存放ctx.state象里,以便能够被另一个中间件读取
ctx . cookies . get (name , [options J ) ; //获取Cookie
ctx .cookies .set(name, value , [options]); //设置Cookie
ctx.throw用于抛出错误,把错误信息返给用户,
app .use(async (ctx) => {
ctx.throw(500);
}); 运行这段示例代码,会在页面上看到个状态码为500的错误页
app.use(async (ctx, next) => {
console.log('one')
await next()
console.log('one end')
})
app.use(async (ctx, next) => {
console.log('two')
await next()
console.log('two end')
})
app.use(async (ctx, next) => {
console.log('three')
await next()
console.log('three end')
})
const compose = require ('koa-compose')
async function func1(ctx, next){
// dosomeing
await next()
}
async function func2(ctx, next){
// dosomeing
await next()
}
async function func3(ctx, next){
// dosomeing
await next()
}
const all = compose([func1 , func2, func3])
app.use(all) ;
koa-bodyparser中间件可以把POST请求的参数解析到ctx.request.body
koa-bodyparser中间件最终解析出来的参数是一个对象。
安装
npm install --save koa-bodyparser
使用
const bodyParser = require('koa-bodyparser')
router.post('/news', async (ctx, next)=>{
ctx.body = ctx.request.body
})
app.use(bodyParser())
.use(router.routes()); //作用:启动路由
.use(router.allowedMethods()); // 作用: 这是官方文档的推荐用法,我们可以
---
app.use(bodyParser())
app.use(router.routes()); //作用:启动路由
app.use(router.allowedMethods()); // 作用: 这是官方文档的推荐用法,我们可以
安装
npm install --save koa -router
使用
const router = require('koa-router')();
或者
const Router = require('koa-router')
const router = new Router() //初始化koa-router中间件
npm install koa-router -save
表现层状态转移
CRUD 增删改查
restful 只提供一个uri =
https//api.test.com/users //POST 方法 请求发送新增用户信息
https//api.test.com/users/:id //DELETE 方法,用户 ID URI 的一部分
https//api.test.com/users/:id // PUT 方法,请求发送用户 信息 ID UR 一部
https//api.test.com/users/:id //GET 方法,用户 ID URI 的一部分
GitHub v4 的 API 使用了全新的设计风格 GraphQL
router
.post('/users', (ctx, next) => {
ctx .body ='新增了一位用户'
})
.del('/users/:id', (ctx, next) => {
ctx .body ='删除了用户编号为 id 的用户'
})
.put('/users/:id', (ctx, next) => {
ctx .body ='修改了用户编号为 id 的用户信息'
})
.get('/users/:id', (ctx, next) => {
ctx .body ='我是编号为工 的用户信息'
})
router
.post('/users', async (ctx, next) => {
ctx.body ='新增了一位用户'
})
.del('/users/:id', async (ctx, next) => {
ctx.body ='删除了用户编号为 id 的用户'
})
.put('/users/:id', async (ctx, next) => {
ctx .body ='修改了用户编号为 id 的用户信息'
})
.get('/users/:id', async (ctx, next) => {
ctx.body ='我是编号为工 的用户信息'
})
.all('/users/:id', async (ctx, next) => {
ctx .body ='我是编号为工 的用户信息'
})
//上述代码中还有all()方法。如果一条路由请求在all()方法和其他方法中同时命中,只有执行了 await next(),这条路由请求才会在all()方法和其他方法中都起作用,
在项目中, all()方法一般用来设置请求头,如设置过期时间、 CORS (Cross-Origin Resource Sharing, 跨域资源共享)
router.all('/*',async (ctx, next) => { //符号*代表允许来自所有域名的请求
ctx.set ("Access-Control-Allow-Origin","https: //www.cctalk.com");
await next () ;
});
router.get('user','/users/:id', (ctx, next) => { // 命名路由
// dosomething
})
router.url('user', 3) //通过调用路由的名称 user ,生成路由 --- '/users/3'
router.url('user', {id: 3})
koa-router 支持单个路由多个中间件的处理。能够为一个路由添加特殊的
中间件,也可以把 个路由要做 事情拆分 多个步骤去实现。当路由处理函数中有异步
操作时 这种写法的可读性和可维护性更高,代码如下
router.get('users/:id', (ctx, next) => {
return User.findOne(ctx.params.id).then(function(user) { //异步操作,首先读取用户的信息,假设用户为{ id l7 name :” Alex
ctx.user = user; //控制权传递,调用下 个中间件
next();
}) ;
},
(ctx , next) => { //在这个中间件中再对用户信息做一些处理
console .log(ctx.user);
// => { id : 17 , name :”Alex”)
}
);
const forums = new Router ();
const posts = new Router ();
posts.get ('/', function (ctx, next) {
//
})
posts.get ('/:pid', function (ctx, next) {
//
})
forums.use('/forums/:fid/posts', posts.routes() , posts.allowedMethods()) ; //获取互联网版块列表的接口
// ” / forums/ :fid/posts”=>”/forums/123/posts” B //获取互联网版块下某篇文章的接口
//" /forums/:fid/posts/:pid”=> "/forums/123/posts/123”
app.use(forums.routes());
通过 prefix 参数,可以为一组路由添加统 的前缀 和嵌套路由类似,这样做有利于管
理路由及简化路由的写法, 代码如下:
let router = new Router ( {
prefix:'/users'
} ) ;
//匹配路由”/users"
router.get ('/',
//
);
//匹配路由"/users/:id"
router . get ('/users/:id',
//
) ;
与嵌套路由不同的是,路由前缀是一个固定的字符串,不能添加动态参数
koa-router 也支持参数,参数会被添加到 ctx.params 中。参数可以是一个正则表达式,这个功能的实现是通过 path-to-regexp 来实现的。原理是把 URL 字符串转化成正则对象,然后再进行正则匹配,之前的例子中的 * 通配符就是一种正则表达式。
router.get('/:category/:title', function (ctx, next) {
console.log(ctx.params);
// => { category: 'programming', title: 'how-to-node' }
});
校验并不依赖于服务器端的Session机制,通过koa-jwt中间件来验证token,JWT会解码井校验Token。
假设这是一个url地址http://localhost:8080/a/b/c?a=1&b=2#abc,里面包含的部分:
protocol: 'http:',//协议
host: 'localhost:8080',
port: '8080',//端口
hostname: 'localhost',域名
fagment: '#abc', // 定位描点
search: '?a=1&b=2',
query: 'a=1&b=2',
pathname: '/a/b/c',
path: '/a/b/c?a=1&b=2',
href: 'http://localhost:8080/a/b/c?a=1&b=2#abc'
HTTP状态码主要包括1**(消息),2**(成功), 3**(重定向), 4**(请求错误), 5** 和6**(服务器错误)
GET:获取资源
POST:传输实体文本
HEAD:获得报文首部
PUT:传输文件
DELETE:删除文件
OPTIONS:询问支持的方法
TRACE:追踪路径
CONNECT:要求用隧道协议连接代理
const querystring = require('querystring')
querystring .escape ("id=1") // 返回id%3D1
querystring .unescape ("id%3Dl") // 返回id = 1
querystring.parse("type=l&status=0") // 返回 { type: '1', status: '0'}
querystring.stringify( { type: '1', status: '0'}) // 返回type=l&status=0
console.log(ctx.request.query); // query 对象
console.log(ctx.request.querystring) ; //querystring字符串
路由部分的代码可以分离成一个独立的文件,并根据个人喜好放置于项目根目录下,或独立放置于 router 文件夹中。在这里,我们将它命名为 router.js并将之放置于根目录下。
router.js
const router = require('koa-router')()
const HomeController = require('./controller/home')
module.exports = (app) => {
router.get( '/', HomeController.index )
router.get('/home', HomeController.home)
router.get('/home/:id/:name', HomeController.homeParams)
router.get('/user', HomeController.login)
router.post('/user/register', HomeController.register)
app.use(router.routes())
.use(router.allowedMethods())
}
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = require('./router')
app.use(bodyParser())
router(app)
app.listen(3000, () => {
console.log('server is running at http://localhost:3000')
})
module.exports = {
index: async(ctx, next) => {
ctx.response.body = `index page
`
},
home: async(ctx, next) => {
console.log(ctx.request.query)
console.log(ctx.request.querystring)
ctx.response.body = 'HOME page
'
},
homeParams: async(ctx, next) => {
console.log(ctx.params)
ctx.response.body = 'HOME page /:id/:name
'
},
login: async(ctx, next) => {
ctx.response.body =
`
`
},
register: async(ctx, next) => {
let {
name,
password
} = ctx.request.body
if (name == 'ikcamp' && password == '123456') {
ctx.response.body = `Hello, ${name}!`
} else {
ctx.response.body = '账号信息错误'
}
}
}
目前的代码结构已经比较清晰了,适用于以 node 作为中间层、中转层的项目。如果想要把 node 作为真正的后端去操作数据库等,建议再分出一层 service,用于处理数据层面的交互,比如调用 model 处理数据库,调用第三方接口等,而controller 里面只做一些简单的参数处理。
module.exports = {
register: async(name, pwd) => {
let data
if (name == 'ikcamp' && pwd == '123456') {
data = `Hello, ${name}!`
} else {
data = '账号信息错误'
}
return data
}
}
修改 controller/home.js
// 引入 service 文件
const HomeService = require('../service/home')
module.exports = {
// ……省略上面代码
// 重写 register 方法
register: async(ctx, next) => {
let {
name,
password
} = ctx.request.body
let data = await HomeService.register(name, password)
ctx.response.body = data
}
}
降低开发成本但是不高效
npm install sequelize -save