4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

S3にファイルをアプロードして メタデータに content-disposition を設定する

Posted at

ゴール

  • Webブラウザを使ってアップロードしたファイルをCloudFront経由でダウンロードする。
  • ダウンロードしたファイルをブラウザで保存する時のダイアログには、S3のKeyとは別の任意のファイル名を指定できる。

キャプチャ2.PNG

上の「iOS の画像 (1).jpg」を content-disposition で指定する。

バージョン情報

  • JDK: amazon-corretto-11.0.3.7.1-windows-x64
  • Spring boot : 2.2.4.RELEASE
  • AWS SDK for Java: 2.14.28

はまりポイント

PutObjectRequest.Builder#contentDisposition(string)) の引数はURLエンコードを行う必要がある。

PutObjectRequest.builder().contentDisposition( "attachment; filename=\"iOS の画像(1).jpg\"")と書くと、下のエラーになる。

software.amazon.awssdk.services.s3.model.S3Exception: The request signature we calculated does not match the signature you provided. Check your key and signing method. (Service: S3, Status Code: 403, Request ID: XXXXXXXXXXXXXXXX, Extended Request ID: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
)

今回は、下の通りにした。 +%20 に置換する理由はファイル名の半角スペースがあるとURLエンコードで %20 に変換されるので半角スペースに戻すため。

PutObjectRequest.builder().contentDisposition( 
    "attachment; filename=\""
    + URLEncoder.encode(fileName, "UTF-8").replace("+", "%20") 
    + "\"")

設定した content-disposition の値は AWSコンソールのメタデータで確認できる。
s3-metadata.png

コントローラーの実装

コントローラー
@RestController
public class SummernoteApiController {

  private WyswygService wyswygService;

  public SummernoteApiController(@Autowired WyswygService wyswygService) {
    this.wyswygService = wyswygService;
  }

  @PostMapping("/api/attachfile")
  public String attachfile(@RequestParam("upload_file") MultipartFile uploadFile) {
    if (uploadFile.isEmpty()) {
      throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "添付するファイルを指定してください");
    }

    try {
      String publishedUrl = wyswygService.uploadToS3(uploadFile);
      return publishedUrl;
    } catch (IOException | S3Exception e) {
      e.printStackTrace();
      throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

サービスクラス(WyswygService.java)
@Component
public class WyswygService {

  /** AWSのリージョン */
  @Value("${aws.region}")
  private String regsion;

  /** 画像などをアップロードするS3バケット */
  @Value("${aws.s3.assetsBucket}")
  private String assetsBucket;

  /** S3バケットに保存する時のパスのPrefix */
  @Value("${aws.s3.assetsPrefix}")
  private String assetsPrefix;

  /** CloudFrontのホスト名 */
  @Value("${aws.s3.cloudFrontHost}")
  private String cloudFrontHost;

  /** https://github.com/huxi/sulky/tree/master/sulky-ulid */
  private ULID ulid = new ULID();

  /**
   * ファイルをAmazonS3にアップロードする
   *
   * @param uploadFile アップロードするファイル
   * @return CloudForntからアクセスできるパス
   * @throws IOException
   */
  public String uploadToS3(MultipartFile uploadFile) throws IOException {
    String contentType =
        uploadFile.getContentType() != null
            ? uploadFile.getContentType()
            : "application/octet-stream ";
    String fileName =
        uploadFile.getOriginalFilename() != null
            ? uploadFile.getOriginalFilename()
            : "attached_file.dat";
    String s3key= this.genrateS3KeyPrefix() + fileName.substring(fileName.lastIndexOf("."));
    String cloudFrontUrl = String.format("https://%s%s", this.cloudFrontHost, key);

    PutObjectRequest putObject =
        PutObjectRequest.builder()
            .bucket(this.assetsBucket)
            .key(s3key.startsWith("/") ? s3key.substring(1) : s3key) // 先頭に「/」があると重複するので削除する
            .contentType(contentType)
            .contentDisposition(
                "attachment; filename=\""
                    + URLEncoder.encode(fileName, "UTF-8").replace("+", "%20")
                    + "\"")
            .build();

    s3Client.putObject(putObject, RequestBody.fromInputStream(uploadFile.getInputStream(), uploadFile.getSize()));

    return cloudFrontUrl ;
  }

  /**
   * S3のキー(ファイルパス)のプレフィックスを生成する
   * ulidを使って時間でソートできる一意な文字列で保存する。
   */
  private String genrateS3KeyPrefix() {
    String month = DateTimeFormatter.ofPattern("yyyy-MM").format(LocalDate.now());
    return String.format(
        "%s/%s/%s", this.assetsPrefix, month, ulid.nextValue().increment().toString());
  }
}
4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?