Skip to content

Commit

Permalink
feat: add favicon middleware and remove koa-onerror (midwayjs#1601)
Browse files Browse the repository at this point in the history
  • Loading branch information
czy88840616 authored Jan 20, 2022
1 parent 0669412 commit 2956174
Show file tree
Hide file tree
Showing 28 changed files with 679 additions and 44 deletions.
3 changes: 1 addition & 2 deletions packages/web-koa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
"@midwayjs/core": "^3.0.0-beta.17",
"@midwayjs/session": "^3.0.0-beta.17",
"koa": "2.13.4",
"koa-bodyparser": "4.3.0",
"koa-onerror": "^4.1.0"
"koa-bodyparser": "4.3.0"
},
"author": "Harry Chen <[email protected]>",
"repository": {
Expand Down
5 changes: 5 additions & 0 deletions packages/web-koa/src/config/config.default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ export const bodyParser = {
throw err;
},
};

export const siteFile = {
enable: true,
favicon: undefined,
};
10 changes: 3 additions & 7 deletions packages/web-koa/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
} from '@midwayjs/core';
import * as session from '@midwayjs/session';
import { MidwayKoaFramework } from './framework';
import * as koaBodyParser from 'koa-bodyparser';
import * as DefaultConfig from './config/config.default';
import { BodyParserMiddleware } from './middleware/bodyparser.middleware';
import { SiteFileMiddleware } from './middleware/fav.middleware';

@Configuration({
namespace: 'koa',
Expand Down Expand Up @@ -49,11 +50,6 @@ export class KoaConfiguration {
}

async onReady() {
// use bodyparser middleware
const bodyparserConfig = this.configService.getConfiguration('bodyParser');
if (bodyparserConfig.enable) {
const bodyParser = koaBodyParser(bodyparserConfig);
this.koaFramework.useMiddleware(bodyParser);
}
this.koaFramework.useMiddleware([SiteFileMiddleware, BodyParserMiddleware]);
}
}
29 changes: 2 additions & 27 deletions packages/web-koa/src/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import * as Router from '@koa/router';
import type { DefaultState, Middleware, Next } from 'koa';
import * as koa from 'koa';
import { Server } from 'net';
import * as onerror from 'koa-onerror';
import { detectStatus } from './utils';
import { setupOnerror } from './onerror';

const COOKIES = Symbol('context#cookies');

Expand Down Expand Up @@ -84,31 +83,7 @@ export class MidwayKoaFramework extends BaseFramework<
});

const onerrorConfig = this.configService.getConfiguration('onerror');

this.app.on('error', (err, ctx) => {
ctx = ctx || this.app.createAnonymousContext();
const status = detectStatus(err);
// 5xx
if (status >= 500) {
try {
ctx.logger.error(err);
} catch (ex) {
this.logger.error(err);
this.logger.error(ex);
}
return;
}

// 4xx
try {
ctx.logger.warn(err);
} catch (ex) {
this.logger.warn(err);
this.logger.error(ex);
}
});

onerror(this.app, onerrorConfig);
setupOnerror(this.app, onerrorConfig, this.logger);

// not found middleware
const notFound = async (ctx, next) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/web-koa/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { MidwayKoaFramework as Framework } from './framework';
export { MidwayKoaContextLogger } from './logger';
export * from './interface';
export { KoaConfiguration as Configuration } from './configuration';
export * from './middleware/fav.middleware';
export * from './middleware/bodyparser.middleware';
6 changes: 5 additions & 1 deletion packages/web-koa/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type Application = IMidwayKoaApplication;
export interface Context extends IMidwayKoaContext {}

interface BodyParserOptions {
enable?: boolean;
/**
* parser will only parse when request type hits enableTypes, default is ['json', 'form'].
*/
Expand Down Expand Up @@ -139,10 +140,13 @@ declare module '@midwayjs/core/dist/interface' {
json?: (err: Error, ctx: IMidwayKoaContext) => void;
html?: (err: Error, ctx: IMidwayKoaContext) => void;
redirect?: string;
template?: string;
accepts?: (...args) => any;
},
bodyParser?: BodyParserOptions;
siteFile?: {
enable?: boolean;
favicon?: undefined | string | Buffer
};
}
}

Expand Down
19 changes: 19 additions & 0 deletions packages/web-koa/src/middleware/bodyparser.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as koaBodyParser from 'koa-bodyparser';
import { Config, Middleware } from '@midwayjs/decorator';

@Middleware()
export class BodyParserMiddleware {
@Config('bodyParser')
bodyparserConfig;

resolve() {
// use bodyparser middleware
if (this.bodyparserConfig.enable) {
return koaBodyParser(this.bodyparserConfig);
}
}

static getName() {
return 'bodyParser';
}
}
48 changes: 48 additions & 0 deletions packages/web-koa/src/middleware/fav.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Config, Middleware } from '@midwayjs/decorator';

const path = require('path');
const MAX_AGE = 'public, max-age=2592000'; // 30 days

@Middleware()
export class SiteFileMiddleware {
@Config('siteFile')
siteFileConfig;

resolve() {
// use bodyparser middleware
if (this.siteFileConfig.enable) {
return async (ctx, next) => {
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') return next();
/* istanbul ignore if */
if (ctx.path[0] !== '/') return next();

if (ctx.path !== '/favicon.ico') {
return next();
}

let content = this.siteFileConfig['favicon'];
if (content === undefined) {
content = Buffer.from('');
}
if (!content) return next();
// content is url
if (typeof content === 'string') return ctx.redirect(content);

// '/robots.txt': Buffer <xx..
// content is buffer
if (Buffer.isBuffer(content)) {
ctx.set('cache-control', MAX_AGE);
ctx.body = content;
ctx.type = path.extname(ctx.path);
return;
}

return next();
};
}
}

static getName() {
return 'siteFile';
}
}
179 changes: 179 additions & 0 deletions packages/web-koa/src/onerror.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import * as http from 'http';
import {
accepts,
detectStatus,
isProduction,
sendToWormhole,
escapeHtml,
tpl,
} from './utils';

export function setupOnerror(app, config, logger) {
const errorOptions = Object.assign(
{
// support customize accepts function
accepts() {
const fn = config.accepts || accepts;
return fn(this);
},
/**
* default text error handler
* @param {Error} err
* @param ctx
*/
text(err, ctx) {
// unset all headers, and set those specified
ctx.res._headers = {};
ctx.set(err.headers);

if (isProduction(app)) {
ctx.body = http.STATUS_CODES[ctx.status];
} else {
ctx.body = err.message;
}
},
/**
* default html error handler
* @param {Error} err
*/
html(err, ctx) {
const status = detectStatus(err);
if (isProduction(app)) {
// 5xx
if (status >= 500) {
ctx.status = 500;
ctx.body = `<h2>Internal Server Error, real status: ${status}</h2>`;
return;
} else {
// 4xx
ctx.status = status;
ctx.body = `<h2>${status} ${http.STATUS_CODES[status]}</h2>`;
return;
}
}

// show simple error format for unittest
if (app.getEnv() === 'unittest' || app.getEnv() === 'test') {
ctx.status = status;
ctx.body = `${err.name}: ${err.message}\n${err.stack}`;
return;
}

ctx.body = tpl
.replace('{{status}}', escapeHtml(err.status))
.replace('{{stack}}', escapeHtml(err.stack));
ctx.type = 'html';
},
/**
* default json error handler
* @param {Error} err
* @param ctx
*/
json(err, ctx) {
const status = detectStatus(err);
const code = err.code || err.type;

if (isProduction(app)) {
if (status >= 500) {
ctx.body = { code, message: http.STATUS_CODES[status] };
} else {
ctx.body = { code, message: err.message };
}
} else {
ctx.body = { code, message: err.message, stack: err.stack };
}
},
},
config
);

app.on('error', (err, ctx) => {
ctx = ctx || app.createAnonymousContext();
const status = detectStatus(err);
// 5xx
if (status >= 500) {
try {
ctx.logger.error(err);
} catch (ex) {
logger.error(err);
logger.error(ex);
}
return;
}

// 4xx
try {
ctx.logger.warn(err);
} catch (ex) {
logger.warn(err);
logger.error(ex);
}
});

app.context.onerror = function (err) {
// don't do anything if there is no error.
// this allows you to pass `this.onerror`
// to node-style callbacks.
if (err == null) return;

// ignore all pedding request stream
if (this.req) sendToWormhole(this.req);

// wrap non-error object
if (!(err instanceof Error)) {
const newError = new Error('non-error thrown: ' + err);
// err maybe an object, try to copy the name, message and stack to the new error instance
if (err) {
if (err.name) newError.name = err.name;
if (err.message) newError.message = err.message;
if (err.stack) newError.stack = err.stack;
if (err.status) newError['status'] = err.status;
if (err.headers) newError['headers'] = err.headers;
}
err = newError;
}

const headerSent = this.headerSent || !this.writable;
if (headerSent) err.headerSent = true;

// delegate
app.emit('error', err, this);

// nothing we can do here other
// than delegate to the app-level
// handler and log.
if (headerSent) return;

// ENOENT support
if (err.code === 'ENOENT') err.status = 404;

if (typeof err.status !== 'number' || !http.STATUS_CODES[err.status]) {
err.status = 500;
}
this.status = err.status;

this.set(err.headers);
let type = 'text';
if (errorOptions.accepts) {
type = errorOptions.accepts.call(this, 'html', 'text', 'json');
} else {
type = this.accepts('html', 'text', 'json');
}
type = type || 'text';
if (errorOptions.all) {
errorOptions.all.call(this, err, this);
} else {
if (errorOptions.redirect && type !== 'json') {
this.redirect(errorOptions.redirect);
} else {
errorOptions[type].call(this, err, this);
this.type = type;
}
}

if (type === 'json') {
this.body = JSON.stringify(this.body);
}
this.res.end(this.body);
};
}
Loading

0 comments on commit 2956174

Please sign in to comment.