Introduction
这一部分将介绍 Farrow 作为一个 Node.js HTTP Server,他的一些基础用法和一些特定场景使用的最佳实践。
info
这里我们假设你已经阅读过了 教程 - HTTP Server 部分的文档,对 Farrow HTTP 部分有了初步的了解,如果还没有,请前往阅读,完成之后再开始本章的进程。
这里我们先介绍 Farrow HTTP 的基础部分,在 Farrow 的 HTTP Server 中,他的请求对象、响应方案、中间件设计和路由方案。
Request
在其他的 HTTP Server 中 Request 基本都是从 Node.js 的原生 Request 对象扩展而来的,但在 Farrow 中不是这样,Farrow 的 Request 对象是一个包含了请求具体数据内容的 Plain Object,它的朴素类型如下
export type RequestInfo = {
readonly pathname: string
readonly method?: string
readonly query?: RequestQuery
readonly body?: any
readonly headers?: RequestHeaders
readonly cookies?: RequestCookies
}
其中基本包含了所有,我们需要从请求中获取的数据信息,可以通过如下的方式获取它们
http.use((request) => {
// access request pathname
console.log('pathname', request.pathname)
// access request method
console.log('method', request.method)
// access request query
console.log('query', request.query)
// access request body
console.log('body', request.body)
// access request headers
console.log('headers', request.headers)
// access request cookies
console.log('cookies', request.cookies)
})
原生对象
当然,有些时候仅仅这些数据是不够的,我们需要用到 Node.js 的原生对象,在 Farrow 中,可以通过以下的方式获取
import { useReq } from 'farrow-http'
http.use((request) => {
// The native request object
const req = useReq()
// ...
})
HOOKS
除了 useReq
这个 Hook 之外,还有许多功能强大的 Hook,详情请移步 HTTP Hooks。
info
需要注意的是这里我们提到的 Request 对象是它的朴素状态,Farrow 中支持通过 Schema 描述,将 Request 的类型收窄,具体请移步 Routing。
Response
Farrow 中的 Response 对象同样与其他框架的不同,在 Farrow 中响应信息和维护函数的集合,如果你要对请求作出响应,只需要在中间件中返回它
import { Response } from 'farrow-http'
http.use(() => {
// returning response in middleware
return Response
})
事实上,如果你使用的是 TypeScript,如果不返回 Response 对象则会类型报错。如果你要设置响应的内容,则需要
// respond text
http.use(() => {
return Response.text(`Farrow`)
})
// respond json
http.use(() => {
return Response.json({
farrow: true,
data: {},
})
})
// respond html
http.use(() => {
return Response.html(`<h1>Farrow</h1>`)
})
// respond file
http.use(() => {
return Response.file(filename)
})
如你所见,设置不同的相应类型则需要使用对应的函数,当然,除了响应的内容之外,你哈可以设置其他
- Headers
http.use(() => {
return Response.headers({
a: '1',
b: '2',
})
})
- Cookies
http.use(() => {
return Response.cookies({
a: '1',
b: '2',
})
})
- Response Status
http.use(() => {
return Response.status(404, 'Not Found')
})
info
如果你不主动设置 Status,默认的状态码是 200。
你还可以将这些设置操作连起来:
http.use(() => {
return Response
.headers({
a: '1',
b: '2',
})
.cookies({
a: '1',
b: '2',
})
.status(200)
.string('Hello Farrow!')
})
info
除了这些基础功能方法,Response 还有许多功能长大的方法,详情可以看 HTTP Response
此外 Response 之间还可以合并,例如
let response0 = Response.status(200)
let response1 = Response.header('a', '1')
let response2 = Response.header('b', '2')
let response3 = Response.cookie('c', '3')
let response = Response.merge(response0, response1, response2, response3)
// equal
let response = Response.status(200).header('a', '1').header('b', '2').cookie('c', '3')
原生对象
Farrow 的 Response 对象也可以通过 Hook 来获取
import { useRes } from 'farrow-http'
http.use((request) => {
// The native response object
const res = useRes()
// ...
})
HOOKS
除了 useReq
这个 Hook 之外,还有许多功能强大的 Hook,详情请移步 HTTP Hooks。
如果你需要自己通过原生响应对象控制响应行为,不期望使用 Farrow 的能力,那么可以使用 Response.custom
import { useRes, Response } from 'farrow-http'
http.use((request) => {
// The native response object
const res = useRes()
// ...
return Response.custom()
})
Middleware
Farrow 的中间件也是经过特殊设计的,与其他的框架不同。Farrow 中 HTTP 的中间件类型如下
type Next = (request: RequestInfo) => Response
type Middleware = (request: RequestInfo, next: Next) => Response
有了这样的设计,我们可以完成一些很有意思的事情。
- 向后面的中间件传递新的请求对象
http.use((request, next) => {
// no need to modify the request, just calling next(new_request) with a new request info
return next({
...request,
pathname: '/fixed',
})
})
http.use((request) => {
// request pathname will be '/fixed'
console.log('pathname', request.pathname)
})
- 合并后面中间件返回的响应对象
http.use(async (request, next) => {
// next() returning response received from downstream
let response = await next()
let headers = {
'header-key': 'header-value',
}
// filter or merge response and return
return Response.headers(headers).merge(response)
})
http.use(async (request) => {
return Response.json(request)
})
Routing
路由是指应用程序的端点 (URI) 如何响应客户端请求。
基于 path 的路由匹配
Farrow HTTP 对象中提供了对应各个 HTTP Method 的路由函数,例如 http.get
、http.post
http.get('/hello').use(async (request) => {
return Response.string('Hello Farrow!')
})
http.route
则可以处理所有 HTTP Method
http.route('/hello').use(async (request) => {
return Response.string('Hello Farrow!')
})
Farrow 也提供了 Router
路由工厂函数
import { Router } from 'farrow-http'
const user = Router()
http.route('/user').use(user)
// this will match /user/info
user.get('/info').use(async (request) => {
return Response.json({
userInfo: {},
})
})
// this will match /user/:id
user.get('/<id:int>').use(async (request) => {
return Response.json({
userId: request.params.id,
})
})
Farrow 的 path 匹配是基于 path-to-regexp 实现的,具体的 path 路由匹配规则,可以看 path-to-regexp。
基于 Schema 的路由匹配
除了传统的 path 的匹配方案,Farrow 还提供了机遇 Schema 的路由匹配,其能力通过 http.match
实现的
http
.match({
pathname: '/product',
query: {
productId: Number,
},
})
.use((request) => {
// productId is a number
console.log('productId', request.query.productId)
})
之前没有使用 http.match
时,RequestInfo
的类型为
export type RequestInfo = {
readonly pathname: string
readonly method?: string
readonly query?: RequestQuery
readonly body?: any
readonly headers?: RequestHeaders
readonly cookies?: RequestCookies
}
而当这里使用了 http.match
之后,则会根据传递给 match 的 Schema 来收窄 RequestInfo
的类型,比如这里就会变成
export type RequestInfo = {
readonly pathname: string
readonly query: {
readonly productId: number
}
}
并且这里的匹配方案是所有数据匹配,而不是 pathname 匹配即可,比如 /product?productId=100
可以通过匹配规则,而 /product
和 /product?productId=foo
均不可。
除了这种方式,Farrow 也提供了在 http.get
、http.post
中使用特定规则的 pathname
来实现类似的功能。
http.get('/product?<product:number>').use((request) => {
return Response.json({
type: 'get',
request,
})
})
即可实现上面 match 那个例子同样的能力。这里更多的匹配书写规则可以看 HTTP Router Match。
静态文件
Farrow HTTP 提供了 http.serve
用来部署静态文件
http.serve(basename, dirname)
如果有一个文件的结构如下
.
├── foo/
│ ├── bar/
│ │ └── aaa.js
│ ├── baz/
│ │ └── bbb.css
│ ├── ccc.png
通过
http.serve('/static', './foo')
则可以通过以下链接访问
http://localhost:3000/static/foo/bar/aaa.js
http://localhost:3000/static/foo/baz/bbb.css
http://localhost:3000/static/foo/ccc.png
info
以上只做了简单的功能介绍,更多的 HTTP Server 相关的 API 可看 farrow-http