├── .github └── FUNDING.yml ├── LICENSE ├── Readme.md ├── _config.yml ├── api ├── context.md ├── index.md ├── request.md └── response.md ├── error-handling.md ├── faq.md ├── guide.md ├── koa-vs-express.md ├── logo.png ├── middleware.gif ├── migration.md └── troubleshooting.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # demopark 4 | patreon: demopark 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://raw.githubusercontent.com/demopark/electron-api-demos-Zh_CN/master/assets/img/td.png",] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 用于 nodejs 的 koa 中间件框架 2 | 3 | [![gitter][gitter-image]][gitter-url] 4 | [![NPM version][npm-image]][npm-url] 5 | [![build status][travis-image]][travis-url] 6 | [![Test coverage][coveralls-image]][coveralls-url] 7 | [![OpenCollective Backers][backers-image]](#backers) 8 | [![OpenCollective Sponsors][sponsors-image]](#sponsors) 9 | [![PR's Welcome][pr-welcoming-image]][pr-welcoming-url] 10 | 11 | > 此项目同步自 [koajs](https://github.com/koajs) / [koa](https://github.com/koajs/koa) 项目中的 docs. 除特殊情况, 将保持每月一次的同步频率. 12 | 13 | Koa 通过 node.js 实现了一个十分具有表现力的 HTTP 中间件框架,力求让 Web 应用开发和 API 使用更加地愉快。Koa 的中间件之间按照编码顺序在栈内依次执行,允许您执行操作并向下传递请求(downstream),之后过滤并逆序返回响应(upstream)。 14 | 15 | 几乎所有 HTTP 服务器通用的方法都被直接集成到 Koa 大约570行源码的代码库中。其中包括内容协商,节点不一致性的规范化,重定向等等操作。 16 | 17 | Koa没有捆绑任何中间件。 18 | 19 | ## 安装 20 | 21 | Koa 依赖 __node v7.6.0__ 或 ES2015及更高版本和 async 方法支持. 22 | 23 | ``` 24 | $ npm install koa 25 | ``` 26 | 27 | ## Hello koa 28 | 29 | ```js 30 | const Koa = require('koa'); 31 | const app = new Koa(); 32 | 33 | // 响应 34 | app.use(ctx => { 35 | ctx.body = 'Hello Koa'; 36 | }); 37 | 38 | app.listen(3000); 39 | ``` 40 | 41 | ## 入门 42 | 43 | - [Kick-Off-Koa](https://github.com/koajs/kick-off-koa) - 通过一系列自身指引的讲解介绍了 Koa。 44 | - [Workshop](https://github.com/koajs/workshop) - 通过学习 Koa 的讲解,快速领会精髓。 45 | - [Introduction Screencast](http://knowthen.com/episode-3-koajs-quickstart-guide/) - 关于 Koa 安装入门的介绍。 46 | 47 | 48 | ## 中间件 49 | 50 | Koa 是一个中间件框架,可以采用两种不同的方法来实现中间件: 51 | 52 | * async function 53 | * common function 54 | 55 | 以下是使用两种不同方法实现一个日志中间件的示例: 56 | 57 | ### ___async___ functions (node v7.6+) 58 | 59 | ```js 60 | app.use(async (ctx, next) => { 61 | const start = Date.now(); 62 | await next(); 63 | const ms = Date.now() - start; 64 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); 65 | }); 66 | ``` 67 | 68 | ### Common function 69 | 70 | ```js 71 | // 中间件通常带有两个参数 (ctx, next), ctx 是一个请求的上下文(context), 72 | // next 是调用执行下游中间件的函数. 在代码执行完成后通过 then 方法返回一个 Promise. 73 | 74 | app.use((ctx, next) => { 75 | const start = Date.now(); 76 | return next().then(() => { 77 | const ms = Date.now() - start; 78 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); 79 | }); 80 | }); 81 | ``` 82 | 83 | ### Koa v1.x 中间件签名 84 | 85 | 中间件签名在 v1.x 和 v2.x 之间已经被更改. 旧的签名已经被弃用. 86 | 87 | **旧的签名中间件支持将在 v3 中删除** 88 | 89 | 请参阅 [迁移指南](migration.md) 获取有关从 v1.x 升级并使用 v2.x 中间件的更多信息。 90 | 91 | ## 上下文, 请求和响应 92 | 93 | 每个中间件都接收一个 Koa 的 `Context` 对象,该对象封装了一个传入的 http 消息,并对该消息进行了相应的响应。 `ctx` 通常用作上下文对象的参数名称。 94 | 95 | ```js 96 | app.use(async (ctx, next) => { await next(); }); 97 | ``` 98 | 99 | Koa 提供了一个 `Request` 对象作为 `Context` 的 `request` 属性。 100 | Koa的 `Request` 对象提供了用于处理 http 请求的方法,该请求委托给 node `http` 模块的[IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)。 101 | 102 | 这是一个检查请求客户端 xml 支持的示例。 103 | 104 | ```js 105 | app.use(async (ctx, next) => { 106 | ctx.assert(ctx.request.accepts('xml'), 406); 107 | // 相当于: 108 | // if (!ctx.request.accepts('xml')) ctx.throw(406); 109 | await next(); 110 | }); 111 | ``` 112 | 113 | Koa提供了一个 `Response` 对象作为 `Context` 的 `response` 属性。 114 | Koa的 `Response` 对象提供了用于处理 http 响应的方法,该响应委托给 [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)。 115 | 116 | Koa 对 Node 的请求和响应对象进行委托而不是扩展它们。这种模式提供了更清晰的接口,并减少了不同中间件与 Node 本身之间的冲突,并为流处理提供了更好的支持。 117 | `IncomingMessage` 仍然可以作为 `Context` 上的 `req` 属性被直接访问,并且`ServerResponse`也可以作为`Context` 上的 `res` 属性被直接访问。 118 | 119 | 这里是一个使用 Koa 的 `Response` 对象将文件作为响应体流式传输的示例。 120 | 121 | ```js 122 | app.use(async (ctx, next) => { 123 | await next(); 124 | ctx.response.type = 'xml'; 125 | ctx.response.body = fs.createReadStream('really_large.xml'); 126 | }); 127 | ``` 128 | 129 | `Context` 对象还提供了其 `request` 和 `response` 方法的快捷方式。在前面的例子中,可以使用 `ctx.type` 而不是 `ctx.response.type`,而 `ctx.accepts` 可以用来代替 `ctx.request.accepts`。 130 | 131 | 关于 `Request`, `Response` 和 `Context` 更多详细信息, 参阅 [请求 API 参考](api/request.md), 132 | [响应 API 参考](api/response.md) 和 [上下文 API 参考](api/context.md). 133 | 134 | ## Koa 应用程序 135 | 136 | 在执行 `new Koa()` 时创建的对象被称为 Koa 应用对象。 137 | 138 | 应用对象是带有 node http 服务的 Koa 接口,它可以处理中间件的注册,将http请求分发到中间件,进行默认错误处理,以及对上下文,请求和响应对象进行配置。 139 | 140 | 了解有关应用程序对象的更多信息请到 [应用 API 参考](api/index.md). 141 | 142 | ## 文档 143 | 144 | - [使用指南](guide.md) 145 | - [错误处理](error-handling.md) 146 | - [Koa 与 Express](koa-vs-express.md) 147 | - [常见问题](faq.md) 148 | - [从 Koa v1.x 迁移到 v2.x](migration.md) 149 | 150 | - [API 文档](api/index.md) 151 | - [上下文(Context)](api/context.md) 152 | - [请求(Request)](api/request.md) 153 | - [响应(Response)](api/response.md) 154 | 155 | - [Koa 中间件列表](https://github.com/koajs/koa/wiki) 156 | 157 | ## Babel 配置 158 | 159 | 如果你正在使用的不是 `node v7.6+`, 我们推荐你用 [`@babel/preset-env`](https://babeljs.io/docs/en/next/babel-preset-env) 配置 `babel` : 160 | 161 | ```bash 162 | $ npm install @babel/register @babel/preset-env @babel/cli --save-dev 163 | ``` 164 | 165 | 在开发环境中, 你可能想要使用 [`@babel/register`](https://babeljs.io/docs/en/next/babel-register): 166 | 167 | ```bash 168 | node --require @babel/register 169 | ``` 170 | 171 | 在生产环境中, 你可能想要使用 [`@babel/cli`](https://babeljs.io/docs/en/babel-cli) 构建文件. 假设你正在编译 `src` 文件夹且想要输出 non-javascript 文件拷贝到新的 `dist` 文件夹中: 172 | 173 | ```bash 174 | babel src --out-dir dist --copy-files 175 | ``` 176 | 177 | 还有你的 `.babelrc` 配置: 178 | 179 | ```json 180 | { 181 | "presets": [ 182 | ["@babel/preset-env", { 183 | "targets": { 184 | "node": true 185 | } 186 | }] 187 | ] 188 | } 189 | ``` 190 | 191 | ## 故障排除 192 | 193 | 在 Koa 指南中查阅 [故障排除指南](troubleshooting.md) 或 [调试 Koa](guide.md#debugging-koa). 194 | 195 | ## 运行测试 196 | 197 | ``` 198 | $ npm test 199 | ``` 200 | 201 | # 赞赏支持 202 | ![赞赏支持](https://raw.githubusercontent.com/demopark/electron-api-demos-Zh_CN/master/assets/img/td.png) 203 | 204 | [npm-image]: https://img.shields.io/npm/v/koa.svg?style=flat-square 205 | [npm-url]: https://www.npmjs.com/package/koa 206 | [travis-image]: https://img.shields.io/travis/koajs/koa/master.svg?style=flat-square 207 | [travis-url]: https://travis-ci.org/koajs/koa 208 | [coveralls-image]: https://img.shields.io/codecov/c/github/koajs/koa.svg?style=flat-square 209 | [coveralls-url]: https://codecov.io/github/koajs/koa?branch=master 210 | [backers-image]: https://opencollective.com/koajs/backers/badge.svg?style=flat-square 211 | [sponsors-image]: https://opencollective.com/koajs/sponsors/badge.svg?style=flat-square 212 | [gitter-image]: https://img.shields.io/gitter/room/koajs/koa.svg?style=flat-square 213 | [gitter-url]: https://gitter.im/koajs/koa?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 214 | [#koajs]: https://webchat.freenode.net/?channels=#koajs 215 | [pr-welcoming-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 216 | [pr-welcoming-url]: https://github.com/koajs/koa/pull/new -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /api/context.md: -------------------------------------------------------------------------------- 1 | # 上下文(Context) 2 | 3 | Koa Context 将 node 的 `request` 和 `response` 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 4 | 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。 5 | 6 | _每个_ 请求都将创建一个 `Context`,并在中间件中作为接收器引用,或者 `ctx` 标识符,如以下代码片段所示: 7 | 8 | ```js 9 | app.use(async ctx => { 10 | ctx; // 这是 Context 11 | ctx.request; // 这是 koa Request 12 | ctx.response; // 这是 koa Response 13 | }); 14 | ``` 15 | 16 | 为方便起见许多上下文的访问器和方法直接委托给它们的 `ctx.request `或 `ctx.response` ,不然的话它们是相同的。 17 | 例如 `ctx.type` 和 `ctx.length` 委托给 `response` 对象,`ctx.path` 和 `ctx.method` 委托给 `request`。 18 | 19 | ## API 20 | 21 | `Context` 具体方法和访问器. 22 | 23 | ### ctx.req 24 | 25 | Node 的 `request` 对象. 26 | 27 | ### ctx.res 28 | 29 | Node 的 `response` 对象. 30 | 31 | 绕过 Koa 的 response 处理是 __不被支持的__. 应避免使用以下 node 属性: 32 | 33 | - `res.statusCode` 34 | - `res.writeHead()` 35 | - `res.write()` 36 | - `res.end()` 37 | 38 | ### ctx.request 39 | 40 | koa 的 `Request` 对象. 41 | 42 | ### ctx.response 43 | 44 | koa 的 `Response` 对象. 45 | 46 | ### ctx.state 47 | 48 | 推荐的命名空间,用于通过中间件传递信息和你的前端视图。 49 | 50 | ```js 51 | ctx.state.user = await User.find(id); 52 | ``` 53 | 54 | ### ctx.app 55 | 56 | 应用程序实例引用 57 | 58 | ### ctx.app.emit 59 | 60 | Koa 应用扩展了内部 [EventEmitter](https://nodejs.org/dist/latest-v11.x/docs/api/events.html)。`ctx.app.emit` 发出一个类型由第一个参数定义的事件。对于每个事件,您可以连接 "listeners",这是在发出事件时调用的函数。有关更多信息,请参阅[错误处理文档](https://koajs.com/#error-handling)。 61 | 62 | ### ctx.cookies.get(name, [options]) 63 | 64 | 通过 `options` 获取 cookie `name`: 65 | 66 | - `signed` 所请求的cookie应该被签名 67 | 68 | koa 使用 [cookies](https://github.com/pillarjs/cookies) 模块,其中只需传递参数。 69 | 70 | ### ctx.cookies.set(name, value, [options]) 71 | 72 | 通过 `options` 设置 cookie `name` 的 `value` : 73 | 74 | * `maxAge`: 一个数字, 表示从 `Date.now()` 得到的毫秒数. 75 | * `expires`: 一个 `Date` 对象, 表示 cookie 的到期日期 (默认情况下在会话结束时过期). 76 | * `path`: 一个字符串, 表示 cookie 的路径 (默认是`/`). 77 | * `domain`: 一个字符串, 指示 cookie 的域 (无默认值). 78 | * `secure`: 一个布尔值, 表示 cookie 是否仅通过 HTTPS 发送 (HTTP 下默认为 `false`, HTTPS 下默认为 `true`). [阅读有关此参数的更多信息](https://github.com/pillarjs/cookies#secure-cookies). 79 | * `httpOnly`: 一个布尔值, 表示 cookie 是否仅通过 HTTP(S) 发送,, 且不提供给客户端 JavaScript (默认为 `true`). 80 | * `sameSite`: 一个布尔值或字符串, 表示该 cookie 是否为 "相同站点" cookie (默认为 `false`). 可以设置为 `'strict'`, `'lax'`, `'none'`, 或 `true` (映射为 `'strict'`). 81 | * `signed`: 一个布尔值, 表示是否要对 cookie 进行签名 (默认为 `false`). 如果为 `true`, 则还会发送另一个后缀为 `.sig` 的同名 cookie, 使用一个 27-byte url-safe base64 SHA1 值来表示针对第一个 [Keygrip](https://www.npmjs.com/package/keygrip) 键的 _cookie-name_=_cookie-value_ 的哈希值. 此签名密钥用于检测下次接收 cookie 时的篡改. 82 | * `overwrite`: 一个布尔值, 表示是否覆盖以前设置的同名的 cookie (默认是 `false`). 如果是 true, 在同一个请求中设置相同名称的所有 Cookie(无论路径或域)是否在设置此Cookie 时从 Set-Cookie 消息头中过滤掉. 83 | 84 | koa 使用传递简单参数的 [cookies](https://github.com/pillarjs/cookies) 模块。 85 | 86 | ### ctx.throw([status], [msg], [properties]) 87 | 88 | 用来抛出一个包含 `.status` 属性错误的帮助方法,其默认值为 `500`。这样 Koa 就可以做出适当地响应。 89 | 90 | 允许以下组合: 91 | 92 | ```js 93 | ctx.throw(400); 94 | ctx.throw(400, 'name required'); 95 | ctx.throw(400, 'name required', { user: user }); 96 | ``` 97 | 98 | 例如 `ctx.throw(400, 'name required')` 等效于: 99 | 100 | ```js 101 | const err = new Error('name required'); 102 | err.status = 400; 103 | err.expose = true; 104 | throw err; 105 | ``` 106 | 107 | 请注意,这些是用户级错误,并用 `err.expose` 标记,这意味着消息适用于客户端响应,这通常不是错误消息的内容,因为您不想泄漏故障详细信息。 108 | 109 | 你可以根据需要将 `properties` 对象传递到错误中,对于装载上传给请求者的机器友好的错误是有用的。这用于修饰其人机友好型错误并向上游的请求者报告非常有用。 110 | 111 | ```js 112 | ctx.throw(401, 'access_denied', { user: user }); 113 | ``` 114 | 115 | koa 使用 [http-errors](https://github.com/jshttp/http-errors) 来创建错误。`status` 只应作为第一个参数传递。 116 | 117 | ### ctx.assert(value, [status], [msg], [properties]) 118 | 119 | 当 `!value` 时抛出一个类似 `.throw` 错误的帮助方法。这与 node 的 [assert()](http://nodejs.org/api/assert.html) 方法类似. 120 | 121 | ```js 122 | ctx.assert(ctx.state.user, 401, 'User not found. Please login!'); 123 | ``` 124 | 125 | koa 使用 [http-assert](https://github.com/jshttp/http-assert) 作为断言。 126 | 127 | ### ctx.respond 128 | 129 | 为了绕过 Koa 的内置 response 处理,你可以显式设置 `ctx.respond = false;`。 如果您想要写入原始的 `res` 对象而不是让 Koa 处理你的 response,请使用此参数。 130 | 131 | 请注意,Koa _不_ 支持使用此功能。这可能会破坏 Koa 中间件和 Koa 本身的预期功能。使用这个属性被认为是一个 hack,只是便于那些希望在 Koa 中使用传统的 `fn(req, res)` 功能和中间件的人。 132 | 133 | ## Request 别名 134 | 135 | 以下访问器和 [Request](request.md) 别名等效: 136 | 137 | - `ctx.header` 138 | - `ctx.headers` 139 | - `ctx.method` 140 | - `ctx.method=` 141 | - `ctx.url` 142 | - `ctx.url=` 143 | - `ctx.originalUrl` 144 | - `ctx.origin` 145 | - `ctx.href` 146 | - `ctx.path` 147 | - `ctx.path=` 148 | - `ctx.query` 149 | - `ctx.query=` 150 | - `ctx.querystring` 151 | - `ctx.querystring=` 152 | - `ctx.host` 153 | - `ctx.hostname` 154 | - `ctx.fresh` 155 | - `ctx.stale` 156 | - `ctx.socket` 157 | - `ctx.protocol` 158 | - `ctx.secure` 159 | - `ctx.ip` 160 | - `ctx.ips` 161 | - `ctx.subdomains` 162 | - `ctx.is()` 163 | - `ctx.accepts()` 164 | - `ctx.acceptsEncodings()` 165 | - `ctx.acceptsCharsets()` 166 | - `ctx.acceptsLanguages()` 167 | - `ctx.get()` 168 | 169 | ## Response 别名 170 | 171 | 以下访问器和 [Response](response.md) 别名等效: 172 | 173 | - `ctx.body` 174 | - `ctx.body=` 175 | - `ctx.status` 176 | - `ctx.status=` 177 | - `ctx.message` 178 | - `ctx.message=` 179 | - `ctx.length=` 180 | - `ctx.length` 181 | - `ctx.type=` 182 | - `ctx.type` 183 | - `ctx.headerSent` 184 | - `ctx.redirect()` 185 | - `ctx.attachment()` 186 | - `ctx.set()` 187 | - `ctx.append()` 188 | - `ctx.remove()` 189 | - `ctx.lastModified=` 190 | - `ctx.etag=` 191 | -------------------------------------------------------------------------------- /api/index.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | Koa 依赖 __node v7.6.0__ 或 ES2015及更高版本和 async 方法支持. 4 | 5 | 你可以使用自己喜欢的版本管理器快速安装支持的 node 版本: 6 | 7 | ```bash 8 | $ nvm install 7 9 | $ npm i koa 10 | $ node my-koa-app.js 11 | ``` 12 | 13 | ## 使用 Babel 实现 Async 方法 14 | 15 | 要在 node < 7.6 版本的 Koa 中使用 `async` 方法, 我们推荐使用 [babel's require hook](http://babeljs.io/docs/usage/babel-register/). 16 | 17 | ```js 18 | require('babel-register'); 19 | // 应用的其余 require 需要被放到 hook 后面 20 | const app = require('./app'); 21 | ``` 22 | 23 | 要解析和编译 async 方法, 你至少应该有 [transform-async-to-generator](http://babeljs.io/docs/plugins/transform-async-to-generator/) 24 | 或 [transform-async-to-module-method](http://babeljs.io/docs/plugins/transform-async-to-module-method/) 插件. 25 | 26 | 例如, 在你的 `.babelrc` 文件中, 你应该有: 27 | 28 | ```json 29 | { 30 | "plugins": ["transform-async-to-generator"] 31 | } 32 | ``` 33 | 34 | 你也可以用 [env preset](http://babeljs.io/docs/plugins/preset-env/) 的 target 参数 `"node": "current"` 替代. 35 | 36 | # 应用程序 37 | 38 | Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。 39 | Koa 类似于你可能遇到过的许多其他中间件系统,例如 Ruby 的 Rack ,Connect 等,然而,一个关键的设计点是在其低级中间件层中提供高级“语法糖”。 这提高了互操作性,稳健性,并使书写中间件更加愉快。 40 | 41 | 这包括诸如内容协商,缓存清理,代理支持和重定向等常见任务的方法。 尽管提供了相当多的有用的方法 Koa 仍保持了一个很小的体积,因为没有捆绑中间件。 42 | 43 | 必修的 hello world 应用: 44 | 45 | ```js 46 | const Koa = require('koa'); 47 | const app = new Koa(); 48 | 49 | app.use(async ctx => { 50 | ctx.body = 'Hello World'; 51 | }); 52 | 53 | app.listen(3000); 54 | ``` 55 | 56 | ## 级联 57 | 58 | Koa 中间件以更传统的方式级联,您可能习惯使用类似的工具 - 之前难以让用户友好地使用 node 的回调。然而,使用 async 功能,我们可以实现 “真实” 的中间件。对比 Connect 的实现,通过一系列功能直接传递控制,直到一个返回,Koa 调用“下游”,然后控制流回“上游”。 59 | 60 | 下面以 “Hello World” 的响应作为示例,当请求开始时首先请求流通过 `x-response-time` 和 `logging` 中间件,然后继续移交控制给 `response` 中间件。当一个中间件调用 `next()` 则该函数暂停并将控制传递给定义的下一个中间件。当在下游没有更多的中间件执行后,堆栈将展开并且每个中间件恢复执行其上游行为。 61 | 62 | ```js 63 | const Koa = require('koa'); 64 | const app = new Koa(); 65 | 66 | // logger 67 | 68 | app.use(async (ctx, next) => { 69 | await next(); 70 | const rt = ctx.response.get('X-Response-Time'); 71 | console.log(`${ctx.method} ${ctx.url} - ${rt}`); 72 | }); 73 | 74 | // x-response-time 75 | 76 | app.use(async (ctx, next) => { 77 | const start = Date.now(); 78 | await next(); 79 | const ms = Date.now() - start; 80 | ctx.set('X-Response-Time', `${ms}ms`); 81 | }); 82 | 83 | // response 84 | 85 | app.use(async ctx => { 86 | ctx.body = 'Hello World'; 87 | }); 88 | 89 | app.listen(3000); 90 | ``` 91 | 92 | ## 设置 93 | 94 | 应用程序设置是 `app` 实例上的属性,目前支持如下: 95 | 96 | - `app.env` 默认是 __NODE_ENV__ 或 "development" 97 | - `app.keys` 签名的 cookie 密钥数组 98 | - `app.proxy` 当真正的代理头字段将被信任时 99 | - 忽略 `.subdomains` 的 `app.subdomainOffset` 偏移量,默认为 2 100 | - `app.proxyIpHeader` 代理 ip 消息头, 默认为 `X-Forwarded-For` 101 | - `app.maxIpsCount` 从代理 ip 消息头读取的最大 ips, 默认为 0 (代表无限) 102 | 103 | 您可以将设置传递给构造函数: 104 | 105 | ```js 106 | const Koa = require('koa'); 107 | const app = new Koa({ proxy: true }); 108 | ``` 109 | 110 | 或动态的: 111 | 112 | ```js 113 | const Koa = require('koa'); 114 | const app = new Koa(); 115 | app.proxy = true; 116 | ``` 117 | 118 | ## app.listen(...) 119 | 120 | Koa 应用程序不是 HTTP 服务器的1对1展现。 121 | 可以将一个或多个 Koa 应用程序安装在一起以形成具有单个HTTP服务器的更大应用程序。 122 | 123 | 创建并返回 HTTP 服务器,将给定的参数传递给 `Server#listen()`。这些内容都记录在 [nodejs.org](http://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback). 124 | 125 | 以下是一个无作用的 Koa 应用程序被绑定到 `3000` 端口: 126 | 127 | ```js 128 | const Koa = require('koa'); 129 | const app = new Koa(); 130 | app.listen(3000); 131 | ``` 132 | 133 | 这里的 `app.listen(...)` 方法只是以下方法的语法糖: 134 | 135 | ```js 136 | const http = require('http'); 137 | const Koa = require('koa'); 138 | const app = new Koa(); 139 | http.createServer(app.callback()).listen(3000); 140 | ``` 141 | 142 | 这意味着您可以将同一个应用程序同时作为 HTTP 和 HTTPS 或多个地址: 143 | 144 | ```js 145 | const http = require('http'); 146 | const https = require('https'); 147 | const Koa = require('koa'); 148 | const app = new Koa(); 149 | http.createServer(app.callback()).listen(3000); 150 | https.createServer(app.callback()).listen(3001); 151 | ``` 152 | 153 | ## app.callback() 154 | 155 | 返回适用于 `http.createServer()` 方法的回调函数来处理请求。你也可以使用此回调函数将 koa 应用程序挂载到 Connect/Express 应用程序中。 156 | 157 | ## app.use(function) 158 | 159 | 将给定的中间件方法添加到此应用程序。`app.use()` 返回 `this`, 因此可以链式表达. 160 | 161 | ```js 162 | app.use(someMiddleware) 163 | app.use(someOtherMiddleware) 164 | app.listen(3000) 165 | ``` 166 | 167 | 它等同于 168 | 169 | ```js 170 | app.use(someMiddleware) 171 | .use(someOtherMiddleware) 172 | .listen(3000) 173 | ``` 174 | 175 | 参阅 [Middleware](https://github.com/koajs/koa/wiki#middleware) 获取更多信息. 176 | 177 | ## app.keys= 178 | 179 | 设置签名的 Cookie 密钥。 180 | 181 | 这些被传递给 [KeyGrip](https://github.com/crypto-utils/keygrip),但是你也可以传递你自己的 `KeyGrip` 实例。 182 | 183 | 例如,以下是可以接受的: 184 | 185 | ```js 186 | app.keys = ['im a newer secret', 'i like turtle']; 187 | app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256'); 188 | ``` 189 | 190 | 这些密钥可以倒换,并在使用 `{ signed: true }` 参数签名 Cookie 时使用。 191 | 192 | ```js 193 | ctx.cookies.set('name', 'tobi', { signed: true }); 194 | ``` 195 | 196 | ## app.context 197 | 198 | `app.context` 是从其创建 `ctx` 的原型。您可以通过编辑 `app.context` 为 `ctx` 添加其他属性。这对于将 `ctx` 添加到整个应用程序中使用的属性或方法非常有用,这可能会更加有效(不需要中间件)和/或 更简单(更少的 `require()`),而更多地依赖于`ctx`,这可以被认为是一种反模式。 199 | 200 | 例如,要从 `ctx` 添加对数据库的引用: 201 | 202 | ```js 203 | app.context.db = db(); 204 | 205 | app.use(async ctx => { 206 | console.log(ctx.db); 207 | }); 208 | ``` 209 | 210 | 注意: 211 | 212 | - `ctx` 上的许多属性都是使用 `getter` ,`setter` 和 `Object.defineProperty()` 定义的。你只能通过在 `app.context` 上使用 `Object.defineProperty()` 来编辑这些属性(不推荐)。查阅 https://github.com/koajs/koa/issues/652. 213 | - 安装的应用程序目前使用其父级的 `ctx` 和设置。 因此,安装的应用程序只是一组中间件。 214 | 215 | ## 错误处理 216 | 217 | 默认情况下,将所有错误输出到 stderr,除非 `app.silent` 为 `true`。 218 | 当 `err.status` 是 `404` 或 `err.expose` 是 `true` 时默认错误处理程序也不会输出错误。 219 | 要执行自定义错误处理逻辑,如集中式日志记录,您可以添加一个 “error” 事件侦听器: 220 | 221 | ```js 222 | app.on('error', err => { 223 | log.error('server error', err) 224 | }); 225 | ``` 226 | 227 | 如果 req/res 期间出现错误,并且 _无法_ 响应客户端,`Context`实例仍然被传递: 228 | 229 | ```js 230 | app.on('error', (err, ctx) => { 231 | log.error('server error', err, ctx) 232 | }); 233 | ``` 234 | 235 | 当发生错误 _并且_ 仍然可以响应客户端时,也没有数据被写入 socket 中,Koa 将用一个 500 “内部服务器错误” 进行适当的响应。在任一情况下,为了记录目的,都会发出应用级 “错误”。 236 | -------------------------------------------------------------------------------- /api/request.md: -------------------------------------------------------------------------------- 1 | # 请求(Request) 2 | 3 | Koa `Request ` 对象是在 node 的 原生请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。 4 | 5 | ## API 6 | 7 | ### request.header 8 | 9 | 请求头对象。这与 node [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) 上的 [`headers`](https://nodejs.org/api/http.html#http_message_headers) 字段相同 10 | 11 | ### request.header= 12 | 13 | 设置请求头对象。 14 | 15 | ### request.headers 16 | 17 | 请求头对象。别名为 `request.header`. 18 | 19 | ### request.headers= 20 | 21 | 设置请求头对象。别名为 `request.header=`. 22 | 23 | ### request.method 24 | 25 | 请求方法。 26 | 27 | ### request.method= 28 | 29 | 设置请求方法,对于实现诸如 `methodOverride()` 的中间件是有用的。 30 | 31 | ### request.length 32 | 33 | 返回以数字返回请求的 Content-Length,或 `undefined`。 34 | 35 | ### request.url 36 | 37 | 获取请求 URL. 38 | 39 | ### request.url= 40 | 41 | 设置请求 URL, 对 url 重写有用。 42 | 43 | ### request.originalUrl 44 | 45 | 获取请求原始URL。 46 | 47 | ### request.origin 48 | 49 | 获取URL的来源,包括 `protocol` 和 `host`。 50 | 51 | ```js 52 | ctx.request.origin 53 | // => http://example.com 54 | ``` 55 | 56 | ### request.href 57 | 58 | 获取完整的请求URL,包括 `protocol`,`host` 和 `url`。 59 | 60 | ```js 61 | ctx.request.href; 62 | // => http://example.com/foo/bar?q=1 63 | ``` 64 | 65 | ### request.path 66 | 67 | 获取请求路径名。 68 | 69 | ### request.path= 70 | 71 | 设置请求路径名,并在存在时保留查询字符串。 72 | 73 | ### request.querystring 74 | 75 | 根据 `?` 获取原始查询字符串. 76 | 77 | ### request.querystring= 78 | 79 | 设置原始查询字符串。 80 | 81 | ### request.search 82 | 83 | 使用 `?` 获取原始查询字符串。 84 | 85 | ### request.search= 86 | 87 | 设置原始查询字符串。 88 | 89 | ### request.host 90 | 91 | 存在时获取主机(hostname:port)。当 `app.proxy` 是 __true__ 时支持 `X-Forwarded-Host`,否则使用 `Host`。 92 | 93 | ### request.hostname 94 | 95 | 存在时获取主机名。当 `app.proxy` 是 __true__ 时支持 `X-Forwarded-Host`,否则使用 `Host`。 96 | 97 | 如果主机是 IPv6, Koa 解析到 98 | [WHATWG URL API](https://nodejs.org/dist/latest-v8.x/docs/api/url.html#url_the_whatwg_url_api), 99 | *注意* 这可能会影响性能。 100 | 101 | ### request.URL 102 | 103 | 获取 WHATWG 解析的 URL 对象。 104 | 105 | ### request.type 106 | 107 | 获取请求 `Content-Type`, 不含 "charset" 等参数。 108 | 109 | > 译者注: 这里其实是只获取 _mime-type_, 详见[源码及其注释](https://github.com/koajs/koa/blob/eda27608f7d39ede86d7b402aae64b1867ce31c6/lib/request.js#L639) 110 | 111 | ```js 112 | const ct = ctx.request.type; 113 | // => "image/png" 114 | ``` 115 | 116 | ### request.charset 117 | 118 | 存在时获取请求字符集,或者 `undefined`: 119 | 120 | ```js 121 | ctx.request.charset; 122 | // => "utf-8" 123 | ``` 124 | 125 | ### request.query 126 | 127 | 获取解析的查询字符串, 当没有查询字符串时,返回一个空对象。请注意,此 getter _不_ 支持嵌套解析。 128 | 129 | 例如 "color=blue&size=small": 130 | 131 | ```js 132 | { 133 | color: 'blue', 134 | size: 'small' 135 | } 136 | ``` 137 | 138 | ### request.query= 139 | 140 | 将查询字符串设置为给定对象。 请注意,此 setter _不_ 支持嵌套对象。 141 | 142 | ```js 143 | ctx.query = { next: '/login' }; 144 | ``` 145 | 146 | ### request.fresh 147 | 148 | 检查请求缓存是否“新鲜”,也就是内容没有改变。此方法用于 `If-None-Match` / `ETag`, 和 `If-Modified-Since` 和 `Last-Modified` 之间的缓存协商。 在设置一个或多个这些响应头后应该引用它。 149 | 150 | ```js 151 | // 新鲜度检查需要状态20x或304 152 | ctx.status = 200; 153 | ctx.set('ETag', '123'); 154 | 155 | // 缓存是好的 156 | if (ctx.fresh) { 157 | ctx.status = 304; 158 | return; 159 | } 160 | 161 | // 缓存是陈旧的 162 | // 获取新数据 163 | ctx.body = await db.find('something'); 164 | ``` 165 | 166 | ### request.stale 167 | 168 | 与 `request.fresh` 相反. 169 | 170 | ### request.protocol 171 | 172 | 返回请求协议,“https” 或 “http”。当 `app.proxy` 是 __true__ 时支持 `X-Forwarded-Proto`。 173 | 174 | ### request.secure 175 | 176 | 通过 `ctx.protocol == "https"` 来检查请求是否通过 TLS 发出。 177 | 178 | ### request.ip 179 | 180 | 请求远程地址。 当 `app.proxy` 是 __true__ 时支持 `X-Forwarded-Proto`。 181 | 182 | ### request.ips 183 | 184 | 当 `X-Forwarded-For` 存在并且 `app.proxy` 被启用时,这些 ips 的数组被返回,从上游 - >下游排序。 禁用时返回一个空数组。 185 | 186 | 例如,如果值是 "client, proxy1, proxy2",将会得到数组 `["client", "proxy1", "proxy2"]`。 187 | 188 | 大多数反向代理(nginx)都通过 `proxy_add_x_forwarded_for` 设置了 x-forwarded-for,这带来了一定的安全风险。恶意攻击者可以通过伪造 `X-Forwarded-For` 请求头来伪造客户端的ip地址。 客户端发送的请求具有 'forged' 的 `X-Forwarded-For` 请求头。 在由反向代理转发之后,`request.ips` 将是 ['forged', 'client', 'proxy1', 'proxy2']。 189 | 190 | Koa 提供了两种方式来避免被绕过。 191 | 192 | 如果您可以控制反向代理,则可以通过调整配置来避免绕过,或者使用 koa 提供的 `app.proxyIpHeader` 来避免读取 `x-forwarded-for` 获取 ips。 193 | 194 | ```js 195 | const app = new Koa({ 196 | proxy: true, 197 | proxyIpHeader: 'X-Real-IP', 198 | }); 199 | ``` 200 | 201 | 如果您确切知道服务器前面有多少个反向代理,则可以通过配置 `app.maxIpsCount` 来避免读取用户的伪造的请求头: 202 | 203 | ```js 204 | const app = new Koa({ 205 | proxy: true, 206 | maxIpsCount: 1, // 服务器前只有一个代理 207 | }); 208 | 209 | // request.header['X-Forwarded-For'] === [ '127.0.0.1', '127.0.0.2' ]; 210 | // ctx.ips === [ '127.0.0.2' ]; 211 | ``` 212 | 213 | ### request.subdomains 214 | 215 | 以数组形式返回子域。 216 | 217 | 子域是应用程序主域之前主机的点分隔部分。默认情况下,应用程序的域名假定为主机的最后两个部分。这可以通过设置 `app.subdomainOffset` 来更改。 218 | 219 | 例如,如果域名为“tobi.ferrets.example.com”: 220 | 221 | 如果 `app.subdomainOffset` 未设置, `ctx.subdomains` 是 `["ferrets", "tobi"]`. 222 | 如果 `app.subdomainOffset` 是 3, `ctx.subdomains` 是 `["tobi"]`. 223 | 224 | ### request.is(types...) 225 | 226 | 检查传入请求是否包含 `Content-Type` 消息头字段, 并且包含任意的 mime `type`。 227 | 如果没有请求主体,返回 `null`。 228 | 如果没有内容类型,或者匹配失败,则返回 `false`。 229 | 反之则返回匹配的 content-type。 230 | 231 | ```js 232 | // 使用 Content-Type: text/html; charset=utf-8 233 | ctx.is('html'); // => 'html' 234 | ctx.is('text/html'); // => 'text/html' 235 | ctx.is('text/*', 'text/html'); // => 'text/html' 236 | 237 | // 当 Content-Type 是 application/json 时 238 | ctx.is('json', 'urlencoded'); // => 'json' 239 | ctx.is('application/json'); // => 'application/json' 240 | ctx.is('html', 'application/*'); // => 'application/json' 241 | 242 | ctx.is('html'); // => false 243 | ``` 244 | 245 | 例如,如果要确保仅将图像发送到给定路由: 246 | 247 | ```js 248 | if (ctx.is('image/*')) { 249 | // 处理 250 | } else { 251 | ctx.throw(415, 'images only!'); 252 | } 253 | ``` 254 | 255 | ### 内容协商 256 | 257 | Koa 的 `request` 对象包括由 [accepts](http://github.com/expressjs/accepts) 和 [negotiator](https://github.com/federomero/negotiator) 提供的内容协商实用函数。 258 | 259 | 这些实用函数是: 260 | 261 | - `request.accepts(types)` 262 | - `request.acceptsEncodings(types)` 263 | - `request.acceptsCharsets(charsets)` 264 | - `request.acceptsLanguages(langs)` 265 | 266 | 如果没有提供类型,则返回 __所有__ 可接受的类型。 267 | 268 | 如果提供多种类型,将返回最佳匹配。 如果没有找到匹配项,则返回一个`false`,你应该向客户端发送一个`406 "Not Acceptable"` 响应。 269 | 270 | 如果接收到任何类型的接收头,则会返回第一个类型。 因此,你提供的类型的顺序很重要。 271 | 272 | ### request.accepts(types) 273 | 274 | 检查给定的 `type(s)` 是否可以接受,如果 `true`,返回最佳匹配,否则为 `false`。 `type` 值可能是一个或多个 mime 类型的字符串,如 `application/json`,扩展名称如 `json`,或数组 `["json", "html", "text/plain"]`。 275 | 276 | ```js 277 | // Accept: text/html 278 | ctx.accepts('html'); 279 | // => "html" 280 | 281 | // Accept: text/*, application/json 282 | ctx.accepts('html'); 283 | // => "html" 284 | ctx.accepts('text/html'); 285 | // => "text/html" 286 | ctx.accepts('json', 'text'); 287 | // => "json" 288 | ctx.accepts('application/json'); 289 | // => "application/json" 290 | 291 | // Accept: text/*, application/json 292 | ctx.accepts('image/png'); 293 | ctx.accepts('png'); 294 | // => false 295 | 296 | // Accept: text/*;q=.5, application/json 297 | ctx.accepts(['html', 'json']); 298 | ctx.accepts('html', 'json'); 299 | // => "json" 300 | 301 | // No Accept header 302 | ctx.accepts('html', 'json'); 303 | // => "html" 304 | ctx.accepts('json', 'html'); 305 | // => "json" 306 | ``` 307 | 308 | 你可以根据需要多次调用 `ctx.accepts()`,或使用 switch: 309 | 310 | ```js 311 | switch (ctx.accepts('json', 'html', 'text')) { 312 | case 'json': break; 313 | case 'html': break; 314 | case 'text': break; 315 | default: ctx.throw(406, 'json, html, or text only'); 316 | } 317 | ``` 318 | 319 | ### request.acceptsEncodings(encodings) 320 | 321 | 检查 `encodings` 是否可以接受,返回最佳匹配为 `true`,否则为 `false`。 请注意,您应该将`identity` 作为编码之一! 322 | 323 | ```js 324 | // Accept-Encoding: gzip 325 | ctx.acceptsEncodings('gzip', 'deflate', 'identity'); 326 | // => "gzip" 327 | 328 | ctx.acceptsEncodings(['gzip', 'deflate', 'identity']); 329 | // => "gzip" 330 | ``` 331 | 332 | 当没有给出参数时,所有接受的编码将作为数组返回: 333 | 334 | ```js 335 | // Accept-Encoding: gzip, deflate 336 | ctx.acceptsEncodings(); 337 | // => ["gzip", "deflate", "identity"] 338 | ``` 339 | 340 | 请注意,如果客户端显式地发送 `identity;q=0`,那么 `identity` 编码(这意味着没有编码)可能是不可接受的。 虽然这是一个边缘的情况,你仍然应该处理这种方法返回 `false` 的情况。 341 | 342 | ### request.acceptsCharsets(charsets) 343 | 344 | 检查 `charsets` 是否可以接受,在 `true` 时返回最佳匹配,否则为 `false`。 345 | 346 | ```js 347 | // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 348 | ctx.acceptsCharsets('utf-8', 'utf-7'); 349 | // => "utf-8" 350 | 351 | ctx.acceptsCharsets(['utf-7', 'utf-8']); 352 | // => "utf-8" 353 | ``` 354 | 355 | 当没有参数被赋予所有被接受的字符集将作为数组返回: 356 | 357 | ```js 358 | // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 359 | ctx.acceptsCharsets(); 360 | // => ["utf-8", "utf-7", "iso-8859-1"] 361 | ``` 362 | 363 | ### request.acceptsLanguages(langs) 364 | 365 | 检查 `langs` 是否可以接受,如果为 `true`,返回最佳匹配,否则为 `false`。 366 | 367 | ```js 368 | // Accept-Language: en;q=0.8, es, pt 369 | ctx.acceptsLanguages('es', 'en'); 370 | // => "es" 371 | 372 | ctx.acceptsLanguages(['en', 'es']); 373 | // => "es" 374 | ``` 375 | 376 | 当没有参数被赋予所有接受的语言将作为数组返回: 377 | 378 | ```js 379 | // Accept-Language: en;q=0.8, es, pt 380 | ctx.acceptsLanguages(); 381 | // => ["es", "pt", "en"] 382 | ``` 383 | 384 | ### request.idempotent 385 | 386 | 检查请求是否是幂等的。 387 | 388 | ### request.socket 389 | 390 | 返回请求套接字。 391 | 392 | ### request.get(field) 393 | 394 | 返回请求头(header), `field` 不区分大小写. 395 | -------------------------------------------------------------------------------- /api/response.md: -------------------------------------------------------------------------------- 1 | # 响应(Response) 2 | 3 | Koa `Response` 对象是在 node 的原生响应对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。 4 | 5 | ## API 6 | 7 | ### response.header 8 | 9 | 响应头对象。 10 | 11 | ### response.headers 12 | 13 | 响应头对象。别名是 `response.header`。 14 | 15 | ### response.socket 16 | 17 | 响应套接字。 作为 `request.socket` 指向 net.Socket 实例。 18 | 19 | ### response.status 20 | 21 | 获取响应状态。默认情况下,`response.status` 设置为 `404` 而不是像 node 的 `res.statusCode` 那样默认为 `200`。 22 | 23 | ### response.status= 24 | 25 | 通过数字代码设置响应状态: 26 | 27 | - 100 "continue" 28 | - 101 "switching protocols" 29 | - 102 "processing" 30 | - 200 "ok" 31 | - 201 "created" 32 | - 202 "accepted" 33 | - 203 "non-authoritative information" 34 | - 204 "no content" 35 | - 205 "reset content" 36 | - 206 "partial content" 37 | - 207 "multi-status" 38 | - 208 "already reported" 39 | - 226 "im used" 40 | - 300 "multiple choices" 41 | - 301 "moved permanently" 42 | - 302 "found" 43 | - 303 "see other" 44 | - 304 "not modified" 45 | - 305 "use proxy" 46 | - 307 "temporary redirect" 47 | - 308 "permanent redirect" 48 | - 400 "bad request" 49 | - 401 "unauthorized" 50 | - 402 "payment required" 51 | - 403 "forbidden" 52 | - 404 "not found" 53 | - 405 "method not allowed" 54 | - 406 "not acceptable" 55 | - 407 "proxy authentication required" 56 | - 408 "request timeout" 57 | - 409 "conflict" 58 | - 410 "gone" 59 | - 411 "length required" 60 | - 412 "precondition failed" 61 | - 413 "payload too large" 62 | - 414 "uri too long" 63 | - 415 "unsupported media type" 64 | - 416 "range not satisfiable" 65 | - 417 "expectation failed" 66 | - 418 "I'm a teapot" 67 | - 422 "unprocessable entity" 68 | - 423 "locked" 69 | - 424 "failed dependency" 70 | - 426 "upgrade required" 71 | - 428 "precondition required" 72 | - 429 "too many requests" 73 | - 431 "request header fields too large" 74 | - 500 "internal server error" 75 | - 501 "not implemented" 76 | - 502 "bad gateway" 77 | - 503 "service unavailable" 78 | - 504 "gateway timeout" 79 | - 505 "http version not supported" 80 | - 506 "variant also negotiates" 81 | - 507 "insufficient storage" 82 | - 508 "loop detected" 83 | - 510 "not extended" 84 | - 511 "network authentication required" 85 | 86 | __注意__: 不用太在意记住这些字符串, 如果你写错了,可以查阅这个列表随时更正. 87 | 88 | 由于 `response.status` 默认设置为 `404`,因此发送没有 body 且状态不同的响应的操作如下: 89 | 90 | ```js 91 | ctx.response.status = 200; 92 | 93 | // 或其他任何状态 94 | ctx.response.status = 204; 95 | ``` 96 | 97 | ### response.message 98 | 99 | 获取响应的状态消息. 默认情况下, `response.message` 与 `response.status` 关联. 100 | 101 | ### response.message= 102 | 103 | 将响应的状态消息设置为给定值。 104 | 105 | ### response.length= 106 | 107 | 将响应的 Content-Length 设置为给定值。 108 | 109 | ### response.length 110 | 111 | 以数字返回响应的 Content-Length,或者从`ctx.body`推导出来,或者`undefined`。 112 | 113 | ### response.body 114 | 115 | 获取响应主体。 116 | 117 | ### response.body= 118 | 119 | 将响应体设置为以下之一: 120 | 121 | - `string` 写入 122 | - `Buffer` 写入 123 | - `Stream` 管道 124 | - `Object` || `Array` JSON-字符串化 125 | - `null` 无内容响应 126 | 127 | 如果 `response.status` 未被设置, Koa 将会自动设置状态为 `200` 或 `204`。 128 | 129 | Koa 没有防范作为响应体的所有内容 - 函数没有有意义地序列化,返回布尔值可能会根据您的应用程序而有意义。并且当错误生效时,它可能无法正常工作 错误的属性无法枚举。 我们建议在您的应用中添加中间件,以确定每个应用的正文类型。 示例中间件可能是: 130 | 131 | ```js 132 | app.use(async (ctx, next) => { 133 | await next() 134 | 135 | ctx.assert.equal('object', typeof ctx, 500, '某些开发错误') 136 | }) 137 | ``` 138 | 139 | #### String 140 | 141 | Content-Type 默认为 `text/html` 或 `text/plain`, 同时默认字符集是 utf-8。Content-Length 字段也是如此。 142 | 143 | #### Buffer 144 | 145 | Content-Type 默认为 `application/octet-stream`, 并且 Content-Length 字段也是如此。 146 | 147 | #### Stream 148 | 149 | Content-Type 默认为 `application/octet-stream`。 150 | 151 | 每当流被设置为响应主体时,`.onerror` 作为侦听器自动添加到 `error` 事件中以捕获任何错误。此外,每当请求关闭(甚至过早)时,流都将被销毁。如果你不想要这两个功能,请勿直接将流设为主体。例如,当将主体设置为代理中的 HTTP 流时,你可能不想要这样做,因为它会破坏底层连接。 152 | 153 | 参阅: https://github.com/koajs/koa/pull/612 获取更多信息。 154 | 155 | 以下是流错误处理的示例,而不会自动破坏流: 156 | 157 | ```js 158 | const PassThrough = require('stream').PassThrough; 159 | 160 | app.use(async ctx => { 161 | ctx.body = someHTTPStream.on('error', (err) => ctx.onerror(err)).pipe(PassThrough()); 162 | }); 163 | ``` 164 | 165 | #### Object 166 | 167 | Content-Type 默认为 `application/json`. 这包括普通的对象 `{ foo: 'bar' }` 和数组 `['foo', 'bar']`。 168 | 169 | ### response.get(field) 170 | 171 | 不区分大小写获取响应头字段值 `field`。 172 | 173 | ```js 174 | const etag = ctx.response.get('ETag'); 175 | ``` 176 | 177 | ### response.has(field) 178 | 179 | 如果当前在响应头中设置了由名称标识的消息头,则返回 `true`. 180 | 消息头名称匹配不区分大小写. 181 | 182 | ```js 183 | const rateLimited = ctx.response.has('X-RateLimit-Limit'); 184 | ``` 185 | 186 | 187 | ### response.set(field, value) 188 | 189 | 设置响应头 `field` 到 `value`: 190 | 191 | ```js 192 | ctx.set('Cache-Control', 'no-cache'); 193 | ``` 194 | 195 | ### response.append(field, value) 196 | 197 | 用值 `val` 附加额外的消息头 `field`。 198 | 199 | ```js 200 | ctx.append('Link', ''); 201 | ``` 202 | 203 | ### response.set(fields) 204 | 205 | 用一个对象设置多个响应头`fields`: 206 | 207 | ```js 208 | ctx.set({ 209 | 'Etag': '1234', 210 | 'Last-Modified': date 211 | }); 212 | ``` 213 | 214 | 这将委托给 [setHeader](https://nodejs.org/dist/latest/docs/api/http.html#http_request_setheader_name_value) ,它通过指定的键设置或更新消息头,并且不重置整个消息头。 215 | 216 | ### response.remove(field) 217 | 218 | 删除消息头 `field`。 219 | 220 | ### response.type 221 | 222 | 获取响应 `Content-Type`, 不含 "charset" 等参数。 223 | 224 | > 译者注: 这里其实是只获取 _mime-type_, 详见[源码及其注释](https://github.com/koajs/koa/blob/eda27608f7d39ede86d7b402aae64b1867ce31c6/lib/response.js#L371) 225 | 226 | ```js 227 | const ct = ctx.type; 228 | // => "image/png" 229 | ``` 230 | 231 | ### response.type= 232 | 233 | 设置响应 `Content-Type` 通过 mime 字符串或文件扩展名。 234 | 235 | ```js 236 | ctx.type = 'text/plain; charset=utf-8'; 237 | ctx.type = 'image/png'; 238 | ctx.type = '.png'; 239 | ctx.type = 'png'; 240 | ``` 241 | 242 | 注意: 在适当的情况下为你选择 `charset`, 比如 `response.type = 'html'` 将默认是 "utf-8". 如果你想覆盖 `charset`, 使用 `ctx.set('Content-Type', 'text/html')` 将响应头字段设置为直接值。 243 | 244 | ### response.is(types...) 245 | 246 | 非常类似 `ctx.request.is()`. 检查响应类型是否是所提供的类型之一。这对于创建操纵响应的中间件特别有用。 247 | 248 | 例如, 这是一个中间件,可以削减除流之外的所有HTML响应。 249 | 250 | ```js 251 | const minify = require('html-minifier'); 252 | 253 | app.use(async (ctx, next) => { 254 | await next(); 255 | 256 | if (!ctx.response.is('html')) return; 257 | 258 | let body = ctx.body; 259 | if (!body || body.pipe) return; 260 | 261 | if (Buffer.isBuffer(body)) body = body.toString(); 262 | ctx.body = minify(body); 263 | }); 264 | ``` 265 | 266 | ### response.redirect(url, [alt]) 267 | 268 | 执行 [302] 重定向到 `url`. 269 | 270 | 字符串 “back” 是特别提供 Referrer 支持的,当 Referrer 不存在时,使用 `alt` 或 “/”。 271 | 272 | ```js 273 | ctx.redirect('back'); 274 | ctx.redirect('back', '/index.html'); 275 | ctx.redirect('/login'); 276 | ctx.redirect('http://google.com'); 277 | ``` 278 | 279 | 要更改 “302” 的默认状态,只需在该调用之前或之后给 `status` 赋值。要变更主体请在此调用之后: 280 | 281 | ```js 282 | ctx.status = 301; 283 | ctx.redirect('/cart'); 284 | ctx.body = 'Redirecting to shopping cart'; 285 | ``` 286 | 287 | ### response.attachment([filename], [options]) 288 | 289 | 将 `Content-Disposition` 设置为 “附件” 以指示客户端提示下载。(可选)指定下载的 `filename` 和部分 [参数](https://github.com/jshttp/content-disposition#options)。 290 | 291 | ### response.headerSent 292 | 293 | 检查是否已经发送了一个响应头。 用于查看客户端是否可能会收到错误通知。 294 | 295 | ### response.lastModified 296 | 297 | 将 `Last-Modified` 消息头返回为 `Date`, 如果存在。 298 | 299 | ### response.lastModified= 300 | 301 | 将 `Last-Modified` 消息头设置为适当的 UTC 字符串。您可以将其设置为 `Date` 或日期字符串。 302 | 303 | ```js 304 | ctx.response.lastModified = new Date(); 305 | ``` 306 | 307 | ### response.etag= 308 | 309 | 设置包含 `"` 包裹的 ETag 响应, 310 | 请注意,没有相应的 `response.etag` getter。 311 | 312 | ```js 313 | ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex'); 314 | ``` 315 | 316 | ### response.vary(field) 317 | 318 | 设置 `field` 的 `vary`。 319 | 320 | ### response.flushHeaders() 321 | 322 | 刷新任何设置的消息头,然后是主体(body)。 323 | -------------------------------------------------------------------------------- /error-handling.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | ## Try-Catch 4 | 5 | 使用 async 方法意味着你可以 try-catch `next`. 6 | 此示例为所有错误添加了一个 `.status`: 7 | 8 | ```js 9 | app.use(async (ctx, next) => { 10 | try { 11 | await next(); 12 | } catch (err) { 13 | err.status = err.statusCode || err.status || 500; 14 | throw err; 15 | } 16 | }); 17 | ``` 18 | 19 | ### 默认错误处理程序 20 | 21 | 默认的错误处理程序本质上是中间件链开始时的一个 try-catch。要使用不同的错误处理程序,只需在中间件链的起始处放置另一个 try-catch,并在那里处理错误。但是,默认错误处理程序对于大多数用例来说都是足够好的。它将使用状态代码 `err.status`,或默认为500。如果 `err.expose` 是 true,那么 `err.message` 就是答复。否则,将使用从错误代码生成的消息(例如,对于代码500,将使用消息“内部服务器错误”)。所有消息头将从请求中清除,但是任何在 `err.headers` 中的消息头将会被设置。你可以使用如上所述的 try-catch 来向此列表添加消息头。 22 | 23 | 以下是创建你自己的错误处理程序的示例: 24 | 25 | ```js 26 | app.use(async (ctx, next) => { 27 | try { 28 | await next(); 29 | } catch (err) { 30 | // will only respond with JSON 31 | ctx.status = err.statusCode || err.status || 500; 32 | ctx.body = { 33 | message: err.message 34 | }; 35 | } 36 | }) 37 | ``` 38 | 39 | ## 错误事件 40 | 41 | 错误事件侦听器可以用 `app.on('error')` 指定。如果未指定错误侦听器,则使用默认错误侦听器。错误侦听器接收所有中间件链返回的错误,如果一个错误被捕获并且不再抛出,它将不会被传递给错误侦听器。如果没有指定错误事件侦听器,那么将使用 `app.onerror`,除非 `error.expose` 为 true 或 `app.silent` 为 true 或 `error.status` 为 404,否则只简单记录错误。 42 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## Koa 替代 Express? 4 | 5 | 它更像是 Connect,但是很多 Express 的好东西被转移到 Koa 的中间件级别,以帮助形成更强大的基础。 这使得中间件对于整个堆栈而言不仅仅是最终应用程序代码,而且更易于书写,并更不容易出错。 6 | 7 | 通常,许多中间件将重新实现类似的功能,甚至更糟的是不正确地实现它们, 如签名的cookie 加密等通常是应用程序特定的,而不是中间件特定的。 8 | 9 | ## Koa 替代 Connect? 10 | 11 | 不,只是不同的功能,现在通过构建器也可以让我们用较少的回调编写代码。 Connect 同样可以,有些人可能仍然喜欢它,这取决于你喜欢什么。 12 | 13 | ## Koa 包含路由吗? 14 | 15 | 不 - Koa 没有开箱即用的路由, 但是有很多路由中间件可用: https://github.com/koajs/koa/wiki 16 | 17 | ## 为什么 Koa 不是 Express 4.0? 18 | 19 | Koa 与现在所知的 Express 差距很大,设计根本上有很大差异,所以从 Express 3.0 迁移到Express 4.0 将有意味着重写整个应用程序,所以我们考虑创建一个新的库。 20 | 21 | ## Koa 对象有什么自定义属性? 22 | 23 | Koa 使用它的自定义对象: `ctx`, `ctx.request`, 和 `ctx.response`. 24 | 这些对象使用便捷的方法和 getter/setter 来抽象 node 的 `req` 和 `res` 对象。 25 | 26 | 通常,添加到这些对象的属性必须遵循以下规则: 27 | 28 | - 它们必须是非常常用的 和/或 必须做一些有用的事情 29 | - 如果一个属性作为一个 setter 存在,那么它也将作为一个 getter 存在,但反之亦然 30 | 31 | 许多 `ctx.request` 和 `ctx.response` 的属性都被委托给 `ctx`。 32 | 如果它是一个 getter/setter,那么 getter 和 setter 都将严格对应于 `ctx.request` 或 `ctx.response`。 33 | 34 | 附加其他属性之前,请考虑这些规则。 35 | -------------------------------------------------------------------------------- /guide.md: -------------------------------------------------------------------------------- 1 | 2 | # 指南 3 | 4 | 本指南涵盖的 Koa 主题不与 API 直接相关,例如编写中间件的最佳做法和应用程序结构建议。在这些例子中,我们使用 async 函数作为中间件 - 您也可以使用 commonFunction 或 generatorFunction,两者有些许不同。 5 | 6 | ## 编写中间件 7 | 8 | Koa 中间件是简单的函数,它返回一个带有签名 (ctx, next) 的`MiddlewareFunction`。当中间件运行时,它必须手动调用 `next()` 来运行 “下游” 中间件。 9 | 10 | 例如,如果你想要跟踪通过添加 `X-Response-Time` 头字段通过 Koa 传播请求需要多长时间,则中间件将如下所示: 11 | 12 | ```js 13 | async function responseTime(ctx, next) { 14 | const start = Date.now(); 15 | await next(); 16 | const ms = Date.now() - start; 17 | ctx.set('X-Response-Time', `${ms}ms`); 18 | } 19 | 20 | app.use(responseTime); 21 | ``` 22 | 23 | 如果您是前端开发人员,您可以将 `next();` 之前的任意代码视为“捕获”阶段,这个简易的 gif 说明了 async 函数如何使我们能够恰当地利用堆栈流来实现请求和响应流: 24 | 25 | ![koa middleware](middleware.gif) 26 | 27 | 1. 创建一个跟踪响应时间的日期 28 | 2. 等待下一个中间件的控制 29 | 3. 创建另一个日期跟踪持续时间 30 | 4. 等待下一个中间件的控制 31 | 5. 将响应主体设置为“Hello World” 32 | 6. 计算持续时间 33 | 7. 输出日志行 34 | 8. 计算响应时间 35 | 9. 设置 `X-Response-Time` 头字段 36 | 10. 交给 Koa 处理响应 37 | 38 | 接下来,我们将介绍创建 Koa 中间件的最佳做法。 39 | 40 | ## 中间件最佳实践 41 | 42 | 本节介绍中间件创作最佳实践,例如中间件接受参数,命名中间件进行调试等等。 43 | 44 | ### 中间件参数 45 | 46 | 当创建公共中间件时,将中间件包装在接受参数的函数中,遵循这个约定是有用的,允许用户扩展功能。即使您的中间件 _不_ 接受任何参数,这仍然是保持统一的好方法。 47 | 48 | 这里我们设计的 `logger` 中间件接受一个 `format` 自定义字符串,并返回中间件本身: 49 | 50 | ```js 51 | function logger(format) { 52 | format = format || ':method ":url"'; 53 | 54 | return async function (ctx, next) { 55 | const str = format 56 | .replace(':method', ctx.method) 57 | .replace(':url', ctx.url); 58 | 59 | console.log(str); 60 | 61 | await next(); 62 | }; 63 | } 64 | 65 | app.use(logger()); 66 | app.use(logger(':method :url')); 67 | ``` 68 | 69 | ### 命名中间件 70 | 71 | 命名中间件是可选的,但是在调试中分配名称很有用。 72 | 73 | ```js 74 | function logger(format) { 75 | return async function logger(ctx, next) { 76 | 77 | }; 78 | } 79 | ``` 80 | 81 | ### 将多个中间件与 koa-compose 相结合 82 | 83 | 有时您想要将多个中间件 “组合” 成一个单一的中间件,便于重用或导出。你可以使用 [koa-compose](https://github.com/koajs/compose) 84 | 85 | ```js 86 | const compose = require('koa-compose'); 87 | 88 | async function random(ctx, next) { 89 | if ('/random' == ctx.path) { 90 | ctx.body = Math.floor(Math.random() * 10); 91 | } else { 92 | await next(); 93 | } 94 | }; 95 | 96 | async function backwards(ctx, next) { 97 | if ('/backwards' == ctx.path) { 98 | ctx.body = 'sdrawkcab'; 99 | } else { 100 | await next(); 101 | } 102 | } 103 | 104 | async function pi(ctx, next) { 105 | if ('/pi' == ctx.path) { 106 | ctx.body = String(Math.PI); 107 | } else { 108 | await next(); 109 | } 110 | } 111 | 112 | const all = compose([random, backwards, pi]); 113 | 114 | app.use(all); 115 | ``` 116 | 117 | 118 | 119 | ### 响应中间件 120 | 121 | 中间件决定响应请求,并希望绕过下游中间件可以简单地省略 `next()`。通常这将在路由中间件中,但这也可以任意执行。例如,以下内容将以 “two” 进行响应,但是所有三个都将被执行,从而使下游的 “three” 中间件有机会操纵响应。 122 | 123 | ```js 124 | app.use(async function (ctx, next) { 125 | console.log('>> one'); 126 | await next(); 127 | console.log('<< one'); 128 | }); 129 | 130 | app.use(async function (ctx, next) { 131 | console.log('>> two'); 132 | ctx.body = 'two'; 133 | await next(); 134 | console.log('<< two'); 135 | }); 136 | 137 | app.use(async function (ctx, next) { 138 | console.log('>> three'); 139 | await next(); 140 | console.log('<< three'); 141 | }); 142 | ``` 143 | 144 | 以下配置在第二个中间件中省略了`next()`,并且仍然会以 “two” 进行响应,然而,第三个(以及任何其他下游中间件)将被忽略: 145 | 146 | ```js 147 | app.use(async function (ctx, next) { 148 | console.log('>> one'); 149 | await next(); 150 | console.log('<< one'); 151 | }); 152 | 153 | app.use(async function (ctx, next) { 154 | console.log('>> two'); 155 | ctx.body = 'two'; 156 | console.log('<< two'); 157 | }); 158 | 159 | app.use(async function (ctx, next) { 160 | console.log('>> three'); 161 | await next(); 162 | console.log('<< three'); 163 | }); 164 | ``` 165 | 166 | 当最远的下游中间件执行 `next();` 时,它实际上是一个 noop 函数,允许中间件在堆栈中的任意位置正确组合。 167 | 168 | ## 异步操作 169 | 170 | Async 方法和 promise 来自 Koa 的底层,可以让你编写非阻塞序列代码。例如,这个中间件从 `./docs` 读取文件名,然后在将给 body 指定合并结果之前并行读取每个 markdown 文件的内容。 171 | 172 | 173 | ```js 174 | const fs = require('mz/fs'); 175 | 176 | app.use(async function (ctx, next) { 177 | const paths = await fs.readdir('docs'); 178 | const files = await Promise.all(paths.map(path => fs.readFile(`docs/${path}`, 'utf8'))); 179 | 180 | ctx.type = 'markdown'; 181 | ctx.body = files.join(''); 182 | }); 183 | ``` 184 | 185 | ## 调试 Koa 186 | 187 | Koa 以及许多构建库,支持来自 [debug](https://github.com/visionmedia/debug) 的 __DEBUG__ 环境变量,它提供简单的条件记录。 188 | 189 | 例如,要查看所有 koa 特定的调试信息,只需通过 `DEBUG=koa*`,并且在启动时,您将看到所使用的中间件的列表。 190 | 191 | ``` 192 | $ DEBUG=koa* node --harmony examples/simple 193 | koa:application use responseTime +0ms 194 | koa:application use logger +4ms 195 | koa:application use contentLength +0ms 196 | koa:application use notfound +0ms 197 | koa:application use response +0ms 198 | koa:application listen +0ms 199 | ``` 200 | 201 | 由于 JavaScript 在运行时没有定义函数名,你也可以将中间件的名称设置为 `._name`。当你无法控制中间件的名称时,这很有用。例如: 202 | 203 | ```js 204 | const path = require('path'); 205 | const serve = require('koa-static'); 206 | 207 | const publicFiles = serve(path.join(__dirname, 'public')); 208 | publicFiles._name = 'static /public'; 209 | 210 | app.use(publicFiles); 211 | ``` 212 | 213 | 现在,在调试时不只会看到 “serve”,你也会看到: 214 | 215 | ``` 216 | koa:application use static /public +0ms 217 | ``` 218 | -------------------------------------------------------------------------------- /koa-vs-express.md: -------------------------------------------------------------------------------- 1 | # Koa 与 Express 2 | 3 | 在理念上,Koa 旨在 “修复和替换节点”,而 Express 旨在 “增加节点”。 4 | Koa 使用Promise(JavaScript一种异步手段)和异步功能来摆脱回调地狱的应用程序,并简化错误处理。 5 | 它暴露了自己的 `ctx.request` 和 `ctx.response` 对象,而不是 node 的 `req` 和 `res` 对象。 6 | 7 | 另一方面,Express 通过附加的属性和方法增加了 node 的 `req` 和 `res` 对象,并且包含许多其他 “框架” 功能,如路由和模板,而 Koa 则没有。 8 | 9 | 因此,Koa 可被视为 node.js 的 `http` 模块的抽象,其中 Express 是 node.js 的应用程序框架。 10 | 11 | | 功能 | Koa | Express | Connect | 12 | |------------------:|-----|---------|---------| 13 | | Middleware Kernel | ✓ | ✓ | ✓ | 14 | | Routing | | ✓ | | 15 | | Templating | | ✓ | | 16 | | Sending Files | | ✓ | | 17 | | JSONP | | ✓ | | 18 | 19 | 因此,如果您想要更接近 node.js 和传统的 node.js 样式编码,那么您可能希望坚持使用Connect/Express 或类似的框架。 20 | 如果你想摆脱回调,请使用 Koa。 21 | 22 | 由于这种不同的理念,其结果是传统的 node.js “中间件”(即“(req,res,next)”的函数)与Koa不兼容。 你的应用基本上要重新改写了。 23 | 24 | ## Koa 替代 Express? 25 | 26 | 它更像是 Connect,但是很多 Express 的好东西被转移到 Koa 的中间件级别,以帮助形成更强大的基础。 这使得中间件对于整个堆栈而言不仅仅是最终应用程序代码,而且更易于书写,并更不容易出错。 27 | 28 | 通常,许多中间件将重新实现类似的功能,甚至更糟的是不正确地实现它们, 如签名的cookie 加密等通常是应用程序特定的,而不是中间件特定的。 29 | 30 | ## Koa 替代 Connect? 31 | 32 | 不,只是不同的功能,现在通过构建器也可以让我们用较少的回调编写代码。 Connect 同样可以,有些人可能仍然喜欢它,这取决于你喜欢什么。 33 | 34 | ## 为什么 Koa 不是 Express 4.0? 35 | 36 | Koa 与现在所知的 Express 差距很大,设计根本上有很大差异,所以从 Express 3.0 迁移到Express 4.0 将有意味着重写整个应用程序,所以我们考虑创建一个新的库。 37 | 38 | ## Koa 与 Connect/Express 有哪些不同? 39 | 40 | ### 基于 Promises 的控制流程 41 | 42 | 没有回调地狱。 43 | 44 | 通过 try/catch 更好的处理错误。 45 | 46 | 无需域。 47 | 48 | ### Koa 非常精简 49 | 50 | 不同于 Connect 和 Express, Koa 不含任何中间件. 51 | 52 | 不同于 Express, 不提供路由. 53 | 54 | 不同于 Express, 不提供许多便捷设施。 例如,发送文件. 55 | 56 | Koa 更加模块化. 57 | 58 | ### Koa 对中间件的依赖较少 59 | 60 | 例如, 不使用 “body parsing” 中间件,而是使用 body 解析函数。 61 | 62 | ### Koa 抽象 node 的 request/response 63 | 64 | 减少攻击。 65 | 66 | 更好的用户体验。 67 | 68 | 恰当的流处理。 69 | 70 | ### Koa 路由(第三方库支持) 71 | 72 | 由于 Express 带有自己的路由,而 Koa 没有任何内置路由,但是有 koa-router 和 koa-route 第三方库可用。同样的, 就像我们在 Express 中有 helmet 保证安全, 对于 koa 我们有 koa-helmet 和一些列的第三方库可用。 73 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demopark/koa-docs-Zh-CN/daaf6f269468c17fae47c8631886991ad214b78e/logo.png -------------------------------------------------------------------------------- /middleware.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demopark/koa-docs-Zh-CN/daaf6f269468c17fae47c8631886991ad214b78e/middleware.gif -------------------------------------------------------------------------------- /migration.md: -------------------------------------------------------------------------------- 1 | # 从 Koa v1.x 迁移到 v2.x 2 | 3 | ## 新的中间件签名 4 | 5 | Koa v2 引入了新的中间件签名。 6 | 7 | **旧签名中间件(v1.x)支持将在 v3 中删除** 8 | 9 | 新的中间件签名是这样的: 10 | 11 | ```js 12 | // 使用异步箭头方法 13 | app.use(async (ctx, next) => { 14 | try { 15 | await next() // next 现在是一个方法 16 | } catch (err) { 17 | ctx.body = { message: err.message } 18 | ctx.status = err.status || 500 19 | } 20 | }) 21 | 22 | app.use(async ctx => { 23 | const user = await User.getById(this.session.userid) // await 替换了 yield 24 | ctx.body = user // ctx 替换了 this 25 | }) 26 | ``` 27 | 28 | 你不必一定使用异步函数 - 你只需要传递一个返回 promise 的函数。返回 promise 的常规方法也可以使用! 29 | 30 | 签名已更改为通过 `ctx` 取代 `this` 显式参数传递 `Context`。 31 | 32 | 上下文传递更改使得 koa 更能兼容 es6 的箭头函数,通过捕获 “this”。 33 | 34 | 35 | ## 在 v2.x 中使用 v1.x 中间件 36 | 37 | Koa v2.x将尝试转换 `app.use` 上的旧签名,生成器中间件, 使用 [koa-convert](https://github.com/koajs/convert). 38 | 不过建议您选择尽快迁移所有 v1.x 中间件。 39 | 40 | ```js 41 | // Koa 将转换 42 | app.use(function *(next) { 43 | const start = Date.now(); 44 | yield next; 45 | const ms = Date.now() - start; 46 | console.log(`${this.method} ${this.url} - ${ms}ms`); 47 | }); 48 | ``` 49 | 50 | 您也可以手动执行,在这种情况下,Koa不会转换。 51 | 52 | ```js 53 | const convert = require('koa-convert'); 54 | 55 | app.use(convert(function *(next) { 56 | const start = Date.now(); 57 | yield next; 58 | const ms = Date.now() - start; 59 | console.log(`${this.method} ${this.url} - ${ms}ms`); 60 | })); 61 | ``` 62 | 63 | ## 升级中间件 64 | 65 | 您将不得不使用新的中间件签名将您的生成器转换为异步功能: 66 | 67 | ```js 68 | app.use(async (ctx, next) => { 69 | const user = await Users.getById(this.session.user_id); 70 | await next(); 71 | ctx.body = { message: 'some message' }; 72 | }) 73 | ``` 74 | 75 | 升级中间件可能需要一些工作。 一个迁移方式是逐个更新它们。 76 | 77 | 1. 将所有当前的中间件包装在 `koa-convert` 中 78 | 2. 测试 79 | 3. `npm outdated` 看看哪个 koa 中间件已经过时了 80 | 4. 更新一个过时的中间件,使用 `koa-convert` 删除 81 | 5. 测试 82 | 6. 重复步骤3-5,直到完成 83 | 84 | ## 升级你的代码 85 | 86 | 您应该开始重构代码,以便轻松迁移到 Koa v2: 87 | 88 | - 各处都是 promises 返回! 89 | - 不要使用 `yield*` 90 | - 不要使用 `yield {}` 或 `yield []`. 91 | - 转换 `yield []` 为 `yield Promise.all([])` 92 | - 转换 `yield {}` 为 `yield Bluebird.props({})` 93 | 94 | 您也可以重构 Koa 中间件功能之外的逻辑。 创建一个方法像 `function* someLogic(ctx) {}` 然后在你的中间件中调用 `const result = yield someLogic(this)`. 95 | 不使用 `this` 将有助于迁移到新的中间件签名,所以不使用 `this`。 96 | 97 | ## 应用对象构造函数需要 new 98 | 99 | 在 v1.x 中,可以直接调用应用构造函数,而不用 `new` 实例化一个应用程序的实例。 例如: 100 | 101 | ```js 102 | var koa = require('koa'); 103 | var app = module.exports = koa(); 104 | ``` 105 | 106 | v2.x 使用 es6 类,需要使用 `new` 关键字。 107 | 108 | ```js 109 | var koa = require('koa'); 110 | var app = module.exports = new koa(); 111 | ``` 112 | 113 | ## 删除 ENV 特定的日志记录行为 114 | 115 | 对于 `test` 环境的显式检查从错误处理中删除。 116 | 117 | ## 依赖变化 118 | 119 | - [co](https://github.com/tj/co) 不再与Koa捆绑在一起。直接 require 或 import 它. 120 | - [composition](https://github.com/thenables/composition) 不再使用并已废弃。 121 | 122 | ## v1.x 支持 123 | 124 | 仍然支持 v1.x 分支,但应该不会得到功能性更新。 除了此迁移指南外,文档将针对最新版本。 125 | 126 | ## 帮帮忙 127 | 128 | 如果您遇到本迁移指南未涉及的迁移相关问题,请考虑提交文档提取请求。 129 | -------------------------------------------------------------------------------- /troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Koa 故障排除 2 | 3 | 如果您遇到问题,后来学习如何解决问题,并认为其他人也可能遇到这个问题,请考虑对本文档做出贡献。 4 | 5 | ## Whenever I try to access my route, it sends back a 404 6 | 7 | This is a common but troublesome problem when working with Koa middleware. First, it is critical to understand when Koa generates a 404. Koa does not care which or how much middleware was run, in many cases a 200 and 404 trigger the same number of middleware. Instead, the default status for any response is 404. The most obvious way this is changed is through `ctx.status`. However, if `ctx.body` is set when the status has not been explicitly defined (through `ctx.status`), the status is set to 200. This explains why simply setting the body results in a 200. Once the middleware is done (when the middleware and any returned promises are complete), Koa sends out the response. After that, nothing can alter the response. If it was a 404 at the time, it will be a 404 at the end, even if `ctx.status` or `ctx.body` are set afterwords. 8 | 9 | Even though we now understand the basis of a 404, it might not be as clear why a 404 is generated in a specific case. This can be especially troublesome when it seems that `ctx.status` or `ctx.body` are set. 10 | 11 | The unexpected 404 is a specific symptom of one of these more general problems: 12 | 13 | - [My response or context changes have no effect](#my-response-or-context-changes-have-no-effect) 14 | - [My middleware is not called](#my-middleware-is-not-called) 15 | 16 | ## My response or context changes have no effect 17 | 18 | This can be caused when the response is sent before the code making the change is 19 | executed. If the change is to the `ctx.body` or `ctx.status` setter, this can cause a 404 and 20 | is by far the most common cause of these problems. 21 | 22 | ### Problematic code 23 | 24 | ```js 25 | router.get('/fetch', function (ctx, next) { 26 | models.Book.findById(parseInt(ctx.query.id)).then(function (book) { 27 | ctx.body = book; 28 | }); 29 | }); 30 | ``` 31 | 32 | When used, this route will always send back a 404, even though `ctx.body` is set. 33 | 34 | The same behavior would occur in this `async` version: 35 | 36 | ```js 37 | router.get('/fetch', async (ctx, next) => { 38 | models.Book.findById(parseInt(ctx.query.id)).then(function (book) { 39 | ctx.body = book; 40 | }); 41 | }); 42 | ``` 43 | 44 | ### Cause 45 | 46 | `ctx.body` is not set until *after* the response has been sent. The code doesn't tell Koa to wait for the database to return the record. Koa sends the response after the middleware has been run, but not after the callback inside the middleware has been run. In the gap there, `ctx.body` has not yet been set, so Koa responds with a 404. 47 | 48 | ### Identifying this as the issue 49 | 50 | Adding another piece of middleware and some logging can be extremely helpful in identifying this issue. 51 | 52 | ```js 53 | router.use('/fetch', function (ctx, next) { 54 | return next().then(function () { 55 | console.log('Middleware done'); 56 | }); 57 | }); 58 | 59 | router.get('/fetch', function (ctx, next) { 60 | models.Book.findById(parseInt(ctx.query.id)).then(function (book) { 61 | ctx.body = book; 62 | console.log('Body set'); 63 | }); 64 | }); 65 | ``` 66 | 67 | If you see this in the logs: 68 | 69 | ``` 70 | Middleware done 71 | Body set 72 | ``` 73 | 74 | It means that the body is being set after the middleware is done, and after the response has been sent. If you see only one or none of these logs, proceed to [My middleware is not called](#my-middleware-is-not-called). If they are in the right order, make sure you haven't explicitly set the status to 404, make sure that it actually is a 404, and if that fails feel free to ask for help. 75 | 76 | ### Solution 77 | 78 | ```js 79 | router.get('/fetch', function (ctx, next) { 80 | return models.Book.findById(parseInt(ctx.query.id)).then(function (book) { 81 | ctx.body = book; 82 | }); 83 | }); 84 | ``` 85 | 86 | Returning the promise given by the database interface tells Koa to wait for the promise to finish before responding. At that time, the body will have been set. This results in Koa sending back a 200 with a proper response. 87 | 88 | The fix in the `async` version is to add an `await` statement: 89 | 90 | ```js 91 | router.get('/fetch', async (ctx, next) => { 92 | await models.Book.findById(parseInt(ctx.query.id)).then(function (book) { 93 | ctx.body = book; 94 | }); 95 | }); 96 | ``` 97 | 98 | ## My middleware is not called 99 | 100 | This can be due to an interrupted chain of middleware calls. This can cause a 404 if the 101 | middleware that is skipped is responsible for the `ctx.body` or `ctx.status` setter. 102 | This is less common than [My response or context changes have no effect](#my-response-or-context-changes-have-no-effect), 103 | but it can be a much bigger pain to troubleshoot. 104 | 105 | ### Problematic code 106 | 107 | ```js 108 | router.use(function (ctx, next) { 109 | // Don't Repeat Yourself! Let's parse the ID here for all our middleware 110 | if (ctx.query.id) { 111 | ctx.state.id = parseInt(ctx.query.id); 112 | } 113 | }); 114 | 115 | router.get('/fetch', function (ctx, next) { 116 | return models.Book.findById(ctx.state.id).then(function (book) { 117 | ctx.body = book; 118 | }); 119 | }); 120 | ``` 121 | 122 | ### Cause 123 | 124 | In the code above, the book is never fetched from the database, and in fact our route was never called. Look closely at our helper middleware. We forgot to `return next()`! This causes the middleware flow to never reach our route, ending our "helper" middleware. 125 | 126 | ### Identifying this as the issue 127 | 128 | Identifying this problem is easier than most, add a log at the beginning of the route. If it doesn't trigger, your route was never reached in the middleware chain. 129 | 130 | ```js 131 | router.use(function (ctx, next) { 132 | // Don't Repeat Yourself! Let's parse the ID here for all our middleware 133 | if (ctx.query.id) { 134 | ctx.state.id = parseInt(ctx.query.id); 135 | } 136 | }); 137 | 138 | router.get('/fetch', function (ctx, next) { 139 | console.log('Route called'); // Never happens 140 | return models.Book.findById(ctx.state.id).then(function (book) { 141 | ctx.body = book; 142 | }); 143 | }); 144 | ``` 145 | 146 | To find the middleware causing the problem, try adding logging at various points in the middleware chain. 147 | 148 | ### Solution 149 | 150 | The solution for this is rather easy, simply add `return next()` to the end of your helper middleware. 151 | 152 | ```js 153 | router.use(function (ctx, next) { 154 | if (ctx.query.id) { 155 | ctx.state.id = parseInt(ctx.query.id); 156 | } 157 | return next(); 158 | }); 159 | 160 | router.get('/fetch', function (ctx, next) { 161 | return models.Book.findById(ctx.state.id).then(function (book) { 162 | ctx.body = book; 163 | }); 164 | }); 165 | ``` 166 | --------------------------------------------------------------------------------