http-proxy-middleware 사용하기
서론
현재 팀에서는 클라이언트가 하나의 프록시 서비스를 바라보고 있고, 해당 프록시 서비스가 요청에 따라 여러 서비스를 호출하고 있다. 때문에 한 서비스에서 새로운 기능이 생겨나면 프록시 서비스가 해당 기능도 호출할 수 있도록 업데이트를 해줘야 된다. 레거시라는 걸 알고 있지만 언제 제거될 지 모르고, 그 사이에 새 기능이 추가되면 프록시 서비스도 업데이트를 해야 하는 상태이기 때문에 업데이트가 없어도 알아서 프록시를 하는 방법이 없을까 팀원과 고민해보던 중 이 라이브러리를 알게 되었다.
이에 해당 라이브러리를 사용해보면서 공부한 것들을 간단히 정리해보기로 했다.
http-proxy-middleware 라이브러리
HTTP 요청 및 응답을 프록시하는 데 사용되는 미들웨어 라이브러리.
- 서버 간에 데이터를 전달하거나 API 요청을 프록시할 수 있다.
- 요청, 응답, 에러 등 다양한 상황에 대한 이벤트 핸들링도 가능하다.
장단점
- 장점
- 간편한 설정
- 다양한 기능(url 재설정, 이벤트 핸들링 등) 제공
- 단점
- 복잡한 프록시 설정은 어려움
- 동적으로 프록시 대상을 변경하는 건 어려움
사용법
yarn add http-proxy-middleware
// main.ts
const proxy = createProxyMiddleware('/api', {
target: 'http://your-backend-server-url',
changeOrigin: true,
pathRewrite: {
'^/api': '',
},
onProxyReq: (proxyReq) => {
proxyReq.setHeader('X-Special-Header', 'Proxying');
},
});
app.use('/api', proxy);
- target: 프록시 요청이 전송될 대상 서버의 URL을 지정.
- changeOrigin: 대상 서버의 Host 헤더를 변경하여 요청을 원하는 서버로 보내는 데 사용.
- pathRewrite: 요청 경로를 변경할 때 사용. 다중 패턴 가능.
- onProxyReq : 프록시 요청이 전송되는 동안 이벤트를 처리할 수 있음.
적용기
기존 코드(회사 코드를 그대로 들고 올 순 없어 변수명과 로직을 일부 변경했다)
// controller
@Get('/animals')
@ApiOperation({ summary: '동물들 목록 가져오기' })
@ApiOkResponse({ type: GetAnimalsResDto })
async getAnimals(@Query() pageOptions: PageOptionsReqDto): Promise<GetAnimalsResDto> {
return await this.animalService.getAnimals(pageOptions);
}
// service
async getAnimals(pageOptions: PageOptionsReqDto): Promise<GetAnimalsResDto> {
try {
const req = this.httpService.get<GetAnimalsResDto>(`${this.animalsHost}/v3/backend/animals`, {
params: {
...pageOptions,
},
});
const { data } = await lastValueFrom(req);
return data;
} catch (e) {
this.logger.error(e);
throw e;
}
}
변경 후 코드
- controller, service에서 바이패스 api 사라짐
- main.ts에 프록시 세팅 추가
// main.ts
// ...
const animalsBaseHostUrl = app.get(ConfigService).get<string>('baseHost.animals');
// ...
// set proxy to animals, plants
const animalsProxy = createAnimalsProxyMiddleWare(animalsBaseHostUrl);
app.use('/api/v3/proxy/animals*', (req, res, next) => {
animalsProxy(req, res, next);
});
// proxyMiddleware.ts
import { createProxyMiddleware, RequestHandler } from 'http-proxy-middleware';
// v3
export function createAnimalsProxyMiddleWare(animalsBaseHostUrl: string): RequestHandler {
const filter = function (pathname): boolean {
return pathname.match('^/api/v3/proxy') && !pathname.includes(['/summary/']);
};
return createProxyMiddleware(filter, {
target:animalsBaseHostUrl,
changeOrigin: true,
pathRewrite: {
'^/api/v3/proxy/animals': '/v3/animals',
},
onProxyReq: (proxyReq) => {
proxyReq.setHeader('X-Special-Header', 'Proxying');
},
});
}
적용 후기
프록시 미들웨어를 적용하면서 가장 크게 달라진 점은 새로운 api가 생겨도 프록시 레포에 추가할 필요가 없어진 것이다. 다만 프론트엔드에서 프록시 서비스의 스웨거를 참고하고 있었는데, 프록시에서 api를 제거하면서 스웨거 정보도 사라지게 되었다. 때문에 노션으로 프록시 경로를 알려주고 백엔드 스웨거를 참고하라고 전달했다. 또한 프록싱 되는 패턴에 해당되는 api가 새로 생긴다면 상관 없지만, 패턴과 다른 api일 경우 프록시를 새로 만들어야 할 것이다.
프록시 미들웨어로 편해진 건 사실이지만, 완전하게 프록싱된 건 아니기 때문에 필요할 경우 계속 보수해야 한다. 그래도 새로운 라이브러리도 공부해보고, 걱정과 달리 적용도 잘 되었다. 레거시 코드를 리팩토링했지만 여전히 레거시이긴 해서 언젠가 사라질 코드라는 것이 아쉽긴 하다. 그래도 기왕 없앤다면 직접 없애고 더 깔끔한 코드로 발전하는 모습을 보고 싶다.