HttpClient5 分片上传
时间: 2023-09-11 18:05:38 浏览: 174
好的,HttpClient5 支持分片上传,以下是一个简单的示例:
```
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.sync.CloseableHttpClient;
import org.apache.hc.client5.http.sync.methods.HttpEntityEnclosingRequestBase;
import org.apache.hc.client5.http.sync.methods.HttpPut;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.EntityUtils;
public class ChunkedUploadExample {
static final int BUFFER_SIZE = 1024 * 1024;
public static void main(String[] args) throws IOException {
// 创建 HttpClient
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create().build())
.build();
// 上传文件
String url = "https://example.com/upload";
String filename = "example.txt";
long fileSize = Files.size(Paths.get(filename));
HttpPost initRequest = new HttpPost(url);
initRequest.setHeader("Content-Type", "application/json");
initRequest.setHeader("X-File-Size", String.valueOf(fileSize));
HttpResponse initResponse = httpClient.execute(initRequest);
int statusCode = initResponse.getCode();
// 初始化请求成功
if (statusCode == HttpStatus.SC_OK) {
// 获取服务端返回的分片大小
int chunkSize = Integer.parseInt(initResponse.getHeader("X-Chunk-Size"));
// 上传分片
byte[] buffer = new byte[BUFFER_SIZE];
for (int i = 0; i < fileSize; i += chunkSize) {
int remainingSize = (int) Math.min(chunkSize, fileSize - i);
HttpEntityEnclosingRequestBase putRequest = new HttpPut(url);
putRequest.setHeader("X-Range", String.format("bytes=%d-%d", i, i + remainingSize - 1));
Path filePath = Paths.get(filename);
HttpEntity entity = new ByteRangeEntity(Files.newInputStream(filePath), i, i + remainingSize - 1,
ContentType.APPLICATION_OCTET_STREAM);
putRequest.setEntity(entity);
httpClient.execute(putRequest, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse response) {
try {
if (response.getCode() == HttpStatus.SC_OK) {
System.out.printf("上传成功:%d-%d%n", i, i + remainingSize - 1);
} else {
System.err.printf("上传失败:%d-%d%n", i, i + remainingSize - 1);
}
// 确保释放连接
EntityUtils.consume(response.getEntity());
} catch (IOException ex) {
ex.printStackTrace();
}
}
@Override
public void failed(Exception ex) {
ex.printStackTrace();
}
@Override
public void cancelled() {
System.err.printf("取消上传:%d-%d%n", i, i + remainingSize - 1);
}
});
}
} else {
System.err.printf("初始化请求失败:%d%n", statusCode);
}
// 等待上传完成
httpClient.close();
}
static class ByteRangeEntity implements HttpEntity {
private final byte[] buffer;
private final long start;
private final long end;
private final ContentType contentType;
public ByteRangeEntity(Path filePath, long start, long end, ContentType contentType) throws IOException {
try (var input = Files.newInputStream(filePath)) {
input.skip(start);
long remainingSize = end - start + 1;
this.buffer = new byte[(int) Math.min(remainingSize, BUFFER_SIZE)];
this.start = start;
this.end = end;
this.contentType = contentType;
int bytesRead = input.read(this.buffer);
if (bytesRead < remainingSize && bytesRead < BUFFER_SIZE) {
throw new IOException(String.format("读取字节失败,文件长度:%d,读取长度:%d", remainingSize, bytesRead));
}
}
}
@Override
public long getContentLength() {
return end - start + 1;
}
@Override
public ContentType getContentType() {
return contentType;
}
@Override
public boolean isRepeatable() {
return true;
}
@Override
public boolean isStreaming() {
return false;
}
@Override
public byte[] getContent() throws IOException, UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public void writeTo(OutputStream outStream) throws IOException {
outStream.write(buffer);
}
@Override
public InputStream getContent() throws IOException, UnsupportedOperationException {
return new ByteArrayInputStream(buffer);
}
@Override
public void close() throws IOException {
// nothing to do
}
}
}
```
在上述代码中,我们使用 `HttpPost` 发送初始化请求,该请求携带文件的总大小。服务端返回分片大小后,我们可以依次上传每个分片。这里使用 `HttpPut` 发送分片请求,并在请求头中指定该分片所在的字节范围。我们使用 `ByteRangeEntity` 类封装分片数据,并通过 `FutureCallback` 异步等待上传结果。
需要注意的是,这里的 `ByteRangeEntity` 类只是一个简单的实现,它会一次性读取分片数据到内存中,如果文件过大可能会导致内存不足。实际应用中,我们可以使用 `RandomAccessFile` 或者 `FileChannel` 等方式读取文件分片。
阅读全文