Restify快速上手

文章目录

  • Restify简介
    • 1. Restify是什么?
    • 2. 安装Restify
    • 3. 实现一个最简单的服务器
  • 处理程序链
    • 1. 什么是处理程序链
    • 2. next()函数
    • 3. 三类处理程序链
      • 3.1. 通用预处理程序链:pre
      • 3.2. 通用处理程序链:use
      • 3.3. 路由处理程序链
    • 4. 错误处理
  • 插件
    • 1. pre插件
      • 1.1. context
      • 1.2. dedupeSlashes
      • 1.3. sanitizePath
    • 2. use插件
      • 2.1. acceptParser
      • 2.2. queryParser
      • 2.3. bodyParser
      • 2.4. serveStatic
  • 鉴权机制
    • 1. 基本使用
    • 2. token过期和刷新

Restify简介

1. Restify是什么?

Restify是一个专注于RESTful API的nodejs框架。

2. 安装Restify

npm init -y
npm i restify -S

3. 实现一个最简单的服务器

var restify=require('restify')
var server=restify.createServer()

server.get('/hello/:name',(req,res,next)=>{
    res.send('hello '+req.params.name)
    return next()
})

server.listen(8000,()=>{
    console.log(server.name)
    console.log(server.url)
})
$ curl -is http://localhost:8000/hello/world
HTTP/1.1 200 OK
Server: restify
Content-Type: application/json
Content-Length: 13
Date: Sat, 20 Jan 2024 09:02:45 GMT
Connection: keep-alive
Keep-Alive: timeout=5

"hello world"

处理程序链

1. 什么是处理程序链

所谓处理程序链,是指一组处理程序,按顺序依次执行。
如下,定义了针对’/hello/:name’路由的一组路由处理程序链:

server.get('/hello/:name',
    function(req,res,next){
        console.log('first step')
        return next() //程序链式执行的关键,不可省略
    },
    function(req,res,next){
        console.log('second step')
        res.send('how are you!') //在一条处理链上,只能向客户端返回一次数据。
        return next()
    },
    function(req,res,next){
        console.log('third step')
        return next()
    }
)

2. next()函数

处理程序链中的每个程序执行之后,都需要手工调用next(),这将移动到链中的下一个函数继续执行。调用res.send()不会自动触发next(),send()之后可能还要继续处理,因此也需要手动调用next()。

正常情况下,next()不需要任何参数。若由于某种原因(条件),需要提前终止程序链向后继续执行,可调用next(false)。

next()也接受Error类型对象,这将导致Restify向客户端发送错误状态码,错误状态码根据Error类型进行推断,Error类型为500,NotFoundError类型为404。关于错误处理,参见后文错误处理

server.get('/hello/:name',(req,res,next)=>{
    if(req.params.name=='') return next(new Error('no name!')
    res.send('hello '+req.params.name)
    return next()
})

3. 三类处理程序链

在Restify服务器中有三类处理程序链:

  • pre 在路由之前的处理程序链
  • use 在路由之后的处理程序链,对所有路由都生效
  • {httpVerb} 对特定路由执行的处理程序链,见上例

3.1. 通用预处理程序链:pre

pre对传入的所有请求都执行,即便请求的路由未注册。这可以用于记录日志或在路由之前清理传入的请求。

server.pre(
    function(req,res,next){
        console.warn('run before route! 1')
        return next()
    },
    function(req,res,next){
        console.warn('run before route! 2')
        return next()
    }
)

3.2. 通用处理程序链:use

use对所有被匹配的路由都执行。

server.use(
    function(req,res,next){
        console.warn('run for all routers! 1')
        return next()
    },
    function(req,res,next){
        console.warn('run for all routers! 2')
        return next()
    }
)

3.3. 路由处理程序链

Restify使用HTTP动词和参数来确定路由处理程序链。参数值可以通过req.params获取。

function send(req,res,next){
    res.send("hello "+req.params.name)
    return next()
}
server.post('/hello',function(req,res,next){
    res.send(201,Math.random().toString(36).substr(3,8))
    return next()
})
server.put('/hello',send)
server.get('/hello/:name',send)
server.del('/hello/:name',function(req,res,next){
    res.send(204)
    return next()
})

路由可以指定的HTTP动词包括:get、post、del、put、head、opts、patch。

4. 错误处理

Restify可以通过事件方式来处理错误。通过next(error)抛出错误事件,并用on()函数为特定错误事件添加侦听器。
restify-errors模块封装了一组常见HTTP和REST错误的构造函数。

const errs=require('restify-errors')

server.get('/hello/:name',function(req,res,next){
    //找不到资源
    return next(new errs.NotFoundError())
})

server.on('NotFound',function(req,res,err,callback){
    //不能调用res.send(),因为现在处于错误处理上下文,不在路由处理程序链中。
    //在此处可以记录错误日志或执行善后处理。
    //当完成错误处理后,调用回调函数回到路由处理程序链,Restify将根据错误类型自动发送包含错误代码的响应报文。
    return callback()
})

常用的错误事件如下:

  • 400 BadDigestError
  • 405 BadMethodError
  • 500 InternalError
  • 409 InvalidArgumentError
  • 400 InvalidContentError
  • 401 InvalidCredentialsError
  • 400 InvalidHeaderError
  • 400 InvalidVersionError
  • 409 MissingParameterError
  • 403 NotAuthorizedError
  • 412 PreconditionFailedError
  • 400 RequestExpiredError
  • 429 RequestThrottledError
  • 404 ResourceNotFoundError
  • 406 WrongAcceptError

插件

restify.plugins模块中提供过了一系列有用的插件,这些插件可以通过server.pre()或server.use()加载。

1. pre插件

pre类插件会在路由之前使用。

1.1. context

该插件给req添加了设置键值对req.set(key.val)和读取值req.get(key)方法。

server.pre(restify.plugins.pre.context())
server.get('/',
    function(req,res,next){
        req.set(myMessage,'hello world')
        return next()
    },
    function(req,res,next){
        res.send(req.get(myMessage))
        return next()
    }
)

1.2. dedupeSlashes

该插件会删除URL中重复斜杠,比如会将请求"/hello/abc"转换为"/hello/abc"。

server.pre(restify.plugins.pre.dedupeSlashes())
server.get('/hello/:name',function(req,res,next){
    res.send(200)
    return next()
})

1.3. sanitizePath

该插件修复URL,比如会将"/foo//bar//“转化为”/foo/bar"

server.pre(restify.plugins.pre.sanitizePath())
server.get('/hello/:name',function(req,res,next){
    res.send(200)
    return next()
})

2. use插件

use类插件会对所有路由生效。

2.1. acceptParser

解析Accept头,确保服务器可以提供客户要求的内容。若请求一个无法处理的类型,该插件会返回NotAcceptableError(406)错误。通过server.acceptable可获取restify服务器可接受的类型。

> server.acceptable
[
  'application/json',
  'text/plain',
  'application/octet-stream',
  'application/javascript'
]

通常,该插件都应该被加载:

server.use(restify.plugins.acceptParser(server.acceptable))

2.2. queryParser

解析HTTP查询字符串(如:/hello?name=abc&job=doctor)。若使用该插件,解析的内容在req.query中可用。
该插件接受一个选项参数mapParams,可配置是否将req.query合并到req.params字段中(req.params字段是针对/hello/:sex这类参数),若将mapParams配置为true,则类似/hello/male?name=abc&job=doctor这类路由,解析后的req.query为。

...
server.use(restify.plugins.queryParser({mapParams:true}))
server.get(
    '/foo/:sex',
    (req,res,next)=>{
        res.send({'req.query':req.query,'req.params':req.params})
        return next()
    }
)
...
>curl -is -l "http://localhost:8000/foo/male?name=abc&title=def"
HTTP/1.1 200 OK
Server: restify
Content-Type: application/json
Content-Length: 72
Date: Sun, 21 Jan 2024 13:11:52 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"req.query":{"name":"abc","title":"def"},"req.params":{"sex":"male","name":"abc","title":"def"}}

2.3. bodyParser

解析HTTP请求主体,解析后生成req.body字段。

...
server.use(
    restify.plugins.queryParser({mapParams:true}),
    restify.plugins.bodyParser()
)
server.get(
    '/foo/:sex',
    (req,res,next)=>{
        res.send(
            {
                'req.query':req.query,
                'req.params':req.params,
                'req.body':JSON.parse(req.body)
            })
        return next()
    }
)
...
>curl -is -X GET -d "{\"a\":1,\"b\":2,\"c\":{\"d\":3,\"e\":\"asdf\"}}" -l "http://localhost:8000/foo/male"
HTTP/1.1 200 OK
Server: restify
Content-Type: application/json
Content-Length: 92
Date: Sun, 21 Jan 2024 14:06:09 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"req.query":{},"req.params":{"sex":"male"},"req.body":{"a":1,"b":2,"c":{"d":3,"e":"asdf"}}}

2.4. serveStatic

提供静态文件。该插件需要映射到路由上:

server.get('/static/*',restify.plugins.serveStatic({
    directory:__dirname,
    default:'index.html'
}))

实际路径为主程序所在目录(__dirname)+‘/static/’。

鉴权机制

Restify-auth是基于jwt的鉴权中间件。使用前需要先安装:

npm i restify-auth -S

1. 基本使用

const restify=require('restify')
const {restifyAuth} =require('restify-auth')

const server=restify.createServer()
const auth=restifyAuth({
    secret:'test-secret'   //设定一个salt密码
})

server.pre(restify.plugins.pre.sanitizePath())
server.use(
    restify.plugins.queryParser(),
    restify.plugins.bodyParser()
)
server.get(
    '/getauth',
    auth.authorizer,    //在需要鉴权的路由添加
    async (req,res)=>{
        res.send(req.auth)
    }
)
server.post(
    '/login',
    async (req,res)=>{
        var user=req.body.user||''
        var password=req.body.password||''
        if(user=="test"&&password=="test"){ //用户验证
            var token=await auth.sign({user})
            res.header('authorization',token)
            res.send({success:true})
        }else{
            res.send({success:false})
        }
    }
)

server.on('restifyError',(req,res,err,cb)=>{
    console.log(err.toString())
    return cb()
})

server.listen(8000,()=>{
    console.log(server.name)
    console.log(server.url)
})

客户端请求:

>curl -is -d "user=test&password=test" -l "http://localhost:8000/login"
HTTP/1.1 200 OK
Server: restify
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlhdCI6MTcwNTg0MDUzOCwiZXhwIjoxNzA1ODQxNzM4fQ.NK9F-FntUQHqXuEACQ94T2XKmTO3U8UxpZdaLegT8ko
Content-Type: application/json
Content-Length: 16
Date: Sun, 21 Jan 2024 12:35:38 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"success":true}

在应答报文头中,authorization字段包含了从服务器返回的token,使用该token可访问服务器需要鉴权的路由:

>curl -is -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCIsImlhdCI6MTcwNTg0MDUzOCwiZXhwIjoxNzA1ODQxNzM4fQ.NK9F-FntUQHqXuEACQ94T2XKmTO3U8UxpZdaLegT8ko" -l "http://localhost:8000/getauth"
HTTP/1.1 200 OK
Server: restify
Content-Type: application/json
Content-Length: 49
Date: Sun, 21 Jan 2024 12:38:20 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"user":"test","iat":1705840538,"exp":1705841738}

若token错误,或不提供token,则无法访问该路由:

>curl -is -l "http://localhost:8000/getauth"
HTTP/1.1 401 Unauthorized
Server: restify
Content-Type: application/json
Content-Length: 53
Date: Sun, 21 Jan 2024 12:40:00 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"code":"Unauthorized","message":"token is required"}

2. token过期和刷新

可设置token有效期和刷新周期:

...
const auth=restifyAuth({
    secret:'test-secret',
    expiresIn:'20m',    //有效期20分钟
    refreshRange:0.6    //刷新时间为超过有效期60%时,向客户端发送新token,新token在报文头的authorization字段中,客户端检测到新token后,应及时更新token。
})
...

你可能感兴趣的:(javascript,开发语言,ecmascript)