Php 生成 Aws Pregisn Request

项目使用laravel开发,用bref部署到aws的lambda上,有一个上传文件的接口,由于lambda限制请求和响应大小为6mb,导致上传大于6mb的文件失败

这时候可以使用s3的presign post让前端直接上传到s3,presign post比presign url多了一个policy功能,你可以限制上传的文件大小,位置

        $client = new S3Client([
            'version' => 'latest',
            'region'  => config('filesystems.disks.s3.region'),
        ]);
        $bucket = config('filesystems.disks.s3.bucket');

        $temp_path = 'uploads/temp/images';

        $options    = [
            ['bucket' => $bucket],
            ['content-length-range' ,0, 20000000], //这里限制上传文件大小,超出大小s3会返回错误
            ['starts-with' ,'$key',$temp_path . '/'], //限制上传到指定目录,你也可以直接指定上传到那个文件夹,文件名
            //['eq', '$Content-Type', 'image/jpeg'], //限制上传文件类型
        ];


        $postObject = new PostObjectV4(
            $client,
            $bucket,
            [],
            $options,
        );
        
         return response()->json([
            'attr'  => $postObject->getFormAttributes(),
            'input' => array_merge($postObject->getFormInputs(),[
                'key'=>$temp_path . '/' . Carbon::now()->format('Y-m-d').'/'.$request->post('name')
            ])
        ]);

示例响应

{
    "attr": {
        "action": "https://cxx.s3.ap-southeast-1.amazonaws.com",
        "method": "POST",
        "enctype": "multipart/form-data"
    },
    "input": {
        "key": "uploads/temp/posts/images/2023-05-04/Screenshot 2023-05-03 at 11.03.33.png",
        "X-Amz-Credential": "AKIAV7ASRH6Z642DM35C/20230504/ap-southeast-1/s3/aws4_request",
        "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
        "X-Amz-Date": "20230504T131615Z",
        "Policy": "eyJleHBpcmF0aW9uIjoiMjAyMy0wNS0wNFQxNDoxNjoxNVoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJmYmJ1Y2NrZXQxMjMifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwwLDEwMDAwMDAwXSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVwbG9hZHNcL3RlbXBcL3Bvc3RzXC9pbWFnZXNcLzIwMjMtMDUtMDRcLyJdLHsiWC1BbXotRGF0ZSI6IjIwMjMwNTA0VDEzMTYxNVoifSx7IlgtQW16LUNyZWRlbnRpYWwiOiJBS0lBVjdBU1JINlo2NDJETTM1Q1wvMjAyMzA1MDRcL2FwLXNvdXRoZWFzdC0xXC9zM1wvYXdzNF9yZXF1ZXN0In0seyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In1dfQ==",
        "X-Amz-Signature": "82120e8d2dd1515cb18dc9a39810d5aca682f4fbbd5cc5b13a1d28007e29dd4e"
    }
}

前端接受到参数后,构建post请求到s3的bucket即可


      const {attr, input} = response;
      const amzForm = new FormData()
      Object.keys(input).forEach(k => {
          amzForm.append(k, input[k])
      })
      amzForm.append('file', fileToUpload)
      return axios.request({
        url: attr.action,
        method: 'post',
        headers: {"Content-Type": "multipart/form-data"},
        data: amzForm,
      })(attr.action, amzForm).then(r => {
        //这里是上传成功,可以通知后端
      }).catch(r => {
        console.log(r)
      })

这里如果上传成功,s3会给一个204的响应,如果失败了,会给一个400的响应,格式是xml

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>EntityTooLarge</Code>
    <Message>Your proposed upload exceeds the maximum allowed size</Message> //可以看到错误原因是文件太大
    <ProposedSize>10002748</ProposedSize>
    <MaxSizeAllowed>10000000</MaxSizeAllowed>
    <RequestId>XBPNJFVJP7MR9SMD</RequestId>
    <HostId>Kg7cWw=</HostId>
</Error>

还有一个问题是即使你在签名时指定了content-type限制,['eq', '$Content-Type', 'image/jpeg'],s3只会验证你的form的content-type字段的值是不是image/jpeg,至于你上传的文件的mime type是不是image/jpeg,s3是不会去验证的