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



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

这就是我如何生成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)

您的代码有缺陷;如果不通过尝试资源来保护它,就无法创建新的资源(例如,表示实际资源的流/读取器/写入器:无论是网络上的套接字、磁盘上的文件等等)。

不过,更普遍的情况是,你不想在这里使用FOS。您希望有一个流,以便通过thatOutputStream.write发送的pdf-gen代码产生的任何字节都直接放在网络上。

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

首先,让我试着解释一下这些库所关注的问题,以便您充分理解为什么这并不容易,因此您可以判断各种解决方案是否适合您的特定项目。

问题的核心是你有两个独立的过程,每个过程都依赖于另一个过程。

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

  • 输出通道代码,在本例中为AWS S3 putObject代码,也希望有阻止的自由:如果网络缓冲区已满,它必须等待:毕竟,它将数据包从计算机后面推出的速度是有限的。它也受到输入的限制:如果PDF gen代码以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);
}

非常简单的代码。然而,请注意,控制这里的"生产者"一方实际上是残疾人。write方法阻止-如果磁盘正忙于处理,则write方法不会立即返回。当磁盘忙于处理所有数据时,CPU处于空闲状态。它本可以花时间制造更多的零!

在这个例子中,这很愚蠢——CPU可以非常快地产生零,"产生"代码比"消耗"代码快很多数量级,当消费者忙于处理这一切时,生产端稍微冷静一下是有道理的。

但想象一下工作方式有点不同的代码:与其写源源不断的零,不如想象一下正在挖掘比特币的代码,并写下挖掘块(每个块价值数千美元,这应该表明它们的生成速度有多慢。一个月一个已经非常令人印象深刻了)。显然,从这个意义上说,障碍是愚蠢的:CPU应该忙于挖掘比特币,而不是无所事事地等待磁盘。在这种情况下,您希望两个进程(或者至少是较慢的进程,在本例中是生产者)永远不要只等待另一方。不应该让瓶颈等待。

当忙于生成PDF数据的代码想要将数据发送到putObject代码时,它已经完成了for循环的一半,但当它想要更多的PDF数据时,putObject代码已经完成了对一段数据的哈希计算的一半。如果任何一方都不应该在另一方忙的时候转动拇指,那么除了拥有2根光纤(有效地说是堆叠通道),并让这些光纤相互传递数据之外,别无选择。在java中,这必须通过线程来完成-Project Loom即将问世,它将为您提供有趣的单核选项,但Project Loom还不是java的一部分。

putObject代码是专门围绕这个想法设计的;通常,输出通过返回OutputStream来工作;Files.newOutputStreamnew FileOutputStream()socket.getOutputStreamservletHttpResponse.getOutputStream()返回一。但不是AWS putObject:它不返回任何内容;它想要一个InputStream。类似地,PDF代码也不返回任何内容,它想要一个OutputStream。

因此,陷入了困境。

不过,线程化解决方案非常简单。您需要一个线程来生成PDF,一个线程将其发送到AWS。你用管道流把两者连接起来。

另一种解决方案是回到一个模型,即一方只需等待并转动拇指一段时间,但AWS API不支持它。下面是一个要点,试图通过使用多部分功能将其提供给您。

最新更新