0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Lambdaを使ってS3にアップロードしたjsファイルのcontent-typeを変える

Posted at

概要

S3にアップロードしたJSファイルのContent-Typeを変更するスクリプトを、Lambdaを使って実装しました。
この記事では、その方法を共有します。

動機

静的サイトをAWS S3でホスティングしようとした際、
フロントエンジニアにIAMのアクセスキーを渡してS3にサイトのソースコード(HTML, CSS, JSファイルなど)をアップロードしてもらいましたが、
JSファイルのContent-Typeがbinary/octet-streamに設定されてしまい、サイトが正常に動作しないという問題が発生しました。

原因

コマンドラインからS3にファイルをアップロードする際に、Content-Type を明示的に指定しないと、AWSがファイルの拡張子に基づいて自動的にContent-Typeを推測します。
この推測が正しく行われない場合があり、特にJSファイルでこの問題が発生するようです。

対応

本来であれば、S3にファイルをアップロードする際に正しいContent-Typeを指定すべきですが、フロントエンジニア側の設定変更ができない状況だったため、サーバサイドで対応することにしました。

S3へのファイルアップロードをトリガーに、AWS Lambda関数を使ってJSファイルのContent-Typeを書き換える処理を実装してます。

また、このLambda関数の管理はTerraformを使用しています。

注意点

この実装では、S3にファイルがアップロードされるたびにLambda関数がトリガーされるため、大量のファイルをアップロードするとLambdaの実行コストが増加する可能性があるので注意が必要です。

コード

lambda

# lambda-replace-content_type.py

import boto3

s3 = boto3.client('s3')
def lambda_handler(event, context):
    # S3イベントからバケット名とオブジェクトキーを取得
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    object_key = event['Records'][0]['s3']['object']['key']

    # Content-Typeを変更
    replace_content_type(bucket_name, object_key)

# S3にアップロードされたオブジェクトのContent-Typeを変更する関数
def replace_content_type(bucket_name, object_key):
    try:
        # 現在のオブジェクトのメタデータを取得
        response = s3.head_object(Bucket=bucket_name, Key=object_key)
        metadata = response['Metadata']

        # content-typeがbinary/octet-stream以外だった場合、正しい設定がされているとして終了
        if response['ContentType'] != 'binary/octet-stream':
            return {}

        # 拡張子によってContent-Typeを判定
        ext = os.path.splitext(object_key)[1][1:] 
        if ext == 'js':
            # 今の所JSファイルのみ問題になっているので、JSファイルの場合のみContent-Typeを変更
            content_type = 'text/javascript'
        else:
            # jsファイル以外はoctet-streamのままにしておく
            content_type = 'binary/octet-stream'

        # オブジェクトの置き換え(Content-Typeを変更)
        s3.copy_object(
            Bucket=bucket_name,
            CopySource={'Bucket': bucket_name, 'Key': object_key},
            Key=object_key,
            ContentType=content_type,  
            Metadata=metadata, 
            MetadataDirective='REPLACE'  # メタデータの置き換えを指示
       )
    except Exception as e:
        print(e)

terraform

#####
# Lambda関数
#####

# terraform実行時にsource_fileのスクリプトをzip化する。
data "archive_file" "lambda-src-zip" {
  type        = "zip"
  source_file = "src/lambda-replace-content_type.py"
  output_path = "src/lambda-replace-content_type.zip"
}
resource "aws_lambda_function" "replace_content_type" {
  function_name = "replace_content_type"
  role          = aws_iam_role.lambda_s3_role.arn
  handler       = "lambda-replace-content_type.lambda_handler"
  runtime       = "python3.9"

  filename = data.archive_file.lambda-src-zip.output_path
  source_code_hash = data.archive_file.lambda-src-zip.output_base64sha256

}


#####
# ポリシー周り
#####

# lambdaのロール
resource "aws_iam_role" "lambda_s3_role" {
  name = "lambda_s3_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

# lambdaのロールにs3のアクセスを制御するポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "lambda_s3_policy_attachment" {
  policy_arn = aws_iam_policy.lambda_s3_policy.arn
  role       = aws_iam_role.lambda_s3_role.name
}

resource "aws_iam_policy" "lambda_s3_policy" {
  name        = "lambda_s3_policy"
  description = "Policy to allow Lambda to perform GetObject, PutObject, and CopyObject actions on S3"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:CopyObject"
        ]
        Resource = [
          "arn:aws:s3:::my-bucket/*",  # ここで具体的なARNを指定
        ]
      }
    ]
  })
}


#####
# Lambdaのトリガー設定
#####

# トリガー対象のイベントを定義
resource "aws_s3_bucket_notification" "s3-notification" {
  bucket = module.static-hosting.website-bucket.name

  lambda_function {
    lambda_function_arn = aws_lambda_function.replace_content_type.arn
    # s3:ObjectCreated:CopyはLambda側で実行するため、ここでは指定しない(ループするかもしれないから)
    events              = [
        "s3:ObjectCreated:Put",
        "s3:ObjectCreated:Post",
        "s3:ObjectCreated:CompleteMultipartUpload"
    ]
  }

  depends_on = [aws_lambda_permission.allow-s3-invoke]
}

# Lambdaが特定サービス(S3とか)によって実行される際の権限を定義
resource "aws_lambda_permission" "allow-s3-invoke" {
  statement_id  = "AllowS3InvokeProd"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.replace_content_type.function_name
  principal     = "s3.amazonaws.com"
  source_arn    = "arn:aws:s3:::my-bucket"  # ここで具体的なARNを指定
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?