Initial commit: upload services folder
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
../../accepts@1.3.8/node_modules/accepts
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../cache-content-type@1.0.1/node_modules/cache-content-type
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../content-disposition@0.5.4/node_modules/content-disposition
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../content-type@1.0.5/node_modules/content-type
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../cookies@0.9.1/node_modules/cookies
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../debug@4.4.3/node_modules/debug
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../delegates@1.0.0/node_modules/delegates
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../depd@2.0.0/node_modules/depd
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../destroy@1.2.0/node_modules/destroy
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../encodeurl@1.0.2/node_modules/encodeurl
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../escape-html@1.0.3/node_modules/escape-html
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../fresh@0.5.2/node_modules/fresh
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../http-assert@1.5.0/node_modules/http-assert
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../http-errors@1.8.1/node_modules/http-errors
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../is-generator-function@1.1.2/node_modules/is-generator-function
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../koa-compose@4.1.0/node_modules/koa-compose
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../koa-convert@2.0.0/node_modules/koa-convert
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
(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.
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
<img src="/docs/logo.png" alt="Koa middleware framework for nodejs"/>
|
||||
|
||||
[![gitter][gitter-image]][gitter-url]
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![build status][travis-image]][travis-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 v7.6.0__ or higher for ES2015 and async function support.
|
||||
|
||||
```
|
||||
$ 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.
|
||||
- [Workshop](https://github.com/koajs/workshop) - A workshop to learn the basics of Koa, Express' spiritual successor.
|
||||
- [Introduction Screencast](https://knowthen.com/episode-3-koajs-quickstart-guide/) - An introduction to installing and getting started with Koa
|
||||
|
||||
|
||||
## 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 will be removed in v3**
|
||||
|
||||
Please see the [Migration Guide](docs/migration.md) for more information on upgrading from v1.x and
|
||||
using v1.x middleware with 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
|
||||
|
||||
- [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
|
||||
|
||||
## Job Board
|
||||
|
||||
Looking for a career upgrade?
|
||||
|
||||
<a href="https://astro.netlify.com/automattic"><img src="https://astro.netlify.com/static/automattic.png"></a>
|
||||
<a href="https://astro.netlify.com/segment"><img src="https://astro.netlify.com/static/segment.png"></a>
|
||||
<a href="https://astro.netlify.com/auth0"><img src="https://astro.netlify.com/static/auth0.png"/></a>
|
||||
|
||||
## 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
|
||||
[travis-image]: https://img.shields.io/travis/koajs/koa/master.svg?style=flat-square
|
||||
[travis-url]: https://travis-ci.org/koajs/koa
|
||||
[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
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import mod from "../lib/application.js";
|
||||
|
||||
export default mod;
|
||||
export const HttpError = mod.HttpError;
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const isGeneratorFunction = require('is-generator-function');
|
||||
const debug = require('debug')('koa:application');
|
||||
const onFinished = require('on-finished');
|
||||
const assert = require('assert');
|
||||
const response = require('./response');
|
||||
const compose = require('koa-compose');
|
||||
const context = require('./context');
|
||||
const request = require('./request');
|
||||
const statuses = require('statuses');
|
||||
const Emitter = require('events');
|
||||
const util = require('util');
|
||||
const Stream = require('stream');
|
||||
const http = require('http');
|
||||
const only = require('only');
|
||||
const convert = require('koa-convert');
|
||||
const deprecate = require('depd')('koa');
|
||||
const { HttpError } = require('http-errors');
|
||||
|
||||
/**
|
||||
* Expose `Application` class.
|
||||
* Inherits from `Emitter.prototype`.
|
||||
*/
|
||||
|
||||
module.exports = class Application extends Emitter {
|
||||
/**
|
||||
* Initialize a new `Application`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} [options] Application options
|
||||
* @param {string} [options.env='development'] Environment
|
||||
* @param {string[]} [options.keys] Signed cookie keys
|
||||
* @param {boolean} [options.proxy] Trust proxy headers
|
||||
* @param {number} [options.subdomainOffset] Subdomain offset
|
||||
* @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For
|
||||
* @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)
|
||||
*
|
||||
*/
|
||||
|
||||
constructor(options) {
|
||||
super();
|
||||
options = options || {};
|
||||
this.proxy = options.proxy || false;
|
||||
this.subdomainOffset = options.subdomainOffset || 2;
|
||||
this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
|
||||
this.maxIpsCount = options.maxIpsCount || 0;
|
||||
this.env = options.env || process.env.NODE_ENV || 'development';
|
||||
if (options.keys) this.keys = options.keys;
|
||||
this.middleware = [];
|
||||
this.context = Object.create(context);
|
||||
this.request = Object.create(request);
|
||||
this.response = Object.create(response);
|
||||
// util.inspect.custom support for node 6+
|
||||
/* istanbul ignore else */
|
||||
if (util.inspect.custom) {
|
||||
this[util.inspect.custom] = this.inspect;
|
||||
}
|
||||
if (options.asyncLocalStorage) {
|
||||
const { AsyncLocalStorage } = require('async_hooks');
|
||||
assert(AsyncLocalStorage, 'Requires node 12.17.0 or higher to enable asyncLocalStorage');
|
||||
this.ctxStorage = new AsyncLocalStorage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for:
|
||||
*
|
||||
* http.createServer(app.callback()).listen(...)
|
||||
*
|
||||
* @param {Mixed} ...
|
||||
* @return {Server}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
listen(...args) {
|
||||
debug('listen');
|
||||
const server = http.createServer(this.callback());
|
||||
return server.listen(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON representation.
|
||||
* We only bother showing settings.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toJSON() {
|
||||
return only(this, [
|
||||
'subdomainOffset',
|
||||
'proxy',
|
||||
'env'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect implementation.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
inspect() {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the given middleware `fn`.
|
||||
*
|
||||
* Old-style middleware will be converted.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Application} self
|
||||
* @api public
|
||||
*/
|
||||
|
||||
use(fn) {
|
||||
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
|
||||
if (isGeneratorFunction(fn)) {
|
||||
deprecate('Support for generators will be removed in v3. ' +
|
||||
'See the documentation for examples of how to convert old middleware ' +
|
||||
'https://github.com/koajs/koa/blob/master/docs/migration.md');
|
||||
fn = convert(fn);
|
||||
}
|
||||
debug('use %s', fn._name || fn.name || '-');
|
||||
this.middleware.push(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a request handler callback
|
||||
* for node's native http server.
|
||||
*
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
callback() {
|
||||
const fn = compose(this.middleware);
|
||||
|
||||
if (!this.listenerCount('error')) this.on('error', this.onerror);
|
||||
|
||||
const handleRequest = (req, res) => {
|
||||
const ctx = this.createContext(req, res);
|
||||
if (!this.ctxStorage) {
|
||||
return this.handleRequest(ctx, fn);
|
||||
}
|
||||
return this.ctxStorage.run(ctx, async() => {
|
||||
return await this.handleRequest(ctx, fn);
|
||||
});
|
||||
};
|
||||
|
||||
return handleRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* return currnect contenxt from async local storage
|
||||
*/
|
||||
get currentContext() {
|
||||
if (this.ctxStorage) return this.ctxStorage.getStore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request in callback.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
handleRequest(ctx, fnMiddleware) {
|
||||
const res = ctx.res;
|
||||
res.statusCode = 404;
|
||||
const onerror = err => ctx.onerror(err);
|
||||
const handleResponse = () => respond(ctx);
|
||||
onFinished(res, onerror);
|
||||
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new context.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
createContext(req, res) {
|
||||
const context = Object.create(this.context);
|
||||
const request = context.request = Object.create(this.request);
|
||||
const response = context.response = Object.create(this.response);
|
||||
context.app = request.app = response.app = this;
|
||||
context.req = request.req = response.req = req;
|
||||
context.res = request.res = response.res = res;
|
||||
request.ctx = response.ctx = context;
|
||||
request.response = response;
|
||||
response.request = request;
|
||||
context.originalUrl = request.originalUrl = req.url;
|
||||
context.state = {};
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default error handler.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @api private
|
||||
*/
|
||||
|
||||
onerror(err) {
|
||||
// When dealing with cross-globals a normal `instanceof` check doesn't work properly.
|
||||
// See https://github.com/koajs/koa/issues/1466
|
||||
// We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
|
||||
const isNativeError =
|
||||
Object.prototype.toString.call(err) === '[object Error]' ||
|
||||
err instanceof Error;
|
||||
if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err));
|
||||
|
||||
if (404 === err.status || err.expose) return;
|
||||
if (this.silent) return;
|
||||
|
||||
const msg = err.stack || err.toString();
|
||||
console.error(`\n${msg.replace(/^/gm, ' ')}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Help TS users comply to CommonJS, ESM, bundler mismatch.
|
||||
* @see https://github.com/koajs/koa/issues/1513
|
||||
*/
|
||||
|
||||
static get default() {
|
||||
return Application;
|
||||
}
|
||||
|
||||
createAsyncCtxStorageMiddleware() {
|
||||
const app = this;
|
||||
return async function asyncCtxStorage(ctx, next) {
|
||||
await app.ctxStorage.run(ctx, async() => {
|
||||
return await next();
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Response helper.
|
||||
*/
|
||||
|
||||
function respond(ctx) {
|
||||
// allow bypassing koa
|
||||
if (false === ctx.respond) return;
|
||||
|
||||
if (!ctx.writable) return;
|
||||
|
||||
const res = ctx.res;
|
||||
let body = ctx.body;
|
||||
const code = ctx.status;
|
||||
|
||||
// ignore body
|
||||
if (statuses.empty[code]) {
|
||||
// strip headers
|
||||
ctx.body = null;
|
||||
return res.end();
|
||||
}
|
||||
|
||||
if ('HEAD' === ctx.method) {
|
||||
if (!res.headersSent && !ctx.response.has('Content-Length')) {
|
||||
const { length } = ctx.response;
|
||||
if (Number.isInteger(length)) ctx.length = length;
|
||||
}
|
||||
return res.end();
|
||||
}
|
||||
|
||||
// status body
|
||||
if (null == body) {
|
||||
if (ctx.response._explicitNullBody) {
|
||||
ctx.response.remove('Content-Type');
|
||||
ctx.response.remove('Transfer-Encoding');
|
||||
return res.end();
|
||||
}
|
||||
if (ctx.req.httpVersionMajor >= 2) {
|
||||
body = String(code);
|
||||
} else {
|
||||
body = ctx.message || String(code);
|
||||
}
|
||||
if (!res.headersSent) {
|
||||
ctx.type = 'text';
|
||||
ctx.length = Buffer.byteLength(body);
|
||||
}
|
||||
return res.end(body);
|
||||
}
|
||||
|
||||
// responses
|
||||
if (Buffer.isBuffer(body)) return res.end(body);
|
||||
if ('string' === typeof body) return res.end(body);
|
||||
if (body instanceof Stream) return body.pipe(res);
|
||||
|
||||
// body: json
|
||||
body = JSON.stringify(body);
|
||||
if (!res.headersSent) {
|
||||
ctx.length = Buffer.byteLength(body);
|
||||
}
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HttpError available to consumers of the library so that consumers don't
|
||||
* have a direct dependency upon `http-errors`
|
||||
*/
|
||||
|
||||
module.exports.HttpError = HttpError;
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const util = require('util');
|
||||
const createError = require('http-errors');
|
||||
const httpAssert = require('http-assert');
|
||||
const delegate = require('delegates');
|
||||
const statuses = require('statuses');
|
||||
const Cookies = require('cookies');
|
||||
|
||||
const COOKIES = Symbol('context#cookies');
|
||||
|
||||
/**
|
||||
* Context prototype.
|
||||
*/
|
||||
|
||||
const proto = module.exports = {
|
||||
|
||||
/**
|
||||
* util.inspect() implementation, which
|
||||
* just returns the JSON output.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
inspect() {
|
||||
if (this === proto) return this;
|
||||
return this.toJSON();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return JSON representation.
|
||||
*
|
||||
* Here we explicitly invoke .toJSON() on each
|
||||
* object, as iteration will otherwise fail due
|
||||
* to the getters and cause utilities such as
|
||||
* clone() to fail.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
request: this.request.toJSON(),
|
||||
response: this.response.toJSON(),
|
||||
app: this.app.toJSON(),
|
||||
originalUrl: this.originalUrl,
|
||||
req: '<original node req>',
|
||||
res: '<original node res>',
|
||||
socket: '<original node socket>'
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Similar to .throw(), adds assertion.
|
||||
*
|
||||
* this.assert(this.user, 401, 'Please login!');
|
||||
*
|
||||
* See: https://github.com/jshttp/http-assert
|
||||
*
|
||||
* @param {Mixed} test
|
||||
* @param {Number} status
|
||||
* @param {String} message
|
||||
* @api public
|
||||
*/
|
||||
|
||||
assert: httpAssert,
|
||||
|
||||
/**
|
||||
* Throw an error with `status` (default 500) and
|
||||
* `msg`. Note that these are user-level
|
||||
* errors, and the message may be exposed to the client.
|
||||
*
|
||||
* this.throw(403)
|
||||
* this.throw(400, 'name required')
|
||||
* this.throw('something exploded')
|
||||
* this.throw(new Error('invalid'))
|
||||
* this.throw(400, new Error('invalid'))
|
||||
*
|
||||
* See: https://github.com/jshttp/http-errors
|
||||
*
|
||||
* Note: `status` should only be passed as the first parameter.
|
||||
*
|
||||
* @param {String|Number|Error} err, msg or status
|
||||
* @param {String|Number|Error} [err, msg or status]
|
||||
* @param {Object} [props]
|
||||
* @api public
|
||||
*/
|
||||
|
||||
throw(...args) {
|
||||
throw createError(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Default error handling.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @api private
|
||||
*/
|
||||
|
||||
onerror(err) {
|
||||
// don't do anything if there is no error.
|
||||
// this allows you to pass `this.onerror`
|
||||
// to node-style callbacks.
|
||||
if (null == err) return;
|
||||
|
||||
// When dealing with cross-globals a normal `instanceof` check doesn't work properly.
|
||||
// See https://github.com/koajs/koa/issues/1466
|
||||
// We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
|
||||
const isNativeError =
|
||||
Object.prototype.toString.call(err) === '[object Error]' ||
|
||||
err instanceof Error;
|
||||
if (!isNativeError) err = new Error(util.format('non-error thrown: %j', err));
|
||||
|
||||
let headerSent = false;
|
||||
if (this.headerSent || !this.writable) {
|
||||
headerSent = err.headerSent = true;
|
||||
}
|
||||
|
||||
// delegate
|
||||
this.app.emit('error', err, this);
|
||||
|
||||
// nothing we can do here other
|
||||
// than delegate to the app-level
|
||||
// handler and log.
|
||||
if (headerSent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { res } = this;
|
||||
|
||||
// first unset all headers
|
||||
/* istanbul ignore else */
|
||||
if (typeof res.getHeaderNames === 'function') {
|
||||
res.getHeaderNames().forEach(name => res.removeHeader(name));
|
||||
} else {
|
||||
res._headers = {}; // Node < 7.7
|
||||
}
|
||||
|
||||
// then set those specified
|
||||
this.set(err.headers);
|
||||
|
||||
// force text/plain
|
||||
this.type = 'text';
|
||||
|
||||
let statusCode = err.status || err.statusCode;
|
||||
|
||||
// ENOENT support
|
||||
if ('ENOENT' === err.code) statusCode = 404;
|
||||
|
||||
// default to 500
|
||||
if ('number' !== typeof statusCode || !statuses[statusCode]) statusCode = 500;
|
||||
|
||||
// respond
|
||||
const code = statuses[statusCode];
|
||||
const msg = err.expose ? err.message : code;
|
||||
this.status = err.status = statusCode;
|
||||
this.length = Buffer.byteLength(msg);
|
||||
res.end(msg);
|
||||
},
|
||||
|
||||
get cookies() {
|
||||
if (!this[COOKIES]) {
|
||||
this[COOKIES] = new Cookies(this.req, this.res, {
|
||||
keys: this.app.keys,
|
||||
secure: this.request.secure
|
||||
});
|
||||
}
|
||||
return this[COOKIES];
|
||||
},
|
||||
|
||||
set cookies(_cookies) {
|
||||
this[COOKIES] = _cookies;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom inspection implementation for newer Node.js versions.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (util.inspect.custom) {
|
||||
module.exports[util.inspect.custom] = module.exports.inspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response delegation.
|
||||
*/
|
||||
|
||||
delegate(proto, 'response')
|
||||
.method('attachment')
|
||||
.method('redirect')
|
||||
.method('remove')
|
||||
.method('vary')
|
||||
.method('has')
|
||||
.method('set')
|
||||
.method('append')
|
||||
.method('flushHeaders')
|
||||
.access('status')
|
||||
.access('message')
|
||||
.access('body')
|
||||
.access('length')
|
||||
.access('type')
|
||||
.access('lastModified')
|
||||
.access('etag')
|
||||
.getter('headerSent')
|
||||
.getter('writable');
|
||||
|
||||
/**
|
||||
* Request delegation.
|
||||
*/
|
||||
|
||||
delegate(proto, 'request')
|
||||
.method('acceptsLanguages')
|
||||
.method('acceptsEncodings')
|
||||
.method('acceptsCharsets')
|
||||
.method('accepts')
|
||||
.method('get')
|
||||
.method('is')
|
||||
.access('querystring')
|
||||
.access('idempotent')
|
||||
.access('socket')
|
||||
.access('search')
|
||||
.access('method')
|
||||
.access('query')
|
||||
.access('path')
|
||||
.access('url')
|
||||
.access('accept')
|
||||
.getter('origin')
|
||||
.getter('href')
|
||||
.getter('subdomains')
|
||||
.getter('protocol')
|
||||
.getter('host')
|
||||
.getter('hostname')
|
||||
.getter('URL')
|
||||
.getter('header')
|
||||
.getter('headers')
|
||||
.getter('secure')
|
||||
.getter('stale')
|
||||
.getter('fresh')
|
||||
.getter('ips')
|
||||
.getter('ip');
|
||||
+748
@@ -0,0 +1,748 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const URL = require('url').URL;
|
||||
const net = require('net');
|
||||
const accepts = require('accepts');
|
||||
const contentType = require('content-type');
|
||||
const stringify = require('url').format;
|
||||
const parse = require('parseurl');
|
||||
const qs = require('querystring');
|
||||
const typeis = require('type-is');
|
||||
const fresh = require('fresh');
|
||||
const only = require('only');
|
||||
const util = require('util');
|
||||
|
||||
const IP = Symbol('context#ip');
|
||||
|
||||
/**
|
||||
* Prototype.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Return request header.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get header() {
|
||||
return this.req.headers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set request header.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set header(val) {
|
||||
this.req.headers = val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return request header, alias as request.header
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get headers() {
|
||||
return this.req.headers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set request header, alias as request.header
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set headers(val) {
|
||||
this.req.headers = val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get request URL.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get url() {
|
||||
return this.req.url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set request URL.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set url(val) {
|
||||
this.req.url = val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get origin of URL.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get origin() {
|
||||
return `${this.protocol}://${this.host}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get full request URL.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get href() {
|
||||
// support: `GET http://example.com/foo`
|
||||
if (/^https?:\/\//i.test(this.originalUrl)) return this.originalUrl;
|
||||
return this.origin + this.originalUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get request method.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get method() {
|
||||
return this.req.method;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set request method.
|
||||
*
|
||||
* @param {String} val
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set method(val) {
|
||||
this.req.method = val;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get request pathname.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get path() {
|
||||
return parse(this.req).pathname;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set pathname, retaining the query string when present.
|
||||
*
|
||||
* @param {String} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set path(path) {
|
||||
const url = parse(this.req);
|
||||
if (url.pathname === path) return;
|
||||
|
||||
url.pathname = path;
|
||||
url.path = null;
|
||||
|
||||
this.url = stringify(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get parsed query string.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get query() {
|
||||
const str = this.querystring;
|
||||
const c = this._querycache = this._querycache || {};
|
||||
return c[str] || (c[str] = qs.parse(str));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set query string as an object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set query(obj) {
|
||||
this.querystring = qs.stringify(obj);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get query string.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get querystring() {
|
||||
if (!this.req) return '';
|
||||
return parse(this.req).query || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Set query string.
|
||||
*
|
||||
* @param {String} str
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set querystring(str) {
|
||||
const url = parse(this.req);
|
||||
if (url.search === `?${str}`) return;
|
||||
|
||||
url.search = str;
|
||||
url.path = null;
|
||||
|
||||
this.url = stringify(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the search string. Same as the query string
|
||||
* except it includes the leading ?.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get search() {
|
||||
if (!this.querystring) return '';
|
||||
return `?${this.querystring}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the search string. Same as
|
||||
* request.querystring= but included for ubiquity.
|
||||
*
|
||||
* @param {String} str
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set search(str) {
|
||||
this.querystring = str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the "Host" header field host
|
||||
* and support X-Forwarded-Host when a
|
||||
* proxy is enabled.
|
||||
*
|
||||
* @return {String} hostname:port
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get host() {
|
||||
const proxy = this.app.proxy;
|
||||
let host = proxy && this.get('X-Forwarded-Host');
|
||||
if (!host) {
|
||||
if (this.req.httpVersionMajor >= 2) host = this.get(':authority');
|
||||
if (!host) host = this.get('Host');
|
||||
}
|
||||
if (!host) return '';
|
||||
host = splitCommaSeparatedValues(host, 1)[0];
|
||||
// Host header may contain userinfo (e.g., "user@host") which is invalid per RFC 7230.
|
||||
// Use URL parser to correctly extract the host portion.
|
||||
if (host.includes('@')) {
|
||||
try {
|
||||
host = new URL(`http://${host}`).host;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return host;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the "Host" header field hostname
|
||||
* and support X-Forwarded-Host when a
|
||||
* proxy is enabled.
|
||||
*
|
||||
* @return {String} hostname
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get hostname() {
|
||||
const host = this.host;
|
||||
if (!host) return '';
|
||||
if ('[' === host[0]) return this.URL.hostname || ''; // IPv6
|
||||
return host.split(':', 1)[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get WHATWG parsed URL.
|
||||
* Lazily memoized.
|
||||
*
|
||||
* @return {URL|Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get URL() {
|
||||
/* istanbul ignore else */
|
||||
if (!this.memoizedURL) {
|
||||
const originalUrl = this.originalUrl || ''; // avoid undefined in template string
|
||||
try {
|
||||
this.memoizedURL = new URL(`${this.origin}${originalUrl}`);
|
||||
} catch (err) {
|
||||
this.memoizedURL = Object.create(null);
|
||||
}
|
||||
}
|
||||
return this.memoizedURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the request is fresh, aka
|
||||
* Last-Modified and/or the ETag
|
||||
* still match.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get fresh() {
|
||||
const method = this.method;
|
||||
const s = this.ctx.status;
|
||||
|
||||
// GET or HEAD for weak freshness validation only
|
||||
if ('GET' !== method && 'HEAD' !== method) return false;
|
||||
|
||||
// 2xx or 304 as per rfc2616 14.26
|
||||
if ((s >= 200 && s < 300) || 304 === s) {
|
||||
return fresh(this.header, this.response.header);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the request is stale, aka
|
||||
* "Last-Modified" and / or the "ETag" for the
|
||||
* resource has changed.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get stale() {
|
||||
return !this.fresh;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the request is idempotent.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get idempotent() {
|
||||
const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'];
|
||||
return !!~methods.indexOf(this.method);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the request socket.
|
||||
*
|
||||
* @return {Connection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get socket() {
|
||||
return this.req.socket;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the charset when present or undefined.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get charset() {
|
||||
try {
|
||||
const { parameters } = contentType.parse(this.req);
|
||||
return parameters.charset || '';
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return parsed Content-Length when present.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get length() {
|
||||
const len = this.get('Content-Length');
|
||||
if (len === '') return;
|
||||
return ~~len;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the protocol string "http" or "https"
|
||||
* when requested with TLS. When the proxy setting
|
||||
* is enabled the "X-Forwarded-Proto" header
|
||||
* field will be trusted. If you're running behind
|
||||
* a reverse proxy that supplies https for you this
|
||||
* may be enabled.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get protocol() {
|
||||
if (this.socket.encrypted) return 'https';
|
||||
if (!this.app.proxy) return 'http';
|
||||
const proto = this.get('X-Forwarded-Proto');
|
||||
return proto ? splitCommaSeparatedValues(proto, 1)[0] : 'http';
|
||||
},
|
||||
|
||||
/**
|
||||
* Shorthand for:
|
||||
*
|
||||
* this.protocol == 'https'
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get secure() {
|
||||
return 'https' === this.protocol;
|
||||
},
|
||||
|
||||
/**
|
||||
* When `app.proxy` is `true`, parse
|
||||
* the "X-Forwarded-For" ip address list.
|
||||
*
|
||||
* For example if the value was "client, proxy1, proxy2"
|
||||
* you would receive the array `["client", "proxy1", "proxy2"]`
|
||||
* where "proxy2" is the furthest down-stream.
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get ips() {
|
||||
const proxy = this.app.proxy;
|
||||
const val = this.get(this.app.proxyIpHeader);
|
||||
let ips = proxy && val
|
||||
? splitCommaSeparatedValues(val)
|
||||
: [];
|
||||
if (this.app.maxIpsCount > 0) {
|
||||
ips = ips.slice(-this.app.maxIpsCount);
|
||||
}
|
||||
return ips;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return request's remote address
|
||||
* When `app.proxy` is `true`, parse
|
||||
* the "X-Forwarded-For" ip address list and return the first one
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get ip() {
|
||||
if (!this[IP]) {
|
||||
this[IP] = this.ips[0] || this.socket.remoteAddress || '';
|
||||
}
|
||||
return this[IP];
|
||||
},
|
||||
|
||||
set ip(_ip) {
|
||||
this[IP] = _ip;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return subdomains as an array.
|
||||
*
|
||||
* Subdomains are the dot-separated parts of the host before the main domain
|
||||
* of the app. By default, the domain of the app is assumed to be the last two
|
||||
* parts of the host. This can be changed by setting `app.subdomainOffset`.
|
||||
*
|
||||
* For example, if the domain is "tobi.ferrets.example.com":
|
||||
* If `app.subdomainOffset` is not set, this.subdomains is
|
||||
* `["ferrets", "tobi"]`.
|
||||
* If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`.
|
||||
*
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get subdomains() {
|
||||
const offset = this.app.subdomainOffset;
|
||||
const hostname = this.hostname;
|
||||
if (net.isIP(hostname)) return [];
|
||||
return hostname
|
||||
.split('.')
|
||||
.reverse()
|
||||
.slice(offset);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get accept object.
|
||||
* Lazily memoized.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
get accept() {
|
||||
return this._accept || (this._accept = accepts(this.req));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set accept object.
|
||||
*
|
||||
* @param {Object}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
set accept(obj) {
|
||||
this._accept = obj;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given `type(s)` is acceptable, returning
|
||||
* the best match when true, otherwise `false`, in which
|
||||
* case you should respond with 406 "Not Acceptable".
|
||||
*
|
||||
* The `type` value may be a single mime type string
|
||||
* such as "application/json", the extension name
|
||||
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
|
||||
* or array is given the _best_ match, if any is returned.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* // Accept: text/html
|
||||
* this.accepts('html');
|
||||
* // => "html"
|
||||
*
|
||||
* // Accept: text/*, application/json
|
||||
* this.accepts('html');
|
||||
* // => "html"
|
||||
* this.accepts('text/html');
|
||||
* // => "text/html"
|
||||
* this.accepts('json', 'text');
|
||||
* // => "json"
|
||||
* this.accepts('application/json');
|
||||
* // => "application/json"
|
||||
*
|
||||
* // Accept: text/*, application/json
|
||||
* this.accepts('image/png');
|
||||
* this.accepts('png');
|
||||
* // => false
|
||||
*
|
||||
* // Accept: text/*;q=.5, application/json
|
||||
* this.accepts(['html', 'json']);
|
||||
* this.accepts('html', 'json');
|
||||
* // => "json"
|
||||
*
|
||||
* @param {String|Array} type(s)...
|
||||
* @return {String|Array|false}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
accepts(...args) {
|
||||
return this.accept.types(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return accepted encodings or best fit based on `encodings`.
|
||||
*
|
||||
* Given `Accept-Encoding: gzip, deflate`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['gzip', 'deflate']
|
||||
*
|
||||
* @param {String|Array} encoding(s)...
|
||||
* @return {String|Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
acceptsEncodings(...args) {
|
||||
return this.accept.encodings(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return accepted charsets or best fit based on `charsets`.
|
||||
*
|
||||
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['utf-8', 'utf-7', 'iso-8859-1']
|
||||
*
|
||||
* @param {String|Array} charset(s)...
|
||||
* @return {String|Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
acceptsCharsets(...args) {
|
||||
return this.accept.charsets(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return accepted languages or best fit based on `langs`.
|
||||
*
|
||||
* Given `Accept-Language: en;q=0.8, es, pt`
|
||||
* an array sorted by quality is returned:
|
||||
*
|
||||
* ['es', 'pt', 'en']
|
||||
*
|
||||
* @param {String|Array} lang(s)...
|
||||
* @return {Array|String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
acceptsLanguages(...args) {
|
||||
return this.accept.languages(...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the incoming request contains the "Content-Type"
|
||||
* header field and if it contains any of the given mime `type`s.
|
||||
* If there is no request body, `null` is returned.
|
||||
* If there is no content type, `false` is returned.
|
||||
* Otherwise, it returns the first `type` that matches.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* // With Content-Type: text/html; charset=utf-8
|
||||
* this.is('html'); // => 'html'
|
||||
* this.is('text/html'); // => 'text/html'
|
||||
* this.is('text/*', 'application/json'); // => 'text/html'
|
||||
*
|
||||
* // When Content-Type is application/json
|
||||
* this.is('json', 'urlencoded'); // => 'json'
|
||||
* this.is('application/json'); // => 'application/json'
|
||||
* this.is('html', 'application/*'); // => 'application/json'
|
||||
*
|
||||
* this.is('html'); // => false
|
||||
*
|
||||
* @param {String|String[]} [type]
|
||||
* @param {String[]} [types]
|
||||
* @return {String|false|null}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
is(type, ...types) {
|
||||
return typeis(this.req, type, ...types);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the request mime type void of
|
||||
* parameters such as "charset".
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get type() {
|
||||
const type = this.get('Content-Type');
|
||||
if (!type) return '';
|
||||
return type.split(';')[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Return request header.
|
||||
*
|
||||
* The `Referrer` header field is special-cased,
|
||||
* both `Referrer` and `Referer` are interchangeable.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* this.get('Content-Type');
|
||||
* // => "text/plain"
|
||||
*
|
||||
* this.get('content-type');
|
||||
* // => "text/plain"
|
||||
*
|
||||
* this.get('Something');
|
||||
* // => ''
|
||||
*
|
||||
* @param {String} field
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get(field) {
|
||||
const req = this.req;
|
||||
switch (field = field.toLowerCase()) {
|
||||
case 'referer':
|
||||
case 'referrer':
|
||||
return req.headers.referrer || req.headers.referer || '';
|
||||
default:
|
||||
return req.headers[field] || '';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Inspect implementation.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
inspect() {
|
||||
if (!this.req) return;
|
||||
return this.toJSON();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return JSON representation.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toJSON() {
|
||||
return only(this, [
|
||||
'method',
|
||||
'url',
|
||||
'header'
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom inspection implementation for newer Node.js versions.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (util.inspect.custom) {
|
||||
module.exports[util.inspect.custom] = module.exports.inspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a comma-separated value string into an array of values, with an optional limit.
|
||||
* All the values are trimmed of whitespace.
|
||||
*
|
||||
* @param {string} value - The comma-separated value string to split.
|
||||
* @param {number} [limit] - The maximum number of values to return.
|
||||
* @returns {string[]} An array of values from the comma-separated string.
|
||||
*/
|
||||
function splitCommaSeparatedValues(value, limit) {
|
||||
return value.split(',', limit).map(v => v.trim());
|
||||
}
|
||||
+607
@@ -0,0 +1,607 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const contentDisposition = require('content-disposition');
|
||||
const getType = require('cache-content-type');
|
||||
const onFinish = require('on-finished');
|
||||
const escape = require('escape-html');
|
||||
const typeis = require('type-is').is;
|
||||
const statuses = require('statuses');
|
||||
const destroy = require('destroy');
|
||||
const assert = require('assert');
|
||||
const extname = require('path').extname;
|
||||
const vary = require('vary');
|
||||
const only = require('only');
|
||||
const util = require('util');
|
||||
const encodeUrl = require('encodeurl');
|
||||
const Stream = require('stream');
|
||||
const URL = require('url').URL;
|
||||
|
||||
/**
|
||||
* Prototype.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Return the request socket.
|
||||
*
|
||||
* @return {Connection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get socket() {
|
||||
return this.res.socket;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return response header.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get header() {
|
||||
const { res } = this;
|
||||
return typeof res.getHeaders === 'function'
|
||||
? res.getHeaders()
|
||||
: res._headers || {}; // Node < 7.7
|
||||
},
|
||||
|
||||
/**
|
||||
* Return response header, alias as response.header
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get headers() {
|
||||
return this.header;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get response status code.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get status() {
|
||||
return this.res.statusCode;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set response status code.
|
||||
*
|
||||
* @param {Number} code
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set status(code) {
|
||||
if (this.headerSent) return;
|
||||
|
||||
assert(Number.isInteger(code), 'status code must be a number');
|
||||
assert(code >= 100 && code <= 999, `invalid status code: ${code}`);
|
||||
this._explicitStatus = true;
|
||||
this.res.statusCode = code;
|
||||
if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code];
|
||||
if (this.body && statuses.empty[code]) this.body = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get response status message
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get message() {
|
||||
return this.res.statusMessage || statuses[this.status];
|
||||
},
|
||||
|
||||
/**
|
||||
* Set response status message
|
||||
*
|
||||
* @param {String} msg
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set message(msg) {
|
||||
this.res.statusMessage = msg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get response body.
|
||||
*
|
||||
* @return {Mixed}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get body() {
|
||||
return this._body;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set response body.
|
||||
*
|
||||
* @param {String|Buffer|Object|Stream} val
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set body(val) {
|
||||
const original = this._body;
|
||||
this._body = val;
|
||||
|
||||
// no content
|
||||
if (null == val) {
|
||||
if (!statuses.empty[this.status]) this.status = 204;
|
||||
if (val === null) this._explicitNullBody = true;
|
||||
this.remove('Content-Type');
|
||||
this.remove('Content-Length');
|
||||
this.remove('Transfer-Encoding');
|
||||
return;
|
||||
}
|
||||
|
||||
// set the status
|
||||
if (!this._explicitStatus) this.status = 200;
|
||||
|
||||
// set the content-type only if not yet set
|
||||
const setType = !this.has('Content-Type');
|
||||
|
||||
// string
|
||||
if ('string' === typeof val) {
|
||||
if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
|
||||
this.length = Buffer.byteLength(val);
|
||||
return;
|
||||
}
|
||||
|
||||
// buffer
|
||||
if (Buffer.isBuffer(val)) {
|
||||
if (setType) this.type = 'bin';
|
||||
this.length = val.length;
|
||||
return;
|
||||
}
|
||||
|
||||
// stream
|
||||
if (val instanceof Stream) {
|
||||
onFinish(this.res, destroy.bind(null, val));
|
||||
if (original != val) {
|
||||
val.once('error', err => this.ctx.onerror(err));
|
||||
// overwriting
|
||||
if (null != original) this.remove('Content-Length');
|
||||
}
|
||||
|
||||
if (setType) this.type = 'bin';
|
||||
return;
|
||||
}
|
||||
|
||||
// json
|
||||
this.remove('Content-Length');
|
||||
this.type = 'json';
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Length field to `n`.
|
||||
*
|
||||
* @param {Number} n
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set length(n) {
|
||||
if (!this.has('Transfer-Encoding')) {
|
||||
this.set('Content-Length', n);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return parsed response Content-Length when present.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get length() {
|
||||
if (this.has('Content-Length')) {
|
||||
return parseInt(this.get('Content-Length'), 10) || 0;
|
||||
}
|
||||
|
||||
const { body } = this;
|
||||
if (!body || body instanceof Stream) return undefined;
|
||||
if ('string' === typeof body) return Buffer.byteLength(body);
|
||||
if (Buffer.isBuffer(body)) return body.length;
|
||||
return Buffer.byteLength(JSON.stringify(body));
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a header has been written to the socket.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get headerSent() {
|
||||
return this.res.headersSent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Vary on `field`.
|
||||
*
|
||||
* @param {String} field
|
||||
* @api public
|
||||
*/
|
||||
|
||||
vary(field) {
|
||||
if (this.headerSent) return;
|
||||
|
||||
vary(this.res, field);
|
||||
},
|
||||
|
||||
_getBackReferrer() {
|
||||
const referrer = this.ctx.get('Referrer');
|
||||
if (referrer) {
|
||||
// referrer is an absolute URL, check if it's the same origin
|
||||
const url = new URL(referrer, this.ctx.href);
|
||||
if (url.host === this.ctx.host) {
|
||||
return referrer;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform a 302 redirect to `url`.
|
||||
*
|
||||
* The string "back" is special-cased
|
||||
* to provide Referrer support, when Referrer
|
||||
* is not present `alt` or "/" is used.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* this.redirect('back');
|
||||
* this.redirect('back', '/index.html');
|
||||
* this.redirect('/login');
|
||||
* this.redirect('http://google.com');
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {String} [alt]
|
||||
* @api public
|
||||
*/
|
||||
|
||||
redirect(url, alt) {
|
||||
// location
|
||||
if ('back' === url) {
|
||||
url = this._getBackReferrer() || alt || '/';
|
||||
}
|
||||
|
||||
if (/^https?:\/\//i.test(url)) {
|
||||
// formatting url again avoid security escapes
|
||||
url = new URL(url).toString();
|
||||
}
|
||||
this.set('Location', encodeUrl(url));
|
||||
|
||||
// status
|
||||
if (!statuses.redirect[this.status]) this.status = 302;
|
||||
|
||||
// html
|
||||
if (this.ctx.accepts('html')) {
|
||||
url = escape(url);
|
||||
this.type = 'text/html; charset=utf-8';
|
||||
this.body = `Redirecting to ${url}.`;
|
||||
return;
|
||||
}
|
||||
|
||||
// text
|
||||
this.type = 'text/plain; charset=utf-8';
|
||||
this.body = `Redirecting to ${url}.`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Disposition header to "attachment" with optional `filename`.
|
||||
*
|
||||
* @param {String} filename
|
||||
* @api public
|
||||
*/
|
||||
|
||||
attachment(filename, options) {
|
||||
if (filename) this.type = extname(filename);
|
||||
this.set('Content-Disposition', contentDisposition(filename, options));
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Type response header with `type` through `mime.lookup()`
|
||||
* when it does not contain a charset.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* this.type = '.html';
|
||||
* this.type = 'html';
|
||||
* this.type = 'json';
|
||||
* this.type = 'application/json';
|
||||
* this.type = 'png';
|
||||
*
|
||||
* @param {String} type
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set type(type) {
|
||||
type = getType(type);
|
||||
if (type) {
|
||||
this.set('Content-Type', type);
|
||||
} else {
|
||||
this.remove('Content-Type');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the Last-Modified date using a string or a Date.
|
||||
*
|
||||
* this.response.lastModified = new Date();
|
||||
* this.response.lastModified = '2013-09-13';
|
||||
*
|
||||
* @param {String|Date} type
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set lastModified(val) {
|
||||
if ('string' === typeof val) val = new Date(val);
|
||||
this.set('Last-Modified', val.toUTCString());
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the Last-Modified date in Date form, if it exists.
|
||||
*
|
||||
* @return {Date}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get lastModified() {
|
||||
const date = this.get('last-modified');
|
||||
if (date) return new Date(date);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the ETag of a response.
|
||||
* This will normalize the quotes if necessary.
|
||||
*
|
||||
* this.response.etag = 'md5hashsum';
|
||||
* this.response.etag = '"md5hashsum"';
|
||||
* this.response.etag = 'W/"123456789"';
|
||||
*
|
||||
* @param {String} etag
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set etag(val) {
|
||||
if (!/^(W\/)?"/.test(val)) val = `"${val}"`;
|
||||
this.set('ETag', val);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the ETag of a response.
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get etag() {
|
||||
return this.get('ETag');
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the response mime type void of
|
||||
* parameters such as "charset".
|
||||
*
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get type() {
|
||||
const type = this.get('Content-Type');
|
||||
if (!type) return '';
|
||||
return type.split(';', 1)[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether the response is one of the listed types.
|
||||
* Pretty much the same as `this.request.is()`.
|
||||
*
|
||||
* @param {String|String[]} [type]
|
||||
* @param {String[]} [types]
|
||||
* @return {String|false}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
is(type, ...types) {
|
||||
return typeis(this.type, type, ...types);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return response header.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* this.get('Content-Type');
|
||||
* // => "text/plain"
|
||||
*
|
||||
* this.get('content-type');
|
||||
* // => "text/plain"
|
||||
*
|
||||
* @param {String} field
|
||||
* @return {String}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get(field) {
|
||||
return this.header[field.toLowerCase()] || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the header identified by name is currently set in the outgoing headers.
|
||||
* The header name matching is case-insensitive.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* this.has('Content-Type');
|
||||
* // => true
|
||||
*
|
||||
* this.get('content-type');
|
||||
* // => true
|
||||
*
|
||||
* @param {String} field
|
||||
* @return {boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
has(field) {
|
||||
return typeof this.res.hasHeader === 'function'
|
||||
? this.res.hasHeader(field)
|
||||
// Node < 7.7
|
||||
: field.toLowerCase() in this.headers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set header `field` to `val` or pass
|
||||
* an object of header fields.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* this.set('Foo', ['bar', 'baz']);
|
||||
* this.set('Accept', 'application/json');
|
||||
* this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
|
||||
*
|
||||
* @param {String|Object|Array} field
|
||||
* @param {String} val
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set(field, val) {
|
||||
if (this.headerSent) return;
|
||||
|
||||
if (2 === arguments.length) {
|
||||
if (Array.isArray(val)) val = val.map(v => typeof v === 'string' ? v : String(v));
|
||||
else if (typeof val !== 'string') val = String(val);
|
||||
this.res.setHeader(field, val);
|
||||
} else {
|
||||
for (const key in field) {
|
||||
this.set(key, field[key]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Append additional header `field` with value `val`.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* ```
|
||||
* this.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
|
||||
* this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
|
||||
* this.append('Warning', '199 Miscellaneous warning');
|
||||
* ```
|
||||
*
|
||||
* @param {String} field
|
||||
* @param {String|Array} val
|
||||
* @api public
|
||||
*/
|
||||
|
||||
append(field, val) {
|
||||
const prev = this.get(field);
|
||||
|
||||
if (prev) {
|
||||
val = Array.isArray(prev)
|
||||
? prev.concat(val)
|
||||
: [prev].concat(val);
|
||||
}
|
||||
|
||||
return this.set(field, val);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove header `field`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @api public
|
||||
*/
|
||||
|
||||
remove(field) {
|
||||
if (this.headerSent) return;
|
||||
|
||||
this.res.removeHeader(field);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the request is writable.
|
||||
* Tests for the existence of the socket
|
||||
* as node sometimes does not set it.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
get writable() {
|
||||
// can't write any more after response finished
|
||||
// response.writableEnded is available since Node > 12.9
|
||||
// https://nodejs.org/api/http.html#http_response_writableended
|
||||
// response.finished is undocumented feature of previous Node versions
|
||||
// https://stackoverflow.com/questions/16254385/undocumented-response-finished-in-node-js
|
||||
if (this.res.writableEnded || this.res.finished) return false;
|
||||
|
||||
const socket = this.res.socket;
|
||||
// There are already pending outgoing res, but still writable
|
||||
// https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
|
||||
if (!socket) return true;
|
||||
return socket.writable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inspect implementation.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
inspect() {
|
||||
if (!this.res) return;
|
||||
const o = this.toJSON();
|
||||
o.body = this.body;
|
||||
return o;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return JSON representation.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
toJSON() {
|
||||
return only(this, [
|
||||
'status',
|
||||
'message',
|
||||
'header'
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Flush any set headers and begin the body
|
||||
*/
|
||||
|
||||
flushHeaders() {
|
||||
this.res.flushHeaders();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom inspection implementation for node 6+.
|
||||
*
|
||||
* @return {Object}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (util.inspect.custom) {
|
||||
module.exports[util.inspect.custom] = module.exports.inspect;
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"name": "koa",
|
||||
"version": "2.16.4",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"tag": "latest-2"
|
||||
},
|
||||
"description": "Koa web app framework",
|
||||
"main": "lib/application.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./lib/application.js",
|
||||
"import": "./dist/koa.mjs"
|
||||
},
|
||||
"./lib/request": "./lib/request.js",
|
||||
"./lib/request.js": "./lib/request.js",
|
||||
"./lib/response": "./lib/response.js",
|
||||
"./lib/response.js": "./lib/response.js",
|
||||
"./lib/application": "./lib/application.js",
|
||||
"./lib/application.js": "./lib/application.js",
|
||||
"./lib/context": "./lib/context.js",
|
||||
"./lib/context.js": "./lib/context.js",
|
||||
"./*": "./*.js",
|
||||
"./*.js": "./*.js",
|
||||
"./package": "./package.json",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest --forceExit",
|
||||
"lint": "eslint --ignore-path .gitignore .",
|
||||
"authors": "git log --format='%aN <%aE>' | sort -u > AUTHORS",
|
||||
"build": "gen-esm-wrapper . ./dist/koa.mjs",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"repository": "koajs/koa",
|
||||
"keywords": [
|
||||
"web",
|
||||
"app",
|
||||
"http",
|
||||
"application",
|
||||
"framework",
|
||||
"middleware",
|
||||
"rack"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^1.3.5",
|
||||
"cache-content-type": "^1.0.0",
|
||||
"content-disposition": "~0.5.2",
|
||||
"content-type": "^1.0.4",
|
||||
"cookies": "~0.9.0",
|
||||
"debug": "^4.3.2",
|
||||
"delegates": "^1.0.0",
|
||||
"depd": "^2.0.0",
|
||||
"destroy": "^1.0.4",
|
||||
"encodeurl": "^1.0.2",
|
||||
"escape-html": "^1.0.3",
|
||||
"fresh": "~0.5.2",
|
||||
"http-assert": "^1.3.0",
|
||||
"http-errors": "^1.6.3",
|
||||
"is-generator-function": "^1.0.7",
|
||||
"koa-compose": "^4.1.0",
|
||||
"koa-convert": "^2.0.0",
|
||||
"on-finished": "^2.3.0",
|
||||
"only": "~0.0.2",
|
||||
"parseurl": "^1.3.2",
|
||||
"statuses": "^1.5.0",
|
||||
"type-is": "^1.6.16",
|
||||
"vary": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-koa": "^2.0.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"gen-esm-wrapper": "^1.0.6",
|
||||
"jest": "^27.0.6",
|
||||
"supertest": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"lib"
|
||||
],
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../on-finished@2.4.1/node_modules/on-finished
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../only@0.0.2/node_modules/only
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../parseurl@1.3.3/node_modules/parseurl
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../statuses@1.5.0/node_modules/statuses
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../type-is@1.6.18/node_modules/type-is
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../vary@1.1.2/node_modules/vary
|
||||
Reference in New Issue
Block a user