我有一个球衣客户端,需要上传足够大的文件才能要求进度条
问题是,对于需要几分钟的上传,我看到一旦应用程序启动就转移到100%的字节。然后打印“on finished”字符串需要几分钟。
就好像将字节发送到缓冲区,我正在读取transfert到缓冲区的速度,而不是实际上传速度。这使得进度条无用。
这是非常简单的代码:
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource resource = client.resource("www.myrestserver.com/uploads");
WebResource.Builder builder = resource.type(MediaType.MULTIPART_FORM_DATA_TYPE);
FormDataMultiPart multiPart = new FormDataMultiPart();
FileDataBodyPart fdbp = new FileDataBodyPart("data.zip", new File("data.zip"));
BodyPart bp = multiPart.bodyPart(fdbp);
String response = builder.post(String.class, multiPart);
为了获得进度状态,我在调用builder.post之前添加了一个ContainerListener过滤器,其中显示了:
final ContainerListener containerListener = new ContainerListener() {
@Override
public void onSent(long delta, long bytes) {
System.out.println(delta + " : " + long);
}
@Override
public void onFinish() {
super.onFinish();
System.out.println("on finish");
}
};
OnStartConnectionListener connectionListenerFactory = new OnStartConnectionListener() {
@Override
public ContainerListener onStart(ClientRequest cr) {
return containerListener;
}
};
resource.addFilter(new ConnectionListenerFilter(connectionListenerFactory));
答案 0 :(得分:4)
在Jersey 2.X中,我使用 WriterInterceptor 将输出流包装为Apache Commons IO CountingOutputStream的子类,该子类跟踪写入并通知我的上传进度代码(未显示) )。
public class UploadMonitorInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
// the original outputstream jersey writes with
final OutputStream os = context.getOutputStream();
// you can use Jersey's target/builder properties or
// special headers to set identifiers of the source of the stream
// and other info needed for progress monitoring
String id = (String) context.getProperty("id");
long fileSize = (long) context.getProperty("fileSize");
// subclass of counting stream which will notify my progress
// indicators.
context.setOutputStream(new MyCountingOutputStream(os, id, fileSize));
// proceed with any other interceptors
context.proceed();
}
}
然后我在客户端注册了这个拦截器,或者在你想要使用拦截器的特定目标上注册了这个拦截器。
答案 1 :(得分:3)
它应该足以为java.io.File提供自己的MessageBodyWriter,它会触发某些事件或通知某些侦听器进度发生变化
@Provider()
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public class MyFileProvider implements MessageBodyWriter<File> {
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return File.class.isAssignableFrom(type);
}
public void writeTo(File t, Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
InputStream in = new FileInputStream(t);
try {
int read;
final byte[] data = new byte[ReaderWriter.BUFFER_SIZE];
while ((read = in.read(data)) != -1) {
entityStream.write(data, 0, read);
// fire some event as progress changes
}
} finally {
in.close();
}
}
@Override
public long getSize(File t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return t.length();
}
}
并使您的客户端应用程序简单地使用此新提供程序:
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(MyFileProvider.class);
或
ClientConfig config = new DefaultClientConfig();
MyFileProvider myProvider = new MyFileProvider ();
cc.getSingletons().add(myProvider);
您还必须包含一些算法来识别接收进度事件时传输的文件。
<强>编辑:强>
我刚发现HTTPUrlConnection默认使用缓冲。要禁用缓冲,你可以做几件事:
所以我建议你的问题的最终解决方案使用第一个选项,看起来像这样:
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(MyFileProvider.class);
URLConnectionClientHandler clientHandler = new URLConnectionClientHandler(new HttpURLConnectionFactory() {
@Override
public HttpURLConnection getHttpURLConnection(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setChunkedStreamingMode(1024);
return connection;
}
});
Client client = new Client(clientHandler, config);
答案 2 :(得分:1)
我已经成功使用了大卫的答案。但是,我想对此进行扩展:
我的aroundWriteTo
的以下WriterInterceptor
实现显示了如何将面板(或类似面板)传递给CountingOutputStream
:
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException
{
final OutputStream outputStream = context.getOutputStream();
long fileSize = (long) context.getProperty(FILE_SIZE_PROPERTY_NAME);
context.setOutputStream(new ProgressFileUploadStream(outputStream, fileSize,
(progressPanel) context
.getProperty(PROGRESS_PANEL_PROPERTY_NAME)));
context.proceed();
}
然后afterWrite
中的CountingOutputStream
可以设置进度:
@Override
protected void afterWrite(int n)
{
double percent = ((double) getByteCount() / fileSize);
progressPanel.setValue((int) (percent * 100));
}
可以在Invocation.Builder
对象上设置属性:
Invocation.Builder invocationBuilder = webTarget.request();
invocationBuilder.property(
UploadMonitorInterceptor.FILE_SIZE_PROPERTY_NAME, newFile.length());
invocationBuilder.property(
UploadMonitorInterceptor.PROGRESS_PANEL_PROPERTY_NAME,
progressPanel);
也许是David回答中最重要的补充,而我决定发布自己的原因可能是以下代码:
client.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024);
client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED");
client
对象是javax.ws.rs.client.Client
。
使用WriterInterceptor
方法也必须禁用缓冲。上面的代码是使用Jersey 2.x做到这一点的直接方法