다음과 같은 API 요청과 API 응답을 가지는 REST API를 Lambda 함수와 API Gateway를 이용하여 구축해본다.
API 요청
GET /devices/{deviceId}/log?from=1000&to=2000
응답모델
{
"data":[
{
"time":timestamp,
"attributes": {
"attribute1": integer3,
"attribute2": "string3",
...
}
},
...
]
}
–
생성된 * GetDeviceLambda*의 build.gradle 파일을 열고, 다음 의존성을 추가하고, 변경사항을 반영합니다.
dependencies {
...
implementation platform('com.amazonaws:aws-java-sdk-bom:1.12.529')
implementation 'com.amazonaws:aws-java-sdk-dynamodb'
...
}
src/main/java/helloworld/App.java 파일을 다음 코드로 바꿉니다.
package helloworld;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.TimeZone;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import com.amazonaws.services.dynamodbv2.document.QueryOutcome;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.document.utils.NameMap;
import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;
/**
* Handler for requests to Lambda function.
*/
public class App implements RequestHandler<Event, String> {
private DynamoDB dynamoDb;
private String DYNAMODB_TABLE_NAME = "Logging";
public String handleRequest(final Event input, final Context context) {
this.initDynamoDbClient();
Table table = dynamoDb.getTable(DYNAMODB_TABLE_NAME);
long from=0;
long to=0;
try {
SimpleDateFormat sdf = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));
from = sdf.parse(input.from).getTime() / 1000;
to = sdf.parse(input.to).getTime() / 1000;
} catch (ParseException e1) {
e1.printStackTrace();
}
QuerySpec querySpec = new QuerySpec()
.withKeyConditionExpression("deviceId = :v_id and #t between :from and :to")
.withNameMap(new NameMap().with("#t", "time"))
.withValueMap(new ValueMap().withString(":v_id",input.device).withNumber(":from", from).withNumber(":to", to));
ItemCollection<QueryOutcome> items=null;
try {
items = table.query(querySpec);
}
catch (Exception e) {
System.err.println("Unable to scan the table:");
System.err.println(e.getMessage());
}
String output = getResponse(items);
return output;
}
private String getResponse(ItemCollection<QueryOutcome> items) {
Iterator<Item> iter = items.iterator();
String response = "{ \"data\": [";
for (int i =0; iter.hasNext(); i++) {
if (i!=0)
response +=",";
response += iter.next().toJSON();
}
response += "]}";
return response;
}
private void initDynamoDbClient() {
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
this.dynamoDb = new DynamoDB(client);
}
}
class Event {
public String device;
public String from;
public String to;
}
–
작성된 Lambda함수가 정상적으로 동작하는 지를 테스트해 보기 위해서 다음 절차를 수행합니다.
IntelliJ IDEA IDE의 화면 상단 타이틀 바에서 “[Local] HelloWorldFunction” 옆의 연두색 실행 버튼 (삼각형)을 클릭
[Edit Configuration] 다이얼로그 화면에서 Text – Event Templates – 부분의 드롭다운 메뉴 중에서 API Gateway AWS Proxy를 선택하고, 다음 입력 문자열을 입력한다.
to 속성: 종료 시간 (yyyy-MM-dd hh:mm:ss) 형식의 문자열
{ "device": "MyMKRWiFi1010", "from":"2023-11-05 13:26:00", "to": "2023-11-05 13:28:00"}
Console 창에 다음과 같은 형식의 메시지가 마지막에 출력되는 지 확인합니다. (본인의 aws 계정에 생성된 사물의 상태가 Json 형식으로 반환됨)
...
Mounting /Users/kwanwoo/Dropbox/2023-2/IOTPlatform/aws-practice/LogDeviceLambda/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
Picked up JAVA_TOOL_OPTIONS: -XX:+TieredCompilation -XX:TieredStopAtLevel=1
END RequestId: 6b6e2735-2dd7-473b-91a7-a9a84d95b1de
REPORT RequestId: 6b6e2735-2dd7-473b-91a7-a9a84d95b1de Init Duration: 2.56 ms Duration: 12914.47 ms Billed Duration: 12915 ms Memory Size: 512 MB Max Memory Used: 512 MB
"{ \"data\": [{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158409,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:26:49\"},{\"temperature\":\"24.30\",\"LED\":\"OFF\",\"time\":1699158414,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:26:54\"},{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158419,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:26:59\"},{\"temperature\":\"24.40\",\"LED\":\"OFF\",\"time\":1699158424,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:04\"},{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158434,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:14\"},{\"temperature\":\"24.40\",\"LED\":\"OFF\",\"time\":1699158444,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:24\"},{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158454,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:34\"},{\"temperature\":\"24.40\",\"LED\":\"OFF\",\"time\":1699158464,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:44\"},{\"temperature\":\"24.30\",\"LED\":\"OFF\",\"time\":1699158469,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:49\"},{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158474,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:54\"}]}"
–
GetDeviceLambda 프로젝트 탐색창에서 template.yaml을 찾아서 선택하고, 선택된 상태에서 오른쪽 마우스 클릭하여 SyncServerless Application (formerly Deploy) 메뉴를 선택
[Confirm development stack] 다이얼로그 화면에서 Confirm 선택
[SyncServerless Application (formerly Deploy)] 다이얼로그 화면에서, Create Stack에 적절한 이름(예, LogDeviceLambda)을 입력 하고, S3 Bucket 중에 하나를 선택(S3 Bucket이 없으면 Create 버튼을 눌러 생성 후 선택)하고, CloudFormation Capabilities: 에서 IAM 체크박스를 선택한 후, Sync 클릭
콘솔 창에 다음 결과가 맨 마지막 줄에 출력되는 지를 확인
...
Stack creation succeeded. Sync infra completed.
Process finished with exit code 0
–
AWS Lambda함수가 다른 AWS 서비스 (예, DynamoDB)를 사용하기 위해서는 필요한 권한이 Lambda함수에 연결된 실행 역할 정책(Execution Role Polcity)에 포함되어 있어야 합니다.
람다함수의 실행 역할 정책 업데이트
원격 테스트를 위해서 AWS Toolkit 창의 탐색기에서 Lambda를 확장하여 LogDeviceLambda-HelloWorldFunction-XXX선택하고, 오른쪽 마우스 클릭하여 Run ‘[Remote] HelloServer…‘메뉴를 선택
–
다음 단계는 API Gateway를 통해 들어오는 클라이언트의 입력을 Lambda 함수에 전달하기 위해서 클라이언트의 입력을 Lambda 함수의 입력으로 매핑하는 과정에 대해서 진행합니다.
탬플릿 생성 드롭다운 메뉴에서 UpdateDeviceInput을 선택하고, 템플릿 본문에 다음을 입력합니다.
{
"device": "$input.params('device')",
"from": "$input.params('from')",
"to": "$input.params('to')"
}
쿼리 문자열에 아래와 같은 내용을 입력합니다.
from="2023-11-05 13:26:00"&to="2023-11-05 13:27:00"
테스트를 클릭하고, 다음과 같은 결과가 나오는 지 확인합니다.
/devices/{device}/log - GET 메서드 테스트 결과
요청
...
응답 본문
"{ \"data\": [{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158409,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:26:49\"},{\"temperature\":\"24.30\",\"LED\":\"OFF\",\"time\":1699158414,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:26:54\"},{\"temperature\":\"24.50\",\"LED\":\"OFF\",\"time\":1699158419,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:26:59\"},{\"temperature\":\"24.40\",\"LED\":\"OFF\",\"time\":1699158424,\"deviceId\":\"MyMKRWiFi1010\",\"timestamp\":\"2023-11-05 13:27:04\"},
...
–
JavaScript는 Cross-Origin Resource Sharing (CORS) 요청을 기본적으로 제한합니다. 즉, JavaScript 코드가 동일 서버 내의 리소스를 접근하는 것은 허용하지만, 다른 서버의 리소스를 사용하고자 하는 경우에는 CORS 헤더 정보가 포함되어 있어야 합니다.
REST API 리소스에 대해 CORS 지원 활성화
지금까지 API를 생성했지만 아직 실제로 사용할 수는 없습니다. 배포해야 하기 때문입니다.
–
https://cry59jagkd.execute-api.ap-northeast-2.amazonaws.com/prod/devices/MyMKRWiFi1010/log?from=2023-11-05%2013:26:00&to=2023-11-05%2013:27:00