Jex’s Note

AWS - S3 上傳 (PHP Codeigniter)

(最後更新: 2016-04-27)

介紹

以下會介紹上傳到 s3 的 2 種方法 :

  • mobile 上傳到 s3
  • server 上傳到 s3

command 上傳到 s3, 請參考此篇 AWS-CLI command 上傳到 s3

安裝 AWS SDK

首先先用 composer 安裝 aws-sdk, composer.json :

{
    "require": {
        "aws/aws-sdk-php": "2.7.0"
    }
}

執行

$ composer install

Codeigniter (application/config/config.php) 打開 : $config['composer_autoload'] = TRUE;

mobile 上傳到 s3

有兩種方法

  • 直接給手機一個 S3 的 IAM User 讓手機以 SDK 上傳圖片
  • S3 server 產生有 expire time 的 pre-signed URL (由 server 取得授權的 url) 給 mobile 上傳圖片

直接給手機一個 S3 的 IAM User 讓手機以 SDK 上傳圖片

原本的 Resource 的值是 *, 將它改成以下

Policy for Programmatic Access : (線上 AWS Console 是沒權限看到的)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::test"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutObjectACL",
        "s3:GetObject",
        "s3:GetObjectACL",
        "s3:DeleteObject"
      ],
      "Resource": ["arn:aws:s3:::test/*"]
    }
  ]
}

但是設定好以上, 雖然程式可以有權限存取, 但是 AWS Console 是沒有權限訪問 S3 的, 所以可以參考以下讓這個 User 能看到 S3 的 Console

Policy for Console Access : (線上 AWS Console 是可以看到目錄結構的)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketLocation",
        "s3:ListAllMyBuckets"
      ],
      "Resource": "arn:aws:s3:::*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::test"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutObjectACL",
        "s3:GetObject",
        "s3:GetObjectACL",
        "s3:DeleteObject"
      ],
      "Resource": ["arn:aws:s3:::test/*"]
    }
  ]
}

這樣 mobile 就只能存取 test-mobile 這個 bucket 下的資源了

注意!! 上傳的檔案到 server 的權限是沒有 public-read 的, 所以多加兩個權限 PutObjectACL(更新權限), GetObjectACL(查看權限)

S3 server 產生有 expire time 的 pre-signed URL (由 server 取得授權的 url) 給 mobile 上傳圖片

create pre-signed url :

public function create_pre_signed_url()
{
    $config = array(
        'key'       => 'A******************Q',
        'secret'    => 'P**************************************4',
        'region'    => 'us-west-2',
    );

    $client = S3Client::factory($config);
    $bucket = "s3_bucket_name";
    $key = 'data.txt';
    $url = "{$bucket}/{$key}";

    // get() returns a Guzzle\Http\Message\Request object
    $request = $client->put($url);

    // Create a signed URL from a completely custom HTTP request that
    // will last for 10 minutes from the current time
    $signedUrl = $client->createPresignedUrl($request, '+10 minutes');
    echo $signedUrl;

    //echo file_get_contents($signedUrl);
    // > Hello!
}
  • 因為要讓這個 pre-signed url 可以被上傳檔案, 所以使用 $client->put(
  • 如果改成 $client->get( 輸出的 URL 就可以看到 bucket 的 data.txt 的內容, 那這功能就跟 getObjectUrl 一樣了

要測試它是否 OK 要實際用上傳檔案,不要用瀏覽器開,不然只會得到 XML 錯誤訊息

<Error>
    <Code>SignatureDoesNotMatch</Code>
</Error>

使用 command 模擬 mobile 上傳。建立 /tmp/data.txt, 然候執行以下指令上傳檔案

curl -v -T /tmp/data.txt https://s3_bucket_name.s3.amazonaws.com/data.txt?AWSAccessKeyId=A********************2Q&Expires=1423551936&Signature=y**************2FvYVo%3D
  • 不管傳什麼格式,不用特別指定 Header
  • -T 是使用 PUT 上傳檔案

ref :

server 上傳至 s3

到 IAM 建立一個 User, policy 新增只允許存取某個 bucket 下某個 folder 的 custom policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListAllMyBuckets"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": ["arn:aws:s3:::bucket_name"]
        },
        {
        "Effect": "Allow",
        "Action": "*",
        "Resource": ["arn:aws:s3:::bucket_name/folder_name/*"]
        }
    ]
}

Uploading files to s3 in php

$config = array(
    'key'       => 'A******************A',
    'secret'    => '9**************************************i',
    'region'    => 'us-west-2',
);

$client = S3Client::factory($config);

$result = $client->putObject([
    'Bucket'    => 'bucket_name',
    'Key'       => 'folder_name/qqq/test.txt',
    'SourceFile'    => '/tmp/qq.txt',
]);

不存在的 folder 會自動建立, 如果檔案已存在會覆蓋原始檔案

刪除 s3 檔案

TODO: 補上 code

這裡有個很容易踩雷的點,而且錯誤訊息不好判斷,就是要刪除的檔案路徑最前面不要加 /,直接路徑加檔名即可 e.g. xxxx/ffff/ssss.mp4

[其他] 產生 S3 只能 GET 不能 PUT 的 Pre-signed URL

get pre-signed url :

use Aws\S3\S3Client;

class S3 extends MY_Controller
{
    public function __construct()
    {
        parent::__construct();
    }

    public function get_pre_signed_url()
    {
        $config = array(
            'key'       => 'A******************Q',
            'secret'    => 'P**************************************4',
        );

        $client = S3Client::factory($config);

        // Get a plain URL for an Amazon S3 object
        $plainUrl = $client->getObjectUrl('s3_bucket_name', 'data.txt');
        // > https://my-bucket.s3.amazonaws.com/data.txt

        // Get a pre-signed URL for an Amazon S3 object
        $signedUrl = $client->getObjectUrl('s3_bucket_name', 'data.txt', '+10 minutes');
        // > https://my-bucket.s3.amazonaws.com/data.txt?AWSAccessKeyId=[...]&Expires=[...]&Signature=[...]

        // Create a vanilla Guzzle HTTP client for accessing the URLs
        $http = new \Guzzle\Http\Client;

        // Try to get the plain URL. This should result in a 403 since the object is private
        try {
            $response = $http->get($plainUrl)->send();
        } catch (\Guzzle\Http\Exception\ClientErrorResponseException $e) {
            $response = $e->getResponse();
        }
        echo $response->getStatusCode();
        // > 403

        // Get the contents of the object using the pre-signed URL
        echo $signedUrl;
        $response = $http->get($signedUrl)->send();
        echo $response->getBody();
        // > Hello!
    }
}

?>

以上面程式產生 pre-signed url, 然候頁面上會印出 url, 再拿這個 url 就可以看到檔案裡的內容了

ref:

Comments