+3

Lời nói đầu

Hi, mình là lập trình viên Java. Vừa rồi dự án mình có tích hợp chức năng upload file lên hệ thống lưu trữ thứ 3.
Và đó là 1 trong những hệ thống lưu trữ dữ liệu lớn nhất: Amazon S3.
Bla bla bla.
Và bài viết này mình viết về các đoạn code simple, cái mà giúp mình bắt đầu làm quen với nó.

1. Thư viện

Tại thời điểm bài viết này, AWS đã cho ra bản SDK 2.0 cho Java, tuy nhiên đây là bản preview cho developer.
=> Mình sử dụng version cũ cho ổn định với project của mình
Import thư viện bằng Maven repository sau

https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3/1.11.359

2. Tạo IAM

Cũng giống như nhiều SDK khác, Amazon AWS sử dụng các secret key để developer implement vô code.
Sử dụng secret key để access tới service của Amazon, thay vì sử dụng username + password như trên giao diện web.
Service quản lý việc này của Amazon là IAM.
Để tạo và config role cho các secret key này bạn truy cập tại đây: https://console.aws.amazon.com/iam
*// tự mò cách tạo và config IAM nhé, cái này google nhiều lắm :v *

Example:

      String AWSAccessKeyId="AKIAIBGCWNEKIYZSKXTA";
      String AWSSecretKey="LZjMW4t/udiEu8UXupg++I0mQsaXsm8Jb99upJBi";

3. Demo

3.1 - Khởi tạo kết nối

Để thao tác được với AWS, Cần khởi tạo client trước.

Nôm na có thể hiểu nhanh là bước này để verify kết nối, xem access key, secret key có đúng không.

BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);
AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(basicAWSCredentials);
AmazonS3ClientBuilder s3ClientBuilder = AmazonS3ClientBuilder.standard().withRegion(awsRegion).withCredentials(credentialsProvider);
AmazonS3 s3connect = s3ClientBuilder.build();

Trong đó String awsRegion = "ap-southeast-1";

Amazon S3 cung cấp nhiều Region để chứa dữ liệu.
Có thể hiểu nhanh Region là vùng lưu trữ,Và AWS có hệ thống servers tại nhiều nơi trên thế giới.
Bạn có thể chọn lấy 1 Region, nơi mà bạn thích để chứa dữ liệu của bạn. (Dựa theo vị trí địa lý chẳng hạn).
Đây là danh sách các Regions AWS cung cấp:

    GovCloud("us-gov-west-1", "AWS GovCloud (US)"),
    US_EAST_1("us-east-1", "US East (N. Virginia)"),
    US_EAST_2("us-east-2", "US East (Ohio)"),
    US_WEST_1("us-west-1", "US West (N. California)"),
    US_WEST_2("us-west-2", "US West (Oregon)"),
    EU_WEST_1("eu-west-1", "EU (Ireland)"),
    EU_WEST_2("eu-west-2", "EU (London)"),
    EU_WEST_3("eu-west-3", "EU (Paris)"),
    EU_CENTRAL_1("eu-central-1", "EU (Frankfurt)"),
    AP_SOUTH_1("ap-south-1", "Asia Pacific (Mumbai)"),
    AP_SOUTHEAST_1("ap-southeast-1", "Asia Pacific (Singapore)"),
    AP_SOUTHEAST_2("ap-southeast-2", "Asia Pacific (Sydney)"),
    AP_NORTHEAST_1("ap-northeast-1", "Asia Pacific (Tokyo)"),
    AP_NORTHEAST_2("ap-northeast-2", "Asia Pacific (Seoul)"),
    SA_EAST_1("sa-east-1", "South America (Sao Paulo)"),
    CN_NORTH_1("cn-north-1", "China (Beijing)"),
    CN_NORTHWEST_1("cn-northwest-1", "China (Ningxia)"),
    CA_CENTRAL_1("ca-central-1", "Canada (Central)");

// Có thể xem danh sách này trong com.amazonaws.regions .

3.2 Tạo bucketName

BucketName là gì? Có thể hiểu nhanh nó là tên 1 folder để chứa tất cả các dữ liệu của mình.

Lưu ý: bucketName là unique với toàn hệ thống AWS (nhắc lại là toàn hệ thống aws, chứ không phải unique trong 1 tài khoản)

   public static Bucket createBucket(AmazonS3 amazonS3, String bucketName) {
        Bucket bucket = null;
        try {
            bucket = amazonS3.createBucket(bucketName);
        } catch (AmazonServiceException ase) {
            LOG.error("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon S3, but was rejected with an error response for some reason.");
            LOG.error("Error Message:    " + ase.getMessage());
            LOG.error("HTTP Status Code: " + ase.getStatusCode());
            LOG.error("AWS Error Code:   " + ase.getErrorCode());
            LOG.error("Error Type:       " + ase.getErrorType());
            LOG.error("Request ID:       " + ase.getRequestId());
        } catch (AmazonClientException ace) {
            LOG.error("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with S3, "
                    + "such as not being able to access the network.");
            LOG.error("Error Message: " + ace.getMessage());
        }
        return bucket;
    }

Thực ra nguyên đoạn code dài ngoằng trên chỉ cô đọng trong 1 dòng:

 Bucket bucket = amazonS3.createBucket(bucketName);

Cơ mà hãy cứ code sử dụng try catch như mình, để lấy được log lỗi cho chuẩn! Dễ debug.

3.3 Upload file to S3

Thực ra thì dùng từ "file" nó không được đúng lắm, với 1 hệ thống lưu trữ Object Storage thì không có khái niệm là "file".

Họ sử dụng từ "object". Cơ mà trong khuôn khổ demo code này mình viết văn theo cách mình nghĩ người khác dễ hiểu nhất.

/**
     * upload file to s3
     * isPrivate = true for private file. Ex: csv report
     * isPrivate = false for public file: Ex: avatar user
     *
     * @param amazonS3
     * @param putObjectRequest
     * @param isPrivate
     * @return link to access file on s3
     */
    public static String uploadFile(AmazonS3 amazonS3, PutObjectRequest putObjectRequest, Boolean isPrivate) {
        String urlResult = "";
        if (putObjectRequest == null) return urlResult;
        try {
            if (isPrivate) {
                amazonS3.putObject(putObjectRequest.withCannedAcl(CannedAccessControlList.Private));
            } else {
                amazonS3.putObject(putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead));
            }
            urlResult = amazonS3.getUrl(putObjectRequest.getBucketName(), putObjectRequest.getKey()).toString();
        } catch (AmazonServiceException ase) {
            LOG.error("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon S3, but was rejected with an error response for some reason.");
            LOG.error("Error Message:    " + ase.getMessage());
            LOG.error("HTTP Status Code: " + ase.getStatusCode());
            LOG.error("AWS Error Code:   " + ase.getErrorCode());
            LOG.error("Error Type:       " + ase.getErrorType());
            LOG.error("Request ID:       " + ase.getRequestId());
        } catch (AmazonClientException ace) {
            LOG.error("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with S3, "
                    + "such as not being able to access the network.");
            LOG.error("Error Message: " + ace.getMessage());
        }
        return urlResult;
    }
    

Nguyên văn đoạn code trên cô đọng trong dòng sau:

 amazonS3.putObject(putObjectRequest.withCannedAcl(CannedAccessControlList.Private));

Trong đó PutObjectRequest được khởi tạo bởi các thuộc tính sau:

PutObjectRequest(String bucketName, String key, File file)

Hoặc

PutObjectRequest(String bucketName, String key, InputStream input, ObjectMetadata metadata)
  • bucketName: đã giải thích bên trên
  • key: fileName, có thể hiểu nhanh là tên của file, nằm trong folder. Và nó cũng là unique trong mỗi bucket
  • CannedAccessControlList.Private : khi 1 file upload lên S3, nếu không có config gì đặc biệt, default nó sẽ là private.

3.4 Get link download file từ S3 (có token)

Với những file config policy là PUBLIC thì thật dễ dàng dể download, chỉ cần copy URL theo format na ná như sau:

https://s3-us-west-2.amazonaws.com/my-tungtv202-avatar/MyObjectKey

là có thể download được mọi lúc mọi nơi.

Tuy nhiên với những file có policy là PRIVATE thì khi truy cập như vậy, sẽ gặp thông báo sau:

Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>AEE10DFAA27FEF26</RequestId>
<HostId>
ipPFZDboIzOCohRl4/RPe9I/IBQVn3esK+8mnGQO3yDKIPcatbnbl41SxC2oMUKPpt7WcG+toqk=
</HostId>
</Error>

Đoạn code dưới đây để getlink download (link có kèm token, token có thời hạn available)

/**
     * get download link for private file. That need token to download
     * bucketName same as folder name
     * fileName is unique per bucketName
     *
     * @param amazonS3
     * @param bucketName
     * @param fileName
     * @return download link
     */
    public static String getDownloadLink(AmazonS3 amazonS3, String bucketName, String fileName) {
        String downloadLink = "";
        if (bucketName.isEmpty() || fileName.isEmpty()) return downloadLink;
        Date expiration = new Date();
        long expTimeMillis = new Date().getTime();
        expiration.setTime(expTimeMillis + TIME_MINUTES_EXPIRED * 1000 * 60);

        try {
            GeneratePresignedUrlRequest generatePresignedUrlRequest =
                    new GeneratePresignedUrlRequest(bucketName, fileName)
                            .withMethod(HttpMethod.GET)
                            .withExpiration(expiration);
            URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
            downloadLink = url.toString();
        } catch (AmazonServiceException ase) {
            LOG.error("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon S3, but was rejected with an error response for some reason.");
            LOG.error("Error Message:    " + ase.getMessage());
            LOG.error("HTTP Status Code: " + ase.getStatusCode());
            LOG.error("AWS Error Code:   " + ase.getErrorCode());
            LOG.error("Error Type:       " + ase.getErrorType());
            LOG.error("Request ID:       " + ase.getRequestId());
        } catch (AmazonClientException ace) {
            LOG.error("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with S3, "
                    + "such as not being able to access the network.");
            LOG.error("Error Message: " + ace.getMessage());
        }
        return downloadLink;
    }

Example 1 download link có token:

https://xinchaovietna222me.s3.ap-southeast-1.amazonaws.com/188?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20180710T065913Z&X-Amz-SignedHeaders=host&X-Amz-Expires=299&X-Amz-Credential=AKIAIP7Y2FP3U3AJWLPQ%2F20180710%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=fc46c9d6cef32a94ee120dac5ab6a33c08e245256b979be977232b76c32e6926

Tổng kết

Trên đây là các đoạn code mình nghĩ là khi demo được nó, thì bạn có thể dễ dàng code các đoạn code tương tự trong các việc như:

  • Xóa bucketName
  • Lấy danh sách bucketName
  • Xóa file Object
  • Config policy cho object, bucket
  • ...

Vì mình đã thử, và thấy nó cũng cùng tư tưởng, cùng cách code, chỉ khó ở giai đoạn đầu, khi mới làm quen với AWS SDK S3 thôi.

Hi vọng bài viết này có ích cho bạn,

Chào thân ái!

Have problems with Amazon Web Services, Amazon S3 or AWS S3? Ask on Viblo »

Comments

Login to comment
+3