Initial commit: upload services folder

This commit is contained in:
董海洋
2026-05-23 14:15:45 +08:00
commit 6775db684d
1494 changed files with 172141 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
../../accepts@1.3.8/node_modules/accepts
+1
View File
@@ -0,0 +1 @@
../../cache-content-type@1.0.1/node_modules/cache-content-type
+1
View File
@@ -0,0 +1 @@
../../content-disposition@0.5.4/node_modules/content-disposition
+1
View File
@@ -0,0 +1 @@
../../content-type@1.0.5/node_modules/content-type
+1
View File
@@ -0,0 +1 @@
../../cookies@0.9.1/node_modules/cookies
+1
View File
@@ -0,0 +1 @@
../../debug@4.4.3/node_modules/debug
+1
View File
@@ -0,0 +1 @@
../../delegates@1.0.0/node_modules/delegates
+1
View File
@@ -0,0 +1 @@
../../depd@2.0.0/node_modules/depd
+1
View File
@@ -0,0 +1 @@
../../destroy@1.2.0/node_modules/destroy
+1
View File
@@ -0,0 +1 @@
../../encodeurl@1.0.2/node_modules/encodeurl
+1
View File
@@ -0,0 +1 @@
../../escape-html@1.0.3/node_modules/escape-html
+1
View File
@@ -0,0 +1 @@
../../fresh@0.5.2/node_modules/fresh
+1
View File
@@ -0,0 +1 @@
../../http-assert@1.5.0/node_modules/http-assert
+1
View File
@@ -0,0 +1 @@
../../http-errors@1.8.1/node_modules/http-errors
+1
View File
@@ -0,0 +1 @@
../../is-generator-function@1.1.2/node_modules/is-generator-function
+1
View File
@@ -0,0 +1 @@
../../koa-compose@4.1.0/node_modules/koa-compose
+1
View File
@@ -0,0 +1 @@
../../koa-convert@2.0.0/node_modules/koa-convert
+22
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,4 @@
import mod from "../lib/application.js";
export default mod;
export const HttpError = mod.HttpError;
+318
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
../../on-finished@2.4.1/node_modules/on-finished
+1
View File
@@ -0,0 +1 @@
../../only@0.0.2/node_modules/only
+1
View File
@@ -0,0 +1 @@
../../parseurl@1.3.3/node_modules/parseurl
+1
View File
@@ -0,0 +1 @@
../../statuses@1.5.0/node_modules/statuses
+1
View File
@@ -0,0 +1 @@
../../type-is@1.6.18/node_modules/type-is
+1
View File
@@ -0,0 +1 @@
../../vary@1.1.2/node_modules/vary