Koa
次世代nodejs 的 web框架
简介
koa是由Express幕后团队打造的,目的是更小,更快,更稳定的web应用和apis。通过杠杆生成器(leveraging generators)Koa可以让你引导(ditch)回调函数,极大的提升错误处理。Koa核心不集成任何的中间件,其本身提供的优雅的功能套件就能够写出既快又nice的服务器。
安装
Koa需要node7.6.0或更高的版本,因为需要async function
支持。
你可以使用自己的版本管理器很快的安装一个支持的版本。
nvm install 7
npm i koa
node my-koa-app.js
Async Function 结合 Babel
想要在较低版本的node中使用async
函数,我们建议使用babel。
require('babel-core/register')
//然后在加载应用的主代码,这个必须在babel后面
const app = require('./app')
为了编译和转化async functions
你需要在最后的压缩版本中使用'transform-async-to-generator'或者transform-async-to-module-method
插件。例如,在你的.babelrc
文件中,进行如下设置。
{
"plugins":["transform-async-to-generator"]
}
你也可以使用stage-3 persent
来代替。
应用 Application
一个Koa应用是一个对象,其包含一个数组,数组有很多函数组成的中间件,这些函数集合起来等待请求,并且执行时是按照类栈的方式。koa和很多其他中间件系统相似,你也许是用过Ruby
的Rack
,Connect
等。然而一个设计的决定行因素是提供高等级"sugar",与此同时低等级中间件层。因此提升了交互性,鲁棒性(软件设计术语,即稳定性)并且使得编写中间件更加的带劲!
这包括一些常用任务的方法——例如链接协调,缓存,代理支持,别用等。尽管提供了大量的有用的方法,但是koa仍然保持了一个较小的体积,因为没有绑定中间件。
怎么能偶少得了一个hello world
应用。
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
ctx.body = 'hello world';
})
app.listen(3000)
串联 Cascading
koa 的串联中间件使用了一个比较传统的方式,跟你通常用的工具很像。这是因为原先很难让用户有好的使用node的回调函数。然而使用异步函数我们能偶“真正得”是有中间件。相较于连接的实现,这个更容易提供一些列函数/功能来控制,在最后返回便可。koa调用"downstream",控制流返回"upstream".
下面的例子返回“hello world”,然而最开始请求先通过x-response-time
和logging
中间件来记录请求的开始。然后通过返回的中间件产出控制。当一个中间件执行next()
函数,来延迟和传递控制给下一个声明的中间件。然后直到没有中间件需要执行downstream
了,栈将会松开然后每个中间件复原去展现自己的“upstream”行为。
设置 Settings
应用设置即在实例app上的属性。当前支持如下:
-
app.env 默认是NODE_ENV或者“development”。
-
app.proxy 当设置为true时,porxy头部将被信任。
-
app.subdomainOffset 设置
.subdomains
的偏移量。替代[2]。 -
app.listen(...)
一个Koa应用不是一对一的呈现一个htpp服务器。一个或者多个应用也许被添加到一块形成大的应用对应一个http服务器。
创建返回一个http服务器,传递给定的参数到Server#listen()
。这些参数在nodejs.org都有说明。下面是一个无意义的Koa应用,绑定了端口3000
app.listen(...)方法是如下的一个语法糖。
const http = require('http')
const Koa = require('koa')
const app = new Koa()
http.createServer(app.callback()).listen(3000)
这说明你可以定义同一个应用为https和http或者多个地址。
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
http.createServer(app.callback()).listen(3001);
-
app.callback()
返回一个回调函数,相当于http.createServer()
方法,来出了请求。
你也可以使用这个方法在你的Connect/Express应用中添加koa应用。
-
app.use(function)
添加一个给定的中间件方法来实现它的功能。查看Middleware了解更多。
-
app.keys=
设置cookie的键。
这些键被传递到KeyGrip,也许你想使用自己的KeyGrip
,可以如下做。
app.keys = ['im a newer secret', 'i like turtle'];
app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');
这些键也许是循环的,并且可以设置{signed:true}
来使用。
ctx.cookies.set('name','tobi',{signed:true})
-
app.context
app.context是ctx的来源。你可以使用app.context
添加额外的属性到ctx。这对于创建跨越整个app应用的属性或者方法来说是有用的,而且性能更好,在依赖上也跟简单,可以考虑做一个anti-pattern
。
例子,从ctx添加一个数据库引用。
add.context.db = db()
app.use(async (ctx)=>{
console.log(ctx.db)
})
注意:
-
通过getter和setter以及Object.difineProperty()设置的属性,你只能在app.context使用Object.defineProperty()来编辑他们。(不推荐)
-
使用父级的
ctx
和设置来添加当前的应用。这样添加的app就能使用到那些中间件。
错误处理 Error Handling
除非设置app.silent是true,不然所有的出无输出都是标准输出。默认的错误输出不会处理像是err.sttus是404或者err.expose是true。为了自定义错误输出例如日志,你可以添加错误事件监听。
app.on('error', err =>
log.error('server error', err)
);
当 req/res 周期中出现任何错误且无法响应客户端时,Koa 会把 Context(上下文) 实例作为第二个参数传递给 error 事件:
app.on('error', (err, ctx) =>
log.error('server error', err, ctx)
);
如果有错误发生, 并且还能响应客户端(即没有数据被写入到 socket), Koa 会返回 500 "Internal Server Error". 这两种情况都会触发 app-level 的 error 事件, 用于 logging.
环境(Context)
一个Koa环境(实例)封装了node原生的请求和返回对象到一个单独的对象中,这个单独的对象提供了许多使用的方法,能够编写web应用和API。这些HTTP服务器开发中经常用到的操作被添加到当前等级,而不是高等级。他将强制中间件重新实现这些常用的功能。
一个环境Context
在每次请求时被创建,并且被引用至中间件作为接收器,或者定义成this
。如下所示。
app.use(function *(){
this;//这里是koa环境context
this.request;//是一个koa请求
this.response;//是一个koa返回
})
很多context
环境访问器和方法只是ctx.request
koa请求或者ctx.response
koa返回的代理,主要是为了方便。例如ctx.type
和ctx.length
代表response
返回对象,ctx.paht
和ctx.methos
代表请求。
API 接口。
环境(Context)定义的方法和访问器。
-
ctx.req Node的
request
对象。 -
ctx.res Node的
response
对象。
绕开使用koa的response处理是不支持的。避免使用下面的node属性。-
res.statusCode
-
res.writeHead()
-
res.write()
-
res.end()
-
-
ctx.request 一个Koa的
request
对象。 -
ctx.response 一个Koa的
response
对象。 -
ctx.state
推荐的通过中间件传递信息给前端view(显示)的命名空间。 -
ctx.app 应用实例的引用。
-
ctx.cookies.get(name,[options])
通过options
获得cookie
名字。-
signed 要求cookie已经签名。
koa使用cookie模块,这里只是传入选项即可。
-
-
ctx.coolies.set(name,value,[options])
使用options
设置name
的值value
。-
signed 签名cookie的值。
-
expires 使cookie的有效期到期。
-
path cookie路径,默认
/
。 -
domain cookie域
-
secure 保护coolie
-
httpOnly 服务器端cookie,默认值
true
。
通过设置options
来使用cookie模块。
-
-
ctx.throw([msg],[status],[properties])
处理抛出错误的辅助方法,默认.status
的值为500时抛出,koa在返回的信息中适当处理。限免的组合也是可疑的。-
this.throw(403);
-
this.throw('name require', 400);
-
this.throw(400,'name require');
-
this.throw('something exploded');
-
例如:this.throw('name require', 400)
等于
var err = new Error('name require');
err.status = 400;
throw err;
注意这些是用户自定义的错误,使用err.expose
发出。因此只适合某些客户端的反馈。这些错误不同于内置的错误信息提醒,因为错误的详细信息不会泄露。
你也许传递一个properties
选项对象,他和原来的错误信息进行了整合,对于人性化体验很有帮助,它报告个给请求者一个回溯流(upsteam
)。
this.throw(401,'access_denied',{user:user});
this.throw('access_denied',{user:user});
koa使用http-errors
来创建错误。
-
ctx.assert(value,[msg],[status],[properties])
跑出错误辅助方法,类似`.throw()`,当`!value`是类似node的`assert()`方法。
this.assert(this.sate.user,401,'User not found, Please login!');
koa使用http-assert
实现断言(assertions
)
-
ctx.response
通过绕开koa内置的返回处理(response handling),你可以明确的设置this.response = false;
如果你想使用原生的res对象处理而不是koa的response处理,那么就使用它。
注意那种用法koa不支持。这也许会打断koa中间件本来的功能,或者koa也被打断。使用这个属性最好考虑一下hack,这是使用传统的fn(req,res)
方法和koa中间件的唯一方便的方法。
请求别名Request aliases
下面的访问起和Request别名相等。
-
ctx.header
-
ctx.headers
-
ctx.method
-
ctx.method=
-
ctx.url
-
ctx.url=
-
ctx.originalUrl
-
ctx.origin
-
ctx.href
-
ctx.path
-
ctx.query
-
ctx.query=
-
ctx.querystring
-
ctx.querystring=
-
ctx.host
-
ctx.hostname
-
ctx.fresh
-
ctx.stale
-
ctx.socket
-
ctx.protocol
-
ctx.secure
-
ctx.ip
-
ctx.ips
-
ctx.subdomains
-
ctx.is()
-
ctx.accepts()
-
ctx.acceptsEncodings()
-
ctx.acceptsCharsets()
-
ctx.acceptsLanguages()
-
ctx.get()
返回别名Response aliases
下面的访问起和返回别名相等
-
ctx.body
-
ctx.body=
-
ctx.status
-
ctx.status=
-
ctx.message
-
ctx.message=
-
ctx.length=
-
ctx.length
-
ctx.type
-
ctx.type=
-
ctx.handerSent
-
ctx.redirect()
-
ctx.attachment()
-
ctx.set()
-
ctx.append()
-
ctx.remove()
-
ctx.lastModified=
-
ctx.etag=
请求 Request
一个koa请求Request对象是个建立在node请求request之上的抽象。提供了一些额外的功能,这对每个http服务器开发者来说非常有用。
API
-
request.header
Request header 对象
-
request.headers
Requests header 对象,别名`request.header`。
-
request.method
request.method
-
request.method=
设置request method,实现中间件很有用,例如`methodoverride()`。
-
request.length
返回request Content-lenght值是数字或者undefined。
-
request.url
返回rquest URL
-
request.url=
设置rquest URL,重写url时有用。
-
request.originalUrl
返回request 原始 URL
-
request.orgin
得到URL的域,包括协议和host(主机号)。
this.request.origin
//=>http://example.com
-
request.href
返回全部request URL,包括协议,主机号,和url。
this.request.href
//=>http://example.com/foo/bar?q=1
-
request.path
返回路径名(pathname)。
-
request.path=
设置请求路径名字,保存查询参数
-
rquest.querystring
得到原始的查询参数,不包含`?`。
-
request.querystring=
设置原始的查询参数。
-
request.search
得到原始的查询字符,带`?`。
-
request.search=
设置原始的查询字符。
-
rquest.host
得到主机号(hostname:port)当呈现时。支持`X-Forwarded-Host`当`app.proxy`是true,否则是常用的`host`。
-
request.hostname
当有时返回hostname,支持`X-Frowarded-Host`当`app.proxy`是true,否则是常用的。
-
request.type
返回request的`Content-type`,无效的一些参数,如`charset`。
var ct = this.request.type.
//=>'image/png'
-
request.charset
当有时返回request的charset,或者`undefined`。
this.request.charset
//=>'utf-8'
-
request.query
返回解析过的查询字符query-string,如果没有则返回一个空对象。注意,这个getter不支持嵌套的解析nested parsing。例如:`color=blue&size=small`。
{
color:'blue',
size:'small'
}
-
request.query=
设置查询字符query-string到给定的对象。注意给设置setter不支持嵌套对象。
this.query = {next:'/login'};
-
request.fresh
检查请求缓存是否“刷新fresh”,或者内容是否发生改变。这个方法是为了`if-None-Match`和`if-Modified-Since`以及`last-modified`之间的缓存沟通。他必须能够引用到更改之后的返回头部response headers
//freshness check requeire stats 20x or 304
this.status = 200;
this.set('ETag','123');
//cache is ok
if(this.fresh) {
this.status = 304;
return;
}
//cache is stale
//fetch new data
shis.body = yield db.find('something');
-
request.stale
与`request.fresh`相反
-
request.protocol
返回请求协议,`https`或者`http`。支持`X-Forwarded-Proto`当`app.proxy`是true。
-
request.secure
`this.protocol == "https"`的速记,用以检查一个求情能否通过安全传输层。
-
request.ip
请求的远程地址。支持`X-Forwarded-For`当`app.proxy`为true。
-
request.ips
当有`X-Forwarded-For`并且`app.proxy`可用,那么返回这些的ip的一个数组。 从回溯upstream——>downstream预定,当上述不可用则返回一个空数组。
-
request.subdomains
返回子域数组。 子域是在主域之前的部分,由点`.`分开。默认情况下,应用的主域都被假设成倒数的那两个。可以通过`app.subdomainOffset`来改变。 例如,如果域是`tobi.ferrest.example.com`,并且`app.subdomainOffset`没有设置,那么这个子域是['ferrets','tobi']。如果设置`app.subdomainOffset`为3,那么子域是['tobi']。
-
request.is(type...)
检查接下来的请求是否包含`Content-Type`头部内容,它包含任何的mime类型。如果这里没有请求体,返回undefined。如果没有内容类型,或者匹配失败,返回false。其他的直接返回内容类型(mime)。
//Contetn-type:text/html;charset=utf-8
this.is('html');//=>'html'
this.is('text/html');//=>'text/html'
this.is('text/*', 'test/html');//=>'test/html'
//when Content-type is application/json
this.is('json','urlencoded');//=>'json'
this.is('application/json',);//=>'application/json'
this.is('html','application/*',);//=>'application/json'
this.is('html');//=>false
例子:你只想只有图片能够发送到路由
if(this.is('image/*')) {
//process
}else{
this.throw(415,'image only!');
}
-
内容协商 Content Negotiation
koa请求request包含有用的内容写上工具,由accepts
和negotaitor
支持实现,这些工具是:-
request accepts(types)
-
rquest acceptsEncoding(types)
-
rquest acceptsCharsets(charsets)
-
rquest acceptsLanguages(langs)
如果没有提供类型,那么所有可接受的类型将被返回。
-
如果提供了多个类型,最优匹配奖杯返回。如果没有匹配到,返回false,并且你应该发送406 "Not Acceptable"
返回response给客户端。
在可以接受任何类型的地方丢失了accept头部。第一个匹配到的将被返回。因此提供科技收的类型是很重要的。
-
request.accepts(types)
检查给定的类型是否是可接受的。当为true则返回最佳匹配,否则false。类型`type`的值也许是一个或者多个mime类型字符,例如'application/json',扩展名是'josn',或者一个数组`['josn','html','text/plain']`。
//Accept:text/html
this.accepts('html')
//=>'html'
//Accept:text/*, application/json
this.accepts('html')
//=>'html'
this.accepts('json', 'text')
//=>'json'
this.accepts('application/json')
//=>'application/json'
//Accept.text/*, application/json
this.accepts('image/png')
this.accepts('png')
//=>false
//Accept:text/*,q=.5, application/json
this.accepts(['html', 'json'])
this.accepts('html', 'json')
//=>json
//No Accepts header
this.accpts('html', 'json')
//=>html
this.accepts('json','html')
//=> json
你也许调用this.accepts()
很多次,或者使用switch语句。
switch(this.accepts('json', 'html', 'text')) {
case 'json': bareak;
case 'html': bareak;
case 'text': bareak;
default: this.throw(406, 'json , html or text only');
}
-
request.acceptsEncodings(encodings)
检查编码`encodings`是否可接受,true时返回最优匹配,否则返回false。 注意,你应该包含一个`indentity`作为编码`encodings`之一。
//Accept-Encoding:gzip
this.acceptsEncodings('gzip', 'deflate', 'identify');
//=>gzip
this.acceptsEncodings(['gzip', 'deflate', 'identify'])
//=>gzip
当没有参数时,所有可接受的编码作为数组元素返回
//Accept-Encoding:gzip, deflate
this.acceptsEncodings();
//=>['gzip','deflate','identify']
注意如果用户明确发送identify为identify,q=0
。虽然这是个特殊例子,你仍然需要处理这个情况,当方法返回false时。
-
request.acceptsCharsets(charsets)
检查charset是否可接受,为true时返回最优匹配,否则返回false。
//Accept-Charset:utf-8, iso-8859-1;q=0.2,utf-7;q=0.5
this.acceptsCharsets('utf-8','utf-7')
//=>utf-8
this.acceptsCharsets(['utf-7','utf-8']);
//=>utf-8
如果没有参数是则返回所有可接受的编码到一个数组。
//Accept-Charset:utf-8,iso-8859-1;q=0.2,utf-7;q=0.5
this.acceptsCharsets();
//=>['utf-8','utf-7','iso-8859-7']
-
request.acceptLanguages(langs)
检查langs是否可接受,如果为true则返回最有匹配,否则返回false。
//Accept-Language:en;q=0.8,es,pt
this.acceptsLanguages('es','en');
//=>'es'
this.acceptsLanguages(['en','es']);
//=>'es'
当没有传入参数则返回所有的语言。
//Accept-Language:en;q=0.8, es,pt
this.acceptsLanguages();
//=>['es', 'pt', 'en']
-
request.idempotent
价差请求是否idempotent(幂等)
-
request.socket
返回请求的socket
-
request.get(field)
返回请求头header
返回 Response
一个koa返回Response对象是个建立在node请求request之上的抽象。提供了一些额外的功能,这对每个http服务器开发者来说非常有用。
API
-
response.header
返回header对象 -
response。headers
返回header对象。response.header的别名 -
response.status
返回response的状态,默认情况下response.status
没有默认值,而res.statusCode
的默认值是200。 -
response.status =
通过数字设置状态值-
100 'continue'继续
-
101 'switch protocols'换协议
-
102 'processing'处理中
-
200 'ok' ok
-
201 'created'已创建
-
202 'accepted' 已接受
-
203 'non-authoritative information'无作者信息
-
204 'no content' 无内容
-
205 'reset content' 重置内容
-
206 "partial content" 部分内容
-
207 "multi-status" 多状态
-
300 "multiple choices" 多选择
-
301 "moved permanently" 移动到永久
-
302 "moved temporarily" 移动到暂时
-
303 "see other" 看其他
-
304 "not modified" 没有改动
-
305 "use proxy" 使用代理
-
307 "temporary redirect" 暂时改向
-
400 "bad request" 坏请求
-
401 "unauthorized" 未经授权
-
402 "payment required" 要求付款
-
403 "forbidden" 禁止
-
404 "not found" 没有发现
-
405 "method not allowed" 方法不允许
-
406 "not acceptable" 不接受
-
407 "proxy authentication required" 要求代理授权
-
408 "request time-out" 请求超时
-
409 "conflict" 冲突
-
410 "gone" 消失
-
411 "length required" 要求长度
-
412 "precondition failed" 预处理失败
-
413 "request entity too large" 请求量太大
-
414 "request-uri too large" 请求同意资源太大
-
415 "unsupported media type" 不支持的媒体类型
-
416 "requested range not satisfiable" 不满足请求范围
-
417 "expectation failed" 不是期望值
-
418 "i'm a teapot" 我是个茶壶???
-
422 "unprocessable entity" 错误实体
-
423 "locked" 已锁定
-
424 "failed dependency" 依赖错误
-
425 "unordered collection" 未预定集合
-
426 "upgrade required" 要求更新
-
428 "precondition required" 要求前提
-
429 "too many requests" 过多请求
-
431 "request header fields too large" 请求头的域太大
-
500 "internal server error" 服务器内部错误
-
501 "not implemented" 没有实现
-
502 "bad gateway" 网关错误
-
503 "service unavailable" 不可服务
-
504 "gateway time-out" 网关超时
-
505 "http version not supported" http版本不支持
-
506 "variant also negotiates" 多样协商
-
507 "insufficient storage" 存储不足
-
509 "bandwidth limit exceeded" 超过带宽
-
510 "not extended" 扩展错误
-
511 "network authentication required" 要求网路授权证明
注意:不要担心要记太多东西,你可以随时查看。
-
-
response.message
得到返回状态的信息。默认情况下,response.message
是和response.status
匹配的。 -
response.message=
设置返回状态信息。 -
response.length=
设置内容的长度 -
response.length
返回内容的长度,或者计算出的this.body
的大小。值为数字。或者undifined -
response.body
得到response的body。 -
response.body=
设置返回体(response.body)为如下之一:-
String written
-
Buffer written
-
Stream piped
-
Object json-stringified
-
null no content response
String
Content-type是text/html或者text/plain,charset是utf-8.Content-length也需要设置。
Buffer
Content-type是application/octet-stream,Content-length也要设置。
Stream
Content-type是application/octet-stream.
Object
Content-type是application/json. -
-
response.get(field)
得到response头部的field的值,不区分大小写。
var etag = this.get('ETag');
-
response.set(field, value)
设置response头部field的值。
this.set('Cache-control', 'no-cache');
-
response.append(field, value)
给头部添加额为的域和值。
this.append('Link', '<http://127.0.0.1/>');
-
response.set(fields)
使用对象设置头部的fields
this.set({
'Etag':'1234',
'Last-modified':date
});
-
response.remove(field)
移除头部的某个域。
-
resposne.type
返回Content-type的类型,没有其他参数——如‘charset’。
var ct = this.type;
//=>image/png
-
response.type
通过名字或者扩展名设置Content-type
this.type = 'text/plain;charset=utf-8';
this.type = 'image/png';
this.type='.png';
this.type='png';
注意,每个字符编码charset都是为你选的最合适的,例如response.type='html'
,那么默认的字符编码是utf-8
,然而明确定义一个完整的类型,如response.type='text/html'
,将不会有指定的字符编码。
-
response.is(type...)
很类似于`this.request.is()`。检查response的类型是否是被支持的类型。这在创建那些对返回进行操作的中间件是非常有用。 示例:这是一个压缩html返回response的中间件,除了stream不被压缩。
var minify = require('html-minifier');
app.use(function *minifyHtml(next){
yield next;
if(!this.response.is('html')) return;
var body = this.body;
if(!body||body.pipe) return;
if(Buffer.isBuffer(body)) body = body.toString();
this.body = minify(body);
})
-
response.redirect(url, [alt])
把[302]状态码重导向至`url`。 字符串`back`是一个特殊的例子,提供了引用这支持,当引用者不存在或者`/`没有使用。
this.redirect('back');
this.redirect('back','/index.html');
this.redirect('login');
this.redirect('http://google.com');
为了改变默认的状态302,只需在这个状态吗出现之前或者出现之后进行重导向即可。为了改变body,在其调用之后进行重定向。
this.status = 301;
this.redirect('/cart');
this.body = 'Redirecting to shopping cart';
-
response.attachment([filename])
设置`Content-disposition`为"attachment"为客户端发出下载的信号。 文件的名字是可以指定的。
-
response.headerSent
检查返回头response header是否早已发送。查看客户端是否通知错误信号非常有用。
-
response.lastModified
返回`Last-Modified`最后修改头部的数据(如果存在)。
-
response.LastModified=
设置`Last-Modified`头部为一个合适的UTC(国际标准时间)字符串。你也可以设置其为一个日期或者日期字符串。
this.response.lastModified = new Date();
-
response.etag=
设置ETag到一个返回中,包括外面的双引号。注意,这里没有相应的response.etag的getter。
this.response.etag = crypto.createHash('md5'),update(this.body).digest('hex');
-
response.vary(field)
激活field。 -
附录上官网连接,方便以后查询下:http://javascript.ruanyifeng.com/nodejs/koa.html#toc8