Skip to content

Commit b336706

Browse files
authored
Merge pull request #129 from wchaws/dev2
fix: post api no content-type and add ecs_desired_count config and en…
2 parents 52f7dc7 + bd8b2e1 commit b336706

File tree

14 files changed

+215
-30
lines changed

14 files changed

+215
-30
lines changed

source/constructs/cdk.context.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@
44
"<image-bucket-1>"
55
],
66
"secret_arn": "arn:aws:secretsmanager:<region>:<account-id-number>:secret:<secret-name>-<random-6-characters>",
7-
"stack_tags":{"key1":"value1", "key2":"value2"}
7+
"stack_tags":{"key1":"value1", "key2":"value2"},
8+
"ecs_desired_count": 10,
9+
"env": {
10+
"SHARP_QUEUE_LIMIT": "1"
11+
}
812
}

source/constructs/lib/ecs-image-handler.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,18 @@ export class ECSImageHandler extends Construct {
4040
memoryLimitMiB: 8 * GB,
4141
minHealthyPercent: 100,
4242
maxHealthyPercent: 200,
43-
desiredCount: 8,
43+
desiredCount: getECSDesiredCount(this),
4444
taskImageOptions: {
4545
image: ecs.ContainerImage.fromAsset(path.join(__dirname, '../../new-image-handler')),
4646
containerPort: 8080,
47-
environment: {
47+
environment: Object.assign({
4848
REGION: Aws.REGION,
4949
AWS_REGION: Aws.REGION,
5050
VIPS_DISC_THRESHOLD: '600m', // https://github.com/lovell/sharp/issues/1851
5151
SRC_BUCKET: buckets[0].bucketName,
5252
STYLE_TABLE_NAME: table.tableName,
5353
SECRET_NAME: secret.secretArn,
54-
},
54+
}, scope.node.tryGetContext('env')),
5555
},
5656
});
5757
albFargateService.targetGroup.configureHealthCheck({
@@ -178,4 +178,12 @@ function getSecret(scope: Construct): secretsmanager.ISecret {
178178
} else {
179179
throw new Error('You must specify one secret manager arn for POST security.');
180180
}
181+
}
182+
183+
function getECSDesiredCount(scope: Construct, defaultCount: number = 8): number {
184+
const desiredCount = scope.node.tryGetContext('ecs_desired_count');
185+
if (desiredCount) {
186+
return desiredCount;
187+
}
188+
return defaultCount;
181189
}

source/new-image-handler/src/config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface IConfig {
66
styleTableName: string;
77
autoWebp: boolean;
88
secretName: string;
9+
sharpQueueLimit: number;
910
}
1011

1112
const conf: IConfig = {
@@ -16,6 +17,7 @@ const conf: IConfig = {
1617
styleTableName: process.env.STYLE_TABLE_NAME || 'style-table-name',
1718
autoWebp: ['yes', '1', 'true'].includes((process.env.AUTO_WEBP ?? '').toLowerCase()),
1819
secretName: process.env.SECRET_NAME ?? 'X-Client-Authorization',
20+
sharpQueueLimit: Number.parseInt(process.env.SHARP_QUEUE_LIMIT ?? '1', 10),
1921
};
2022

2123
export default conf;

source/new-image-handler/src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ router.post('/images', async (ctx) => {
3737
await _s3.putObject({
3838
Bucket: opt.targetBucket,
3939
Key: opt.targetObject,
40+
ContentType: type,
4041
Body: data,
4142
}).promise();
4243

@@ -55,7 +56,7 @@ router.get(['/debug', '/_debug'], async (ctx) => {
5556

5657
router.get('/(.*)', async (ctx) => {
5758
const queue = sharp.counters().queue;
58-
if (queue > 1) {
59+
if (queue > config.sharpQueueLimit) {
5960
ctx.body = { message: 'Too many requests, please try again later.' };
6061
ctx.status = 429;
6162
return;
@@ -76,6 +77,7 @@ app.on('error', (err: Error) => {
7677

7778
app.listen(config.port, () => {
7879
console.log(`Server running on port ${config.port}`);
80+
console.log('Config:', config);
7981
});
8082

8183
function errorHandler(): Koa.Middleware<Koa.DefaultState, Koa.DefaultContext, any> {
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,72 @@
1-
import { IAction, IActionOpts, IProcessContext, ReadOnly } from '..';
1+
import { IImageContext } from '.';
2+
import { IAction, IActionOpts, IProcessContext, ReadOnly, IActionMask } from '..';
23

34

45
export abstract class BaseImageAction implements IAction {
56
public name: string = 'unknown';
67
abstract validate(params: string[]): ReadOnly<IActionOpts>;
78
abstract process(ctx: IProcessContext, params: string[]): Promise<void>;
8-
public beforeNewContext(_: IProcessContext, params: string[]): void {
9+
public beforeNewContext(_1: IProcessContext, params: string[], _3: number): void {
910
this.validate(params);
1011
}
12+
public beforeProcess(_1: IImageContext, _2: string[], _3: number): void { }
1113
}
14+
15+
export class ActionMask implements IActionMask {
16+
private readonly _masks: boolean[];
17+
18+
public constructor(private readonly _actions: string[]) {
19+
this._masks = _actions.map(() => true);
20+
}
21+
22+
public get length(): number {
23+
return this._actions.length;
24+
}
25+
26+
private _check(index: number): void {
27+
if (!(0 <= index && index < this.length)) {
28+
throw new Error('Index out of range');
29+
}
30+
}
31+
32+
public getAction(index: number): string {
33+
this._check(index);
34+
return this._actions[index];
35+
}
36+
37+
public isEnabled(index: number): boolean {
38+
this._check(index);
39+
return this._masks[index];
40+
}
41+
42+
public isDisabled(index: number): boolean {
43+
this._check(index);
44+
return !this._masks[index];
45+
}
46+
47+
public enable(index: number) {
48+
this._check(index);
49+
this._masks[index] = true;
50+
}
51+
52+
public disable(index: number) {
53+
this._check(index);
54+
this._masks[index] = false;
55+
}
56+
57+
public disableAll() {
58+
for (let i = 0; i < this._masks.length; i++) {
59+
this._masks[i] = false;
60+
}
61+
}
62+
63+
public filterEnabledActions(): string[] {
64+
return this._actions.filter((_, index) => this._masks[index]);
65+
}
66+
67+
public forEachAction(cb: (action: string, enabled: boolean, index: number) => void): void {
68+
this._actions.forEach((action, index) => {
69+
cb(action, this.isEnabled(index), index);
70+
});
71+
}
72+
}

source/new-image-handler/src/processor/image/format.ts

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export class FormatAction extends BaseImageAction {
1818
}
1919
}
2020

21+
public beforeProcess(ctx: IImageContext, params: string[], index: number): void {
22+
const opts = this.validate(params);
23+
if (('gif' === ctx.metadata.format) && ('gif' === opts.format)) {
24+
ctx.mask.disable(index);
25+
}
26+
}
27+
2128
public validate(params: string[]): ReadOnly<FormatOpts> {
2229
let opt: FormatOpts = { format: '' };
2330

source/new-image-handler/src/processor/image/index.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as sharp from 'sharp';
22
import { Features, IAction, InvalidArgument, IProcessContext, IProcessor, IProcessResponse } from '../../processor';
33
import { IBufferStore } from '../../store';
4+
import { ActionMask } from './_base';
45
import { AutoOrientAction } from './auto-orient';
56
import { BlurAction } from './blur';
67
import { BrightAction } from './bright';
@@ -46,14 +47,16 @@ export class ImageProcessor implements IProcessor {
4647
const ctx: IProcessContext = {
4748
uri,
4849
actions,
50+
mask: new ActionMask(actions),
4951
bufferStore,
5052
features: {
5153
[Features.AutoOrient]: true,
5254
[Features.ReadAllAnimatedFrames]: true,
5355
},
5456
headers: {},
5557
};
56-
for (const action of actions) {
58+
for (let i = 0; i < actions.length; i++) {
59+
const action = actions[i];
5760
if ((this.name === action) || (!action)) {
5861
continue;
5962
}
@@ -64,7 +67,7 @@ export class ImageProcessor implements IProcessor {
6467
if (!act) {
6568
throw new InvalidArgument(`Unkown action: "${name}"`);
6669
}
67-
act.beforeNewContext.bind(act)(ctx, params);
70+
act.beforeNewContext.bind(act)(ctx, params, i);
6871
}
6972
const { buffer, headers } = await bufferStore.get(uri);
7073
const image = sharp(buffer, { failOnError: false, animated: ctx.features[Features.ReadAllAnimatedFrames] });
@@ -77,7 +80,7 @@ export class ImageProcessor implements IProcessor {
7780
return {
7881
uri: ctx.uri,
7982
actions: ctx.actions,
80-
effectiveActions: ctx.effectiveActions,
83+
mask: ctx.mask,
8184
bufferStore: ctx.bufferStore,
8285
features: ctx.features,
8386
headers: Object.assign(ctx.headers, headers),
@@ -96,8 +99,28 @@ export class ImageProcessor implements IProcessor {
9699

97100
if (ctx.features[Features.AutoOrient]) { ctx.image.rotate(); }
98101

99-
const actions = (ctx.effectiveActions && ctx.effectiveActions.length) ? ctx.effectiveActions : ctx.actions;
100-
for (const action of actions) {
102+
ctx.mask.forEachAction((action, _, index) => {
103+
if ((this.name === action) || (!action)) {
104+
return;
105+
}
106+
// "<action-name>,<param-1>,<param-2>,..."
107+
const params = action.split(',');
108+
const name = params[0];
109+
const act = this.action(name);
110+
if (!act) {
111+
throw new InvalidArgument(`Unkown action: "${name}"`);
112+
}
113+
act.beforeProcess.bind(act)(ctx, params, index);
114+
});
115+
const enabledActions = ctx.mask.filterEnabledActions();
116+
const nothing2do = (enabledActions.length === 1) && (this.name === enabledActions[0]);
117+
118+
if (nothing2do && (!ctx.features[Features.AutoWebp])) {
119+
const { buffer } = await ctx.bufferStore.get(ctx.uri);
120+
return { data: buffer, type: ctx.metadata.format! };
121+
}
122+
123+
for (const action of enabledActions) {
101124
if ((this.name === action) || (!action)) {
102125
continue;
103126
}

source/new-image-handler/src/processor/image/info.ts

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import { IImageContext } from '.';
2-
import { IActionOpts, ReadOnly, InvalidArgument, Features, IProcessContext } from '..';
2+
import { IActionOpts, ReadOnly, InvalidArgument, Features } from '..';
33
import { BaseImageAction } from './_base';
44

55

66
export class InfoAction extends BaseImageAction {
77
public readonly name: string = 'info';
88

9-
public beforeNewContext(ctx: IProcessContext, params: string[]): void {
10-
this.validate(params);
11-
12-
const action = params.join(',');
13-
if (ctx.effectiveActions) {
14-
ctx.effectiveActions.push(action);
15-
} else {
16-
ctx.effectiveActions = [action];
17-
}
9+
public beforeProcess(ctx: IImageContext, _2: string[], index: number): void {
10+
ctx.mask.disableAll();
11+
ctx.mask.enable(index);
1812
}
1913

2014
public validate(params: string[]): ReadOnly<IActionOpts> {

source/new-image-handler/src/processor/image/quality.ts

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export interface QualityOpts extends IActionOpts {
1818
export class QualityAction extends BaseImageAction {
1919
public readonly name: string = 'quality';
2020

21+
public beforeProcess(ctx: IImageContext, _2: string[], index: number): void {
22+
if ('gif' === ctx.metadata.format) {
23+
ctx.mask.disable(index);
24+
}
25+
}
26+
2127
public validate(params: string[]): ReadOnly<QualityOpts> {
2228
const opt: QualityOpts = {};
2329
for (const param of params) {

source/new-image-handler/src/processor/index.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ export type ReadOnly<T> = {
88
readonly [K in keyof T]: ReadOnly<T[K]>;
99
}
1010

11+
export interface IActionMask {
12+
readonly length: number;
13+
getAction(index: number): string;
14+
isEnabled(index: number): boolean;
15+
isDisabled(index: number): boolean;
16+
enable(index: number): void;
17+
disable(index: number): void;
18+
disableAll(): void;
19+
filterEnabledActions(): string[];
20+
forEachAction(cb: (action: string, enabled: boolean, index: number) => void): void;
21+
}
22+
1123
/**
1224
* Context object for processor.
1325
*/
@@ -22,12 +34,7 @@ export interface IProcessContext {
2234
*/
2335
readonly actions: string[];
2436

25-
/**
26-
* The effective actions.
27-
* If this value is undefined or empty list. All actions will be effective.
28-
* Otherwise, only the action in this list will be effective.
29-
*/
30-
effectiveActions?: string[];
37+
readonly mask: IActionMask;
3138

3239
/**
3340
* A abstract store to get file data.
@@ -149,7 +156,9 @@ export interface IAction {
149156
* @param ctx the context
150157
* @param params the parameters
151158
*/
152-
beforeNewContext(ctx: IProcessContext, params: string[]): void;
159+
beforeNewContext(ctx: IProcessContext, params: string[], index: number): void;
160+
161+
beforeProcess(ctx: IProcessContext, params: string[], index: number): void;
153162
}
154163

155164
/**

source/new-image-handler/src/processor/style.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IAction, InvalidArgument, IProcessContext, IProcessor, IProcessResponse } from '.';
22
import * as is from '../is';
33
import { IBufferStore, IKVStore, MemKVStore } from '../store';
4+
import { ActionMask } from './image/_base';
45
import { ImageProcessor } from './image/index';
56
import { VideoProcessor } from './video';
67

@@ -32,6 +33,7 @@ export class StyleProcessor implements IProcessor {
3233
return Promise.resolve({
3334
uri,
3435
actions,
36+
mask: new ActionMask(actions),
3537
bufferStore,
3638
headers: {},
3739
features: {},

source/new-image-handler/src/processor/video.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as child_process from 'child_process';
22
import { IAction, InvalidArgument, IProcessContext, IProcessor, IProcessResponse, IActionOpts, ReadOnly } from '.';
33
import * as is from '../is';
44
import { IBufferStore } from '../store';
5+
import { ActionMask } from './image/_base';
56

67
export interface VideoOpts extends IActionOpts {
78
t: number; // 指定截图时间, 单位:s
@@ -27,6 +28,7 @@ export class VideoProcessor implements IProcessor {
2728
return Promise.resolve({
2829
uri,
2930
actions,
31+
mask: new ActionMask(actions),
3032
bufferStore,
3133
features: {},
3234
headers: {},

0 commit comments

Comments
 (0)