Showing preview only (359K chars total). Download the full file or copy to clipboard to get everything.
Repository: koajs/koa
Branch: master
Commit: d3ea8bf9649d
Files: 108
Total size: 333.7 KB
Directory structure:
gitextract_l_l5wk61/
├── .codecov.yml
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── node.js.yml
│ └── npm-publish.yml
├── .gitignore
├── .mailmap
├── AUTHORS
├── CODE_OF_CONDUCT.md
├── History.md
├── LICENSE
├── Readme.md
├── __tests__/
│ ├── .eslintrc.yml
│ ├── application/
│ │ ├── compose.test.js
│ │ ├── context.test.js
│ │ ├── currentContext.test.js
│ │ ├── index.test.js
│ │ ├── inspect.test.js
│ │ ├── onerror.test.js
│ │ ├── request.test.js
│ │ ├── respond.test.js
│ │ ├── response.test.js
│ │ ├── toJSON.test.js
│ │ └── use.test.js
│ ├── context/
│ │ ├── assert.test.js
│ │ ├── cookies.test.js
│ │ ├── inspect.test.js
│ │ ├── onerror.test.js
│ │ ├── state.test.js
│ │ ├── throw.test.js
│ │ └── toJSON.test.js
│ ├── lib/
│ │ └── search-params.test.js
│ ├── load-with-esm.test.js
│ ├── request/
│ │ ├── accept.test.js
│ │ ├── accepts.test.js
│ │ ├── acceptsCharsets.test.js
│ │ ├── acceptsEncodings.test.js
│ │ ├── acceptsLanguages.test.js
│ │ ├── charset.test.js
│ │ ├── fresh.test.js
│ │ ├── get.test.js
│ │ ├── header.test.js
│ │ ├── headers.test.js
│ │ ├── host.test.js
│ │ ├── hostname.test.js
│ │ ├── href.test.js
│ │ ├── idempotent.test.js
│ │ ├── inspect.test.js
│ │ ├── ip.test.js
│ │ ├── ips.test.js
│ │ ├── is.test.js
│ │ ├── length.test.js
│ │ ├── origin.test.js
│ │ ├── path.test.js
│ │ ├── protocol.test.js
│ │ ├── query.test.js
│ │ ├── querystring.test.js
│ │ ├── search.test.js
│ │ ├── secure.test.js
│ │ ├── stale.test.js
│ │ ├── subdomains.test.js
│ │ ├── type.test.js
│ │ └── whatwg-url.test.js
│ └── response/
│ ├── append.test.js
│ ├── attachment.test.js
│ ├── back.test.js
│ ├── body.test.js
│ ├── etag.test.js
│ ├── flushHeaders.test.js
│ ├── get.test.js
│ ├── has.test.js
│ ├── header.test.js
│ ├── headers.test.js
│ ├── inspect.test.js
│ ├── is.test.js
│ ├── last-modified.test.js
│ ├── length.test.js
│ ├── message.test.js
│ ├── redirect.test.js
│ ├── remove.test.js
│ ├── set.test.js
│ ├── socket.test.js
│ ├── status.test.js
│ ├── type.test.js
│ ├── vary.test.js
│ └── writable.test.js
├── docs/
│ ├── api/
│ │ ├── context.md
│ │ ├── index.md
│ │ ├── request.md
│ │ └── response.md
│ ├── error-handling.md
│ ├── faq.md
│ ├── guide.md
│ ├── koa-vs-express.md
│ ├── migration-v1-to-v2.md
│ ├── migration-v2-to-v3.md
│ └── troubleshooting.md
├── lib/
│ ├── application.js
│ ├── context.js
│ ├── is-stream.js
│ ├── only.js
│ ├── request.js
│ ├── response.js
│ └── search-params.js
├── package.json
└── test-helpers/
├── context.js
└── stream.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .codecov.yml
================================================
coverage:
parsers:
javascript:
enable_partials: yes
================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
================================================
FILE: .github/FUNDING.yml
================================================
open_collective: koajs
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 5
versioning-strategy: increase-if-necessary
================================================
FILE: .github/workflows/node.js.yml
================================================
name: Node.js CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run lint
- run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: NPM Publish
# Trigger when tags matching semver format are pushed, or manually via workflow_dispatch.
# Manual triggers allow selecting a specific tag to publish (e.g. tags from the v2.x branch).
#
# Patterns match common semver formats:
# - v1.0.0 (standard)
# - v1.0.0-alpha (pre-release)
# - v1.0.0-beta.1 (pre-release with number)
#
# Note: GitHub Actions uses glob patterns (not full regex), which limits
# complex semver matching. These patterns cover most npm publishing scenarios.
# For complex dotted pre-releases (v1.0.0-alpha.beta.1), use simpler formats
# like v1.0.0-alphabeta1 or create the workflow manually.
"on":
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z0-9]+.[0-9]+'
workflow_dispatch:
inputs:
tag:
description: 'Git tag to checkout and publish (e.g. v2.15.4)'
required: true
type: string
# Permissions for NPM trusted publishing with provenance
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ inputs.tag || github.ref }}
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #v6
with:
node-version: 22
- name: Install npm@latest
run: npm install -g npm@latest
- name: Install dependencies
run: npm ci
- name: Publish to NPM
run: npm publish
================================================
FILE: .gitignore
================================================
node_modules
test.js
coverage
npm-debug.log
.idea
*.iml
dist
================================================
FILE: .mailmap
================================================
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
================================================
FILE: AUTHORS
================================================
小菜 <xtx1130@gmail.com>
Aaron Heckmann <aaron.heckmann+github@gmail.com>
Adam L <skyros@gmail.com>
Adam Lau <skyros@gmail.com>
Aesop Wolf <aesopwolf@users.noreply.github.com>
AlexeyKhristov <AlexeyKhristov@users.noreply.github.com>
Alexsey <agat00@gmail.com>
Amit Portnoy <amit.portnoy@gmail.com>
Anton Harniakou <anton.harniakou@gmail.com>
Arjun <arjun453@gmail.com>
Asiel Leal <lealceldeiro@gmail.com>
Avindra Goolcharan <aavindraa@gmail.com>
Bartol Karuza <bartol.k@gmail.com>
Ben Reinhart <breinhart@groupon.com>
Bernie Stern <bernzs@gmail.com>
Bryan Bess <squarejaw@bsbess.com>
C.T. Lin <chentsulin@gmail.com>
Chiahao Lin <purepennons@users.noreply.github.com>
Chris Tarquini <chris@ilsken.com>
Christoffer Hallas <hallas@users.noreply.github.com>
Clark Du <clark.duxin@gmail.com>
Darren Cauthon <darren@cauthon.com>
Debjeet Biswas <debjeet@vxtindia.com>
Dmitry Mazuro <dmitry.mazuro@icloud.com>
Douglas Christopher Wilson <doug@somethingdoug.com>
Eivind Fjeldstad <eivind.fjeldstad@gmail.com>
Equim <sayaka@ekyu.moe>
Fangdun Cai <fundon@users.noreply.github.com>
Felix Becker <felix.b@outlook.com>
Filip Skokan <panva.ip@gmail.com>
Francisco Presencia <franciscop@users.noreply.github.com>
Gao Sheng <gaosheng08@meituan.com>
George Chung <Gerhut@GMail.com>
Gilles De Mey <gilles.de.mey@gmail.com>
Grand <sungg12138@163.com>
Guilherme Pacheco <guilherme.f.pacheco@hotmail.com>
HanHor Wu <hanhor.wu@gmail.com>
Hartley Melamed <hartley@melamed.biz>
Hrvoje Šimić <hrvoje@twobucks.co>
Hugh Kennedy <hughskennedy@gmail.com>
Ian Storm Taylor <ian@ianstormtaylor.com>
Ilkka Oksanen <iao@iki.fi>
Ivan Kleshnin <ivan@paqmind.com>
Ivan Lyons <iliyang.cn@gmail.com>
Jacob Bass <jacob@jacobbass.net>
JamesWang <likegun94@gmail.com>
Jan Buschtöns <buschtoens@gmail.com>
Jan Carlo Viray <virayjancarlo@yahoo.com>
Jason Macgowan <jason.macgowan@icloud.com>
Jed Schmidt <where@jed.is>
Jeff Moore <jeff@procata.com>
Jesus Rodriguez <foxandxss@gmail.com>
Jesús Rodríguez Rodríguez <Foxandxss@gmail.com>
Jingwei "John" Liu <liujingwei@gmail.com>
Johan Bergström <bugs@bergstroem.nu>
Jonas Zhang <106856363@qq.com>
Jonathan Ong <jonathanrichardong@gmail.com>
Jonathan Ong <me@jongleberry.com>
Joseph Lin <josephlin55555@gmail.com>
Julian Gruber <julian@juliangruber.com>
Kareem Kwong <kareem.kwong@gmail.com>
Karl Böhlmark <karl.bohlmark@gmail.com>
Kenneth Ormandy <kenneth@chloi.io>
Kim Joar Bekkelund <kjbekkelund@gmail.com>
Kwyn Alice Meagher <kwyn.meagher@gmail.com>
Kyle Suss <susskyle@gmail.com>
Lee Bousfield <ljbousfield@gmail.com>
Louis DeScioli <louis.descioli@gmail.com>
Luke Bousfield <math.master.champion@gmail.com>
Malcolm <noinkling@users.noreply.github.com>
Marceli.no <me@marceli.no>
Mars Wong <marswong618@gmail.com>
Martin Iwanowski <martin@iwanowski.se>
Martin Iwanowski <me@fl0w.io>
Martin fl0w Iwanowski <martin@iwanowski.se>
Matheus Azzi <matheuslazzi@gmail.com>
Mathieu Gallé-Tessonneau <mathieu.galletessonneau@gmail.com>
Matthew Chase Whittemore <matthew@socialtables.com>
Matthew King <mking@users.noreply.github.com>
Matthew Mueller <mattmuelle@gmail.com>
Mengdi Gao <gaomdev@gmail.com>
Michaël Zasso <mic.besace@gmail.com>
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
Nathan Rajlich <nathan@tootallnate.net>
New Now Nohow <empty@cqdr.es>
Nick McCurdy <nick@nickmccurdy.com>
Nicolae Vartolomei <nvartolomei@gmail.com>
PatrickJS <github@gdi2290.com>
Paul Anderson <thesamuraipanda@gmail.com>
Pedro Pablo Aste Kompen <wachunei@gmail.com>
Peeyush Kushwaha <peeyush.p97@gmail.com>
Phillip Alexander <git@phillipalexander.io>
PlasmaPower <ljbousfield@gmail.com>
Prayag Verma <prayag.verma@gmail.com>
Qiming zhao <chemzqm@gmail.com>
Remek Ambroziak <remek.ambroziak@gmail.com>
Riceball LEE <snowyu.lee@gmail.com>
Richard Marmorstein <twitchard@users.noreply.github.com>
Rico Sta. Cruz <rstacruz@users.noreply.github.com>
Robert Sköld <robert@publicclass.se>
Robin Pokorný <me@robinpokorny.com>
Ruben Bridgewater <ruben@bridgewater.de>
Rui Marinho <rpm@seegno.com>
Rui Marinho <ruipmarinho@gmail.com>
Ryunosuke SATO <tricknotes.rs@gmail.com>
Saad Quadri <saad@saadq.com>
Santiago Sotomayor <sansoto2003@yahoo.com.ar>
Sergei Osipov <hcz@users.noreply.github.com>
Shaun Warman <shaunwarman1@gmail.com>
Shawn Cheung <958033967@qq.com>
Shawn Sit <xueqingxiao@gmail.com>
Slobodan Stojanovic <slobodan@cloudhorizon.com>
Sonny Piers <sonny@fastmail.net>
Sterling Williams <sterlingw@qualtrics.com>
Stéphane Bisinger <stephane.bisinger@protonmail.com>
TJ Holowaychuk <tj@apex.sh>
TJ Holowaychuk <tj@vision-media.ca>
Taehwan, No <taehwanno.dev@gmail.com>
Tejas Manohar <me@tejas.io>
Teoman Soygul <teo@soygul.com>
Thiago Lagden <lagden@gmail.com>
Tiago Ribeiro <tlr@seegno.com>
Tim Schaub <tim.schaub@gmail.com>
Todor Stoychev <pretodor@gmail.com>
Tomas Ruud <tomasruud@users.noreply.github.com>
Travis Jeffery <tj@travisjeffery.com>
Usman Hussain <usmandap@gmail.com>
Veselin Todorov <veselin@veselin.bg>
Wang Dàpéng <wonderfuly@gmail.com>
Xavier Damman <xdamman@gmail.com>
Xiang Gao <geekplux@qq.com>
Yanick Rochon <yanick.rochon@gmail.com>
Yazhong Liu <l900422@vip.qq.com>
Yazhong Liu <yorkiefixer@gmail.com>
Yiyu He <dead-horse@users.noreply.github.com>
Yiyu He <dead_horse@qq.com>
Yoshua Wuyts <yoshuawuyts@gmail.com>
Yu Qi <iyuq@outlook.com>
Yu Qi <njuyuqi@gmail.com>
Zack Tanner <zacktanner@gmail.com>
alsotang <alsotang@gmail.com>
bananaappletw <bananaappletw@gmail.com>
bhanuc <bhanuc@iitk.ac.in>
blaz <blaz@menems.net>
broucz <broucapierre@gmail.com>
d3v <cr1s@users.noreply.github.com>
dead-horse <dead_horse@qq.com>
dead_horse <dead_horse@qq.com>
designgrill <anshul@designgrill.com>
fengmk2 <fengmk2@gmail.com>
fengmk2 <m@fengmk2.com>
frank <frankxin93@hotmail.com>
fundon <cfddream@gmail.com>
gyson <eilian.yunsong@gmail.com>
haoxin <coderhaoxin@outlook.com>
haoxin <haoxins@icloud.com>
iamchenxin <iamchenxin@gmail.com>
initial-wu <initial-wu@outlook.com>
jeromew <jerome.wagner@m4x.org>
joehecn <leanbrown@live.cn>
jongleberry <jonathanong@users.noreply.github.com>
jongleberry <me@jongleberry.com>
llambda <xxgsoftware@gmail.com>
mako-taco <jake.y.scott@gmail.com>
mdemo <mds@xue.bi>
nicoder <nicolas.dermine@gmail.com>
nswbmw <gxqzk@126.com>
pana <pana.wang@outlook.com>
qingming <358242939@qq.com>
song <xiongsongsong@outlook.com>
superchink <superchink@gmail.com>
tmilewski <tmilewski@gmail.com>
yoshuawuyts <i@yoshuawuyts.com>
yosssi <yoshida.keiji.84@gmail.com>
zensh <admin@zensh.com>
ziyunfei <446240525@qq.com>
石发磊 <sshsfl@yeah.net>
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at tj@tjholowaychuk.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: History.md
================================================
> [!IMPORTANT]
> Moving forwards we are using the GitHub releases page at <https://github.com/koajs/koa/releases> in combination with [np](https://www.npmjs.com/package/np) for publishing releases and their changelogs.
---
3.0.0-alpha.3 / 2025-02-11
==================
**fixes**
- Avoid redos on host and protocol getter
3.0.0-alpha.2 / 2024-11-04
==================
**breaking changes**
- Update `http-errors` to `v2.0.0` [#1486](https://github.com/koajs/koa/pull/1486)
- `ctx.throw` now requires a format of `ctx.throw(status, error, properties)`. See: https://www.npmjs.com/package/http-errors
- Remove `res.redirect('back')`, add `back()` method to `ctx` [#1115](https://github.com/koajs/koa/pull/1115)
- Replace node querystring with `URLSearchParams` [#1828](https://github.com/koajs/koa/pull/1828)
- Remove obsolete `createAsyncCtxStorageMiddleware` [#1817](https://github.com/koajs/koa/pull/1817)
**features**
- Add support for web WHATWG [#1830](https://github.com/koajs/koa/pull/1830)
**updates**
- Update `cookies` to `~0.9.1` [#1846](https://github.com/koajs/koa/pull/1846)
- Update `statuses` to `^2.0.1`
- Update `supertest` to `^7.0.0` [#1841](https://github.com/koajs/koa/pull/1841)
**fixes**
- Fix `exports.defaults` in `package.json` [#1630](https://github.com/koajs/koa/pull/1630)
- Fix leaky handles in tests [#1838](https://github.com/koajs/koa/pull/1838)
- Fix body null checks [#1814](https://github.com/koajs/koa/pull/1814)
- Fix reformatting redirect URLs [#1805](https://github.com/koajs/koa/pull/1805) [#1804](https://github.com/koajs/koa/pull/1804)
- Fix passing `ctx` in error handler [#1758](https://github.com/koajs/koa/pull/1758)
**migrations**
- Migrate from `jest` to the native node test runner [#1845](https://github.com/koajs/koa/pull/1845)
3.0.0-alpha.1 / 2023-04-12
==================
**fixes**
* [[`e98b8d1`](http://github.com/koajs/koa/commit/e98b8d1918376dc2957aa62906bf5893bef66c4c)] - fix: can not get currentContext in error handler (#1758) (Gxkl <<gxkl203@gmail.com>>)
3.0.0-alpha.0 / 2023-01-02
==================
## Breaking Changes
- Supports node@12+ only.
- Removes generator deprecation messages.
Generators are no longer supported.
Koa no longer asserts if generators are used.
- Set `content-length: 0` if body is explicitly set to `null` @ognjenjevremovic #1528
## Features
- Use asyncLocalStorage to get current context from app, e.g.: `const ctx = app.currentContext`.
## Fixes
- fix: Do not response Content-Length if Transfer-Encoding is defined #1562 @charlyzeng
- fix: Set body to `null` if `ctx.type = json` and `ctx.body = null` #1059 @likegun
2.13.1 / 2021-01-04
==================
**fixes**
* [[`b5472f4`](http://github.com/koajs/koa/commit/b5472f4cbb87349becae36b4a9ad5f76a825abb8)] - fix: make ESM transpiled CommonJS play nice for TS folks, fix #1513 (#1518) (miwnwski <<m@iwnw.ski>>)
* [[`68d97d6`](http://github.com/koajs/koa/commit/68d97d69e4536065504bf9ef1e348a66b3f35709)] - fix: fixed order of vulnerability disclosure addresses (niftylettuce <<niftylettuce@gmail.com>>)
**others**
* [[`b4398f5`](http://github.com/koajs/koa/commit/b4398f5d68f9546167419f394a686afdcb5e10e2)] - correct verb tense in doc (#1512) (Matan Shavit <<71092861+matanshavit@users.noreply.github.com>>)
* [[`39e1a5a`](http://github.com/koajs/koa/commit/39e1a5a380aa2bbc4e2d164e8e4bf37cfd512516)] - fixed multiple grammatical errors in docs. (#1497) (Hridayesh Sharma <<vyasriday7@gmail.com>>)
* [[`aeb5d19`](http://github.com/koajs/koa/commit/aeb5d1984dcc5f8e3386f8f9724807ae6f3aa1c4)] - docs: added niftylettuce@gmail.com to vulnerability disclosure (niftylettuce <<niftylettuce@gmail.com>>)
* [[`6e1093b`](http://github.com/koajs/koa/commit/6e1093be27b41135c8e67fce108743d54e9cab67)] - docs: remove babel from readme (#1494) (miwnwski <<m@iwnw.ski>>)
* [[`38cb591`](http://github.com/koajs/koa/commit/38cb591254ff5f65a04e8fb57be293afe697c46e)] - docs: update specific for auto response status (AlbertAZ1992 <<ziyuximing@163.com>>)
* [[`2224cd9`](http://github.com/koajs/koa/commit/2224cd9b6a648e7ac2eb27eac332e7d6de7db26c)] - docs: remove babel ref. (#1488) (Imed Jaberi <<imed_jebari@hotmail.fr>>)
* [[`d51f983`](http://github.com/koajs/koa/commit/d51f98328c3b84493cc6bda0732aabb69e20e3a1)] - docs: fix assert example for response (#1489) (Imed Jaberi <<imed_jebari@hotmail.fr>>)
* [[`f8b49b8`](http://github.com/koajs/koa/commit/f8b49b859363ad6c3d9ea5c11ee62341407ceafd)] - chore: fix grammatical and spelling errors in comments and tests (#1490) (Matt Kubej <<mkubej@gmail.com>>)
* [[`d1c9263`](http://github.com/koajs/koa/commit/d1c92638c95d799df2fdff5576b96fc43a62813f)] - deps: update depd >> v2.0.0 (#1482) (imed jaberi <<imed_jebari@hotmail.fr>>)
2.13.0 / 2020-06-21
==================
**features**
* [[`bbcde76`](http://github.com/koajs/koa/commit/bbcde76f5cb5b67bbcd3201791cf0ef648fd3a8b)] - feat: support esm (#1474) (ZYSzys <<zhangyongsheng@youzan.com>>)
**others**
* [[`20e58cf`](http://github.com/koajs/koa/commit/20e58cf3e4f20fc5d5886df1d0ac6dd8c33bd202)] - test: imporve coverage to 100% (dead-horse <<dead_horse@qq.com>>)
* [[`4a40d63`](http://github.com/koajs/koa/commit/4a40d633c4b4a203c6656078f9952ccef65c5875)] - build: use prepare instead of prepublish (dead-horse <<dead_horse@qq.com>>)
* [[`226ba8c`](http://github.com/koajs/koa/commit/226ba8c8e81e83da48e7bf137be3f146d03f40b8)] - build: use prepublish instead of prepack (dead-horse <<dead_horse@qq.com>>)
2.12.1 / 2020-06-13
==================
**fixes**
* [[`e2030c7`](http://github.com/koajs/koa/commit/e2030c7249c7ae24e28158d8eae405a02fefc9f8)] - fix: Improve checks for Error in onerror handlers (#1468) (Julien Wajsberg <<felash@gmail.com>>)
**others**
* [[`5208c5e`](http://github.com/koajs/koa/commit/5208c5e15d35b3653fce6b8ed68d09865abea843)] - chore: Use single console.error() statement in error handler (#1471) (Mike Vosseller <<michael.vosseller@gmail.com>>)
2.12.0 / 2020-05-18
==================
**features**
* [[`0d2f421`](http://github.com/koajs/koa/commit/0d2f421c265350d3d84e1bc261572954479f27d3)] - feat: error handler treat err.statusCode as the same as err.status (#1460) (Vijay Krishnavanshi <<vijaykrishnavanshi@gmail.com>>)
* [[`8d52105`](http://github.com/koajs/koa/commit/8d52105a34234be9e771ff3b76b43e4e30328943)] - feat: allow bodyless responses for non empty status codes (#1447) (ejose19 <<8742215+ejose19@users.noreply.github.com>>)
**others**
* [[`faeaff5`](http://github.com/koajs/koa/commit/faeaff5c149a81a188ab8e5af0b994029e45acbb)] - fox: remove `error-inject` and fix error handling (#1409) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`f7c732f`](http://github.com/koajs/koa/commit/f7c732fd06f724505e9090add4d977e667da55a8)] - docs: fixed incorrect onerror example (#1459) (Paul Annekov <<paul.annekov@gmail.com>>)
* [[`143d8f7`](http://github.com/koajs/koa/commit/143d8f72f2a232b4c97eac00e7811015911e4f7c)] - Always use strict equality. (#1225) (Yazan Medanat <<medanat@gmail.com>>)
* [[`6b6b0dd`](http://github.com/koajs/koa/commit/6b6b0ddf7aff073e65493c6efaffab8331c0331c)] - docs(api): add app.use chainability note (#1449) (Zac Anger <<zac@zacanger.com>>)
* [[`8ddab48`](http://github.com/koajs/koa/commit/8ddab48cbdbca1e6d1cc8c3ddae45491db524d51)] - docs: Document response status with empty body (#1445) (Marc-Aurèle DARCHE <<152407+madarche@users.noreply.github.com>>)
* [[`7deedb2`](http://github.com/koajs/koa/commit/7deedb235274223f1b9da46dee296545b23598de)] - docs: Updating context.md with the latest cookies opts (#1433) (Brad Ito <<phlogisticfugu@users.noreply.github.com>>)
* [[`3e97a10`](http://github.com/koajs/koa/commit/3e97a106bb846d9337737011bb85149ddd797229)] - docs(links): remove Google+ link (#1439) (laffachan <<45162759+laffachan@users.noreply.github.com>>)
* [[`eda2760`](http://github.com/koajs/koa/commit/eda27608f7d39ede86d7b402aae64b1867ce31c6)] - build: Drop unused Travis sudo: false directive (#1416) (Olle Jonsson <<olle.jonsson@gmail.com>>)
2.11.0 / 2019-10-28
==================
**features**
* [[`422e539`](http://github.com/koajs/koa/commit/422e539e8989e65ba43ecc39ddbaa3c4f755d465)] - feat: support app.proxyIPHeader and app.maxIpsCount to make ctx.ips more security (Yiyu He <<dead_horse@qq.com>>)
* [[`d48d88e`](http://github.com/koajs/koa/commit/d48d88ee17b780c02123e6d657274cab456e943e)] - feat: implement response.has (#1397) (Konstantin Vyatkin <<tino@vtkn.io>>)
**others**
* [[`4dc56f6`](http://github.com/koajs/koa/commit/4dc56f6d04e8f5fe12ba53a8a776653b3d7b60ed)] - chore: update ESLint and plugins/configs (#1407) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`be7d334`](http://github.com/koajs/koa/commit/be7d334778481639294cdf87f5c359a230aeb65b)] - chore: removes code duplication at handling HEAD method (#1400) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`f155785`](http://github.com/koajs/koa/commit/f155785e2bb42b5ddf0a8156401c6dafdf57ba8b)] - chore: support `writableEnded` (#1402) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`b968688`](http://github.com/koajs/koa/commit/b968688afe2c727ae141f50aa983d481dbc1dbbf)] - chore: add FUNDING.yml (#1403) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`4f96829`](http://github.com/koajs/koa/commit/4f968298f97394e488297ec32c8e927a3a322076)] - chore: remove isJSON in res.length (#1399) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`8be5626`](http://github.com/koajs/koa/commit/8be5626bbb54e6c899a1b71d22411709126d9fea)] - build: enable codecov partial coverage and use bash uploader (#1396) (Konstantin Vyatkin <<tino@vtkn.io>>)
* [[`ef5c43b`](http://github.com/koajs/koa/commit/ef5c43bcbcf31819e032c3b7ae7654b7f8e9358b)] - chore: use rest params (#1393) (Konstantin Vyatkin <<tino@vtkn.io>>)
2.10.0 / 2019-10-12
==================
**features**
* [[`d7f7f77`](http://github.com/koajs/koa/commit/d7f7f77689e2eaef050686be2bdf3e72881a79ac)] - feat: support sameSite=none cookies (bump cookies dependency) (#1390) (Filip Skokan <<panva.ip@gmail.com>>)
2.9.0 / 2019-10-12
==================
**features**
* [[`2d1c598`](http://github.com/koajs/koa/commit/2d1c5981869e0fe6f5bc71b5c5582accfd125cc6)] - feat: export HttpError from http-errors library (Micheal Hill <<micheal.hill@trunkplatform.com>>)
**others**
* [[`cf70dbc`](http://github.com/koajs/koa/commit/cf70dbc6d2ba62bf1eb12b563dd5ecd27af6e2be)] - Chore: Use https in readme (#1389) (谭九鼎 <<109224573@qq.com>>)
2.8.2 / 2019-09-28
==================
**fixes**
* [[`54e8fab`](http://github.com/koajs/koa/commit/54e8fab3e3d907bbb264caf3e28a24773d0d6fdb)] - fix: encode redirect url if not already encoded (#1384) (fengmk2 <<fengmk2@gmail.com>>)
**others**
* [[`817b498`](http://github.com/koajs/koa/commit/817b49830571b45a8aec6b1fc1525434f5798c58)] - test: fix body test (#1375) (Robert Nagy <<ronagy@icloud.com>>)
* [[`f75d445`](http://github.com/koajs/koa/commit/f75d4455359ecdf30eeb676e2c7f31d4cf7b42ed)] - test: fix end after end (#1374) (Robert Nagy <<ronagy@icloud.com>>)
2.8.1 / 2019-08-19
==================
**fixes**
* [[`287e589`](http://github.com/koajs/koa/commit/287e589ac773d3738b2aa7d40e0b6d43dde5261b)] - fix: make options more compatibility (dead-horse <<dead_horse@qq.com>>)
2.8.0 / 2019-08-19
==================
**features**
* [[`5afff89`](http://github.com/koajs/koa/commit/5afff89eca0efe7081309dc2d123309e825df221)] - feat: accept options in the Application constructor (#1372) (Jake <<djakelambert@gmail.com>>)
**fixes**
* [[`ff70bdc`](http://github.com/koajs/koa/commit/ff70bdc75a30a37f63fc1f7d8cbae3204df3d982)] - fix: typo on document (#1355) (Jeff <<jeff.tian@outlook.com>>)
**others**
* [[`3b23865`](http://github.com/koajs/koa/commit/3b23865340cfba075f61f7dba0ea31fcc27260ec)] - docs: parameter of request.get is case-insensitive (#1373) (Gunnlaugur Thor Briem <<gunnlaugur@gmail.com>>)
* [[`a245d18`](http://github.com/koajs/koa/commit/a245d18a131341feec4f87659746954e78cae780)] - docs: Update response.socket (#1357) (Jeff <<jeff.tian@outlook.com>>)
* [[`d1d65dd`](http://github.com/koajs/koa/commit/d1d65dd29d7bbaf9ea42eaa5fcb0da3fb4df98e9)] - chore(deps): install egg-bin, mm as devDeps not deps (#1366) (Edvard Chen <<pigeon73101@gmail.com>>)
* [[`2c86b10`](http://github.com/koajs/koa/commit/2c86b10feafd868ebd071dda3a222e6f51972b5d)] - test: remove jest and use egg-bin(mocha) (#1363) (Yiyu He <<dead_horse@qq.com>>)
* [[`219bf22`](http://github.com/koajs/koa/commit/219bf22237b11bc375e2e110b93db512f1acfdd4)] - docs(context): update link (#1354) (Peng Jie <<bivinity.pengzjie@gmail.com>>)
* [[`52a6737`](http://github.com/koajs/koa/commit/52a673703a87a93c0f6a8552e6bd73caba66d2eb)] - chore: ignore Intellij IDEA project files (#1361) (Imon-Haque <<38266345+Imon-Haque@users.noreply.github.com>>)
* [[`b9e3546`](http://github.com/koajs/koa/commit/b9e35469d3bbd0a1ee92e0a815ce2512904d4a18)] - docs(api): fix keygrip link (#1350) (Peng Jie <<bivinity.pengzjie@gmail.com>>)
* [[`d4bdb5e`](http://github.com/koajs/koa/commit/d4bdb5ed9e2fe06ec44698b66c029f624135a0ab)] - chore: update eslint and fix lint errors (dead-horse <<dead_horse@qq.com>>)
* [[`12960c4`](http://github.com/koajs/koa/commit/12960c437cc25c53e682cfe5bff06d74a5bb1eb9)] - build: test on 8/10/12 (dead-horse <<dead_horse@qq.com>>)
* [[`00e8f7a`](http://github.com/koajs/koa/commit/00e8f7a1b7603aabdb7fb3567f485cb1c2076702)] - docs: ctx.type aliases ctx.response, not ctx.request (#1343) (Alex Berk <<berkalexanderc@gmail.com>>)
* [[`62f29eb`](http://github.com/koajs/koa/commit/62f29eb0c4dee01170a5511615e5bcc9faca26ca)] - docs(context): update cookies link (#1348) (Peng Jie <<dean.leehom@gmail.com>>)
* [[`b7fc526`](http://github.com/koajs/koa/commit/b7fc526ea49894f366153bd32997e02568c0b8a6)] - docs: fix typo in cookie path default value docs (#1340) (Igor Adamenko <<igoradamenko@users.noreply.github.com>>)
* [[`23f7f54`](http://github.com/koajs/koa/commit/23f7f545abfe1fb6499cd61cc8ff41fd86cef4a0)] - chore: simplify variable (#1332) (kzhang <<godky@users.noreply.github.com>>)
* [[`132c9ee`](http://github.com/koajs/koa/commit/132c9ee63f92a586a120ed3bd6b7ef023badb8bb)] - docs: Clarify the format of request.headers (#1325) (Dobes Vandermeer <<dobesv@gmail.com>>)
* [[`5810f27`](http://github.com/koajs/koa/commit/5810f279a4caeda115f39e429c9671795613abf8)] - docs: Removed Document in Progress note in Koa vs Express (#1336) (Andrew Peterson <<andrew@andpeterson.com>>)
* [[`75233d9`](http://github.com/koajs/koa/commit/75233d974a30af6e3b8ab38a73e5ede67172fc1c)] - chore: Consider removing this return statement; it will be ignored. (#1322) (Vern Brandl <<tkvern@users.noreply.github.com>>)
* [[`04e07fd`](http://github.com/koajs/koa/commit/04e07fdc620841068f12b8edf36f27e6592a0a18)] - test: Buffer() is deprecated due to security and usability issues. so use the Buffer.alloc() instead (#1321) (Vern Brandl <<tkvern@users.noreply.github.com>>)
* [[`130e363`](http://github.com/koajs/koa/commit/130e363856747b487652f04b5550056d7778e43a)] - docs: use 'fs-extra' instead of 'fs-promise' (#1309) (rosald <<35028438+rosald@users.noreply.github.com>>)
* [[`2f2078b`](http://github.com/koajs/koa/commit/2f2078bf998bd3f44289ebd17eeccf5e12e4c134)] - chore: Update PR-welcome badge url (#1299) (James George <<jamesgeorge998001@gmail.com>>)
2.7.0 / 2019-01-28
==================
**features**
* [[`b7bfa71`](http://github.com/koajs/koa/commit/b7bfa7113b8d1af49a57ab767f24a599ed92044f)] - feat: change set status assert, allowing valid custom statuses (#1308) (Martin Iwanowski <<martin@iwanowski.se>>)
**others**
* [[`72f325b`](http://github.com/koajs/koa/commit/72f325b78edd0dc2aac940a76ce5f644005ce4c3)] - chore: add pr welcoming badge (#1291) (James George <<jamesgeorge998001@gmail.com>>)
* [[`b15115b`](http://github.com/koajs/koa/commit/b15115b2cbfffe15827cd5e4368267d417b72f08)] - chore: Reduce unnecessary variable declarations (#1298) (call me saisai <<1457358080@qq.com>>)
* [[`ad91ce2`](http://github.com/koajs/koa/commit/ad91ce2346cb34e5d5a49d07dd952d15f6c832a3)] - chore: license 2019 (dead-horse <<dead_horse@qq.com>>)
* [[`b25e79d`](http://github.com/koajs/koa/commit/b25e79dfb599777a38157bd419395bd28369ee86)] - Mark two examples as live for the corresponding documentation change in https://github.com/koajs/koajs.com/pull/38. (#1031) (Francisco Ryan Tolmasky I <<tolmasky@gmail.com>>)
* [[`d9ef603`](http://github.com/koajs/koa/commit/d9ef60398e88f2c2f958ab2b159d38052ffe7f8a)] - chore: Optimize array split (#1295) (Mikhail Bodrov <<connormiha1@gmail.com>>)
* [[`9be8583`](http://github.com/koajs/koa/commit/9be858312553002841725b617050aaff3c48951d)] - chore: replace ~~ with Math.trunc in res.length (option) (#1288) (jeremiG <<gendronjeremi@gmail.com>>)
* [[`7e46c20`](http://github.com/koajs/koa/commit/7e46c2058cb5994809eab5f4dbb12f21e937c72b)] - docs: add link to the license file (#1290) (James George <<jamesgeorge998001@gmail.com>>)
* [[`48993ad`](http://github.com/koajs/koa/commit/48993ade9b0831fbce28d94b3b0963a4b0dccbdd)] - docs: Document other body types (#1285) (Douglas Wade <<douglas.b.wade@gmail.com>>)
* [[`acb388b`](http://github.com/koajs/koa/commit/acb388bc0546b48fca11dce8aa7a595af2cda5e2)] - docs: Add security vulnerability disclosure instructions to the Readme (#1283) (Douglas Wade <<douglas.b.wade@gmail.com>>)
* [[`a007198`](http://github.com/koajs/koa/commit/a007198fa23c19902b1f3ffb81498629e0e9c875)] - docs: Document ctx.app.emit (#1284) (Douglas Wade <<douglas.b.wade@gmail.com>>)
* [[`f90e825`](http://github.com/koajs/koa/commit/f90e825da9d505c11b4262c50cd54553f979c300)] - docs: response.set(fields) won't overwrites previous header fields(#1282) (Douglas Wade <<douglas.b.wade@gmail.com>>)
* [[`fc93c05`](http://github.com/koajs/koa/commit/fc93c05f68398f30abc46fd16ae6c673a1eee099)] - docs: update readme to add babel 7 instructions (#1274) (Vikram Rangaraj <<vik120@icloud.com>>)
* [[`5560f72`](http://github.com/koajs/koa/commit/5560f729124f022ffed00085aafea43dded7fb03)] - chore: use the ability of `content-type` lib directly (#1276) (Jordan <<mingmingwon@gmail.com>>)
2.6.2 / 2018-11-10
==================
**fixes**
* [[`9905199`](http://github.com/koajs/koa/commit/99051992a9f45eb0dd79e062681d6f5d366deb41)] - fix: Status message is not supported on HTTP/2 (#1264) (André Cruz <<andre@cabine.org>>)
**others**
* [[`325792a`](http://github.com/koajs/koa/commit/325792aee92de0ba6fea306657933fc63dc00474)] - docs: add table of contents for guide.md (#1267) (ZYSzys <<zyszys98@gmail.com>>)
* [[`71aaa29`](http://github.com/koajs/koa/commit/71aaa29591d6681f8579486f18d32ba1ee651a5b)] - docs: fix spelling in throw docs (#1269) (Martin Iwanowski <<martin@iwanowski.se>>)
* [[`bc81ca9`](http://github.com/koajs/koa/commit/bc81ca9414296234c764b7306a19ba72b2e59b52)] - chore: use res instead of this.res (#1271) (Jordan <<mingmingwon@gmail.com>>)
* [[`0251b38`](http://github.com/koajs/koa/commit/0251b38a8405471892c5eeaba7c8d54bd7028214)] - test: node v11 on travis (#1265) (Martin Iwanowski <<martin@iwanowski.se>>)
* [[`88b92b4`](http://github.com/koajs/koa/commit/88b92b43153f21609aee71d47abcd4dc27a6586d)] - doc: updated docs for throw() to pass status as first param. (#1268) (Waleed Ashraf <<waleedashraf@outlook.com>>)
2.6.1 / 2018-10-23
==================
**fixes**
* [[`4964242`](http://github.com/koajs/koa/commit/49642428342e5f291eb9d690802e83ed830623b5)] - fix: use X-Forwarded-Host first on app.proxy present (#1263) (fengmk2 <<fengmk2@gmail.com>>)
2.6.0 / 2018-10-23
==================
**features**
* [[`9c5c58b`](http://github.com/koajs/koa/commit/9c5c58b18363494976185e7ddc790ac63de840ed)] - feat: use :authority header of http2 requests as host (#1262) (Martin Michaelis <<code@mgjm.de>>)
* [[`9146024`](http://github.com/koajs/koa/commit/9146024e1094e8bb871ab15d1b7fc556a710732f)] - feat: response.attachment append a parameter: options from contentDisposition (#1240) (小雷 <<863837949@qq.com>>)
**others**
* [[`d32623b`](http://github.com/koajs/koa/commit/d32623baa7a6273d47be67d587ad4ea0ecffc5de)] - docs: Update error-handling.md (#1239) (urugator <<j.placek@centrum.cz>>)
2.5.3 / 2018-09-11
==================
**fixes**
* [[`2ee32f5`](http://github.com/koajs/koa/commit/2ee32f50b88b383317e33cc0a4bfaa5f2eadead7)] - fix: pin debug@~3.1.0 avoid deprecated warnning (#1245) (fengmk2 <<fengmk2@gmail.com>>)
**others**
* [[`2180839`](http://github.com/koajs/koa/commit/2180839eda2cb16edcfda46ccfe24711680af850)] - docs: Update koa-vs-express.md (#1230) (Clayton Ray <<iamclaytonray@gmail.com>>)
2.5.2 / 2018-07-12
==================
* deps: upgrade all dependencies
* perf: avoid stringify when set header (#1220)
* perf: cache content type's result (#1218)
* perf: lazy init cookies and ip when first time use it (#1216)
* chore: fix comment & approve cov (#1214)
* docs: fix grammar
* test&cov: add test case (#1211)
* Lazily initialize `request.accept` and delegate `context.accept` (#1209)
* fix: use non deprecated custom inspect (#1198)
* Simplify processes in the getter `request.protocol` (#1203)
* docs: better demonstrate middleware flow (#1195)
* fix: Throw a TypeError instead of a AssertionError (#1199)
* chore: mistake in a comment (#1201)
* chore: use this.res.socket insteadof this.ctx.req.socket (#1177)
* chore: Using "listenerCount" instead of "listeners" (#1184)
2.5.1 / 2018-04-27
==================
* test: node v10 on travis (#1182)
* fix tests: remove unnecessary assert doesNotThrow and api calls (#1170)
* use this.response insteadof this.ctx.response (#1163)
* deps: remove istanbul (#1151)
* Update guide.md (#1150)
2.5.0 / 2018-02-11
==================
* feat: ignore set header/status when header sent (#1137)
* run coverage using --runInBand (#1141)
* [Update] license year to 2018 (#1130)
* docs: small grammatical fix in api docs index (#1111)
* docs: fixed typo (#1112)
* docs: capitalize K in word koa (#1126)
* Error handling: on non-error throw try to stringify if error is an object (#1113)
* Use eslint-config-koa (#1105)
* Update mgol's name in AUTHORS, add .mailmap (#1100)
* Avoid generating package locks instead of ignoring them (#1108)
* chore: update copyright year to 2017 (#1095)
2.4.1 / 2017-11-06
==================
* fix bad merge w/ 2.4.0
2.4.0 / 2017-11-06
==================
UNPUBLISHED
* update `package.engines.node` to be more strict
* update `fresh@^0.5.2`
* fix: `inspect()` no longer crashes `context`
* fix: gated `res.statusMessage` for HTTP/2
* added: `app.handleRequest()` is exposed
2.3.0 / 2017-06-20
==================
* fix: use `Buffer.from()`
* test on node 7 & 8
* add `package-lock.json` to `.gitignore`
* run `lint --fix`
* add `request.header` in addition to `request.headers`
* add IPv6 hostname support
2.2.0 / 2017-03-14
==================
* fix: drop `package.engines.node` requirement to >= 6.0.0
* this fixes `yarn`, which errors when this semver range is not satisfied
* bump `cookies@~0.7.0`
* bump `fresh@^0.5.0`
2.1.0 / 2017-03-07
==================
* added: return middleware chain promise from `callback()` #848
* added: node v7.7+ `res.getHeaderNames()` support #930
* added: `err.headerSent` in error handling #919
* added: lots of docs!
2.0.1 / 2017-02-25
==================
NOTE: we hit a versioning snafu. `v2.0.0` was previously released,
so `v2.0.1` is released as the first `v2.x` with a `latest` tag.
* upgrade mocha #900
* add names to `application`'s request and response handlers #805
* breaking: remove unused `app.name` #899
* breaking: drop official support for node < 7.6
2.0.0 / ??????????
==================
* Fix malformed content-type header causing exception on charset get (#898)
* fix: subdomains should be [] if the host is an ip (#808)
* don't pre-bound onerror [breaking change] (#800)
* fix `ctx.flushHeaders()` to use `res.flushHeaders()` instead of `res.writeHead()` (#795)
* fix(response): correct response.writable logic (#782)
* merge v1.1.2 and v1.2.0 changes
* include `koa-convert` so that generator functions still work
* NOTE: generator functions are deprecated in v2 and will be removed in v3
* improve linting
* improve docs
2.0.0-alpha.8 / 2017-02-13
==================
* Fix malformed content-type header causing exception on charset get (#898)
2.0.0-alpha.7 / 2016-09-07
==================
* fix: subdomains should be [] if the host is an ip (#808)
2.0.0-alpha.6 / 2016-08-29
==================
* don't pre-bound onerror [breaking change]
2.0.0-alpha.5 / 2016-08-10
==================
* fix `ctx.flushHeaders()` to use `res.flushHeaders()` instead of `res.writeHead()`
2.0.0-alpha.4 / 2016-07-23
==================
* fix `response.writeable` during pipelined requests
1.2.0 / 2016-03-03
==================
* add support for `err.headers` in `ctx.onerror()`
- see: https://github.com/koajs/koa/pull/668
- note: you should set these headers in your custom error handlers as well
- docs: https://github.com/koajs/koa/blob/master/docs/error-handling.md
* fix `cookies`' detection of http/https
- see: https://github.com/koajs/koa/pull/614
* deprecate `app.experimental = true`. Koa v2 does not use this signature.
* add a code of conduct
* test against the latest version of node
* add a lot of docs
1.1.2 / 2015-11-05
==================
* ensure parseurl always working as expected
* fix Application.inspect() – missing .proxy value.
2.0.0-alpha.3 / 2015-11-05
==================
* ensure parseurl always working as expected. #586
* fix Application.inspect() – missing .proxy value. Closes #563
2.0.0-alpha.2 / 2015-10-27
==================
* remove `co` and generator support completely
* improved documentation
* more refactoring into ES6
2.0.0-alpha.1 / 2015-10-22
==================
* change the middleware signature to `async (ctx, next) => await next()`
* drop node < 4 support and rewrite the codebase in ES6
1.1.1 / 2015-10-22
==================
* do not send a content-type when the type is unknown #536
1.1.0 / 2015-10-11
==================
* add `app.silent=<Boolean>` to toggle error logging @tejasmanohar #486
* add `ctx.origin` @chentsulin #480
* various refactoring
- add `use strict` everywhere
1.0.0 / 2015-08-22
==================
* add `this.req` check for `querystring()`
* don't log errors with `err.expose`
* `koa` now follows semver!
0.21.0 / 2015-05-23
==================
* empty `request.query` objects are now always the same instance
* bump `fresh@0.3.0`
0.20.0 / 2015-04-30
==================
Breaking change if you're using `this.get('ua') === undefined` etc.
For more details please checkout [#438](https://github.com/koajs/koa/pull/438).
* make sure helpers return strict string
* feat: alias response.headers to response.header
0.19.1 / 2015-04-14
==================
* non-error thrown, fixed #432
0.19.0 / 2015-04-05
==================
* `req.host` and `req.hostname` now always return a string (semi-breaking change)
* improved test coverage
0.18.1 / 2015-03-01
==================
* move babel to `devDependencies`
0.18.0 / 2015-02-14
==================
* experimental es7 async function support via `app.experimental = true`
* use `content-type` instead of `media-typer`
0.17.0 / 2015-02-05
==================
Breaking change if you're using an old version of node v0.11!
Otherwise, you should have no trouble upgrading.
* official iojs support
* drop support for node.js `>= 0.11.0 < 0.11.16`
* use `Object.setPrototypeOf()` instead of `__proto__`
* update dependencies
0.16.0 / 2015-01-27
==================
* add `res.append()`
* fix path usage for node@0.11.15
0.15.0 / 2015-01-18
==================
* add `this.href`
0.14.0 / 2014-12-15
==================
* remove `x-powered-by` response header
* fix the content type on plain-text redirects
* add ctx.state
* bump `co@4`
* bump dependencies
0.13.0 / 2014-10-17
==================
* add this.message
* custom status support via `statuses`
0.12.2 / 2014-09-28
==================
* use wider semver ranges for dependencies koa maintainers also maintain
0.12.1 / 2014-09-21
==================
* bump content-disposition
* bump statuses
0.12.0 / 2014-09-20
==================
* add this.assert()
* use content-disposition
0.11.0 / 2014-09-08
==================
* fix app.use() assertion #337
* bump a lot of dependencies
0.10.0 / 2014-08-12
==================
* add `ctx.throw(err, object)` support
* add `ctx.throw(err, status, object)` support
0.9.0 / 2014-08-07
==================
* add: do not set `err.expose` to true when err.status not a valid http status code
* add: alias `request.headers` as `request.header`
* add context.inspect(), cleanup app.inspect()
* update cookies
* fix `err.status` invalid lead to uncaughtException
* fix middleware gif, close #322
0.8.2 / 2014-07-27
==================
* bump co
* bump parseurl
0.8.1 / 2014-06-24
==================
* bump type-is
0.8.0 / 2014-06-13
==================
* add `this.response.is()``
* remove `.status=string` and `res.statusString` #298
0.7.0 / 2014-06-07
==================
* add `this.lastModified` and `this.etag` as both getters and setters for ubiquity #292.
See koajs/koa@4065bf7 for an explanation.
* refactor `this.response.vary()` to use [vary](https://github.com/expressjs/vary) #291
* remove `this.response.append()` #291
0.6.3 / 2014-06-06
==================
* fix res.type= when the extension is unknown
* assert when non-error is passed to app.onerror #287
* bump finished
0.6.2 / 2014-06-03
==================
* switch from set-type to mime-types
0.6.1 / 2014-05-11
==================
* bump type-is
* bump koa-compose
0.6.0 / 2014-05-01
==================
* add nicer error formatting
* add: assert object type in ctx.onerror
* change .status default to 404. Closes #263
* remove .outputErrors, suppress output when handled by the dev. Closes #272
* fix content-length when body is re-assigned. Closes #267
0.5.5 / 2014-04-14
==================
* fix length when .body is missing
* fix: make sure all intermediate stream bodies will be destroyed
0.5.4 / 2014-04-12
==================
* fix header stripping in a few cases
0.5.3 / 2014-04-09
==================
* change res.type= to always default charset. Closes #252
* remove ctx.inspect() implementation. Closes #164
0.5.2 / 2014-03-23
==================
* fix: inspection of `app` and `app.toJSON()`
* fix: let `this.throw`n errors provide their own status
* fix: overwriting of `content-type` w/ `HEAD` requests
* refactor: use statuses
* refactor: use escape-html
* bump dev deps
0.5.1 / 2014-03-06
==================
* add request.hostname(getter). Closes #224
* remove response.charset and ctx.charset (too confusing in relation to ctx.type) [breaking change]
* fix a debug() name
0.5.0 / 2014-02-19
==================
* add context.charset
* add context.charset=
* add request.charset
* add response.charset
* add response.charset=
* fix response.body= html content sniffing
* change ctx.length and ctx.type to always delegate to response object [breaking change]
0.4.0 / 2014-02-11
==================
* remove app.jsonSpaces settings - moved to [koa-json](https://github.com/koajs/json)
* add this.response=false to bypass koa's response handling
* fix response handling after body has been sent
* changed ctx.throw() to no longer .expose 5xx errors
* remove app.keys getter/setter, update cookies, and remove keygrip deps
* update fresh
* update koa-compose
0.3.0 / 2014-01-17
==================
* add ctx.host= delegate
* add req.host=
* add: context.throw supports Error instances
* update co
* update cookies
0.2.1 / 2013-12-30
==================
* add better 404 handling
* add check for fn._name in debug() output
* add explicit .toJSON() calls to ctx.toJSON()
0.2.0 / 2013-12-28
==================
* add support for .throw(status, msg). Closes #130
* add GeneratorFunction assertion for app.use(). Closes #120
* refactor: move `.is()` to `type-is`
* refactor: move content negotiation to "accepts"
* refactor: allow any streams with .pipe method
* remove `next` in callback for now
0.1.2 / 2013-12-21
==================
* update co, koa-compose, keygrip
* use on-socket-error
* add throw(status, msg) support
* assert middleware is GeneratorFunction
* ducktype stream checks
* remove `next` is `app.callback()`
0.1.1 / 2013-12-19
==================
* fix: cleanup socker error handler on response
================================================
FILE: LICENSE
================================================
(The MIT License)
Copyright (c) 2019 Koa contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: Readme.md
================================================
<img src="/docs/logo.png" alt="Koa middleware framework for nodejs"/>
[![gitter][gitter-image]][gitter-url]
[![NPM version][npm-image]][npm-url]
[![build status][github-action-image]][github-action-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![OpenCollective Backers][backers-image]](#backers)
[![OpenCollective Sponsors][sponsors-image]](#sponsors)
[![PR's Welcome][pr-welcoming-image]][pr-welcoming-url]
Expressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream.
Only methods that are common to nearly all HTTP servers are integrated directly into Koa's small ~570 SLOC codebase. This
includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others.
Koa is not bundled with any middleware.
## Installation
Koa requires __node v18.0.0__ or higher for ES2015 and async function support.
```sh
npm install koa
```
## Hello Koa
```js
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
```
## Getting started
- [Kick-Off-Koa](https://github.com/koajs/kick-off-koa) - An intro to Koa via a set of self-guided workshops.
- [Guide](docs/guide.md) - Go straight to the docs.
## Middleware
Koa is a middleware framework that can take two different kinds of functions as middleware:
* async function
* common function
Here is an example of logger middleware with each of the different functions:
### ___async___ functions (node v7.6+)
```js
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
```
### Common function
```js
// Middleware normally takes two parameters (ctx, next), ctx is the context for one request,
// next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.
app.use((ctx, next) => {
const start = Date.now();
return next().then(() => {
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
```
### Koa v1.x Middleware Signature
The middleware signature changed between v1.x and v2.x. The older signature is deprecated.
**Old signature middleware support has been removed in v3**
Please see the [Migration Guide from v2.x to v3.x](docs/migration-v2-to-v3.md) for information on upgrading from v2.x to v3.x, and the [Migration Guide from v1.x to v2.x](docs/migration-v1-to-v2.md) for information on upgrading from v1.x to v2.x.
## Context, Request and Response
Each middleware receives a Koa `Context` object that encapsulates an incoming
http message and the corresponding response to that message. `ctx` is often used
as the parameter name for the context object.
```js
app.use(async (ctx, next) => { await next(); });
```
Koa provides a `Request` object as the `request` property of the `Context`.
Koa's `Request` object provides helpful methods for working with
http requests which delegate to an [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
from the node `http` module.
Here is an example of checking that a requesting client supports xml.
```js
app.use(async (ctx, next) => {
ctx.assert(ctx.request.accepts('xml'), 406);
// equivalent to:
// if (!ctx.request.accepts('xml')) ctx.throw(406);
await next();
});
```
Koa provides a `Response` object as the `response` property of the `Context`.
Koa's `Response` object provides helpful methods for working with
http responses which delegate to a [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)
.
Koa's pattern of delegating to Node's request and response objects rather than extending them
provides a cleaner interface and reduces conflicts between different middleware and with Node
itself as well as providing better support for stream handling. The `IncomingMessage` can still be
directly accessed as the `req` property on the `Context` and `ServerResponse` can be directly
accessed as the `res` property on the `Context`.
Here is an example using Koa's `Response` object to stream a file as the response body.
```js
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'xml';
ctx.response.body = fs.createReadStream('really_large.xml');
});
```
The `Context` object also provides shortcuts for methods on its `request` and `response`. In the prior
examples, `ctx.type` can be used instead of `ctx.response.type` and `ctx.accepts` can be used
instead of `ctx.request.accepts`.
For more information on `Request`, `Response` and `Context`, see the [Request API Reference](docs/api/request.md),
[Response API Reference](docs/api/response.md) and [Context API Reference](docs/api/context.md).
## Koa Application
The object created when executing `new Koa()` is known as the Koa application object.
The application object is Koa's interface with node's http server and handles the registration
of middleware, dispatching to the middleware from http, default error handling, as well as
configuration of the context, request and response objects.
Learn more about the application object in the [Application API Reference](docs/api/index.md).
## Documentation
- [Usage Guide](docs/guide.md)
- [Error Handling](docs/error-handling.md)
- [Koa for Express Users](docs/koa-vs-express.md)
- [FAQ](docs/faq.md)
- [API documentation](docs/api/index.md)
## Troubleshooting
Check the [Troubleshooting Guide](docs/troubleshooting.md) or [Debugging Koa](docs/guide.md#debugging-koa) in
the general Koa guide.
## Running tests
```
$ npm test
```
## Reporting vulnerabilities
To report a security vulnerability, please do not open an issue, as this notifies attackers of the vulnerability. Instead, please email [dead_horse](mailto:heyiyu.deadhorse@gmail.com), [jonathanong](mailto:me@jongleberry.com), and [niftylettuce](mailto:niftylettuce@gmail.com) to disclose.
## Authors
See [AUTHORS](AUTHORS).
## Community
- [KoaJS Slack Group](https://join.slack.com/t/koa-js/shared_invite/zt-5pjgthmb-1JeKDbByqqcARtlPbtf~vQ)
- [Badgeboard](https://koajs.github.io/badgeboard) and list of official modules
- [Examples](https://github.com/koajs/examples)
- [Middleware](https://github.com/koajs/koa/wiki) list
- [Wiki](https://github.com/koajs/koa/wiki)
- [Reddit Community](https://www.reddit.com/r/koajs)
- [Mailing list](https://groups.google.com/forum/#!forum/koajs)
- [中文文档 v1.x](https://github.com/guo-yu/koa-guide)
- [中文文档 v2.x](https://github.com/demopark/koa-docs-Zh-CN)
- __[#koajs]__ on freenode
## Backers
Support us with a monthly donation and help us continue our activities.
<a href="https://opencollective.com/koajs/backer/0/website" target="_blank"><img src="https://opencollective.com/koajs/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/1/website" target="_blank"><img src="https://opencollective.com/koajs/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/2/website" target="_blank"><img src="https://opencollective.com/koajs/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/3/website" target="_blank"><img src="https://opencollective.com/koajs/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/4/website" target="_blank"><img src="https://opencollective.com/koajs/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/5/website" target="_blank"><img src="https://opencollective.com/koajs/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/6/website" target="_blank"><img src="https://opencollective.com/koajs/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/7/website" target="_blank"><img src="https://opencollective.com/koajs/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/8/website" target="_blank"><img src="https://opencollective.com/koajs/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/9/website" target="_blank"><img src="https://opencollective.com/koajs/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/10/website" target="_blank"><img src="https://opencollective.com/koajs/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/11/website" target="_blank"><img src="https://opencollective.com/koajs/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/12/website" target="_blank"><img src="https://opencollective.com/koajs/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/13/website" target="_blank"><img src="https://opencollective.com/koajs/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/14/website" target="_blank"><img src="https://opencollective.com/koajs/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/15/website" target="_blank"><img src="https://opencollective.com/koajs/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/16/website" target="_blank"><img src="https://opencollective.com/koajs/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/17/website" target="_blank"><img src="https://opencollective.com/koajs/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/18/website" target="_blank"><img src="https://opencollective.com/koajs/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/19/website" target="_blank"><img src="https://opencollective.com/koajs/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/20/website" target="_blank"><img src="https://opencollective.com/koajs/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/21/website" target="_blank"><img src="https://opencollective.com/koajs/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/22/website" target="_blank"><img src="https://opencollective.com/koajs/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/23/website" target="_blank"><img src="https://opencollective.com/koajs/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/24/website" target="_blank"><img src="https://opencollective.com/koajs/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/25/website" target="_blank"><img src="https://opencollective.com/koajs/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/26/website" target="_blank"><img src="https://opencollective.com/koajs/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/27/website" target="_blank"><img src="https://opencollective.com/koajs/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/28/website" target="_blank"><img src="https://opencollective.com/koajs/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/koajs/backer/29/website" target="_blank"><img src="https://opencollective.com/koajs/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site.
<a href="https://opencollective.com/koajs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/1/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/2/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/3/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/4/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/5/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/6/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/7/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/8/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/9/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/10/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/11/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/12/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/13/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/14/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/15/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/16/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/17/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/18/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/19/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/20/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/21/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/22/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/23/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/24/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/25/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/26/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/27/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/28/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/koajs/sponsor/29/website" target="_blank"><img src="https://opencollective.com/koajs/sponsor/29/avatar.svg"></a>
# License
[MIT](https://github.com/koajs/koa/blob/master/LICENSE)
[npm-image]: https://img.shields.io/npm/v/koa.svg?style=flat-square
[npm-url]: https://www.npmjs.com/package/koa
[github-action-image]: https://github.com/koajs/koa/actions/workflows/node.js.yml/badge.svg
[github-action-url]: https://github.com/koajs/koa/actions/workflows/node.js.yml
[coveralls-image]: https://img.shields.io/codecov/c/github/koajs/koa.svg?style=flat-square
[coveralls-url]: https://codecov.io/github/koajs/koa?branch=master
[backers-image]: https://opencollective.com/koajs/backers/badge.svg?style=flat-square
[sponsors-image]: https://opencollective.com/koajs/sponsors/badge.svg?style=flat-square
[gitter-image]: https://img.shields.io/gitter/room/koajs/koa.svg?style=flat-square
[gitter-url]: https://gitter.im/koajs/koa?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[#koajs]: https://webchat.freenode.net/?channels=#koajs
[pr-welcoming-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
[pr-welcoming-url]: https://github.com/koajs/koa/pull/new
================================================
FILE: __tests__/.eslintrc.yml
================================================
env:
jest: true
rules:
space-before-blocks: [2, {functions: never, keywords: always}]
no-unused-expressions: 0
node/no-deprecated-api: 'warn'
quote-props: 'warn'
no-prototype-builtins: 'warn'
array-bracket-spacing: 'warn'
object-curly-spacing: 'warn'
dot-notation: 'warn'
================================================
FILE: __tests__/application/compose.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.compose', () => {
it('should work with default compose ', async () => {
const app = new Koa()
const calls = []
app.use((ctx, next) => {
calls.push(1)
return next().then(() => {
calls.push(4)
})
})
app.use((ctx, next) => {
calls.push(2)
return next().then(() => {
calls.push(3)
})
})
await request(app.callback())
.get('/')
.expect(404)
assert.deepStrictEqual(calls, [1, 2, 3, 4])
})
it('should work with configurable compose', async () => {
const calls = []
let count = 0
const app = new Koa({
compose (fns) {
return async (ctx) => {
const dispatch = async () => {
count++
const fn = fns.shift()
fn && fn(ctx, dispatch)
}
dispatch()
}
}
})
app.use((ctx, next) => {
calls.push(1)
next()
calls.push(4)
})
app.use((ctx, next) => {
calls.push(2)
next()
calls.push(3)
})
await request(app.callback())
.get('/')
assert.deepStrictEqual(calls, [1, 2, 3, 4])
assert.equal(count, 3)
})
})
================================================
FILE: __tests__/application/context.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.context', () => {
const app1 = new Koa()
app1.context.msg = 'hello'
const app2 = new Koa()
it('should merge properties', () => {
app1.use((ctx, next) => {
assert.strictEqual(ctx.msg, 'hello')
ctx.status = 204
})
return request(app1.callback())
.get('/')
.expect(204)
})
it('should not affect the original prototype', () => {
app2.use((ctx, next) => {
assert.strictEqual(ctx.msg, undefined)
ctx.status = 204
})
return request(app2.callback())
.get('/')
.expect(204)
})
})
================================================
FILE: __tests__/application/currentContext.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
const { AsyncLocalStorage } = require('async_hooks')
describe('app.currentContext', () => {
it('should get currentContext return context when asyncLocalStorage enable', async () => {
const app = new Koa({ asyncLocalStorage: true })
app.use(async ctx => {
assert(ctx === app.currentContext)
await new Promise(resolve => {
setTimeout(() => {
assert(ctx === app.currentContext)
resolve()
}, 1)
})
await new Promise(resolve => {
assert(ctx === app.currentContext)
setImmediate(() => {
assert(ctx === app.currentContext)
resolve()
})
})
assert(ctx === app.currentContext)
app.currentContext.body = 'ok'
})
const requestServer = async () => {
assert(app.currentContext === undefined)
await request(app.callback()).get('/').expect('ok')
assert(app.currentContext === undefined)
}
await Promise.all([
requestServer(),
requestServer(),
requestServer(),
requestServer(),
requestServer()
])
})
it('should get currentContext return undefined when asyncLocalStorage disable', async () => {
const app = new Koa()
app.use(async ctx => {
assert(app.currentContext === undefined)
ctx.body = 'ok'
})
await request(app.callback()).get('/').expect('ok')
})
it('should get currentContext return context in error handler when asyncLocalStorage enable', async () => {
const app = new Koa({ asyncLocalStorage: true })
app.use(async () => {
throw new Error('error message')
})
const handleError = new Promise((resolve, reject) => {
app.on('error', (err, ctx) => {
try {
assert.strictEqual(err.message, 'error message')
assert.strictEqual(app.currentContext, ctx)
resolve()
} catch (e) {
reject(e)
}
})
})
await request(app.callback()).get('/').expect('Internal Server Error')
await handleError
})
it('should get currentContext return undefined in error handler when asyncLocalStorage disable', async () => {
const app = new Koa()
app.use(async () => {
throw new Error('error message')
})
const handleError = new Promise((resolve, reject) => {
app.on('error', (err, ctx) => {
try {
assert.strictEqual(err.message, 'error message')
assert.strictEqual(app.currentContext, undefined)
resolve()
} catch (e) {
reject(e)
}
})
})
await request(app.callback()).get('/').expect('Internal Server Error')
await handleError
})
it('should support a custom asyncLocalStorage', async () => {
const asyncLocalStorage = new AsyncLocalStorage()
const app = new Koa({ asyncLocalStorage })
assert(app.currentContext === undefined)
app.use(async ctx => {
assert(ctx === app.currentContext)
assert(asyncLocalStorage.getStore() === ctx)
ctx.body = 'ok'
})
await request(app.callback()).get('/').expect('ok')
assert(app.currentContext === undefined)
})
})
================================================
FILE: __tests__/application/index.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const { once } = require('node:events')
const Koa = require('../..')
describe('app', () => {
it('should handle socket errors', async () => {
const app = new Koa()
let errorCaught = false
app.use((ctx) => {
ctx.socket.destroy(new Error('boom'))
})
app.on('error', err => {
assert.strictEqual(err.message, 'boom')
errorCaught = true
})
const server = app.listen()
try {
const req = require('http').get({
port: server.address().port
})
req.on('error', () => {})
const [err] = await once(app, 'error')
assert.strictEqual(err.message, 'boom')
assert.strictEqual(errorCaught, true)
} finally {
await server.close()
}
})
it('should set development env when NODE_ENV missing', () => {
const NODE_ENV = process.env.NODE_ENV
process.env.NODE_ENV = ''
const app = new Koa()
process.env.NODE_ENV = NODE_ENV
assert.strictEqual(app.env, 'development')
})
it('should set env from the constructor', () => {
const env = 'custom'
const app = new Koa({ env })
assert.strictEqual(app.env, env)
})
it('should set proxy flag from the constructor', () => {
const proxy = true
const app = new Koa({ proxy })
assert.strictEqual(app.proxy, proxy)
})
it('should set signed cookie keys from the constructor', () => {
const keys = ['customkey']
const app = new Koa({ keys })
assert.strictEqual(app.keys, keys)
})
it('should set subdomainOffset from the constructor', () => {
const subdomainOffset = 3
const app = new Koa({ subdomainOffset })
assert.strictEqual(app.subdomainOffset, subdomainOffset)
})
it('should set compose from the constructor', () => {
const compose = () => (ctx) => {}
const app = new Koa.default({ compose }) // eslint-disable-line new-cap
assert.strictEqual(app.compose, compose)
})
it('should have a static property exporting `HttpError` from http-errors library', () => {
const CreateError = require('http-errors')
assert.notEqual(Koa.HttpError, undefined)
assert.deepStrictEqual(Koa.HttpError, CreateError.HttpError)
assert.throws(() => { throw new CreateError(500, 'test error') }, Koa.HttpError)
})
})
================================================
FILE: __tests__/application/inspect.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const util = require('util')
const Koa = require('../..')
process.env.NODE_ENV = 'test'
const app = new Koa()
describe('app.inspect()', () => {
it('should work', () => {
const str = util.inspect(app)
assert.strictEqual("{ subdomainOffset: 2, proxy: false, env: 'test' }", str)
})
it('should return a json representation', () => {
assert.deepStrictEqual(
{ subdomainOffset: 2, proxy: false, env: 'test' },
app.inspect()
)
})
})
================================================
FILE: __tests__/application/onerror.test.js
================================================
'use strict'
const { describe, it, mock } = require('node:test')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.onerror(err)', () => {
it('should throw an error if a non-error is given', () => {
const app = new Koa()
assert.throws(() => {
app.onerror('foo')
}, TypeError, 'non-error thrown: foo')
})
it('should accept errors coming from other scopes', () => {
const ExternError = require('vm').runInNewContext('Error')
const app = new Koa()
const error = Object.assign(new ExternError('boom'), {
status: 418,
expose: true
})
assert.doesNotThrow(() => app.onerror(error))
})
it('should do nothing if status is 404', () => {
const app = new Koa()
const err = new Error()
err.status = 404
const spy = mock.method(console, 'error', () => {})
app.onerror(err)
assert.strictEqual(spy.mock.calls.length, 0)
spy.mock.restore()
})
it('should do nothing if .silent', () => {
const app = new Koa()
app.silent = true
const err = new Error()
const spy = mock.method(console, 'error', () => {})
app.onerror(err)
assert.strictEqual(spy.mock.calls.length, 0)
spy.mock.restore()
})
it('should log the error to stderr', () => {
const app = new Koa()
app.env = 'dev'
const err = new Error()
err.stack = 'Foo'
const spy = mock.method(console, 'error', () => {})
app.onerror(err)
assert.notStrictEqual(spy.mock.calls.length, 0)
spy.mock.restore()
})
})
================================================
FILE: __tests__/application/request.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.request', () => {
const app1 = new Koa()
app1.request.message = 'hello'
const app2 = new Koa()
it('should merge properties', () => {
app1.use((ctx, next) => {
assert.strictEqual(ctx.request.message, 'hello')
ctx.status = 204
})
return request(app1.callback())
.get('/')
.expect(204)
})
it('should not affect the original prototype', () => {
app2.use((ctx, next) => {
assert.strictEqual(ctx.request.message, undefined)
ctx.status = 204
})
return request(app2.callback())
.get('/')
.expect(204)
})
})
================================================
FILE: __tests__/application/respond.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const statuses = require('statuses')
const assert = require('node:assert/strict')
const Koa = require('../..')
const fs = require('fs')
describe('app.respond', () => {
describe('when ctx.respond === false', () => {
it('should function (ctx)', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'Hello'
ctx.respond = false
const res = ctx.res
res.statusCode = 200
setImmediate(() => {
res.setHeader('Content-Type', 'text/plain')
res.setHeader('Content-Length', '3')
res.end('lol')
})
})
return request(app.callback())
.get('/')
.expect(200)
.expect('lol')
})
it('should ignore set header after header sent', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'Hello'
ctx.respond = false
const res = ctx.res
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.setHeader('Content-Length', '3')
res.end('lol')
ctx.set('foo', 'bar')
})
return request(app.callback())
.get('/')
.expect(200)
.expect('lol')
.expect(res => {
assert(!res.headers.foo)
})
})
it('should ignore set status after header sent', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'Hello'
ctx.respond = false
const res = ctx.res
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.setHeader('Content-Length', '3')
res.end('lol')
ctx.status = 201
})
return request(app.callback())
.get('/')
.expect(200)
.expect('lol')
})
})
describe('when this.type === null', () => {
it('should not send Content-Type header', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = ''
ctx.type = null
})
const res = await request(app.callback())
.get('/')
.expect(200)
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('when HEAD is used', () => {
it('should not respond with the body', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'Hello'
})
const res = await request(app.callback())
.head('/')
.expect(200)
assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8')
assert.strictEqual(res.headers['content-length'], '5')
assert(!res.text)
})
it('should keep json headers', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = { hello: 'world' }
})
const res = await request(app.callback())
.head('/')
.expect(200)
assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
assert.strictEqual(res.headers['content-length'], '17')
assert(!res.text)
})
it('should keep string headers', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'hello world'
})
const res = await request(app.callback())
.head('/')
.expect(200)
assert.strictEqual(res.headers['content-type'], 'text/plain; charset=utf-8')
assert.strictEqual(res.headers['content-length'], '11')
assert(!res.text)
})
it('should keep buffer headers', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = Buffer.from('hello world')
})
const res = await request(app.callback())
.head('/')
.expect(200)
assert.strictEqual(res.headers['content-type'], 'application/octet-stream')
assert.strictEqual(res.headers['content-length'], '11')
assert(!res.text)
})
it('should keep stream header if set manually', async () => {
const app = new Koa()
const { length } = fs.readFileSync('package.json')
app.use(ctx => {
ctx.length = length
ctx.body = fs.createReadStream('package.json')
})
const res = await request(app.callback())
.head('/')
.expect(200)
assert.strictEqual(~~res.header['content-length'], length)
assert(!res.text)
})
it('should respond with a 404 if no body was set', () => {
const app = new Koa()
app.use(ctx => {
})
return request(app.callback())
.head('/')
.expect(404)
})
it('should respond with a 200 if body = ""', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = ''
})
return request(app.callback())
.head('/')
.expect(200)
})
it('should not overwrite the content-type', () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 200
ctx.type = 'application/javascript'
})
return request(app.callback())
.head('/')
.expect('content-type', /application\/javascript/)
.expect(200)
})
})
describe('when no middleware is present', () => {
it('should 404', () => {
const app = new Koa()
return request(app.callback())
.get('/')
.expect(404)
})
})
describe('when res has already been written to', () => {
it('should not cause an app error', () => {
const app = new Koa()
app.use((ctx, next) => {
const res = ctx.res
ctx.status = 200
res.setHeader('Content-Type', 'text/html')
res.write('Hello')
})
app.on('error', err => { throw err })
return request(app.callback())
.get('/')
.expect(200)
})
it('should send the right body', () => {
const app = new Koa()
app.use((ctx, next) => {
const res = ctx.res
ctx.status = 200
res.setHeader('Content-Type', 'text/html')
res.write('Hello')
return new Promise(resolve => {
setTimeout(() => {
res.end('Goodbye')
resolve()
}, 0)
})
})
return request(app.callback())
.get('/')
.expect(200)
.expect('HelloGoodbye')
})
})
describe('when .body is missing', () => {
describe('with status=400', () => {
it('should respond with the associated status message', () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 400
})
return request(app.callback())
.get('/')
.expect(400)
.expect('Content-Length', '11')
.expect('Bad Request')
})
})
describe('with status=204', () => {
it('should respond without a body', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 204
})
const res = await request(app.callback())
.get('/')
.expect(204)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('with status=205', () => {
it('should respond without a body', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 205
})
const res = await request(app.callback())
.get('/')
.expect(205)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('with status=304', () => {
it('should respond without a body', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 304
})
const res = await request(app.callback())
.get('/')
.expect(304)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('with custom status=700', () => {
it('should respond with the associated status message', async () => {
const app = new Koa()
statuses.message['700'] = 'custom status'
app.use(ctx => {
ctx.status = 700
})
const res = await request(app.callback())
.get('/')
.expect(700)
.expect('custom status')
assert.strictEqual(res.res.statusMessage, 'custom status')
})
})
describe('with custom statusMessage=ok', () => {
it('should respond with the custom status message', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 200
ctx.message = 'ok'
})
const res = await request(app.callback())
.get('/')
.expect(200)
.expect('ok')
assert.strictEqual(res.res.statusMessage, 'ok')
})
})
describe('with custom status without message', () => {
it('should respond with the status code number', () => {
const app = new Koa()
app.use(ctx => {
ctx.res.statusCode = 701
})
return request(app.callback())
.get('/')
.expect(701)
.expect('701')
})
})
})
describe('when .body is a null', () => {
it('should respond 204 by default', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = null
})
const res = await request(app.callback())
.get('/')
.expect(204)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
it('should respond 204 with status=200', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 200
ctx.body = null
})
const res = await request(app.callback())
.get('/')
.expect(204)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
it('should respond 205 with status=205', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 205
ctx.body = null
})
const res = await request(app.callback())
.get('/')
.expect(205)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
it('should respond 304 with status=304', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 304
ctx.body = null
})
const res = await request(app.callback())
.get('/')
.expect(304)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('when .body is undefined', () => {
it('should respond 204 by default', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = undefined
})
const res = await request(app.callback())
.get('/')
.expect(204)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
it('should respond 204 with status=200', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 200
ctx.body = undefined
})
const res = await request(app.callback())
.get('/')
.expect(204)
.expect('')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('when .body is a string', () => {
it('should respond', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'Hello'
})
return request(app.callback())
.get('/')
.expect('Hello')
})
})
describe('when .body is a Buffer', () => {
it('should respond', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = Buffer.from('Hello')
})
return request(app.callback())
.get('/')
.expect(200)
.expect(Buffer.from('Hello'))
})
})
describe('when .body is a Blob', () => {
it('should respond', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = new Blob(['Hello'])
})
const expectedBlob = new Blob(['Hello'])
const res = await request(app.callback())
.get('/')
.expect(200)
assert.deepStrictEqual(res.body, Buffer.from(await expectedBlob.arrayBuffer()))
})
it('should keep Blob headers', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = new Blob(['hello world'])
})
return request(app.callback())
.head('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
.expect('content-length', '11')
})
})
describe('when .body is a ReadableStream', () => {
it('should respond', async () => {
const app = new Koa()
app.use(async ctx => {
ctx.body = new ReadableStream()
})
return request(app.callback())
.head('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
})
it('should respond hello', async () => {
const app = new Koa()
app.use(async ctx => {
const blob = new Blob(['hello'])
ctx.body = blob.stream()
})
return request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
.expect(Buffer.from('hello'))
})
it('should handle ReadableStream with chunks', async () => {
const app = new Koa()
app.use(async ctx => {
const stream = new ReadableStream({
start (controller) {
controller.enqueue(new TextEncoder().encode('Hello '))
controller.enqueue(new TextEncoder().encode('World'))
controller.close()
}
})
ctx.body = stream
})
return request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
.expect(Buffer.from('Hello World'))
})
it('should handle ReadableStream with custom headers', async () => {
const app = new Koa()
app.use(async ctx => {
ctx.type = 'text/plain'
ctx.body = new ReadableStream({
start (controller) {
controller.enqueue(new TextEncoder().encode('test content'))
controller.close()
}
})
})
const res = await request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'text/plain; charset=utf-8')
assert.strictEqual(res.text, 'test content')
})
})
describe('when .body is a Response', () => {
it('should keep Response headers', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = new Response(null, { status: 201, statusText: 'OK', headers: { 'Content-Type': 'text/plain' } })
})
return request(app.callback())
.head('/')
.expect(201)
.expect('content-type', 'text/plain')
.expect('content-length', '2')
})
it('should default to octet-stream', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = new Response(null, { status: 200, statusText: 'OK' })
})
return request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'application/octet-stream')
.expect(Buffer.from([]))
})
it('should respond with body content', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = new Response('Hello World', { status: 200, headers: { 'Content-Type': 'text/plain' } })
})
const res = await request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'text/plain')
assert.strictEqual(res.text, 'Hello World')
})
it('should handle Response from fetch() with JSON', async () => {
const app = new Koa()
app.use(async ctx => {
const jsonData = JSON.stringify({ message: 'Hello from fetch', timestamp: Date.now() })
const response = new Response(jsonData, {
status: 200,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value'
}
})
ctx.body = response
})
const res = await request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'application/json')
const body = JSON.parse(res.text)
assert.strictEqual(body.message, 'Hello from fetch')
assert(body.timestamp)
})
it('should handle Response from fetch() with streaming body', async () => {
const app = new Koa()
app.use(async ctx => {
const stream = new ReadableStream({
start (controller) {
controller.enqueue(new TextEncoder().encode('Streaming '))
controller.enqueue(new TextEncoder().encode('response '))
controller.enqueue(new TextEncoder().encode('from fetch'))
controller.close()
}
})
const response = new Response(stream, {
status: 200,
headers: {
'Content-Type': 'text/plain'
}
})
ctx.body = response
})
const res = await request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'text/plain')
assert.strictEqual(res.text, 'Streaming response from fetch')
})
it('should handle Response from fetch() with Blob body', async () => {
const app = new Koa()
app.use(async ctx => {
const blob = new Blob(['Hello from Blob'], { type: 'text/plain' })
const response = new Response(blob, {
status: 200,
headers: {
'Content-Type': 'text/plain'
}
})
ctx.body = response
})
const res = await request(app.callback())
.get('/')
.expect(200)
.expect('content-type', 'text/plain')
assert.strictEqual(res.text, 'Hello from Blob')
})
})
describe('when .body is a Stream', () => {
it('should respond', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = fs.createReadStream('package.json')
ctx.set('Content-Type', 'application/json; charset=utf-8')
})
const res = await request(app.callback())
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
const pkg = require('../../package')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), false)
assert.deepStrictEqual(res.body, pkg)
})
it('should strip content-length when overwriting', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = 'hello'
ctx.body = fs.createReadStream('package.json')
ctx.set('Content-Type', 'application/json; charset=utf-8')
})
const res = await request(app.callback())
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
const pkg = require('../../package')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), false)
assert.deepStrictEqual(res.body, pkg)
})
it('should keep content-length if not overwritten', async () => {
const app = new Koa()
app.use(ctx => {
ctx.length = fs.readFileSync('package.json').length
ctx.body = fs.createReadStream('package.json')
ctx.set('Content-Type', 'application/json; charset=utf-8')
})
const res = await request(app.callback())
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
const pkg = require('../../package')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), true)
assert.deepStrictEqual(res.body, pkg)
})
it('should keep content-length if overwritten with the same stream',
async () => {
const app = new Koa()
app.use(ctx => {
ctx.length = fs.readFileSync('package.json').length
const stream = fs.createReadStream('package.json')
ctx.body = stream
ctx.body = stream
ctx.set('Content-Type', 'application/json; charset=utf-8')
})
const res = await request(app.callback())
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
const pkg = require('../../package')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), true)
assert.deepStrictEqual(res.body, pkg)
})
it('should handle errors when no content status', () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 204
ctx.body = fs.createReadStream('does not exist')
})
return request(app.callback())
.get('/')
.expect(204)
})
})
describe('when using pipeline for streams', () => {
it('should handle stream errors when error listener exists', async () => {
const app = new Koa()
const PassThrough = require('stream').PassThrough
let errorCaught = false
app.once('error', err => {
assert(err.message === 'stream error')
errorCaught = true
})
app.use(ctx => {
const stream = new PassThrough()
ctx.body = stream
setImmediate(() => {
stream.emit('error', new Error('stream error'))
})
})
await request(app.callback())
.get('/')
.catch(() => {})
await new Promise(resolve => setTimeout(resolve, 50))
assert(errorCaught, 'Error should have been caught')
})
it('should not crash when stream errors and no error listener exists', async () => {
const app = new Koa()
const PassThrough = require('stream').PassThrough
app.use(ctx => {
const stream = new PassThrough()
ctx.body = stream
setImmediate(() => {
stream.emit('error', new Error('stream error'))
})
})
await request(app.callback())
.get('/')
.catch(() => {})
await new Promise(resolve => setTimeout(resolve, 50))
})
it('should handle ReadableStream errors when error listener exists', async () => {
const app = new Koa()
let errorCaught = false
app.once('error', err => {
assert(err.message === 'readable stream error')
errorCaught = true
})
app.use(ctx => {
const readable = new ReadableStream({
start (controller) {
controller.enqueue(new TextEncoder().encode('data'))
controller.error(new Error('readable stream error'))
}
})
ctx.body = readable
})
await request(app.callback())
.get('/')
.catch(() => {})
await new Promise(resolve => setTimeout(resolve, 50))
assert(errorCaught, 'Error should have been caught')
})
it('should cleanup streams on client abort', async () => {
const app = new Koa()
const PassThrough = require('stream').PassThrough
const http = require('http')
let streamDestroyed = false
app.use(ctx => {
const stream = new PassThrough()
stream.on('close', () => {
streamDestroyed = true
})
ctx.body = stream
setImmediate(() => {
stream.write('some data')
})
})
const server = app.listen()
await new Promise((resolve) => {
const req = http.request({
port: server.address().port,
path: '/'
})
req.on('response', (res) => {
res.on('data', () => {
req.destroy()
setTimeout(() => {
server.close()
resolve()
}, 50)
})
})
req.end()
})
assert(streamDestroyed, 'Stream should be destroyed on client abort')
})
})
describe('when .body is an Object', () => {
it('should respond with json', () => {
const app = new Koa()
app.use(ctx => {
ctx.body = { hello: 'world' }
})
return request(app.callback())
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect('{"hello":"world"}')
})
describe('and headers sent', () => {
it('should respond with json body and headers', () => {
const app = new Koa()
app.use(ctx => {
ctx.length = 17
ctx.type = 'json'
ctx.set('foo', 'bar')
ctx.res.flushHeaders()
ctx.body = { hello: 'world' }
})
return request(app.callback())
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect('Content-Length', '17')
.expect('foo', 'bar')
.expect('{"hello":"world"}')
})
})
})
describe('when an error occurs', () => {
it('should emit "error" on the app', async () => {
const app = new Koa()
let errorCaught = false
app.on('error', err => {
assert.strictEqual(err.message, 'test error')
errorCaught = true
})
app.use(ctx => {
throw new Error('test error')
})
await request(app.callback())
.get('/')
.expect(500)
assert.strictEqual(errorCaught, true)
})
describe('with an .expose property', () => {
it('should expose the message', () => {
const app = new Koa()
app.use(ctx => {
const err = new Error('sorry!')
err.status = 403
err.expose = true
throw err
})
return request(app.callback())
.get('/')
.expect(403, 'sorry!')
})
})
describe('with a .status property', () => {
it('should respond with .status', () => {
const app = new Koa()
app.use(ctx => {
const err = new Error('s3 explodes')
err.status = 403
throw err
})
return request(app.callback())
.get('/')
.expect(403, 'Forbidden')
})
})
it('should respond with 500', () => {
const app = new Koa()
app.use(ctx => {
throw new Error('boom!')
})
return request(app.callback())
.get('/')
.expect(500, 'Internal Server Error')
})
it('should be catchable', () => {
const app = new Koa()
app.use((ctx, next) => {
return next().then(() => {
ctx.body = 'Hello'
}).catch(() => {
ctx.body = 'Got error'
})
})
app.use((ctx, next) => {
throw new Error('boom!')
})
return request(app.callback())
.get('/')
.expect(200, 'Got error')
})
})
describe('when status and body property', () => {
it('should 200', () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 304
ctx.body = 'hello'
ctx.status = 200
})
return request(app.callback())
.get('/')
.expect(200)
.expect('hello')
})
it('should 204', async () => {
const app = new Koa()
app.use(ctx => {
ctx.status = 200
ctx.body = 'hello'
ctx.set('content-type', 'text/plain; charset=utf8')
ctx.status = 204
})
const res = await request(app.callback())
.get('/')
.expect(204)
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
})
})
describe('with explicit null body', () => {
it('should preserve given status', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = null
ctx.status = 404
})
return request(app.callback())
.get('/')
.expect(404)
.expect('')
.expect({})
})
it('should respond with correct headers', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = null
ctx.status = 401
})
const res = await request(app.callback())
.get('/')
.expect(401)
.expect('')
.expect({})
assert.equal(Object.prototype.hasOwnProperty.call(res.headers, 'transfer-encoding'), false)
assert.equal(Object.prototype.hasOwnProperty.call(res.headers, 'Content-Type'), false)
assert.equal(Object.prototype.hasOwnProperty.call(res.headers, 'content-length'), true)
})
it('should return content-length equal to 0', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = null
ctx.status = 401
})
const res = await request(app.callback())
.get('/')
.expect(401)
.expect('')
.expect({})
assert.equal(res.headers['content-length'], '0')
})
it('should not overwrite the content-length', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = null
ctx.length = 10
ctx.status = 404
})
const res = await request(app.callback())
.get('/')
.expect(404)
.expect('')
.expect({})
assert.equal(res.headers['content-length'], '0')
})
})
})
================================================
FILE: __tests__/application/response.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.response', () => {
const app1 = new Koa()
app1.response.msg = 'hello'
const app2 = new Koa()
const app3 = new Koa()
const app4 = new Koa()
const app5 = new Koa()
const app6 = new Koa()
const app7 = new Koa()
it('should merge properties', () => {
app1.use((ctx, next) => {
assert.strictEqual(ctx.response.msg, 'hello')
ctx.status = 204
})
return request(app1.callback())
.get('/')
.expect(204)
})
it('should not affect the original prototype', () => {
app2.use((ctx, next) => {
assert.strictEqual(ctx.response.msg, undefined)
ctx.status = 204
})
return request(app2.callback())
.get('/')
.expect(204)
})
it('should not include status message in body for http2', async () => {
app3.use((ctx, next) => {
ctx.req.httpVersionMajor = 2
ctx.status = 404
})
const response = await request(app3.callback())
.get('/')
.expect(404)
assert.strictEqual(response.text, '404')
})
it('should set ._explicitNullBody correctly', async () => {
app4.use((ctx, next) => {
ctx.body = null
assert.strictEqual(ctx.response._explicitNullBody, true)
})
return request(app4.callback())
.get('/')
.expect(204)
})
it('should not set ._explicitNullBody incorrectly', async () => {
app5.use((ctx, next) => {
ctx.body = undefined
assert.strictEqual(ctx.response._explicitNullBody, undefined)
ctx.body = ''
assert.strictEqual(ctx.response._explicitNullBody, undefined)
ctx.body = false
assert.strictEqual(ctx.response._explicitNullBody, undefined)
})
return request(app5.callback())
.get('/')
.expect(204)
})
it('should add Content-Length when Transfer-Encoding is not defined', () => {
app6.use((ctx, next) => {
ctx.body = 'hello world'
})
return request(app6.callback())
.get('/')
.expect('Content-Length', '11')
.expect(200)
})
it('should not add Content-Length when Transfer-Encoding is defined', () => {
app7.use((ctx, next) => {
ctx.set('Transfer-Encoding', 'chunked')
ctx.body = 'hello world'
assert.strictEqual(ctx.response.get('Content-Length'), undefined)
})
return request(app7.callback())
.get('/')
.expect('Transfer-Encoding', 'chunked')
.expect(200)
})
})
================================================
FILE: __tests__/application/toJSON.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.toJSON()', () => {
it('should work', () => {
const app = new Koa({ env: 'test' })
const obj = app.toJSON()
assert.deepStrictEqual({
subdomainOffset: 2,
proxy: false,
env: 'test'
}, obj)
})
})
================================================
FILE: __tests__/application/use.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('app.use(fn)', () => {
it('should compose middleware', async () => {
const app = new Koa()
const calls = []
app.use((ctx, next) => {
calls.push(1)
return next().then(() => {
calls.push(6)
})
})
app.use((ctx, next) => {
calls.push(2)
return next().then(() => {
calls.push(5)
})
})
app.use((ctx, next) => {
calls.push(3)
return next().then(() => {
calls.push(4)
})
})
await request(app.callback())
.get('/')
.expect(404)
assert.deepStrictEqual(calls, [1, 2, 3, 4, 5, 6])
})
it('should compose mixed middleware', async () => {
const app = new Koa()
const calls = []
app.use((ctx, next) => {
calls.push(1)
return next().then(() => {
calls.push(6)
})
})
app.use(async (ctx, next) => {
calls.push(2)
await next()
calls.push(5)
})
app.use((ctx, next) => {
calls.push(3)
return next().then(() => {
calls.push(4)
})
})
await request(app.callback())
.get('/')
.expect(404)
assert.deepStrictEqual(calls, [1, 2, 3, 4, 5, 6])
})
// https://github.com/koajs/koa/pull/530#issuecomment-148138051
it('should catch thrown errors in non-async functions', () => {
const app = new Koa()
app.use(ctx => ctx.throw(404, 'Not Found'))
return request(app.callback()).get('/').expect(404)
})
it('should throw error for non-function', () => {
const app = new Koa();
[null, undefined, 0, false, 'not a function'].forEach(v => {
assert.throws(() => app.use(v), /middleware must be a function!/)
})
})
})
================================================
FILE: __tests__/context/assert.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const context = require('../../test-helpers/context')
const assert = require('node:assert/strict')
describe('ctx.assert(value, status)', () => {
it('should throw an error', () => {
const ctx = context()
let assertionRan = false
try {
ctx.assert(false, 404, 'custom message')
throw new Error('should not reach here')
} catch (err) {
assertionRan = true
assert.strictEqual(err.status, 404)
assert.strictEqual(err.message, 'custom message')
assert.strictEqual(err.expose, true)
}
assert(assertionRan)
})
})
================================================
FILE: __tests__/context/cookies.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('supertest')
const Koa = require('../..')
describe('ctx.cookies', () => {
describe('ctx.cookies.set()', () => {
it('should set an unsigned cookie', async () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.cookies.set('name', 'jon')
ctx.status = 204
})
const res = await request(app.callback())
.get('/')
.expect(204)
const cookie = res.headers['set-cookie'].some(cookie => /^name=/.test(cookie))
assert.strictEqual(cookie, true)
})
describe('with .signed', () => {
describe('when no .keys are set', () => {
it('should error', () => {
const app = new Koa()
app.use((ctx, next) => {
try {
ctx.cookies.set('foo', 'bar', { signed: true })
} catch (err) {
ctx.body = err.message
}
})
return request(app.callback())
.get('/')
.expect('.keys required for signed cookies')
})
})
it('should send a signed cookie', async () => {
const app = new Koa()
app.keys = ['a', 'b']
app.use((ctx, next) => {
ctx.cookies.set('name', 'jon', { signed: true })
ctx.status = 204
})
const res = await request(app.callback())
.get('/')
.expect(204)
const cookies = res.headers['set-cookie']
assert.strictEqual(cookies.some(cookie => /^name=/.test(cookie)), true)
assert.strictEqual(cookies.some(cookie => /(,|^)name\.sig=/.test(cookie)), true)
})
})
describe('with secure', () => {
it('should get secure from request', async () => {
const app = new Koa()
app.proxy = true
app.keys = ['a', 'b']
app.use(ctx => {
ctx.cookies.set('name', 'jon', { signed: true })
ctx.status = 204
})
const res = await request(app.callback())
.get('/')
.set('x-forwarded-proto', 'https') // mock secure
.expect(204)
const cookies = res.headers['set-cookie']
assert.strictEqual(cookies.some(cookie => /^name=/.test(cookie)), true)
assert.strictEqual(cookies.some(cookie => /(,|^)name\.sig=/.test(cookie)), true)
assert.strictEqual(cookies.every(cookie => /secure/.test(cookie)), true)
})
})
})
describe('ctx.cookies=', () => {
it('should override cookie work', async () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.cookies = {
set (key, value) {
ctx.set(key, value)
}
}
ctx.cookies.set('name', 'jon')
ctx.status = 204
})
await request(app.callback())
.get('/')
.expect('name', 'jon')
.expect(204)
})
})
})
================================================
FILE: __tests__/context/inspect.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const prototype = require('../../lib/context')
const assert = require('node:assert/strict')
const util = require('util')
const context = require('../../test-helpers/context')
describe('ctx.inspect()', () => {
it('should return a json representation', () => {
const ctx = context()
const toJSON = ctx.toJSON(ctx)
assert.deepStrictEqual(toJSON, ctx.inspect())
assert.deepStrictEqual(util.inspect(toJSON), util.inspect(ctx))
})
// console.log(require.cache) will call prototype.inspect()
it('should not crash when called on the prototype', () => {
assert.deepStrictEqual(prototype, prototype.inspect())
assert.deepStrictEqual(util.inspect(prototype.inspect()), util.inspect(prototype))
})
})
================================================
FILE: __tests__/context/onerror.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('supertest')
const Koa = require('../..')
const context = require('../../test-helpers/context')
describe('ctx.onerror(err)', () => {
it('should respond', () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'something else'
ctx.throw(418, 'boom')
})
return request(app.callback())
.get('/')
.expect(418)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Content-Length', '4')
})
it('should unset all headers', async () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.set('Vary', 'Accept-Encoding')
ctx.set('X-CSRF-Token', 'asdf')
ctx.body = 'response'
ctx.throw(418, 'boom')
})
const res = await request(app.callback())
.get('/')
.expect(418)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Content-Length', '4')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'vary'), false)
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'x-csrf-token'), false)
})
it('should set headers specified in the error', async () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.set('Vary', 'Accept-Encoding')
ctx.set('X-CSRF-Token', 'asdf')
ctx.body = 'response'
throw Object.assign(new Error('boom'), {
status: 418,
expose: true,
headers: {
'X-New-Header': 'Value'
}
})
})
const res = await request(app.callback())
.get('/')
.expect(418)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('X-New-Header', 'Value')
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'vary'), false)
assert.strictEqual(Object.prototype.hasOwnProperty.call(res.headers, 'x-csrf-token'), false)
})
it('should ignore error after headerSent', async () => {
const app = new Koa()
app.on('error', (err, { res }) => {
assert.strictEqual(err.message, 'mock error')
assert.strictEqual(err.headerSent, true)
res.end()
})
app.use(async ctx => {
ctx.status = 200
ctx.set('X-Foo', 'Bar')
ctx.flushHeaders()
await Promise.reject(new Error('mock error'))
ctx.body = 'response'
})
await request(app.callback())
.get('/')
.expect('X-Foo', 'Bar')
.expect(200)
})
it('should set status specified in the error using statusCode', () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'something else'
const err = new Error('Not found')
err.statusCode = 404
throw err
})
return request(app.callback())
.get('/')
.expect(404)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Not Found')
})
describe('when invalid err.statusCode', () => {
describe('not number', () => {
it('should respond 500', () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'something else'
const err = new Error('some error')
err.statusCode = 'notnumber'
throw err
})
return request(app.callback())
.get('/')
.expect(500)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Internal Server Error')
})
})
})
describe('when invalid err.status', () => {
describe('not number', () => {
it('should respond 500', () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'something else'
const err = new Error('some error')
err.status = 'notnumber'
throw err
})
return request(app.callback())
.get('/')
.expect(500)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Internal Server Error')
})
})
describe('not http status code', () => {
it('should respond 500', () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'something else'
const err = new Error('some error')
err.status = 9999
throw err
})
return request(app.callback())
.get('/')
.expect(500)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Internal Server Error')
})
})
})
describe('when error from another scope thrown', () => {
it('should handle it like a normal error', async () => {
const ExternError = require('vm').runInNewContext('Error')
const app = new Koa()
const error = Object.assign(new ExternError('boom'), {
status: 418,
expose: true
})
app.use((ctx, next) => {
throw error
})
const gotRightErrorPromise = new Promise((resolve, reject) => {
app.on('error', receivedError => {
try {
assert.strictEqual(receivedError, error)
resolve()
} catch (e) {
reject(e)
}
})
})
await request(app.callback())
.get('/')
.expect(418)
await gotRightErrorPromise
})
})
describe('when non-error thrown', () => {
it('should respond with non-error thrown message', () => {
const app = new Koa()
app.use((ctx, next) => {
throw 'string error' // eslint-disable-line no-throw-literal
})
return request(app.callback())
.get('/')
.expect(500)
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect('Internal Server Error')
})
it('should use res.getHeaderNames() accessor when available', () => {
let removed = 0
const ctx = context()
ctx.app.emit = () => {}
ctx.res = {
getHeaderNames: () => ['content-type', 'content-length'],
removeHeader: () => removed++,
end: () => {},
emit: () => {}
}
ctx.onerror(new Error('error'))
assert.strictEqual(removed, 2)
})
it('should stringify error if it is an object', async () => {
const app = new Koa()
app.on('error', err => {
let assertionRan = false
assert.strictEqual(err.message, 'non-error thrown: {"key":"value"}')
assertionRan = true
assert(assertionRan, 'assertion was not executed')
})
app.use(async ctx => {
throw { key: 'value' } // eslint-disable-line no-throw-literal
})
await request(app.callback())
.get('/')
.expect(500)
.expect('Internal Server Error')
})
})
})
================================================
FILE: __tests__/context/state.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('supertest')
const assert = require('node:assert/strict')
const Koa = require('../..')
describe('ctx.state', () => {
it('should provide a ctx.state namespace', () => {
const app = new Koa()
app.use(ctx => {
assert.deepStrictEqual(ctx.state, {})
})
return request(app.callback())
.get('/')
.expect(404)
})
})
================================================
FILE: __tests__/context/throw.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const context = require('../../test-helpers/context')
const assert = require('node:assert/strict')
describe('ctx.throw(msg)', () => {
it('should set .status to 500', () => {
const ctx = context()
try {
ctx.throw('boom')
} catch (err) {
assert.strictEqual(err.status, 500)
assert.strictEqual(err.expose, false)
}
})
})
describe('ctx.throw(err)', () => {
it('should set .status to 500', () => {
const ctx = context()
const err = new Error('test')
try {
ctx.throw(err)
} catch (err) {
assert.strictEqual(err.status, 500)
assert.strictEqual(err.message, 'test')
assert.strictEqual(err.expose, false)
}
})
})
describe('ctx.throw(status, err)', () => {
it('should throw the error and set .status', () => {
const ctx = context()
const error = new Error('test')
try {
ctx.throw(422, error)
} catch (err) {
assert.strictEqual(err.status, 422)
assert.strictEqual(err.message, 'test')
assert.strictEqual(err.expose, true)
}
})
})
describe('ctx.throw(status, msg)', () => {
it('should throw an error', () => {
const ctx = context()
try {
ctx.throw(400, 'name required')
} catch (err) {
assert.strictEqual(err.message, 'name required')
assert.strictEqual(400, err.status)
assert.strictEqual(true, err.expose)
}
})
})
describe('ctx.throw(status)', () => {
it('should throw an error', () => {
const ctx = context()
try {
ctx.throw(400)
} catch (err) {
assert.strictEqual(err.message, 'Bad Request')
assert.strictEqual(err.status, 400)
assert.strictEqual(err.expose, true)
}
})
describe('when not valid status', () => {
it('should not expose', () => {
const ctx = context()
try {
const err = new Error('some error')
err.status = -1
ctx.throw(err)
} catch (err) {
assert.strictEqual(err.message, 'some error')
assert.strictEqual(err.expose, false)
}
})
})
})
describe('ctx.throw(status, msg, props)', () => {
it('should mixin props', () => {
const ctx = context()
try {
ctx.throw(400, 'msg', { prop: true })
} catch (err) {
assert.strictEqual(err.message, 'msg')
assert.strictEqual(err.status, 400)
assert.strictEqual(err.expose, true)
assert.strictEqual(err.prop, true)
}
})
describe('when props include status', () => {
it('should be ignored', () => {
const ctx = context()
try {
ctx.throw(400, 'msg', {
prop: true,
status: -1
})
} catch (err) {
assert.strictEqual(err.message, 'msg')
assert.strictEqual(err.status, 400)
assert.strictEqual(err.expose, true)
assert.strictEqual(err.prop, true)
}
})
})
})
describe('ctx.throw(msg, props)', () => {
it('should mixin props', () => {
const ctx = context()
try {
ctx.throw('msg', { prop: true })
} catch (err) {
assert.strictEqual(err.message, 'msg')
assert.strictEqual(err.status, 500)
assert.strictEqual(err.expose, false)
assert.strictEqual(err.prop, true)
}
})
})
describe('ctx.throw(status, props)', () => {
it('should mixin props', () => {
const ctx = context()
try {
ctx.throw(400, { prop: true })
} catch (err) {
assert.strictEqual(err.message, 'Bad Request')
assert.strictEqual(err.status, 400)
assert.strictEqual(err.expose, true)
assert.strictEqual(err.prop, true)
}
})
})
describe('ctx.throw(err, props)', () => {
it('should mixin props', () => {
const ctx = context()
try {
ctx.throw(new Error('test'), { prop: true })
} catch (err) {
assert.strictEqual(err.message, 'test')
assert.strictEqual(err.status, 500)
assert.strictEqual(err.expose, false)
assert.strictEqual(err.prop, true)
}
})
})
================================================
FILE: __tests__/context/toJSON.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.toJSON()', () => {
it('should return a json representation', () => {
const ctx = context()
ctx.req.method = 'POST'
ctx.req.url = '/items'
ctx.req.headers['content-type'] = 'text/plain'
ctx.status = 200
ctx.body = '<p>Hey</p>'
const obj = JSON.parse(JSON.stringify(ctx))
const req = obj.request
const res = obj.response
assert.deepStrictEqual({
method: 'POST',
url: '/items',
header: {
'content-type': 'text/plain'
}
}, req)
assert.deepStrictEqual({
status: 200,
message: 'OK',
header: {
'content-type': 'text/html; charset=utf-8',
'content-length': 10
}
}, res)
})
})
================================================
FILE: __tests__/lib/search-params.test.js
================================================
const { describe, it } = require('node:test')
const sp = require('../../lib/search-params')
const assert = require('node:assert/strict')
describe('search-params', () => {
describe('stringify', () => {
it('Should stringify a simple object', () => {
assert.deepStrictEqual(sp.stringify({ a: 1, b: 'b' }), 'a=1&b=b')
})
it('Should stringify an object with an array', () => {
assert.deepStrictEqual(sp.stringify({ a: [1, 2] }), 'a=1&a=2')
})
it('Should stringify an object with an array with a single value', () => {
assert.deepStrictEqual(sp.stringify({ a: [1] }), 'a=1')
})
it('Stringify an object with an array with a single empty value', () => {
assert.deepStrictEqual(sp.stringify({ a: [''] }), 'a=')
})
it('Should not stringify an object with a nested object', () => {
assert.deepStrictEqual(sp.stringify({ a: { b: 1 } }), 'a=')
})
})
describe('parse', () => {
it('Should parse a simple query string', () => {
assert.deepStrictEqual(sp.parse('a=1&b=2'), { a: '1', b: '2' })
})
it('Should parse a query string with same key and multiple values', () => {
assert.deepEqual(sp.parse('a=1&a=2'), { a: ['1', '2'] })
})
it('Should parse a query string with an array with a single empty value', () => {
assert.deepStrictEqual(sp.parse('a='), { a: '' })
})
})
})
================================================
FILE: __tests__/load-with-esm.test.js
================================================
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
describe('Load with esm', () => {
it('should default export koa', async () => {
const exported = await import('koa')
const required = require('../')
assert.strictEqual(exported.default, required)
})
it('should match exports own property names', async () => {
const exported = new Set(Object.getOwnPropertyNames(await import('koa')))
const required = new Set(Object.getOwnPropertyNames(require('../')))
// Remove constructor properties + default export.
for (const k of ['prototype', 'length', 'name']) {
required.delete(k)
}
// Commented out to "fix" CommonJS, ESM, bundling issue.
// @see https://github.com/koajs/koa/issues/1513
// exported.delete('default');
assert.strictEqual(exported.size, required.size)
assert.strictEqual([...exported].every(property => required.has(property)), true)
})
it('CommonJS exports default property', async () => {
const required = require('../')
assert.strictEqual(Object.prototype.hasOwnProperty.call(required, 'default'), true)
})
it('CommonJS exports default property referencing self', async () => {
const required = require('../')
assert.strictEqual(required.default, required)
})
})
================================================
FILE: __tests__/request/accept.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const Accept = require('accepts')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.accept', () => {
it('should return an Accept instance', () => {
const ctx = context()
ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain'
assert(ctx.accept instanceof Accept)
})
})
describe('ctx.accept=', () => {
it('should replace the accept object', () => {
const ctx = context()
ctx.req.headers.accept = 'text/plain'
assert.deepStrictEqual(ctx.accepts(), ['text/plain'])
const request = context.request()
request.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain'
ctx.accept = Accept(request.req)
assert.deepStrictEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*'])
})
})
================================================
FILE: __tests__/request/accepts.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.accepts(types)', () => {
describe('with no arguments', () => {
describe('when Accept is populated', () => {
it('should return all accepted types', () => {
const ctx = context()
ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain'
assert.deepStrictEqual(ctx.accepts(), ['text/html', 'text/plain', 'image/jpeg', 'application/*'])
})
})
})
describe('with no valid types', () => {
describe('when Accept is populated', () => {
it('should return false', () => {
const ctx = context()
ctx.req.headers.accept = 'application/*;q=0.2, image/jpeg;q=0.8, text/html, text/plain'
assert.strictEqual(ctx.accepts('image/png', 'image/tiff'), false)
})
})
describe('when Accept is not populated', () => {
it('should return the first type', () => {
const ctx = context()
assert.strictEqual(ctx.accepts('text/html', 'text/plain', 'image/jpeg', 'application/*'), 'text/html')
})
})
})
describe('when extensions are given', () => {
it('should convert to mime types', () => {
const ctx = context()
ctx.req.headers.accept = 'text/plain, text/html'
assert.strictEqual(ctx.accepts('html'), 'html')
assert.strictEqual(ctx.accepts('.html'), '.html')
assert.strictEqual(ctx.accepts('txt'), 'txt')
assert.strictEqual(ctx.accepts('.txt'), '.txt')
assert.strictEqual(ctx.accepts('png'), false)
})
})
describe('when an array is given', () => {
it('should return the first match', () => {
const ctx = context()
ctx.req.headers.accept = 'text/plain, text/html'
assert.strictEqual(ctx.accepts(['png', 'text', 'html']), 'text')
assert.strictEqual(ctx.accepts(['png', 'html']), 'html')
})
})
describe('when multiple arguments are given', () => {
it('should return the first match', () => {
const ctx = context()
ctx.req.headers.accept = 'text/plain, text/html'
assert.strictEqual(ctx.accepts('png', 'text', 'html'), 'text')
assert.strictEqual(ctx.accepts('png', 'html'), 'html')
})
})
describe('when value present in Accept is an exact match', () => {
it('should return the type', () => {
const ctx = context()
ctx.req.headers.accept = 'text/plain, text/html'
assert.strictEqual(ctx.accepts('text/html'), 'text/html')
assert.strictEqual(ctx.accepts('text/plain'), 'text/plain')
})
})
describe('when value present in Accept is a type match', () => {
it('should return the type', () => {
const ctx = context()
ctx.req.headers.accept = 'application/json, */*'
assert.strictEqual(ctx.accepts('text/html'), 'text/html')
assert.strictEqual(ctx.accepts('text/plain'), 'text/plain')
assert.strictEqual(ctx.accepts('image/png'), 'image/png')
})
})
describe('when value present in Accept is a subtype match', () => {
it('should return the type', () => {
const ctx = context()
ctx.req.headers.accept = 'application/json, text/*'
assert.strictEqual(ctx.accepts('text/html'), 'text/html')
assert.strictEqual(ctx.accepts('text/plain'), 'text/plain')
assert.strictEqual(ctx.accepts('image/png'), false)
assert.strictEqual(ctx.accepts('png'), false)
})
})
})
================================================
FILE: __tests__/request/acceptsCharsets.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.acceptsCharsets()', () => {
describe('with no arguments', () => {
describe('when Accept-Charset is populated', () => {
it('should return accepted types', () => {
const ctx = context()
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5'
assert.deepStrictEqual(ctx.acceptsCharsets(), ['utf-8', 'utf-7', 'iso-8859-1'])
})
})
})
describe('with multiple arguments', () => {
describe('when Accept-Charset is populated', () => {
describe('if any types match', () => {
it('should return the best fit', () => {
const ctx = context()
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5'
assert.strictEqual(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-8')
})
})
describe('if no types match', () => {
it('should return false', () => {
const ctx = context()
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5'
assert.strictEqual(ctx.acceptsCharsets('utf-16'), false)
})
})
})
describe('when Accept-Charset is not populated', () => {
it('should return the first type', () => {
const ctx = context()
assert.strictEqual(ctx.acceptsCharsets('utf-7', 'utf-8'), 'utf-7')
})
})
})
describe('with an array', () => {
it('should return the best fit', () => {
const ctx = context()
ctx.req.headers['accept-charset'] = 'utf-8, iso-8859-1;q=0.2, utf-7;q=0.5'
assert.strictEqual(ctx.acceptsCharsets(['utf-7', 'utf-8']), 'utf-8')
})
})
})
================================================
FILE: __tests__/request/acceptsEncodings.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.acceptsEncodings()', () => {
describe('with no arguments', () => {
describe('when Accept-Encoding is populated', () => {
it('should return accepted types', () => {
const ctx = context()
ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2'
assert.deepStrictEqual(ctx.acceptsEncodings(), ['gzip', 'compress', 'identity'])
assert.strictEqual(ctx.acceptsEncodings('gzip', 'compress'), 'gzip')
})
})
describe('when Accept-Encoding is not populated', () => {
it('should return identity', () => {
const ctx = context()
assert.deepStrictEqual(ctx.acceptsEncodings(), ['identity'])
assert.strictEqual(ctx.acceptsEncodings('gzip', 'deflate', 'identity'), 'identity')
})
})
})
describe('with multiple arguments', () => {
it('should return the best fit', () => {
const ctx = context()
ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2'
assert.strictEqual(ctx.acceptsEncodings('compress', 'gzip'), 'gzip')
assert.strictEqual(ctx.acceptsEncodings('gzip', 'compress'), 'gzip')
})
})
describe('with an array', () => {
it('should return the best fit', () => {
const ctx = context()
ctx.req.headers['accept-encoding'] = 'gzip, compress;q=0.2'
assert.strictEqual(ctx.acceptsEncodings(['compress', 'gzip']), 'gzip')
})
})
})
================================================
FILE: __tests__/request/acceptsLanguages.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.acceptsLanguages(langs)', () => {
describe('with no arguments', () => {
describe('when Accept-Language is populated', () => {
it('should return accepted types', () => {
const ctx = context()
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt'
assert.deepStrictEqual(ctx.acceptsLanguages(), ['es', 'pt', 'en'])
})
})
})
describe('with multiple arguments', () => {
describe('when Accept-Language is populated', () => {
describe('if any types types match', () => {
it('should return the best fit', () => {
const ctx = context()
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt'
assert.strictEqual(ctx.acceptsLanguages('es', 'en'), 'es')
})
})
describe('if no types match', () => {
it('should return false', () => {
const ctx = context()
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt'
assert.strictEqual(ctx.acceptsLanguages('fr', 'au'), false)
})
})
})
describe('when Accept-Language is not populated', () => {
it('should return the first type', () => {
const ctx = context()
assert.strictEqual(ctx.acceptsLanguages('es', 'en'), 'es')
})
})
})
describe('with an array', () => {
it('should return the best fit', () => {
const ctx = context()
ctx.req.headers['accept-language'] = 'en;q=0.8, es, pt'
assert.strictEqual(ctx.acceptsLanguages(['es', 'en']), 'es')
})
})
})
================================================
FILE: __tests__/request/charset.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
describe('req.charset', () => {
describe('with no content-type present', () => {
it('should return ""', () => {
const req = request()
assert(req.charset === '')
})
})
describe('with charset present', () => {
it('should return ""', () => {
const req = request()
req.header['content-type'] = 'text/plain'
assert(req.charset === '')
})
})
describe('with a charset', () => {
it('should return the charset', () => {
const req = request()
req.header['content-type'] = 'text/plain; charset=utf-8'
assert.strictEqual(req.charset, 'utf-8')
})
it('should return "" if content-type is invalid', () => {
const req = request()
req.header['content-type'] = 'application/json; application/text; charset=utf-8'
assert.strictEqual(req.charset, '')
})
})
})
================================================
FILE: __tests__/request/fresh.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.fresh', () => {
describe('the request method is not GET and HEAD', () => {
it('should return false', () => {
const ctx = context()
ctx.req.method = 'POST'
assert.strictEqual(ctx.fresh, false)
})
})
describe('the response is non-2xx', () => {
it('should return false', () => {
const ctx = context()
ctx.status = 404
ctx.req.method = 'GET'
ctx.req.headers['if-none-match'] = '123'
ctx.set('ETag', '123')
assert.strictEqual(ctx.fresh, false)
})
})
describe('the response is 2xx', () => {
describe('and etag matches', () => {
it('should return true', () => {
const ctx = context()
ctx.status = 200
ctx.req.method = 'GET'
ctx.req.headers['if-none-match'] = '123'
ctx.set('ETag', '123')
assert.strictEqual(ctx.fresh, true)
})
})
describe('and etag does not match', () => {
it('should return false', () => {
const ctx = context()
ctx.status = 200
ctx.req.method = 'GET'
ctx.req.headers['if-none-match'] = '123'
ctx.set('ETag', 'hey')
assert.strictEqual(ctx.fresh, false)
})
})
})
})
================================================
FILE: __tests__/request/get.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.get(name)', () => {
it('should return the field value', () => {
const ctx = context()
ctx.req.headers.host = 'http://google.com'
ctx.req.headers.referer = 'http://google.com'
assert.strictEqual(ctx.get('HOST'), 'http://google.com')
assert.strictEqual(ctx.get('Host'), 'http://google.com')
assert.strictEqual(ctx.get('host'), 'http://google.com')
assert.strictEqual(ctx.get('referer'), 'http://google.com')
assert.strictEqual(ctx.get('referrer'), 'http://google.com')
})
})
================================================
FILE: __tests__/request/header.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('req.header', () => {
it('should return the request header object', () => {
const req = request()
assert.deepStrictEqual(req.header, req.req.headers)
})
it('should set the request header object', () => {
const req = request()
req.header = { 'X-Custom-Headerfield': 'Its one header, with headerfields' }
assert.deepStrictEqual(req.header, req.req.headers)
})
})
================================================
FILE: __tests__/request/headers.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('req.headers', () => {
it('should return the request header object', () => {
const req = request()
assert.deepStrictEqual(req.headers, req.req.headers)
})
it('should set the request header object', () => {
const req = request()
req.headers = { 'X-Custom-Headerfield': 'Its one header, with headerfields' }
assert.deepStrictEqual(req.headers, req.req.headers)
})
})
================================================
FILE: __tests__/request/host.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
describe('req.host', () => {
it('should return host with port', () => {
const req = request()
req.header.host = 'foo.com:3000'
assert.strictEqual(req.host, 'foo.com:3000')
})
describe('with no host present', () => {
it('should return ""', () => {
const req = request()
assert.strictEqual(req.host, '')
})
})
describe('when less then HTTP/2', () => {
it('should not use :authority header', () => {
const req = request({
httpVersionMajor: 1,
httpVersion: '1.1'
})
req.header[':authority'] = 'foo.com:3000'
req.header.host = 'bar.com:8000'
assert.strictEqual(req.host, 'bar.com:8000')
})
})
describe('when HTTP/2', () => {
it('should use :authority header', () => {
const req = request({
httpVersionMajor: 2,
httpVersion: '2.0'
})
req.header[':authority'] = 'foo.com:3000'
req.header.host = 'bar.com:8000'
assert.strictEqual(req.host, 'foo.com:3000')
})
it('should use host header as fallback', () => {
const req = request({
httpVersionMajor: 2,
httpVersion: '2.0'
})
req.header.host = 'bar.com:8000'
assert.strictEqual(req.host, 'bar.com:8000')
})
})
describe('when X-Forwarded-Host is present', () => {
describe('and proxy is not trusted', () => {
it('should be ignored on HTTP/1', () => {
const req = request()
req.header['x-forwarded-host'] = 'bar.com'
req.header.host = 'foo.com'
assert.strictEqual(req.host, 'foo.com')
})
it('should be ignored on HTTP/2', () => {
const req = request({
httpVersionMajor: 2,
httpVersion: '2.0'
})
req.header['x-forwarded-host'] = 'proxy.com:8080'
req.header[':authority'] = 'foo.com:3000'
req.header.host = 'bar.com:8000'
assert.strictEqual(req.host, 'foo.com:3000')
})
})
describe('and proxy is trusted', () => {
it('should be used on HTTP/1', () => {
const req = request()
req.app.proxy = true
req.header['x-forwarded-host'] = 'bar.com, baz.com'
req.header.host = 'foo.com'
assert.strictEqual(req.host, 'bar.com')
})
it('should be used on HTTP/2', () => {
const req = request({
httpVersionMajor: 2,
httpVersion: '2.0'
})
req.app.proxy = true
req.header['x-forwarded-host'] = 'proxy.com:8080'
req.header[':authority'] = 'foo.com:3000'
req.header.host = 'bar.com:8000'
assert.strictEqual(req.host, 'proxy.com:8080')
})
})
})
describe('with Host header containing @', () => {
it('should correctly parse host from userinfo@host format', () => {
const req = request()
req.header.host = 'evil.com:fake@legitimate.com'
assert.strictEqual(req.host, 'legitimate.com')
})
it('should correctly parse host from user@host format', () => {
const req = request()
req.header.host = 'user@example.com'
assert.strictEqual(req.host, 'example.com')
})
it('should correctly parse host with port from userinfo@host:port format', () => {
const req = request()
req.header.host = 'user:pass@example.com:8080'
assert.strictEqual(req.host, 'example.com:8080')
})
it('should correctly parse @ in X-Forwarded-Host when proxy is trusted', () => {
const req = request()
req.app.proxy = true
req.header['x-forwarded-host'] = 'evil.com:fake@legitimate.com'
req.header.host = 'foo.com'
assert.strictEqual(req.host, 'legitimate.com')
})
it('should correctly parse @ in :authority on HTTP/2', () => {
const req = request({
httpVersionMajor: 2,
httpVersion: '2.0'
})
req.header[':authority'] = 'evil.com:fake@legitimate.com'
req.header.host = 'foo.com'
assert.strictEqual(req.host, 'legitimate.com')
})
it('should return empty string for invalid host with @', () => {
const req = request()
req.header.host = 'user@'
assert.strictEqual(req.host, '')
})
})
})
================================================
FILE: __tests__/request/hostname.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
describe('req.hostname', () => {
it('should return hostname void of port', () => {
const req = request()
req.header.host = 'foo.com:3000'
assert.strictEqual(req.hostname, 'foo.com')
})
describe('with no host present', () => {
it('should return ""', () => {
const req = request()
assert.strictEqual(req.hostname, '')
})
})
describe('with IPv6 in host', () => {
it('should parse localhost void of port', () => {
const req = request()
req.header.host = '[::1]'
assert.strictEqual(req.hostname, '[::1]')
})
it('should parse localhost with port 80', () => {
const req = request()
req.header.host = '[::1]:80'
assert.strictEqual(req.hostname, '[::1]')
})
it('should parse localhost with non-special schema port', () => {
const req = request()
req.header.host = '[::1]:1337'
assert.strictEqual(req.hostname, '[::1]')
})
it('should reduce IPv6 with non-special schema port as hostname', () => {
const req = request()
req.header.host = '[2001:cdba:0000:0000:0000:0000:3257:9652]:1337'
assert.strictEqual(req.hostname, '[2001:cdba::3257:9652]')
})
it('should return empty string when invalid', () => {
const req = request()
req.header.host = '[invalidIPv6]'
assert.strictEqual(req.hostname, '')
})
})
describe('when X-Forwarded-Host is present', () => {
describe('and proxy is not trusted', () => {
it('should be ignored', () => {
const req = request()
req.header['x-forwarded-host'] = 'bar.com'
req.header.host = 'foo.com'
assert.strictEqual(req.hostname, 'foo.com')
})
})
describe('and proxy is trusted', () => {
it('should be used', () => {
const req = request()
req.app.proxy = true
req.header['x-forwarded-host'] = 'bar.com, baz.com'
req.header.host = 'foo.com'
assert.strictEqual(req.hostname, 'bar.com')
})
})
})
describe('with Host header containing @', () => {
it('should correctly parse hostname from userinfo@host format', () => {
const req = request()
req.header.host = 'evil.com:fake@legitimate.com'
assert.strictEqual(req.hostname, 'legitimate.com')
})
it('should correctly parse hostname from user@host format', () => {
const req = request()
req.header.host = 'user@example.com'
assert.strictEqual(req.hostname, 'example.com')
})
it('should correctly parse hostname with port from userinfo@host:port format', () => {
const req = request()
req.header.host = 'user:pass@example.com:8080'
assert.strictEqual(req.hostname, 'example.com')
})
it('should correctly parse @ in X-Forwarded-Host when proxy is trusted', () => {
const req = request()
req.app.proxy = true
req.header['x-forwarded-host'] = 'evil.com:fake@legitimate.com'
req.header.host = 'foo.com'
assert.strictEqual(req.hostname, 'legitimate.com')
})
})
})
================================================
FILE: __tests__/request/href.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const Stream = require('stream')
const request = require('supertest')
const Koa = require('../../')
const context = require('../../test-helpers/context')
describe('ctx.href', () => {
it('should return the full request url', () => {
const socket = new Stream.Duplex()
const req = {
url: '/users/1?next=/dashboard',
headers: {
host: 'localhost'
},
socket,
__proto__: Stream.Readable.prototype
}
const ctx = context(req)
assert.strictEqual(ctx.href, 'http://localhost/users/1?next=/dashboard')
// change it also work
ctx.url = '/foo/users/1?next=/dashboard'
assert.strictEqual(ctx.href, 'http://localhost/users/1?next=/dashboard')
})
it('should work with `GET http://example.com/foo`', async () => {
const app = new Koa()
app.use(ctx => {
ctx.body = ctx.href
})
const res = await request(app.callback())
.get('/foo')
.set('Host', 'example.com')
.expect(200)
assert.strictEqual(res.text, 'http://example.com/foo')
})
})
================================================
FILE: __tests__/request/idempotent.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('ctx.idempotent', () => {
describe('when the request method is idempotent', () => {
it('should return true', () => {
['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'].forEach(check)
function check (method) {
const req = request()
req.method = method
assert.strictEqual(req.idempotent, true)
}
})
})
describe('when the request method is not idempotent', () => {
it('should return false', () => {
const req = request()
req.method = 'POST'
assert.strictEqual(req.idempotent, false)
})
})
})
================================================
FILE: __tests__/request/inspect.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
const util = require('util')
describe('req.inspect()', () => {
describe('with no request.req present', () => {
it('should return null', () => {
const req = request()
req.method = 'GET'
delete req.req
assert(undefined === req.inspect())
assert(util.inspect(req) === 'undefined')
})
})
it('should return a json representation', () => {
const req = request()
req.method = 'GET'
req.url = 'example.com'
req.header.host = 'example.com'
const expected = {
method: 'GET',
url: 'example.com',
header: {
host: 'example.com'
}
}
assert.deepStrictEqual(req.inspect(), expected)
assert.deepStrictEqual(util.inspect(req), util.inspect(expected))
})
})
================================================
FILE: __tests__/request/ip.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const Stream = require('stream')
const Koa = require('../..')
const Request = require('../../test-helpers/context').request
describe('req.ip', () => {
describe('with req.ips present', () => {
it('should return req.ips[0]', () => {
const app = new Koa()
const req = { headers: {}, socket: new Stream.Duplex() }
app.proxy = true
req.headers['x-forwarded-for'] = '127.0.0.1'
req.socket.remoteAddress = '127.0.0.2'
const request = Request(req, undefined, app)
assert.strictEqual(request.ip, '127.0.0.1')
})
})
describe('with no req.ips present', () => {
it('should return req.socket.remoteAddress', () => {
const req = { socket: new Stream.Duplex() }
req.socket.remoteAddress = '127.0.0.2'
const request = Request(req)
assert.strictEqual(request.ip, '127.0.0.2')
})
describe('with req.socket.remoteAddress not present', () => {
it('should return an empty string', () => {
const socket = new Stream.Duplex()
Object.defineProperty(socket, 'remoteAddress', {
get: () => undefined, // So that the helper doesn't override it with a reasonable value
set: () => {}
})
assert.strictEqual(Request({ socket }).ip, '')
})
})
})
it('should be lazy inited and cached', () => {
const req = { socket: new Stream.Duplex() }
req.socket.remoteAddress = '127.0.0.2'
const request = Request(req)
assert.strictEqual(request.ip, '127.0.0.2')
req.socket.remoteAddress = '127.0.0.1'
assert.strictEqual(request.ip, '127.0.0.2')
})
it('should reset ip work', () => {
const req = { socket: new Stream.Duplex() }
req.socket.remoteAddress = '127.0.0.2'
const request = Request(req)
assert.strictEqual(request.ip, '127.0.0.2')
request.ip = '127.0.0.1'
assert.strictEqual(request.ip, '127.0.0.1')
})
})
================================================
FILE: __tests__/request/ips.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('req.ips', () => {
describe('when X-Forwarded-For is present', () => {
describe('and proxy is not trusted', () => {
it('should be ignored', () => {
const req = request()
req.app.proxy = false
req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2'
assert.deepStrictEqual(req.ips, [])
})
})
describe('and proxy is trusted', () => {
it('should be used', () => {
const req = request()
req.app.proxy = true
req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2'
assert.deepStrictEqual(req.ips, ['127.0.0.1', '127.0.0.2'])
})
})
})
describe('when options.proxyIpHeader is present', () => {
describe('and proxy is not trusted', () => {
it('should be ignored', () => {
const req = request()
req.app.proxy = false
req.app.proxyIpHeader = 'x-client-ip'
req.header['x-client-ip'] = '127.0.0.1,127.0.0.2'
assert.deepStrictEqual(req.ips, [])
})
})
describe('and proxy is trusted', () => {
it('should be used', () => {
const req = request()
req.app.proxy = true
req.app.proxyIpHeader = 'x-client-ip'
req.header['x-client-ip'] = '127.0.0.1,127.0.0.2'
assert.deepStrictEqual(req.ips, ['127.0.0.1', '127.0.0.2'])
})
})
})
describe('when options.maxIpsCount is present', () => {
describe('and proxy is not trusted', () => {
it('should be ignored', () => {
const req = request()
req.app.proxy = false
req.app.maxIpsCount = 1
req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2'
assert.deepStrictEqual(req.ips, [])
})
})
describe('and proxy is trusted', () => {
it('should be used', () => {
const req = request()
req.app.proxy = true
req.app.maxIpsCount = 1
req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2'
assert.deepStrictEqual(req.ips, ['127.0.0.2'])
})
})
})
})
================================================
FILE: __tests__/request/is.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const context = require('../../test-helpers/context')
const assert = require('node:assert/strict')
describe('ctx.is(type)', () => {
it('should ignore params', () => {
const ctx = context()
ctx.header['content-type'] = 'text/html; charset=utf-8'
ctx.header['transfer-encoding'] = 'chunked'
assert.strictEqual(ctx.is('text/*'), 'text/html')
})
describe('when no body is given', () => {
it('should return null', () => {
const ctx = context()
assert.strictEqual(ctx.is(), null)
assert.strictEqual(ctx.is('image/*'), null)
assert.strictEqual(ctx.is('image/*', 'text/*'), null)
})
})
describe('when no content type is given', () => {
it('should return false', () => {
const ctx = context()
ctx.header['transfer-encoding'] = 'chunked'
assert.strictEqual(ctx.is(), false)
assert.strictEqual(ctx.is('image/*'), false)
assert.strictEqual(ctx.is('text/*', 'image/*'), false)
})
})
describe('give no types', () => {
it('should return the mime type', () => {
const ctx = context()
ctx.header['content-type'] = 'image/png'
ctx.header['transfer-encoding'] = 'chunked'
assert.strictEqual(ctx.is(), 'image/png')
})
})
describe('given one type', () => {
it('should return the type or false', () => {
const ctx = context()
ctx.header['content-type'] = 'image/png'
ctx.header['transfer-encoding'] = 'chunked'
assert.strictEqual(ctx.is('png'), 'png')
assert.strictEqual(ctx.is('.png'), '.png')
assert.strictEqual(ctx.is('image/png'), 'image/png')
assert.strictEqual(ctx.is('image/*'), 'image/png')
assert.strictEqual(ctx.is('*/png'), 'image/png')
assert.strictEqual(ctx.is('jpeg'), false)
assert.strictEqual(ctx.is('.jpeg'), false)
assert.strictEqual(ctx.is('image/jpeg'), false)
assert.strictEqual(ctx.is('text/*'), false)
assert.strictEqual(ctx.is('*/jpeg'), false)
})
})
describe('given multiple types', () => {
it('should return the first match or false', () => {
const ctx = context()
ctx.header['content-type'] = 'image/png'
ctx.header['transfer-encoding'] = 'chunked'
assert.strictEqual(ctx.is('png'), 'png')
assert.strictEqual(ctx.is('.png'), '.png')
assert.strictEqual(ctx.is('text/*', 'image/*'), 'image/png')
assert.strictEqual(ctx.is('image/*', 'text/*'), 'image/png')
assert.strictEqual(ctx.is('image/*', 'image/png'), 'image/png')
assert.strictEqual(ctx.is('image/png', 'image/*'), 'image/png')
assert.strictEqual(ctx.is(['text/*', 'image/*']), 'image/png')
assert.strictEqual(ctx.is(['image/*', 'text/*']), 'image/png')
assert.strictEqual(ctx.is(['image/*', 'image/png']), 'image/png')
assert.strictEqual(ctx.is(['image/png', 'image/*']), 'image/png')
assert.strictEqual(ctx.is('jpeg'), false)
assert.strictEqual(ctx.is('.jpeg'), false)
assert.strictEqual(ctx.is('text/*', 'application/*'), false)
assert.strictEqual(ctx.is('text/html', 'text/plain', 'application/json; charset=utf-8'), false)
})
})
describe('when Content-Type: application/x-www-form-urlencoded', () => {
it('should match "urlencoded"', () => {
const ctx = context()
ctx.header['content-type'] = 'application/x-www-form-urlencoded'
ctx.header['transfer-encoding'] = 'chunked'
assert.strictEqual(ctx.is('urlencoded'), 'urlencoded')
assert.strictEqual(ctx.is('json', 'urlencoded'), 'urlencoded')
assert.strictEqual(ctx.is('urlencoded', 'json'), 'urlencoded')
})
})
})
================================================
FILE: __tests__/request/length.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
describe('ctx.length', () => {
it('should return length in content-length', () => {
const req = request()
req.header['content-length'] = '10'
assert.strictEqual(req.length, 10)
})
it('should return undefined with no content-length present', () => {
const req = request()
assert.strictEqual(req.length, undefined)
})
})
================================================
FILE: __tests__/request/origin.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const Stream = require('stream')
const context = require('../../test-helpers/context')
describe('ctx.origin', () => {
it('should return the origin of url', () => {
const socket = new Stream.Duplex()
const req = {
url: '/users/1?next=/dashboard',
headers: {
host: 'localhost',
origin: 'http://example.com'
},
socket,
__proto__: Stream.Readable.prototype
}
const ctx = context(req)
assert.strictEqual(ctx.origin, 'http://example.com')
// change it also work
ctx.url = '/foo/users/1?next=/dashboard'
assert.strictEqual(ctx.origin, 'http://example.com')
})
})
================================================
FILE: __tests__/request/path.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
const parseurl = require('parseurl')
describe('ctx.path', () => {
it('should return the pathname', () => {
const ctx = context()
ctx.url = '/login?next=/dashboard'
assert.strictEqual(ctx.path, '/login')
})
})
describe('ctx.path=', () => {
it('should set the pathname', () => {
const ctx = context()
ctx.url = '/login?next=/dashboard'
ctx.path = '/logout'
assert.strictEqual(ctx.path, '/logout')
assert.strictEqual(ctx.url, '/logout?next=/dashboard')
})
it('should change .url but not .originalUrl', () => {
const ctx = context({ url: '/login' })
ctx.path = '/logout'
assert.strictEqual(ctx.url, '/logout')
assert.strictEqual(ctx.originalUrl, '/login')
assert.strictEqual(ctx.request.originalUrl, '/login')
})
it('should not affect parseurl', () => {
const ctx = context({ url: '/login?foo=bar' })
ctx.path = '/login'
const url = parseurl(ctx.req)
assert.strictEqual(url.path, '/login?foo=bar')
})
})
================================================
FILE: __tests__/request/protocol.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('req.protocol', () => {
describe('when encrypted', () => {
it('should return "https"', () => {
const req = request()
req.req.socket = { encrypted: true }
assert.strictEqual(req.protocol, 'https')
})
})
describe('when unencrypted', () => {
it('should return "http"', () => {
const req = request()
req.req.socket = {}
assert.strictEqual(req.protocol, 'http')
})
})
describe('when X-Forwarded-Proto is set', () => {
describe('and proxy is trusted', () => {
it('should be used', () => {
const req = request()
req.app.proxy = true
req.req.socket = {}
req.header['x-forwarded-proto'] = 'https, http'
assert.strictEqual(req.protocol, 'https')
})
describe('and X-Forwarded-Proto is empty', () => {
it('should return "http"', () => {
const req = request()
req.app.proxy = true
req.req.socket = {}
req.header['x-forwarded-proto'] = ''
assert.strictEqual(req.protocol, 'http')
})
})
})
describe('and proxy is not trusted', () => {
it('should not be used', () => {
const req = request()
req.req.socket = {}
req.header['x-forwarded-proto'] = 'https, http'
assert.strictEqual(req.protocol, 'http')
})
})
})
})
================================================
FILE: __tests__/request/query.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.query', () => {
describe('when missing', () => {
it('should return an empty object', () => {
const ctx = context({ url: '/' })
assert(!Object.keys(ctx.query).length)
})
it('should return the same object each time it\'s accessed', () => {
const ctx = context({ url: '/' })
ctx.query.a = '2'
assert.strictEqual(ctx.query.a, '2')
})
})
it('should return a parsed query string', () => {
const ctx = context({ url: '/?page=2' })
assert.strictEqual(ctx.query.page, '2')
})
})
describe('ctx.query=', () => {
it('should stringify and replace the query string and search', () => {
const ctx = context({ url: '/store/shoes' })
ctx.query = { page: 2, color: 'blue' }
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.querystring, 'page=2&color=blue')
assert.strictEqual(ctx.search, '?page=2&color=blue')
})
it('should change .url but not .originalUrl', () => {
const ctx = context({ url: '/store/shoes' })
ctx.query = { page: 2 }
assert.strictEqual(ctx.url, '/store/shoes?page=2')
assert.strictEqual(ctx.originalUrl, '/store/shoes')
assert.strictEqual(ctx.request.originalUrl, '/store/shoes')
})
})
================================================
FILE: __tests__/request/querystring.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
const parseurl = require('parseurl')
describe('ctx.querystring', () => {
it('should return the querystring', () => {
const ctx = context({ url: '/store/shoes?page=2&color=blue' })
assert.strictEqual(ctx.querystring, 'page=2&color=blue')
})
describe('when ctx.req not present', () => {
it('should return an empty string', () => {
const ctx = context()
ctx.request.req = null
assert.strictEqual(ctx.querystring, '')
})
})
})
describe('ctx.querystring=', () => {
it('should replace the querystring', () => {
const ctx = context({ url: '/store/shoes' })
ctx.querystring = 'page=2&color=blue'
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.querystring, 'page=2&color=blue')
})
it('should update ctx.search and ctx.query', () => {
const ctx = context({ url: '/store/shoes' })
ctx.querystring = 'page=2&color=blue'
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.search, '?page=2&color=blue')
assert.strictEqual(ctx.query.page, '2')
assert.strictEqual(ctx.query.color, 'blue')
})
it('should change .url but not .originalUrl', () => {
const ctx = context({ url: '/store/shoes' })
ctx.querystring = 'page=2&color=blue'
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.originalUrl, '/store/shoes')
assert.strictEqual(ctx.request.originalUrl, '/store/shoes')
})
it('should not affect parseurl', () => {
const ctx = context({ url: '/login?foo=bar' })
ctx.querystring = 'foo=bar'
const url = parseurl(ctx.req)
assert.strictEqual(url.path, '/login?foo=bar')
})
})
================================================
FILE: __tests__/request/search.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.search=', () => {
it('should replace the search', () => {
const ctx = context({ url: '/store/shoes' })
ctx.search = '?page=2&color=blue'
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.search, '?page=2&color=blue')
})
it('should update ctx.querystring and ctx.query', () => {
const ctx = context({ url: '/store/shoes' })
ctx.search = '?page=2&color=blue'
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.querystring, 'page=2&color=blue')
assert.strictEqual(ctx.query.page, '2')
assert.strictEqual(ctx.query.color, 'blue')
})
it('should change .url but not .originalUrl', () => {
const ctx = context({ url: '/store/shoes' })
ctx.search = '?page=2&color=blue'
assert.strictEqual(ctx.url, '/store/shoes?page=2&color=blue')
assert.strictEqual(ctx.originalUrl, '/store/shoes')
assert.strictEqual(ctx.request.originalUrl, '/store/shoes')
})
describe('when missing', () => {
it('should return ""', () => {
const ctx = context({ url: '/store/shoes' })
assert.strictEqual(ctx.search, '')
})
})
})
================================================
FILE: __tests__/request/secure.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('req.secure', () => {
it('should return true when encrypted', () => {
const req = request()
req.req.socket = { encrypted: true }
assert.strictEqual(req.secure, true)
})
})
================================================
FILE: __tests__/request/stale.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('req.stale', () => {
it('should be the inverse of req.fresh', () => {
const ctx = context()
ctx.status = 200
ctx.method = 'GET'
ctx.req.headers['if-none-match'] = '"123"'
ctx.set('ETag', '"123"')
assert.strictEqual(ctx.fresh, true)
assert.strictEqual(ctx.stale, false)
})
})
================================================
FILE: __tests__/request/subdomains.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const request = require('../../test-helpers/context').request
describe('req.subdomains', () => {
it('should return subdomain array', () => {
const req = request()
req.header.host = 'tobi.ferrets.example.com'
req.app.subdomainOffset = 2
assert.deepStrictEqual(req.subdomains, ['ferrets', 'tobi'])
req.app.subdomainOffset = 3
assert.deepStrictEqual(req.subdomains, ['tobi'])
})
it('should work with no host present', () => {
const req = request()
assert.deepStrictEqual(req.subdomains, [])
})
it('should check if the host is an ip address, even with a port', () => {
const req = request()
req.header.host = '127.0.0.1:3000'
assert.deepStrictEqual(req.subdomains, [])
})
})
================================================
FILE: __tests__/request/type.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
describe('req.type', () => {
it('should return type void of parameters', () => {
const req = request()
req.header['content-type'] = 'text/html; charset=utf-8'
assert.strictEqual(req.type, 'text/html')
})
it('should return empty string with no host present', () => {
const req = request()
assert.strictEqual(req.type, '')
})
})
================================================
FILE: __tests__/request/whatwg-url.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const request = require('../../test-helpers/context').request
const assert = require('node:assert/strict')
describe('req.URL', () => {
it('should not throw when host is void', () => {
// Accessing the URL should not throw.
request().URL // eslint-disable-line no-unused-expressions
})
it('should not throw when header.host is invalid', () => {
const req = request()
req.header.host = 'invalid host'
// Accessing the URL should not throw.
req.URL // eslint-disable-line no-unused-expressions
})
it('should return empty object when invalid', () => {
const req = request()
req.header.host = 'invalid host'
assert.deepStrictEqual(req.URL, Object.create(null))
})
})
================================================
FILE: __tests__/response/append.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.append(name, val)', () => {
it('should append multiple headers', () => {
const ctx = context()
ctx.append('x-foo', 'bar1')
ctx.append('x-foo', 'bar2')
assert.deepStrictEqual(ctx.response.header['x-foo'], ['bar1', 'bar2'])
})
it('should accept array of values', () => {
const ctx = context()
ctx.append('Set-Cookie', ['foo=bar', 'fizz=buzz'])
ctx.append('Set-Cookie', 'hi=again')
assert.deepStrictEqual(ctx.response.header['set-cookie'], ['foo=bar', 'fizz=buzz', 'hi=again'])
})
it('should get reset by res.set(field, val)', () => {
const ctx = context()
ctx.append('Link', '<http://localhost/>')
ctx.append('Link', '<http://localhost:80/>')
ctx.set('Link', '<http://127.0.0.1/>')
assert.strictEqual(ctx.response.header.link, '<http://127.0.0.1/>')
})
it('should work with res.set(field, val) first', () => {
const ctx = context()
ctx.set('Link', '<http://localhost/>')
ctx.append('Link', '<http://localhost:80/>')
assert.deepStrictEqual(ctx.response.header.link, ['<http://localhost/>', '<http://localhost:80/>'])
})
})
================================================
FILE: __tests__/response/attachment.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
const request = require('supertest')
const Koa = require('../..')
describe('ctx.attachment([filename])', () => {
describe('security: prevent Content-Type override (GHSA-c5vw-j4hf-j526)', () => {
it('should NOT override Content-Type when already set', () => {
const ctx = context()
ctx.response.set('Content-Type', 'application/octet-stream')
ctx.attachment('malicious.html')
assert.strictEqual(ctx.response.get('Content-Type'), 'application/octet-stream')
assert.strictEqual(ctx.response.header['content-disposition'], 'attachment; filename="malicious.html"')
})
it('should preserve safe Content-Type for SVG files', () => {
const ctx = context()
ctx.response.set('Content-Type', 'application/octet-stream')
ctx.attachment('image.svg')
assert.strictEqual(ctx.response.get('Content-Type'), 'application/octet-stream')
})
it('should set Content-Type when not previously set', () => {
const ctx = context()
ctx.attachment('document.pdf')
assert.strictEqual(ctx.response.get('Content-Type'), 'application/pdf')
})
})
describe('when given a filename', () => {
it('should set the filename param', () => {
const ctx = context()
ctx.attachment('path/to/tobi.png')
const str = 'attachment; filename="tobi.png"'
assert.strictEqual(ctx.response.header['content-disposition'], str)
})
})
describe('when omitting filename', () => {
it('should not set filename param', () => {
const ctx = context()
ctx.attachment()
assert.strictEqual(ctx.response.header['content-disposition'], 'attachment')
})
})
describe('when given a non-ascii filename', () => {
it('should set the encodeURI filename param', () => {
const ctx = context()
ctx.attachment('path/to/include-no-ascii-char-中文名-ok.png')
const str = 'attachment; filename="include-no-ascii-char-???-ok.png"; filename*=UTF-8\'\'include-no-ascii-char-%E4%B8%AD%E6%96%87%E5%90%8D-ok.png'
assert.strictEqual(ctx.response.header['content-disposition'], str)
})
it('should work with http client', () => {
const app = new Koa()
app.use((ctx, next) => {
ctx.attachment('path/to/include-no-ascii-char-中文名-ok.json')
ctx.body = { foo: 'bar' }
})
return request(app.callback())
.get('/')
.expect('content-disposition', 'attachment; filename="include-no-ascii-char-???-ok.json"; filename*=UTF-8\'\'include-no-ascii-char-%E4%B8%AD%E6%96%87%E5%90%8D-ok.json')
.expect({ foo: 'bar' })
.expect(200)
})
})
})
// reference test case of content-disposition module
describe('contentDisposition(filename, options)', () => {
describe('with "fallback" option', () => {
it('should require a string or Boolean', () => {
const ctx = context()
assert.throws(() => { ctx.attachment('plans.pdf', { fallback: 42 }) },
/fallback.*string/)
})
it('should default to true', () => {
const ctx = context()
ctx.attachment('€ rates.pdf')
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="? rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf')
})
describe('when "false"', () => {
it('should not generate ISO-8859-1 fallback', () => {
const ctx = context()
ctx.attachment('£ and € rates.pdf', { fallback: false })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
})
it('should keep ISO-8859-1 filename', () => {
const ctx = context()
ctx.attachment('£ rates.pdf', { fallback: false })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="£ rates.pdf"')
})
})
describe('when "true"', () => {
it('should generate ISO-8859-1 fallback', () => {
const ctx = context()
ctx.attachment('£ and € rates.pdf', { fallback: true })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="£ and ? rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
})
it('should pass through ISO-8859-1 filename', () => {
const ctx = context()
ctx.attachment('£ rates.pdf', { fallback: true })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="£ rates.pdf"')
})
})
describe('when a string', () => {
it('should require an ISO-8859-1 string', () => {
const ctx = context()
assert.throws(() => { ctx.attachment('€ rates.pdf', { fallback: '€ rates.pdf' }) },
/fallback.*iso-8859-1/i)
})
it('should use as ISO-8859-1 fallback', () => {
const ctx = context()
ctx.attachment('£ and € rates.pdf', { fallback: '£ and EURO rates.pdf' })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="£ and EURO rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf')
})
it('should use as fallback even when filename is ISO-8859-1', () => {
const ctx = context()
ctx.attachment('"£ rates".pdf', { fallback: '£ rates.pdf' })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="£ rates.pdf"; filename*=UTF-8\'\'%22%C2%A3%20rates%22.pdf')
})
it('should do nothing if equal to filename', () => {
const ctx = context()
ctx.attachment('plans.pdf', { fallback: 'plans.pdf' })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="plans.pdf"')
})
it('should use the basename of the string', () => {
const ctx = context()
ctx.attachment('€ rates.pdf', { fallback: '/path/to/EURO rates.pdf' })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment; filename="EURO rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf')
})
it('should do nothing without filename option', () => {
const ctx = context()
ctx.attachment(undefined, { fallback: 'plans.pdf' })
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment')
})
})
})
describe('with "type" option', () => {
it('should default to attachment', () => {
const ctx = context()
ctx.attachment()
assert.strictEqual(ctx.response.header['content-disposition'],
'attachment')
})
it('should require a string', () => {
const ctx = context()
assert.throws(() => { ctx.attachment(undefined, { type: 42 }) },
/invalid type/)
})
it('should require a valid type', () => {
const ctx = context()
assert.throws(() => { ctx.attachment(undefined, { type: 'invlaid;type' }) },
/invalid type/)
})
it('should create a header with inline type', () => {
const ctx = context()
ctx.attachment(undefined, { type: 'inline' })
assert.strictEqual(ctx.response.header['content-disposition'],
'inline')
})
it('should create a header with inline type and filename', () => {
const ctx = context()
ctx.attachment('plans.pdf', { type: 'inline' })
assert.strictEqual(ctx.response.header['content-disposition'],
'inline; filename="plans.pdf"')
})
it('should normalize type', () => {
const ctx = context()
ctx.attachment(undefined, { type: 'INLINE' })
assert.strictEqual(ctx.response.header['content-disposition'],
'inline')
})
})
})
================================================
FILE: __tests__/response/back.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const context = require('../../test-helpers/context')
describe('ctx.back([alt])', () => {
it('should redirect to Referrer', () => {
const ctx = context({ url: '/', headers: { host: 'example.com' } })
ctx.req.headers.referrer = '/login'
ctx.back()
assert.equal(ctx.response.header.location, '/login')
})
it('should redirect to the same origin referrer', () => {
const ctx = context()
ctx.req.headers.host = 'example.com'
ctx.req.headers.referrer = 'https://example.com/login'
ctx.back()
assert.equal(ctx.response.header.location, 'https://example.com/login')
})
it('should redirect to root if the same origin referrer is not present', () => {
const ctx = context()
ctx.req.headers.host = 'example.com'
ctx.req.headers.referrer = 'https://other.com/login'
ctx.back()
assert.equal(ctx.response.header.location, '/')
})
it('should redirect to Referer to a relative path', () => {
const ctx = context({ url: '/', headers: { host: 'example.com' } })
ctx.req.headers.referer = '/login'
ctx.back()
assert.equal(ctx.response.header.location, '/login')
})
it('should redirect to Referer to a same origin url', () => {
const ctx = context({ url: '/', headers: { host: 'example.com', referer: 'https://example.com/login' } })
ctx.back()
assert.equal(ctx.response.header.location, 'https://example.com/login')
})
it('should default to alt', () => {
const ctx = context()
ctx.back('/index.html')
assert.equal(ctx.response.header.location, '/index.html')
})
it('should default redirect to /', () => {
const ctx = context()
ctx.back()
assert.equal(ctx.response.header.location, '/')
})
it('should fix Trailing Double-Slash security issue', () => {
const ctx = context({ url: '/', headers: { host: 'example.com' } })
ctx.req.headers.referrer = '//evil.com/login/'
ctx.back()
assert.equal(ctx.response.header.location, '/')
ctx.back('/home')
assert.equal(ctx.response.header.location, '/home')
})
})
================================================
FILE: __tests__/response/body.test.js
================================================
'use strict'
const { describe, it } = require('node:test')
const response = require('../../test-helpers/context').response
const CustomStream = require('../../test-helpers/stream')
const assert = require('node:assert/strict')
const fs = require('fs')
const Stream = require('stream')
describe('res.body=', () => {
describe('when Content-Type is set', () => {
it('should not override', () => {
const res = response()
res.type = 'png'
res.body = Buffer.from('something')
assert.strictEqual('image/png', res.header['content-type'])
})
describe('when body is an object', () => {
it('should override as json', () => {
const res = response()
res.body = '<em>hey</em>'
assert.strictEqual('text/html; charset=utf-8', res.header['content-type'])
res.body = { foo: 'bar' }
assert.strictEqual('application/json; charset=utf-8', res.header['content-type'])
})
})
it('should override length', () => {
const res = response()
res.type = 'html'
res.body = 'something'
assert.strictEqual(res.length, 9)
})
})
describe('when a string is given', () => {
it('should default to text', () => {
const res = response()
res.body = 'Tobi'
assert.strictEqual('text/plain; charset=utf-8', res.header['content-type'])
})
it('should set length', () => {
const res = response()
res.body = 'Tobi'
assert.strictEqual(4, res.header['content-length'])
})
describe('and contains a non-leading <', () => {
it('should default to text', () => {
const res = response()
res.body = 'aklsdjf < klajsdlfjasd'
assert.strictEqual('text/plain; charset=utf-8', res.header['content-type'])
})
})
})
describe('when an html string is given', () => {
it('should default to html', () => {
const res = response()
res.body = '<h1>Tobi</h1>'
assert.strictEqual('text/html; charset=utf-8', res.header['content-type'])
})
it('should set length', () => {
const string = '<h1>Tobi</h1>'
const res = response()
res.body = string
assert.strictEqual(res.length, Buffer.byteLength(string))
})
it('should set length when body is overridden', () => {
const string = '<h1>Tobi</h1>'
const res = response()
res.body = string
res.body = string + string
assert.strictEqual(res.length, 2 * Buffer.byteLength(string))
})
describe('when it contains leading whitespace', () => {
it('should default to html', () => {
const res = response()
res.body = ' <h1>Tobi</h1>'
assert.strictEqual('text/html; charset=utf-8', res.header['content-type'])
})
})
})
describe('when an xml string is given', () => {
it('should default to html', () => {
/**
* ctx test is to show that we're not going
* to be stricter with the html sniff
* or that we will sniff other string types.
* You should `.type=` if ctx simple test fails.
*/
const res = response()
res.body = '<?xml version="1.0" encoding="UTF-8"?>\n<俄语>данные</俄语>'
assert.strictEqual('text/html; charset=utf-8', res.header['content-type'])
})
})
describe('when a stream is given', () => {
it('should default to an octet stream', () => {
const res = response()
res.body = fs.createReadStream('LICENSE')
assert.strictEqual('application/octet-stream', res.header['content-type'])
})
it('should support custom stream', () => {
const res = response()
res.body = new CustomStream.Readable()
assert.strictEqual('application/octet-stream', res.header['content-type'])
})
it('should not add error handler to stream (handled by pipeline)', () => {
const res = response()
const body = new Stream.PassThrough()
assert.strictEqual(body.listenerCount('error'), 0)
res.body = body
assert.strictEqual(body.listenerCount('error'), 0)
res.body = body
assert.strictEqual(body.listenerCount('error'), 0)
})
it('should NOT cleanup original stream when replaced by new stream (to support wrapping middleware)', () => {
const res = response()
const stream1 = new Stream.PassThrough()
const stream2 = new Stream.PassThrough()
res.body = stream1
res.body = stream2
assert.strictEqual(stream1.destroyed, false)
assert.strictEqual(stream2.destroyed, false)
})
it('should cleanup original stream when replaced by null', () => {
const res = response()
const stream = new Stream.PassThrough()
res.body = stream
res.body = null
assert.strictEqual(stream.destroyed, true)
})
it('should not throw unhandled errors when replacing failing stream', async () => {
const res = response()
const stream1 = new Stream.Readable({
read () {
}
})
const stream2 = new Stream.PassThrough()
res.body = stream1
res.body = stream2
await new Promise((resolve) => {
process.nextTick(() => {
stream1.emit('error', new Error('stream1 error'))
setTimeout(resolve, 10)
})
})
})
it('should handle multiple sequential stream replacements', () => {
const res = response()
const stream1 = new Stream.PassThrough()
const stream2 = new Stream.PassThrough()
const stream3 = new Stream.PassThrough()
res.body = stream1
res.body = stream2
res.body = stream3
assert.strictEqual(stream1.destroyed, false)
assert.strictEqual(stream2.destroyed, false)
assert.strictEqual(stream3.destroyed, false)
})
it('should handle four sequential stream replacements', () => {
const res = response()
const stream1 = new Stream.PassThrough()
const stream2 = new Stream.PassThrough()
const stream3 = new Stream.PassThrough()
const stream4 = new Stream.PassThrough()
res.body = stream1
res.body = stream2
res.body = stream3
res.body = stream4
assert.strictEqual(stream1.destroyed, false)
assert.strictEqual(stream2.destroyed, false)
assert.strictEqual(stream3.destroyed, false)
assert.strictEqual(stream4.destroyed, false)
})
it('should cleanup stream when replaced by string', () => {
const res = response()
const stream = new Stream.PassThrough()
res.body = stream
res.body = 'hello'
assert.strictEqual(stream.destroyed, true)
})
it('should cleanup stream when replaced by buffer', () => {
const res = response()
const stream = new Stream.PassThrough()
res.body = stream
res.body = Buffer.from('hello')
assert.strictEqual(stream.destroyed, true)
})
it('should cleanup stream when replaced by object', () => {
const res = response()
const stream = new Stream.PassThrough()
res.body = stream
res.body = { foo: 'bar' }
assert.strictEqual(stream.destroyed, true)
})
it('should support wrapping stream middleware (like koa-compress)', async () => {
const res = response()
const sourceStream = new Stream.Readable({
read () {
this.push('hello world')
this.push(null)
}
})
res.body = sourceStream
const wrappedStream = new Stream.PassThrough()
sourceStream.pipe(wrappedStream)
res.body = wrappedStream
assert.strictEqual(sourceStream.destroyed, false)
assert.strictEqual(wrappedStream.destroyed, false)
const chunks = []
for await (const chunk of wrappedStream) {
chunks.push(chunk)
}
const result = Buffer.concat(chunks).toString()
assert.strictEqual(result, 'hello world')
})
})
describe('when a buffer is given', () => {
it('should default to an octet stream', () => {
const res = response()
res.body = Buffer.from('hey')
assert.strictEqual('application/octet-stream', res.header['content-type'])
})
it('should set length', ()
gitextract_l_l5wk61/
├── .codecov.yml
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── node.js.yml
│ └── npm-publish.yml
├── .gitignore
├── .mailmap
├── AUTHORS
├── CODE_OF_CONDUCT.md
├── History.md
├── LICENSE
├── Readme.md
├── __tests__/
│ ├── .eslintrc.yml
│ ├── application/
│ │ ├── compose.test.js
│ │ ├── context.test.js
│ │ ├── currentContext.test.js
│ │ ├── index.test.js
│ │ ├── inspect.test.js
│ │ ├── onerror.test.js
│ │ ├── request.test.js
│ │ ├── respond.test.js
│ │ ├── response.test.js
│ │ ├── toJSON.test.js
│ │ └── use.test.js
│ ├── context/
│ │ ├── assert.test.js
│ │ ├── cookies.test.js
│ │ ├── inspect.test.js
│ │ ├── onerror.test.js
│ │ ├── state.test.js
│ │ ├── throw.test.js
│ │ └── toJSON.test.js
│ ├── lib/
│ │ └── search-params.test.js
│ ├── load-with-esm.test.js
│ ├── request/
│ │ ├── accept.test.js
│ │ ├── accepts.test.js
│ │ ├── acceptsCharsets.test.js
│ │ ├── acceptsEncodings.test.js
│ │ ├── acceptsLanguages.test.js
│ │ ├── charset.test.js
│ │ ├── fresh.test.js
│ │ ├── get.test.js
│ │ ├── header.test.js
│ │ ├── headers.test.js
│ │ ├── host.test.js
│ │ ├── hostname.test.js
│ │ ├── href.test.js
│ │ ├── idempotent.test.js
│ │ ├── inspect.test.js
│ │ ├── ip.test.js
│ │ ├── ips.test.js
│ │ ├── is.test.js
│ │ ├── length.test.js
│ │ ├── origin.test.js
│ │ ├── path.test.js
│ │ ├── protocol.test.js
│ │ ├── query.test.js
│ │ ├── querystring.test.js
│ │ ├── search.test.js
│ │ ├── secure.test.js
│ │ ├── stale.test.js
│ │ ├── subdomains.test.js
│ │ ├── type.test.js
│ │ └── whatwg-url.test.js
│ └── response/
│ ├── append.test.js
│ ├── attachment.test.js
│ ├── back.test.js
│ ├── body.test.js
│ ├── etag.test.js
│ ├── flushHeaders.test.js
│ ├── get.test.js
│ ├── has.test.js
│ ├── header.test.js
│ ├── headers.test.js
│ ├── inspect.test.js
│ ├── is.test.js
│ ├── last-modified.test.js
│ ├── length.test.js
│ ├── message.test.js
│ ├── redirect.test.js
│ ├── remove.test.js
│ ├── set.test.js
│ ├── socket.test.js
│ ├── status.test.js
│ ├── type.test.js
│ ├── vary.test.js
│ └── writable.test.js
├── docs/
│ ├── api/
│ │ ├── context.md
│ │ ├── index.md
│ │ ├── request.md
│ │ └── response.md
│ ├── error-handling.md
│ ├── faq.md
│ ├── guide.md
│ ├── koa-vs-express.md
│ ├── migration-v1-to-v2.md
│ ├── migration-v2-to-v3.md
│ └── troubleshooting.md
├── lib/
│ ├── application.js
│ ├── context.js
│ ├── is-stream.js
│ ├── only.js
│ ├── request.js
│ ├── response.js
│ └── search-params.js
├── package.json
└── test-helpers/
├── context.js
└── stream.js
SYMBOL INDEX (115 symbols across 12 files)
FILE: __tests__/application/compose.test.js
method compose (line 38) | compose (fns) {
FILE: __tests__/application/respond.test.js
method start (line 580) | start (controller) {
method start (line 602) | start (controller) {
method start (line 692) | start (controller) {
method start (line 888) | start (controller) {
FILE: __tests__/context/cookies.test.js
method set (line 97) | set (key, value) {
FILE: __tests__/request/idempotent.test.js
function check (line 11) | function check (method) {
FILE: __tests__/response/body.test.js
method read (line 156) | read () {
method read (line 239) | read () {
FILE: __tests__/response/redirect.test.js
function escape (line 114) | function escape (html) {
FILE: __tests__/response/status.test.js
function strip (line 64) | function strip (status) {
FILE: lib/application.js
method constructor (line 64) | constructor (options) {
method listen (line 102) | listen (...args) {
method toJSON (line 116) | toJSON () {
method inspect (line 127) | inspect () {
method use (line 141) | use (fn) {
method callback (line 156) | callback () {
method currentContext (line 177) | get currentContext () {
method handleRequest (line 187) | handleRequest (ctx, fnMiddleware) {
method createContext (line 202) | createContext (req, res) {
method onerror (line 227) | onerror (err) {
method default (line 248) | static get default () {
function respond (line 257) | function respond (ctx) {
FILE: lib/context.js
constant COOKIES (line 14) | const COOKIES = Symbol('context#cookies')
method inspect (line 30) | inspect () {
method toJSON (line 47) | toJSON () {
method throw (line 95) | throw (...args) {
method onerror (line 106) | onerror (err) {
method cookies (line 164) | get cookies () {
method cookies (line 174) | set cookies (_cookies) {
FILE: lib/request.js
constant URL (line 7) | const URL = require('url').URL
method header (line 35) | get header () {
method header (line 45) | set header (val) {
method headers (line 56) | get headers () {
method headers (line 66) | set headers (val) {
method url (line 77) | get url () {
method url (line 87) | set url (val) {
method origin (line 98) | get origin () {
method href (line 109) | get href () {
method method (line 122) | get method () {
method method (line 133) | set method (val) {
method path (line 144) | get path () {
method path (line 155) | set path (path) {
method query (line 172) | get query () {
method query (line 185) | set query (obj) {
method querystring (line 196) | get querystring () {
method querystring (line 208) | set querystring (str) {
method search (line 225) | get search () {
method search (line 238) | set search (str) {
method host (line 251) | get host () {
method hostname (line 281) | get hostname () {
method URL (line 296) | get URL () {
method fresh (line 318) | get fresh () {
method stale (line 342) | get stale () {
method idempotent (line 353) | get idempotent () {
method socket (line 365) | get socket () {
method charset (line 376) | get charset () {
method length (line 392) | get length () {
method protocol (line 410) | get protocol () {
method secure (line 426) | get secure () {
method ips (line 442) | get ips () {
method ip (line 463) | get ip () {
method ip (line 470) | set ip (_ip) {
method subdomains (line 490) | get subdomains () {
method accept (line 508) | get accept () {
method accept (line 519) | set accept (obj) {
method accepts (line 564) | accepts (...args) {
method acceptsEncodings (line 581) | acceptsEncodings (...args) {
method acceptsCharsets (line 598) | acceptsCharsets (...args) {
method acceptsLanguages (line 615) | acceptsLanguages (...args) {
method is (line 646) | is (type, ...types) {
method type (line 658) | get type () {
method get (line 686) | get (field) {
method inspect (line 704) | inspect () {
method toJSON (line 716) | toJSON () {
function splitCommaSeparatedValues (line 745) | function splitCommaSeparatedValues (value, limit) {
FILE: lib/response.js
method socket (line 37) | get socket () {
method header (line 48) | get header () {
method headers (line 62) | get headers () {
method status (line 73) | get status () {
method status (line 84) | set status (code) {
method message (line 102) | get message () {
method message (line 113) | set message (msg) {
method body (line 124) | get body () {
method body (line 135) | set body (val) {
method length (line 241) | set length (n) {
method length (line 254) | get length () {
method headerSent (line 273) | get headerSent () {
method vary (line 284) | vary (field) {
method redirect (line 302) | redirect (url) {
method back (line 338) | back (alt) {
method attachment (line 363) | attachment (filename, options) {
method type (line 386) | set type (type) {
method lastModified (line 405) | set lastModified (val) {
method lastModified (line 417) | get lastModified () {
method etag (line 434) | set etag (val) {
method etag (line 446) | get etag () {
method type (line 458) | get type () {
method is (line 474) | is (type, ...types) {
method get (line 494) | get (field) {
method has (line 515) | has (field) {
method set (line 537) | set (field, val) {
method append (line 563) | append (field, val) {
method remove (line 582) | remove (field) {
method writable (line 597) | get writable () {
method inspect (line 619) | inspect () {
method toJSON (line 633) | toJSON () {
method flushHeaders (line 645) | flushHeaders () {
FILE: test-helpers/stream.js
class Readable (line 5) | class Readable extends EventEmitter {
method pipe (line 6) | pipe () {}
method read (line 7) | read () {}
method destroy (line 8) | destroy () {}
method readable (line 9) | get readable () {
method readableObjectMode (line 13) | get readableObjectMode () {
method destroyed (line 17) | get destroyed () {
Condensed preview — 108 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (361K chars).
[
{
"path": ".codecov.yml",
"chars": 64,
"preview": "coverage:\n parsers:\n javascript:\n enable_partials: yes\n"
},
{
"path": ".editorconfig",
"chars": 238,
"preview": "# editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_"
},
{
"path": ".github/FUNDING.yml",
"chars": 23,
"preview": "open_collective: koajs\n"
},
{
"path": ".github/dependabot.yml",
"chars": 181,
"preview": "version: 2\nupdates:\n - package-ecosystem: npm\n directory: \"/\"\n schedule:\n interval: daily\n open-pull-requ"
},
{
"path": ".github/workflows/node.js.yml",
"chars": 604,
"preview": "name: Node.js CI\n\non:\n push:\n branches: [master]\n pull_request:\n branches: [master]\n\njobs:\n build:\n\n runs-on"
},
{
"path": ".github/workflows/npm-publish.yml",
"chars": 1598,
"preview": "name: NPM Publish\n\n# Trigger when tags matching semver format are pushed, or manually via workflow_dispatch.\n# Manual tr"
},
{
"path": ".gitignore",
"chars": 61,
"preview": "node_modules\ntest.js\ncoverage\nnpm-debug.log\n.idea\n*.iml\ndist\n"
},
{
"path": ".mailmap",
"chars": 48,
"preview": "Michał Gołębiowski-Owczarek <m.goleb@gmail.com>\n"
},
{
"path": "AUTHORS",
"chars": 6458,
"preview": "小菜 <xtx1130@gmail.com>\nAaron Heckmann <aaron.heckmann+github@gmail.com>\nAdam L <skyros@gmail.com>\nAdam Lau <skyros@gmail"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3228,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "History.md",
"chars": 32665,
"preview": "\n> [!IMPORTANT]\n> Moving forwards we are using the GitHub releases page at <https://github.com/koajs/koa/releases> in co"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "(The MIT License)\n\nCopyright (c) 2019 Koa contributors\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "Readme.md",
"chars": 16953,
"preview": "<img src=\"/docs/logo.png\" alt=\"Koa middleware framework for nodejs\"/>\n\n [![gitter][gitter-image]][gitter-url]\n [![NPM "
},
{
"path": "__tests__/.eslintrc.yml",
"chars": 291,
"preview": "env:\n jest: true\n\nrules:\n space-before-blocks: [2, {functions: never, keywords: always}]\n no-unused-expressions: 0\n "
},
{
"path": "__tests__/application/compose.test.js",
"chars": 1357,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/application/context.test.js",
"chars": 747,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/application/currentContext.test.js",
"chars": 3305,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/application/index.test.js",
"chars": 2356,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst { once } "
},
{
"path": "__tests__/application/inspect.test.js",
"chars": 571,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst util = re"
},
{
"path": "__tests__/application/onerror.test.js",
"chars": 1545,
"preview": "'use strict'\n\nconst { describe, it, mock } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Koa"
},
{
"path": "__tests__/application/request.test.js",
"chars": 775,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/application/respond.test.js",
"chars": 29633,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst statuses = requir"
},
{
"path": "__tests__/application/response.test.js",
"chars": 2572,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/application/toJSON.test.js",
"chars": 380,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Koa = req"
},
{
"path": "__tests__/application/use.test.js",
"chars": 1871,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/context/assert.test.js",
"chars": 630,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst "
},
{
"path": "__tests__/context/cookies.test.js",
"chars": 2960,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/context/inspect.test.js",
"chars": 786,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst prototype = require('../../lib/context')\nconst assert "
},
{
"path": "__tests__/context/onerror.test.js",
"chars": 6756,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/context/state.test.js",
"chars": 433,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/context/throw.test.js",
"chars": 4018,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst "
},
{
"path": "__tests__/context/toJSON.test.js",
"chars": 874,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/lib/search-params.test.js",
"chars": 1384,
"preview": "const { describe, it } = require('node:test')\nconst sp = require('../../lib/search-params')\nconst assert = require('node"
},
{
"path": "__tests__/load-with-esm.test.js",
"chars": 1313,
"preview": "const { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\n\ndescribe('Load with esm', ()"
},
{
"path": "__tests__/request/accept.test.js",
"chars": 931,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst Accept = require('accepts')\nconst assert = require('no"
},
{
"path": "__tests__/request/accepts.test.js",
"chars": 3506,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/acceptsCharsets.test.js",
"chars": 1798,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/acceptsEncodings.test.js",
"chars": 1564,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/acceptsLanguages.test.js",
"chars": 1703,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/charset.test.js",
"chars": 1021,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/request/fresh.test.js",
"chars": 1371,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/get.test.js",
"chars": 683,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/header.test.js",
"chars": 567,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/headers.test.js",
"chars": 571,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/host.test.js",
"chars": 4328,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/request/hostname.test.js",
"chars": 3208,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/request/href.test.js",
"chars": 1147,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Stream = "
},
{
"path": "__tests__/request/idempotent.test.js",
"chars": 753,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/inspect.test.js",
"chars": 920,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/request/ip.test.js",
"chars": 1996,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Stream = "
},
{
"path": "__tests__/request/ips.test.js",
"chars": 2188,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/is.test.js",
"chars": 3691,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst "
},
{
"path": "__tests__/request/length.test.js",
"chars": 516,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/request/origin.test.js",
"chars": 745,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Stream = "
},
{
"path": "__tests__/request/path.test.js",
"chars": 1148,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/protocol.test.js",
"chars": 1531,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/query.test.js",
"chars": 1411,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/querystring.test.js",
"chars": 1860,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/search.test.js",
"chars": 1334,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/secure.test.js",
"chars": 365,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/stale.test.js",
"chars": 476,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/request/subdomains.test.js",
"chars": 836,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/request/type.test.js",
"chars": 524,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/request/whatwg-url.test.js",
"chars": 773,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('../../test-helpers/context').reques"
},
{
"path": "__tests__/response/append.test.js",
"chars": 1287,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/attachment.test.js",
"chars": 7874,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/back.test.js",
"chars": 2160,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/body.test.js",
"chars": 10099,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst response = require('../../test-helpers/context').respo"
},
{
"path": "__tests__/response/etag.test.js",
"chars": 854,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response "
},
{
"path": "__tests__/response/flushHeaders.test.js",
"chars": 4362,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst request = require('supertest')\nconst assert = require("
},
{
"path": "__tests__/response/get.test.js",
"chars": 1677,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/has.test.js",
"chars": 619,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/header.test.js",
"chars": 1299,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/response/headers.test.js",
"chars": 596,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response "
},
{
"path": "__tests__/response/inspect.test.js",
"chars": 946,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst response = require('../../test-helpers/context').respo"
},
{
"path": "__tests__/response/is.test.js",
"chars": 3030,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst "
},
{
"path": "__tests__/response/last-modified.test.js",
"chars": 1130,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response "
},
{
"path": "__tests__/response/length.test.js",
"chars": 2158,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst response = require('../../test-helpers/context').respo"
},
{
"path": "__tests__/response/message.test.js",
"chars": 839,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response "
},
{
"path": "__tests__/response/redirect.test.js",
"chars": 3716,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst request ="
},
{
"path": "__tests__/response/remove.test.js",
"chars": 373,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/set.test.js",
"chars": 1172,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/socket.test.js",
"chars": 384,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst response "
},
{
"path": "__tests__/response/status.test.js",
"chars": 3569,
"preview": "'use strict'\n\nconst { describe, it, beforeEach } = require('node:test')\nconst response = require('../../test-helpers/con"
},
{
"path": "__tests__/response/type.test.js",
"chars": 2262,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst context = require('../../test-helpers/context')\nconst "
},
{
"path": "__tests__/response/vary.test.js",
"chars": 981,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst context ="
},
{
"path": "__tests__/response/writable.test.js",
"chars": 2904,
"preview": "'use strict'\n\nconst { describe, it } = require('node:test')\nconst assert = require('node:assert/strict')\nconst Koa = req"
},
{
"path": "docs/api/context.md",
"chars": 7593,
"preview": "# Context\n\n A Koa Context encapsulates Node's `request` and `response` objects\n into a single object which provides ma"
},
{
"path": "docs/api/index.md",
"chars": 8612,
"preview": "# Installation\n\n Koa requires __node v18.0.0__ or higher for ES2015 and async function support.\n\n You can quickly inst"
},
{
"path": "docs/api/request.md",
"chars": 10580,
"preview": "# Request\n\n A Koa `Request` object is an abstraction on top of Node's vanilla request object,\n providing additional fu"
},
{
"path": "docs/api/response.md",
"chars": 9645,
"preview": "# Response\n\n A Koa `Response` object is an abstraction on top of Node's vanilla response object,\n providing additional"
},
{
"path": "docs/error-handling.md",
"chars": 1942,
"preview": "# Error Handling\n\n## Try-Catch\n\n Using async functions means that you can try-catch `next`.\n This example adds a `.sta"
},
{
"path": "docs/faq.md",
"chars": 2069,
"preview": "# Frequently Asked Questions\n\n## Does Koa replace Express?\n\n It's more like Connect, but a lot of the Express goodies\n "
},
{
"path": "docs/guide.md",
"chars": 9025,
"preview": "\n# Guide\n\n This guide covers Koa topics that are not directly API related, such as best practices for writing middlewar"
},
{
"path": "docs/koa-vs-express.md",
"chars": 3778,
"preview": "# Koa vs Express\n\n Philosophically, Koa aims to \"fix and replace Node\", whereas Express \"augments Node\".\n Koa uses pro"
},
{
"path": "docs/migration-v1-to-v2.md",
"chars": 3980,
"preview": "# Migrating from Koa v1.x to v2.x\n\n## New middleware signature \n\nKoa v2 introduces a new signature for middleware.\n\n**Ol"
},
{
"path": "docs/migration-v2-to-v3.md",
"chars": 4810,
"preview": "# Migrating from Koa v2.x to v3.x\n\n## Breaking Changes\n\n### Node.js Version Requirement\n\nKoa v3 requires **Node.js v18.0"
},
{
"path": "docs/troubleshooting.md",
"chars": 6598,
"preview": "# Troubleshooting Koa\n\n- [Whenever I try to access my route, it sends back a 404](#whenever-i-try-to-access-my-route-it-"
},
{
"path": "lib/application.js",
"chars": 8637,
"preview": "'use strict'\n\n/**\n * Module dependencies.\n */\nconst util = require('node:util')\nconst debug = util.debuglog('koa:applica"
},
{
"path": "lib/context.js",
"chars": 5511,
"preview": "'use strict'\n\n/**\n * Module dependencies.\n */\n\nconst util = require('util')\nconst createError = require('http-errors')\nc"
},
{
"path": "lib/is-stream.js",
"chars": 539,
"preview": "'use strict'\n\nconst Stream = require('stream')\n\n// TODO: use a third party library for this\n\nmodule.exports = (stream) ="
},
{
"path": "lib/only.js",
"chars": 195,
"preview": "module.exports = (obj, keys) => {\n const ret = {}\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i]\n "
},
{
"path": "lib/request.js",
"chars": 15265,
"preview": "'use strict'\n\n/**\n * Module dependencies.\n */\n\nconst URL = require('url').URL\nconst net = require('net')\nconst accepts ="
},
{
"path": "lib/response.js",
"chars": 13704,
"preview": "'use strict'\n\n/**\n * Module dependencies.\n */\n\nconst assert = require('node:assert')\nconst extname = require('node:path'"
},
{
"path": "lib/search-params.js",
"chars": 888,
"preview": "const URLSearchParams = require('url').URLSearchParams\n\nmodule.exports = {\n stringify: (obj) => {\n const searchParam"
},
{
"path": "package.json",
"chars": 1711,
"preview": "{\n \"name\": \"koa\",\n \"version\": \"3.1.2\",\n \"description\": \"Koa web app framework\",\n \"main\": \"lib/application.js\",\n \"ex"
},
{
"path": "test-helpers/context.js",
"chars": 812,
"preview": "'use strict'\n\nconst Stream = require('stream')\nconst Koa = require('../lib/application')\n\nmodule.exports = (req, res, ap"
},
{
"path": "test-helpers/stream.js",
"chars": 308,
"preview": "'use strict'\n\nconst { EventEmitter } = require('events')\n\nclass Readable extends EventEmitter {\n pipe () {}\n read () {"
}
]
About this extraction
This page contains the full source code of the koajs/koa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 108 files (333.7 KB), approximately 92.9k tokens, and a symbol index with 115 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.