Jak przesłać plik do S3 za pomocą API Gateway
Nie zawsze możemy lub chcemy skorzystać z presigned URL aby umożliwić upload pliku do S3. Nie jest to oczywiście jedyny możliwy sposób. Dziś pokażę jak przesłać plik do S3 za pomocą API Gateway. Bezpośrednio.
Jak przesłać plik do S3 za pomocą API Gateway
Infrastrukturę stworzymy za pomocą cdk. Cały kod dostępny jest oczywiście na moim GitHub-ie.
Do przyrządzenia potrawy będziemy potrzebowali dwa główne składniki:
- bucket S3
- API Gateway REST API
oraz kilka przypraw:
- CloudWatch LogGroup
- rola IAM dla API Gateway
- API Gateway Resource
- API Gateway Method
- integracja API z S3
- obowiązkowo!!! tagi
Krok po kroku pokażę Wam jak przesłać plik do S3 za pomocą API Gateway.
Jedna rzecz którą trzeba brać pod uwagę. Na chwilę obecną, maksymalna wielkość payload dla API Gateway wynosi 10MB. Warto sprawdzić to tutaj.
Tworzymy zasoby
No to po kolei…
S3 Bucket
Na początku utworzymy bucket, w którym będą lądowały przesłane pliki.
const bucket = new s3.Bucket( this, 'Filesbucket', { autoDeleteObjects: true, removalPolicy: cdk.RemovalPolicy.DESTROY, encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, } )
Zakładam, że testujemy rozwiązanie. W związku z tym w linii 5 i 6 usuwamy bucket po usunięciu całego stacka. Tak, to jest takie proste przy pomocy cdk. W innym przypadku trzeba zrobić to tak.
Na produkcji zapewne będziecie chc
API Gateway REST API
Chyba najważniejsza część rozwiązania. API Gateway REST API.
const api = new apigateway.RestApi( this, 'Api', { description: 'S3 Proxy Api', binaryMediaTypes: ['*/*'], minimumCompressionSize: 0, cloudWatchRole: true, endpointTypes: [apigateway.EndpointType.EDGE], deployOptions: { stageName: 'dev', tracingEnabled: true, accessLogDestination: new apigateway.LogGroupLogDestination(logGroup), accessLogFormat: apigateway.AccessLogFormat.clf(), loggingLevel: MethodLoggingLevel.INFO, metricsEnabled: true, }, } );
Zwróćcie uwagę na trzy ustawienia:
- binaryMediaTypes – jeżeli chcemy przesyłać pliki binarne przez API Gateway, musimy tutaj podać rodzaj plików
- minimumCompressionSize – rozmiar, od którego API Gateway będzie kompresował payload
- cloudWatchRole – zostanie automatycznie utworzona rola CloudWatch dla API Gateway
Jeszcze słówko odnośnie binaryMediaTypes. Tu ustawiamy wszystkie przesyłane pliki jako binarne. Dla ułatwienia. W rzeczywistości zapewne to nie będzie dobre rozwiązanie, gdyż rzeczywiście wszystkie pliki, nawet z ustawieniami Content-Type:application/json będą traktowane jako binarne.
Pozostałe ustawienia można znaleźć tutaj.
Jeżeli chodzi o deployment, włączamy logi, metryki oraz X-Ray. Jeżeli chcecie zmienić coś jeszcze, wszystkie możliwości opisane są tu.
CloudWatch LogGroup
Tworzymy następnie LogGroup dla Access Logs z API.
const logGroup = new logs.LogGroup(this, "ApiGatewayAccessLogs");
IAM Role
API Gateway musi mieć oczywiście uprawnienia do uploadu plików do naszego buc keta S3. Poniżej tworzymy rolę z niezbędnymi uprawnieniami.
const apiExecuteRole = new iam.Role( this, 'api-gateway-s3-assume-role', { assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"), roleName: "API-Gateway-S3-Integration-Role", }); bucket.grantPut(apiExecuteRole);
cdk utworzy dla nas następującą rolę:
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:Abort*", "s3:PutObject", "s3:PutObjectLegalHold", "s3:PutObjectRetention", "s3:PutObjectTagging", "s3:PutObjectVersionTagging" ], "Resource": "arn:aws:s3:::apigtws3stack-filesbucket536fd8f5-e4j5pag9ze3o/*", "Effect": "Allow" } ] }
Użyjemy jej później, w momencie tworzenia integracji API z S3.
API Gateway Resource
Mamy już API, teraz nadszedł czas na utworzenie w nim zasobu. Podajemy tam „zmienną” {key}, którą wykorzystamy później. Będzie to nazwa pliku, który zostanie zapisany w S3.
const resource = api.root.addResource( "{key}" );
API Integration
Przejdźmy teraz do utworzenia integracji API z S3.
const apiIntegration = new apigateway.AwsIntegration({ service: "s3", integrationHttpMethod: "PUT", path: `${bucket.bucketName}/{key}`, options: { credentialsRole: apiExecuteRole, integrationResponses: [ { statusCode: "200", responseParameters: { "method.response.header.Content-Type": "integration.response.header.Content-Type", }, }, ], requestParameters: { "integration.request.path.key": "method.request.path.key", }, }, });
Aby zapisać pliki w S3, jako metodę wybieramy PUT. Ścieżka to nazwa naszego, utworzonego na początku, bucketa oraz nazwa pliku pobrana z URL-a.
Za pomocą parametru credentialsRole ustawiamy utworzoną wcześniej rolę.
Następnie za pomocą integrationResponses
integrationResponses: [ { statusCode: "200", responseParameters: { "method.response.header.Content-Type": "integration.response.header.Content-Type", }, }, ],
przekazujemy do odpowiedzi API wartość nagłówka ContentType.
Za pomocą requestParameters przekazujemy dalej nazwę naszego pliku
requestParameters: { "integration.request.path.key": "method.request.path.key", }
API Gateway Method
Nadszedł czas na dodanie metody do naszego zasobu API. Jak się zapewne domyślacie, będzie to metoda PUT.
const putFileMethod = resource.addMethod( 'PUT', apiIntegration, { methodResponses: [ { statusCode: "200", responseParameters: { "method.response.header.Content-Type": true, }, }, ], requestParameters: { "method.request.path.key": true, "method.request.header.Content-Type": true, }, } );
W response zwrócimy nagłówek ContentType, a w requeście będziemy wymagali wartości key w path, oraz nagłówka http z wartością ContentType.
Tags
Tutaj dodamy sobie tagi do tworzonych zasobów. Polecam. Szczególnie w środowiskach, gdzie mamy możliwość zapomnienia w jakim celu stworzyliśmy konkretny zasób
Tags.of(this).add('Name', 'S3 Proxy Api'); Tags.of(this).add('Owner', 'przemek.malak@example.com');
Deployment i testy
Po wykonaniu cdk deploy
na naszym koncie powinniśmy mieć następujące zasoby
a przesłane na nasz endpoint pliki powinny wylądować w buckecie Filesbucket.
Poniżej polecenie curl, które prześle plik jpg do naszego API i zapisze je w pliku o nazwie plik.pdf:
curl -X "PUT" "https://<API ID>.execute-api.eu-central-1.amazonaws.com/dev/plik.pdf" \ -H 'Content-Type: image/jpeg' -F 'data=@nasz_plik.jpeg'
Możemy teraz sprawdzić czy plik rzeczywiście wylądował z buckecie za pomocą polecenia aws s3 ls s3://nazwa-bucketa
Działa. Już wiecie jak przesłać plik do S3 za pomocą API Gateway.
Kończymy
Wypada po sobie posprzątać. Jeżeli chcecie skasować wszystkie utworzone zasoby to wystarczy polecenie cdk destroy
. Wszystko zostanie usunięte z Waszego konta.
To co zrobiliśmy nie zawsze będzie tym sposobem, który powinniśmy stosować w przypadku przesyłania plików do S3. Stwierdzę nawet, że rzadko będzie preferowanym sposobem. Mogą się jednak pojawić sytuacje kiedy będzie preferowaną, bądź nawet jedyną możliwą do zastosowania metodą.
Warto wiedzieć, że tak się też da.
Przypominam, kod rozwiązania można pobrać z GitHub.