Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Request decorators for HTTPController #159

Merged
merged 25 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/controller-decorator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"@eggjs/tegg-common-util": "^3.23.0",
"@eggjs/tegg-metadata": "^3.23.0",
"path-to-regexp": "^1.8.0",
"reflect-metadata": "^0.1.13"
"reflect-metadata": "^0.1.13",
"undici": "^5.26.5"
},
"devDependencies": {
"@types/mocha": "^10.0.1",
Expand Down
13 changes: 13 additions & 0 deletions core/controller-decorator/src/decorator/http/HTTPParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,16 @@ export function HTTPParam(param?: HTTPParamParams) {
HTTPInfoUtil.setHTTPMethodParamName(name, parameterIndex, controllerClazz, methodName);
};
}

export function Request() {
return function(target: any, propertyKey: PropertyKey, parameterIndex: number) {
const [ nodeMajor ] = process.versions.node.split('.').map(v => Number(v));
assert(nodeMajor >= 16,
`[controller/${target.name}] expect node version >=16, but now is ${nodeMajor}`);
assert(typeof propertyKey === 'string',
`[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`);
const methodName = propertyKey as string;
const controllerClazz = target.constructor as EggProtoImplClass;
HTTPInfoUtil.setHTTPMethodParamType(HTTPParamType.REQUEST, parameterIndex, controllerClazz, methodName);
};
}
10 changes: 10 additions & 0 deletions core/controller-decorator/src/model/HTTPMethodMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export abstract class ParamMeta {

abstract validate(httpPath: string);
}
export class RequestParamMeta extends ParamMeta {
type = HTTPParamType.REQUEST;

validate() {
return;
}
}

export class BodyParamMeta extends ParamMeta {
type = HTTPParamType.BODY;
Expand Down Expand Up @@ -118,6 +125,9 @@ export class ParamMetaUtil {
assert(name, 'query param must has name');
return new QueryParamMeta(name!);
}
case HTTPParamType.REQUEST: {
return new RequestParamMeta();
}
default:
assert.fail('never arrive');
}
Expand Down
4 changes: 4 additions & 0 deletions core/controller-decorator/src/model/HTTPRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import undici from 'undici';
// https://github.com/nodejs/undici/blob/main/index.js#L118
// 只有 nodejs >= 16 才支持 Request
export class HTTPRequest extends (undici.Request || Object) {}
4 changes: 4 additions & 0 deletions core/controller-decorator/src/model/HTTPResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import undici from 'undici';
// https://github.com/nodejs/undici/blob/main/index.js#L118
// 只有 nodejs >= 16 才支持 Request
export class HTTPResponse extends (undici.Response || Object) {}
2 changes: 2 additions & 0 deletions core/controller-decorator/src/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export * from './MethodMeta';
export * from './ControllerMetadata';
export * from './HTTPMethodMeta';
export * from './HTTPControllerMeta';
export * from './HTTPRequest';
export * from './HTTPResponse';
1 change: 1 addition & 0 deletions core/controller-decorator/src/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ export enum HTTPParamType {
QUERIES = 'QUERIES',
BODY = 'BODY',
PARAM = 'PARAM',
REQUEST = 'REQUEST',
}
5 changes: 5 additions & 0 deletions plugin/controller/lib/impl/http/HTTPMethodRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EggPrototype } from '@eggjs/tegg-metadata';
import { RootProtoManager } from '../../RootProtoManager';
import pathToRegexp from 'path-to-regexp';
import { aclMiddlewareFactory } from './Acl';
import { HTTPRequest } from './Req';
import { RouterConflictError } from '../../errors';
import { FrameworkErrorFormater } from 'egg-errors';
import { EggRouter } from '@eggjs/router';
Expand Down Expand Up @@ -88,6 +89,10 @@ export class HTTPMethodRegister {
args[index] = ctx.queries[queryParam.name];
break;
}
case HTTPParamType.REQUEST: {
args[index] = new HTTPRequest(ctx);
break;
}
default:
assert.fail('never arrive');
}
Expand Down
13 changes: 13 additions & 0 deletions plugin/controller/lib/impl/http/Req.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Context } from 'egg';
import { HTTPRequest as BaseHTTPRequest } from '@eggjs/tegg';
export class HTTPRequest extends BaseHTTPRequest {
constructor(ctx:Context) {
const request = ctx.request;
// href: https://github.com/eggjs/koa/blob/master/src/request.ts#L90C1-L98C4
super(request.href, {
method: request.method,
headers: request.headers as Record<string, string | string[]>,
body: (ctx.request as any).rawBody,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Context as EggContext } from 'egg';
import {
Context,
HTTPController,
HTTPMethod,
HTTPMethodEnum,
Middleware,
Request,
HTTPRequest,
} from '@eggjs/tegg';
import { countMw } from '../middleware/count_mw';

@HTTPController({
path: '/apps',
})
@Middleware(countMw)
export class AppController {

@HTTPMethod({
method: HTTPMethodEnum.POST,
path: '/testRequest',
})
async testRequest(@Context() ctx: EggContext, @Request() request: HTTPRequest) {
const traceId = await ctx.tracer.traceId;
return {
success: true,
traceId,
headers: Object.fromEntries(request.headers),
method: request.method,
requestBody: await request.text(),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Context } from 'egg';
import { Next } from '@eggjs/tegg';

export async function callModuleCtx(ctx: Context, next: Next) {
await (ctx.module as any).multiModuleService.appService.findApp('foo');
await next();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Context } from 'egg';
import { Next } from '@eggjs/tegg';

let index = 0;

export async function countMw(ctx: Context, next: Next) {
await next();
if (ctx.body) ctx.body.count = index++;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Context } from 'egg';
import { Next } from '@eggjs/tegg';

export function logMwFactory(log: string) {
return async function logMw(ctx: Context, next: Next) {
await next();
ctx.body.log = log;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

module.exports = function() {
const config = {
keys: 'test key',
security: {
csrf: {
ignoreJSON: false,
}
},
};
return config;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

exports.tracer = {
package: 'egg-tracer',
enable: true,
};

exports.tegg = {
package: '@eggjs/tegg-plugin',
enable: true,
};

exports.teggConfig = {
package: '@eggjs/tegg-config',
enable: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "http-inject-app"
}
1 change: 1 addition & 0 deletions plugin/controller/test/http/params.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,5 @@ describe('test/params.test.ts', () => {
});
});
});

});
53 changes: 53 additions & 0 deletions plugin/controller/test/http/request.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import mm from 'egg-mock';
import path from 'path';
import assert from 'assert';

describe('test/params.test.ts', () => {
let app;

beforeEach(() => {
mm(process.env, 'EGG_TYPESCRIPT', true);
});

afterEach(() => {
mm.restore();
});

before(async () => {
mm(process.env, 'EGG_TYPESCRIPT', true);
mm(process, 'cwd', () => {
return path.join(__dirname, '../..');
});
app = mm.app({
baseDir: path.join(__dirname, '../fixtures/apps/http-inject-app'),
framework: require.resolve('egg'),
});
await app.ready();
});

after(() => {
return app.close();
});
const [ nodeMajor ] = process.versions.node.split('.').map(v => Number(v));
if (nodeMajor >= 16) {
it('Request should work', async () => {
app.mockCsrf();
const param = {
name: 'foo',
desc: 'mock-desc',
};
const headerKey = 'test-header';
await app.httpRequest()
.post('/apps/testRequest')
.send(param)
.set('test', headerKey)
.expect(200)
.expect(res => {
assert(res.body.headers.test === headerKey);
assert(res.body.method === 'POST');
assert(res.body.requestBody === JSON.stringify(param));
});
});
}

});