将文本 pdf 直接保存到 s3,而无需在本地保存

Save text pdf direct to s3 without having to save it locally

我正在使用 itext 5 和 Java 生成 pdf 文件,并将它们保存在本地,然后将这些本地保存的文件保存在 AWS S3 上。有没有办法直接将它们发送到 S3 而不必在本地保存它们。我看过几个例子,但 none 对我有用。

这就是生成 pdf 文件的方式

 String path = //local directory on my computer
 Document document = new Document();
 PdfWriter pdfWriter = PdfWriter.getInstance(document, new FileOutputStream(path));
 document.open();
 
 // add text to document
 document.close();

这是我在 s3 上保存它的方式

public void saveFileToS3(String pathLocal, String pathAws) {
   
    // init aws 

    PutObjectRequest objectRequest = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(folderName + "/" + pathAws)
        .build();
    
    CompletableFuture<PutObjectResponse> future = s3Client.putObject(objectRequest,
        AsyncRequestBody.fromFile(Paths.get(pathLocal))
    );
    
    future.whenComplete((resp, err) -> {
      try {
        if (resp != null) {
          System.out.println("Object uploaded. Details: " + resp);
        } else {
          err.printStackTrace();
        }
      } finally {
        s3Client.close();
      }
    });
    
    future.join();
  }

String pathLocal 是本地保存文件的路径,而 String pathAws 是文件在S3上的保存路径

所以我找到了一个方法,我将itext文件转换为字节数组,然后将pdf文件作为字节数组上传

Document document = new Document();
 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  
 PdfWriter.getInstance(document, byteArrayOutputStream);

 document.open();

//add stuff to pdf

 document.close();

//convert it into a byte array
byte[] pdfBytes = byteArrayOutputStream.toByteArray()

将它上传到 S3 时,我传递的是字节而不是我之前所做的文件路径

CompletableFuture<PutObjectResponse> future = s3Client.putObject(objectRequest,
        AsyncRequestBody.fromBytes(pdfBytes)
    );

首先是旁注:

new FileOutputStream(path)

您的代码有问题;您不能创建新资源(例如,streams/readers/writers代表实际资源:网络上的套接字,磁盘上的文件,诸如此类),没有通过 try-with-resources 保护它。

不过,更一般地说,您不需要此处的 FOS。你想要一个流,这样任何由 pdf gen 代码产生并通过 thatOutputStream.write 发送的字节都直接放在网络上。

这是可能的,但在这种特殊情况下并不容易。

首先让我尝试解释一下这些库的顾虑,以便您完全理解为什么这不是很容易,因此您可以判断周围的各种解决方案是否适合您的特定项目。

问题的核心是您有 2 个独立的进程,每个进程都依赖于另一个。

  • PDF 生成代码想要生成 PDF 字节,并且想要完全自由:如果它需要查询数据库作为工作的一部分,它希望能够做到这一点。然而,它受到输出通道的限制:理论上它可以每秒生成许多 GB:它想要最终停止自己(block 它的线程,或者放弃对正在处理的任何东西的控制它的输出,以便它可以处理一堆)如果输出通道是 'full'。例如,如果您的磁盘可以 1GB/s 的速度存储,而您的 PDF 代码正在生成无限大小的 PDF,并且可以 2GB/s 的速度生成,则 PDF 代码需要放慢速度。

  • 输出通道代码,在本例中为 AWS S3 putObject 代码,也希望自由阻塞:如果网络缓冲区已满,则必须等待:有一个限制毕竟,它能以多快的速度将数据包从计算机背面推出。它还受到输入的限制:如果 PDF 生成代码以 1GB/s 的速度生成,但 AWS S3 putObject 代码可以以 2GB/s 的速度发送,那么 putObject 代码必须自行减速;如果没有任何字节要发送,它就无法发送更多字节。

通常在java代码中,模型非常简单:一侧(生产者或消费者)被认为是控制,而不是瓶颈。例如,如果您有生成无限零并将其写入磁盘的代码:

byte[] allZeroes = new byte[60000];
try (FileOutputStream fos = new FileOutputStream("test.dat")) {
    while (true) fos.write(allZeroes);
}

非常简单的代码。但是,请注意,此处控制的 'producer' 方实际上是有缺陷的。 write 方法 blocks - 如果磁盘正忙于处理,该 write 方法不会立即 return。当磁盘忙于处理所有内容时,CPU 处于空闲状态。它本可以花时间制造更多的零!

在这个例子中,这很愚蠢 - CPU 可以非常快地产生零,'producing' 代码比 'consuming' 代码快很多数量级,这对生产端稍微放松一下,因为消费者正忙于处理这一切。

但想象一下工作方式稍有不同的代码:与其编写无穷无尽的零流,不如想象一下正在挖掘比特币并编写挖掘块(每个价值数千美元,这应该表明它们的速度有多慢)的代码产生。一个月一个已经很可观了)。显然,障碍在这个意义上是愚蠢的:CPU 应该忙于挖掘比特币,而不是在等待磁盘上摆弄拇指。在这种情况下,您希望两个进程(或至少是较慢的进程,在本例中为生产者)永远不会只等待另一方。不应让瓶颈等待。

忙于生成 PDF 数据的代码在要将数据发送到 putObject 代码时在 for 循环中执行了一半,但 putObject 代码在计算一段数据的散列时需要更多数据时却在执行一半PDF数据。如果任何一方都不应该在另一方忙碌时摆弄自己的拇指,那么除了拥有 2 条光纤(有效的堆栈跟踪)并让这些光纤相互传递数据外,别无他法。在 java 中,这个 必须 使用线程来完成 - Project Loom 即将推出,会给你有趣的单核选项,但 Project Loom 不是其中的一部分java还不错。

putObject代码就是围绕这个想法专门设计的;通常,输出通过 returning 一个 OutputStream 来工作; Files.newOutputStreamreturn一个,new FileOutputStream()一个,socket.getOutputStream一个,servletHttpResponse.getOutputStream()一个。但不是 AWS putObject:它没有 return 任何东西;它想要 InputStream。同样,PDF 代码也没有 return 任何东西,它 需要 一个 OutputStream。

因此,进退两难。

不过,线程解决方案非常简单。您需要一个线程来生成 PDF,并需要一个线程将其发送到 AWS。你link两个同一个piped stream.

另一种解决方案是返回到一方只是等待并摆弄它的拇指一段时间的模型,但 AWS API 不支持它。这是一个gist that tries to give it to you by using the multipart feature.