로컬에서 API Gateway 엔드포인트 호출하기
API Gateway 엔드포인트 로컬에서 호출하기
우리의 노트 앱 백엔드에는 API Gateway 엔드포인트가 있습니다. 이 엔드포인트를 로컬에서 개발할 수 있도록 하기 위해 serverless-offline 플러그인을 사용하여 로컬 웹 서버를 시작할 것입니다.
로컬에서 API 호출하기
모든 API 서비스가 이 플러그인을 필요로 하기 때문에, 위 플러그인을 레포지토리 루트에 설치했습니다. notes-api
폴더 안의 serverless.yml
파일을 열어보세요. serverless-offline
이 플러그인 목록에 있는 것을 확인할 수 있습니다.
service: notes-api
plugins:
- serverless-offline
이제 로컬 웹 서버를 시작해 보겠습니다.
$ cd notes-api
$ serverless offline
기본적으로 서버는 http://localhost
주소와 3000
포트에서 시작됩니다. 엔드포인트에 요청을 보내보겠습니다.
$ curl http://localhost:3000/notes
Cognito Identity Pool 인증 모킹하기
우리 API 엔드포인트는 Cognito Identity Pool을 사용해 보안이 설정되어 있습니다. serverless-offline 플러그인을 사용하면 요청 헤더를 통해 Cognito 인증 정보를 전달할 수 있습니다. 이를 통해 Cognito Identity Pool에 의해 인증된 것처럼 Lambda 함수를 호출할 수 있습니다.
User Pool 사용자 ID를 모킹하려면:
$ curl --header "cognito-identity-id: 13179724-6380-41c4-8936-64bca3f3a25b" \
http://localhost:3000/notes
Lambda 함수 내에서 event.requestContext.identity.cognitoIdentityId
를 통해 이 ID에 접근할 수 있습니다.
Identity Pool 사용자 ID를 모킹하려면:
$ curl --header "cognito-authentication-provider: cognito-idp.us-east-1.amazonaws.com/us-east-1_Jw6lUuyG2,cognito-idp.us-east-1.amazonaws.com/us-east-1_Jw6lUuyG2:CognitoSignIn:5f24dbc9-d3ab-4bce-8d5f-eafaeced67ff" \
http://localhost:3000/notes
Lambda 함수 내에서 event.requestContext.identity.cognitoAuthenticationProvider
를 통해 이 ID에 접근할 수 있습니다.
여러 서비스 작업하기
우리 앱은 여러 API 서비스로 구성되어 있습니다. notes-api
와 billing-api
는 두 개의 별도 Serverless Framework 서비스입니다. 각각 /notes
와 /billing
경로에 응답합니다.
serverless-offline 플러그인은 전체 API 엔드포인트를 에뮬레이트할 수 없습니다. 요청을 처리하고 해당 서비스로 라우팅할 수 없습니다. 이는 플러그인이 앱 수준이 아닌 서비스 수준에서 동작하기 때문입니다.
그래도 /notes
와 /billing
을 각각의 서비스로 라우팅하면서 8080
포트에서 서버를 실행할 수 있는 간단한 스크립트를 제공합니다.
#!/usr/bin/env node
const { spawn } = require("child_process");
const http = require("http");
const httpProxy = require("http-proxy");
const services = [
{ route: "/billing/*", path: "services/billing-api", port: 3001 },
{ route: "/notes/*", path: "services/notes-api", port: 3002 },
];
// 각 서비스에 대해 `serverless offline` 시작
services.forEach((service) => {
const child = spawn(
"serverless",
["offline", "start", "--stage", "dev", "--port", service.port],
{ cwd: service.path }
);
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk) => console.log(chunk));
child.stderr.on("data", (chunk) => console.log(chunk));
child.on("close", (code) => console.log(`child exited with code ${code}`));
});
// 8080 포트에서 프록시 서버 시작, URL 경로 기반으로 요청 전달
const proxy = httpProxy.createProxyServer({});
const server = http.createServer(function (req, res) {
const service = services.find((per) => urlMatchRoute(req.url, per.route));
// 케이스 1: 매칭되는 서비스가 있으면 요청을 해당 서비스로 전달
if (service) {
proxy.web(req, res, { target: `http://localhost:${service.port}` });
}
// 케이스 2: 매칭되는 서비스가 없으면 사용 가능한 라우트 표시
else {
res.writeHead(200, { "Content-Type": "text/plain" });
res.write(
`Url path "${req.url}" does not match routes defined in services\n\n`
);
res.write(`Available routes are:\n`);
services.map((service) => res.write(`- ${service.route}\n`));
res.end();
}
});
server.listen(8080);
// 라우트 매칭 확인
// - 예: url은 '/notes/123'
// - 예: route는 '/notes/*'
function urlMatchRoute(url, route) {
const urlParts = url.split("/");
const routeParts = route.split("/");
for (let i = 0, l = routeParts.length; i < l; i++) {
const urlPart = urlParts[i];
const routePart = routeParts[i];
// 케이스 1: 둘 중 하나가 undefined면 매칭되지 않음
if (urlPart === undefined || routePart === undefined) {
return false;
}
// 케이스 2: route part가 match all(*)이면 매칭
if (routePart === "*") {
return true;
}
// 케이스 3: 정확히 일치하면 계속 확인
if (urlPart === routePart) {
continue;
}
// 케이스 4: route part가 변수면 계속 확인
if (routePart.startsWith("{")) {
continue;
}
}
return true;
}
이 스크립트는 샘플 저장소에 startServer
로 포함되어 있습니다. 이 스크립트가 어떻게 동작하는지 간단히 살펴보겠습니다. 크게 4가지 섹션으로 나뉩니다:
- 가장 상단에서 시작할 서비스를 정의합니다. 새로운 서비스를 추가할 때 이 부분을 수정하면 됩니다.
- serverless-offline 플러그인을 사용해 정의된 포트로 각 서비스를 시작합니다.
- 8080 포트에서 HTTP 서버를 시작합니다. 요청 처리 로직에서 매칭되는 라우트를 가진 서비스를 찾습니다. 매칭되는 서비스가 있으면 요청을 해당 서비스로 프록시합니다.
- 하단에는 라우트가 URL과 매칭되는지 확인하는 함수가 있습니다.
프로젝트 루트에서 이 서버를 로컬로 실행할 수 있습니다:
$ ./startServer
이제 Lambda 함수를 로컬에서 개발하는 방법을 잘 알게 되었으니, 새로운 기능을 위한 환경을 생성할 때 어떤 일이 일어나는지 살펴보겠습니다.
For help and discussion
Comments on this chapter