Java 中的 Http 客户端 API:管理文件
概述
本文的目的是展示如何使用 Http Client API 从 REST 端点上传/下载文件内容。
让我们回顾一下调用 Web 资源的步骤,因为此过程与上传/下载文件数据相同。
如果需要,将创建 HttpClient 对象并进一步配置(超时、身份验证器、http 版本、followRedirects...)
Http 请求是通过 URL 创建的。可以选择设置其他功能。例如,http 方法、http 标头、超时和 http 版本。
然后,HttpResponse 不是直接创建的,而是作为使用 HttpClient 发送 HttpRequest 的结果返回的。完成后,可以从中检查状态代码和正文(如果有)。
现在,让我们继续下载文件用例。
下载文件内容
我们将按照上述步骤来执行此操作。但首先,让我们看看后端。端点以字节数组的形式发回文件内容
@RestController
@RequestMapping("api/v1/documents")
public class DocumentController {
// ommited code here
@GetMapping(value = "/{documentId}")
ResponseEntity<Resource> downloadDocument(@PathVariable Long
documentId) {
Optional<Document> document = documentRepo.findById(documentId);
if (document.isEmpty())
return ResponseEntity.notFound().build();
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType(
document.get().getType()))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;
filename=\"" + document.get().getName() + "\"")
.body(new ByteArrayResource(
document.get().getContents()));
}
API 提供了 BodyHandlers 类(HttpResponse 内部静态类)来管理常见的响应类型,例如 String 或 File。以下是此类中可用的一些有用方法的列表:
- ofByteArray:主体写入字节数组。
- ofFile:正文写入文件,HttpResponse.body() 返回对其 Path 的引用。
- ofFileDownload:主体完全写入文件,并且 HttpResponse.body() 返回文件的 Path 对象。
- ofString:正文写入字符串并使用 Content-Type 响应标头中指定的字符集进行解码。
- ofInputStream:返回一个输入流,可以在接收到正文时从中读取正文。
- ofLines:返回一个 Stream。尸体可能还没有收到。
- ofPublisher:返回一个发布者,在接收到正文响应字节时可以从中获取它们。发布者只能而且必须订阅一次。
由于端点返回一个字节数组,因此 ofByteArray 方法似乎是一个很好的匹配。文件名是从 Content-Disposition 标头中提取的。代码片段如下所示:
// Byte Array
HttpResponse<byte[]> response = client
.send(request, HttpResponse.BodyHandlers.ofByteArray());
String headerContentDisposition =
(String)response.headers().firstValue("content-
disposition").get();
String fileName =
"C:/workspace/files/"+getFileName(headerContentDisposition);
// Save to file
Files.write(Paths.get(fileName),
response.body(), StandardOpenOption.CREATE_NEW);
执行程序后,文件将成功下载到指定位置。
Directory: C:\workspace\files
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/1/2023 7:28 PM 388374 LinuxCommands.jpg
可以使用不同的 BodyHandler 来下载文件,如以下几行所示
HttpResponse<Path> response = client.send(request,
HttpResponse.BodyHandlers.ofFile(
Path.of("C:/workspace/files/DownloadedFile.jpg")));
另一种选择是使用 ofInputStream 方法。
HttpResponse<InputStream> response = client
.send(request,
HttpResponse.BodyHandlers.ofInputStream());
String headerContentDisposition =
(String)response.headers().firstValue("content-
disposition").get();
String fileName = "C:/workspace/files/"+
getFileName(headerContentDisposition);
Files.copy(response.body(), Paths.get(fileName),
StandardCopyOption.REPLACE_EXISTING);
正如您所看到的,有很多选择可以完成工作:-)。
上传文件内容
在此用户案例中,已设置两个不同的端点来上传文件。因此,我们将看到不同的方式来使身体发挥作用。
请求正文通过提供给 POST 或 PUT 方法之一的 BodyPublisher 提供。BodyPublishers 类提供了许多常见发布者的实现。以下是 BodyPublishers 中可用的一些方法的列表
- ofString:返回一个 BodyPublisher,其正文是使用 UTF_8 字符转换的给定字符串。
- ofInputStream:从输入流读取数据的主体发布者。
- ofByteArray:返回一个请求主体发布者,其主体是给定的字节数组。
- ofFile:请求主体发布者,从文件内容中获取数据。
- ofByteArrays:请求主体发布者,从字节数组的 Iterable 中获取数据。
现在我们可以继续第一个上传示例。其余端点期望文件内容为字符串。文件类型和名称位于标头中。
@PostMapping()
ResponseEntity<Void> uploadDocument(@RequestBody String data,
@RequestHeader("Content-Type") String type,
@RequestHeader("fileName") String fileName) {
Document document = new Document();
document.setName(fileName);
document.setCreationDate(LocalDate.now());
document.setType(type);
document.setContents(data.getBytes());
documentRepo.save(document);
return ResponseEntity.created(URI.create(
"http://localhost:8080/api/v1/documents/"+document.getId()
)).build();
}
如您所见,BodyPublishers.ofFile 方法采用 Path 作为参数。发送请求时,正文将包含文件的内容。
Path file = Paths.get("C:/workspace/files/trees.jpg");
var request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/v1/documents"))
.header("Content-Type", "image/jpg")
.header("fileName", file.getFileName().toString())
.header("Authorization",
getBasicAuthenticationHeader("admin1234","password5678"))
.POST(HttpRequest.BodyPublishers.ofFile(file))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.printf("Status %s \n", response.statusCode());
System.out.printf("Headers %s \n",
response.headers().firstValue("location"));
输出
Status 201
Headers Optional[http://localhost:8080/api/v1/documents/3]
文件被保存到数据库中,并且 URL 会在位置标头中返回以查找它。
在第二个示例中,控制器将主体视为字节数组。
@RestController
@RequestMapping("api/v2/documents")
public class DocumentControllerV2 {
@PostMapping()
ResponseEntity<Void> uploadDocument(@RequestBody byte[] data
,@RequestHeader("Content-Type") String type
,@RequestHeader("fileName") String fileName) {
// omitted code
}
}
ByteArray 方法非常适合这种情况。所需要做的就是将文件的内容转换为字节数组。为此,FileInputStream 将完成这项工作。
String fileName = "C:/workspace/files/java_tutorial.pdf";
var request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/api/v2/documents"))
.header("Content-Type", "image/png")
.header("filename",
fileName.substring(fileName.lastIndexOf("/")))
.POST(HttpRequest.BodyPublishers.ofByteArray(new
FileInputStream(fileName).readAllBytes()))
.build();
var response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.printf("Status %s \n", response.statusCode());
System.out.printf("Headers %s \n",
response.headers().firstValue("location"));
输出
Status 201
Headers Optional[http://localhost:8080/api/v1/documents/4]
最后,为了验证新文件是否存储在数据库中,将调用列表文件端点。
[
{
"name": "PlainText.txt",
"creationDate": "2023-03-15",
"size": 26,
"type": "text/plain"
},
{
"name": "LinuxCommands.jpg",
"creationDate": "2023-02-07",
"size": 388374,
"type": "image/jpeg"
},
{
"name": "trees.jpg",
"creationDate": "2023-04-02",
"size": 21011905,
"type": "image/jpg"
},
{
"name": "/java_tutorial.pdf",
"creationDate": "2023-04-02",
"size": 1012786,
"type": "image/png"
}
概括
就是这样了。使用 Java 的核心 API 下载和上传文件非常简单。我们必须选择一个合适的正文发布者或正文处理程序来分别发送文件内容和接收文件内容。
- 点赞
- 收藏
- 关注作者
评论(0)