如何使用 Apache HttpClient 4 获取文件上传的进度条?
Posted
技术标签:
【中文标题】如何使用 Apache HttpClient 4 获取文件上传的进度条?【英文标题】:How to get a progress bar for a file upload with Apache HttpClient 4? 【发布时间】:2011-10-26 19:20:52 【问题描述】:我有以下代码用于使用 Apache 的 HTTP 客户端 (org.apache.http.client) 上传文件:
public static void main(String[] args) throws Exception
String fileName = "test.avi";
File file = new File(fileName);
String serverResponse = null;
HttpParams params = new BasicHttpParams();
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, true);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpClient client = new DefaultHttpClient(params);
HttpPut put = new HttpPut("http://localhost:8080/" + fileName);
FileEntity fileEntity = new FileEntity(file, "binary/octet-stream");
put.setEntity(fileEntity);
HttpResponse response = client.execute(put);
HttpEntity entity = response.getEntity();
if (entity != null)
serverResponse = EntityUtils.toString(entity);
System.out.println(serverResponse);
它工作得很好,但现在我想要一个显示文件上传进度的进度条。这怎么能做?我在File Upload with Java (with progress bar) 找到了一个代码 sn-p,但它是为 Apache HTTP Client 3 (org.apache.commons.httpclient) 设计的,并且 RequestEntity 类在 Apache HTTP Client 4 中不存在。;(
也许你们中的某个人有办法?
许多问候
本尼
【问题讨论】:
有没有办法获得上传的字节数?请添加一个代码sn-p,我可能会有你的答案 感谢您的帮助!我自己找到了解决办法,会及时介绍。 :) 您可能还喜欢:***.com/questions/22932821/… 它具有包装解决方案,对您的类层次结构的干扰较小。 【参考方案1】:我介绍了一个派生的 FileEntity
,它只计算写入的字节数。
它使用 OutputStreamProgress
来进行实际计数(类似于实际 OutputStream
的 装饰器)。
这个(和一般装饰)的优点是我确实不需要需要复制实际的实现,就像从文件流实际复制到输出流。我还可以更改为使用不同的(更新的)实现,例如 NFileEntity
。
享受...
FileEntity.java
public class FileEntity extends org.apache.http.entity.FileEntity
private OutputStreamProgress outstream;
public FileEntity(File file, String contentType)
super(file, contentType);
@Override
public void writeTo(OutputStream outstream) throws IOException
this.outstream = new OutputStreamProgress(outstream);
super.writeTo(this.outstream);
/**
* Progress: 0-100
*/
public int getProgress()
if (outstream == null)
return 0;
long contentLength = getContentLength();
if (contentLength <= 0) // Prevent division by zero and negative values
return 0;
long writtenLength = outstream.getWrittenLength();
return (int) (100*writtenLength/contentLength);
OutputStreamProgress.java
public class OutputStreamProgress extends OutputStream
private final OutputStream outstream;
private volatile long bytesWritten=0;
public OutputStreamProgress(OutputStream outstream)
this.outstream = outstream;
@Override
public void write(int b) throws IOException
outstream.write(b);
bytesWritten++;
@Override
public void write(byte[] b) throws IOException
outstream.write(b);
bytesWritten += b.length;
@Override
public void write(byte[] b, int off, int len) throws IOException
outstream.write(b, off, len);
bytesWritten += len;
@Override
public void flush() throws IOException
outstream.flush();
@Override
public void close() throws IOException
outstream.close();
public long getWrittenLength()
return bytesWritten;
【讨论】:
喜欢装饰者的想法。我在另一个答案中对其进行了扩展。 你在哪里扩展它?也许它在这里很有价值。 糟糕!我的意思是投票赞成。我在你的下方回复了这个帖子。 这不只是监控文件写入输出流的进度,而不是实际上传的进度吗?如果我理解正确,则在刷新输出流之前不会发生实际上传。【参考方案2】:使用 commons-io (2.4) 中的包 org.apache.commons.io.output 及其类 CountingOutputStream 的新版本.
我更改了初始代码以反映我的项目需要使用多部分表单作为输入和 post 方法(这是由于服务器端施加的要求)。
考虑到我的测试中大文件的增量对应于 4096 字节。这意味着每传输 4096 个字节的数据就会调用侦听器方法 counterChanged(),这对于我的用例来说是可以接受的。
方法如下:
public void post(String url, File sendFile)
HttpParams params = new BasicHttpParams();
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, true);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpClient client = new DefaultHttpClient(params);
HttpPost post = new HttpPost(url + "/" + sendFile.getName());
MultipartEntity multiEntity = new MultipartEntity();
MyFileBody fileBody = new MyFileBody(sendFile);
fileBody.setListener(new IStreamListener()
@Override
public void counterChanged(int delta)
// do something
System.out.println(delta);
);
multiEntity.addPart("file", fileBody);
StringBody stringBody = new StringBody(sendFile.getName());
multiEntity.addPart("fileName", stringBody);
post.setEntity(multiEntity);
HttpResponse response = client.execute(post);
MyFileBody 类变成:
public class MyFileBody extends FileBody
private IStreamListener listener;
public MyFileBody(File file)
super(file);
@Override
public void writeTo(OutputStream out) throws IOException
CountingOutputStream output = new CountingOutputStream(out)
@Override
protected void beforeWrite(int n)
if (listener != null && n != 0)
listener.counterChanged(n);
super.beforeWrite(n);
;
super.writeTo(output);
public void setListener(IStreamListener listener)
this.listener = listener;
public IStreamListener getListener()
return listener;
最后,监听器界面如下:
public interface IStreamListener
void counterChanged(int delta);
【讨论】:
【参考方案3】:这个答案通过向 OutputStreamProgress.java 类添加一个简单的侦听器而不是公共 getProgress() 方法来扩展 kilaka 的答案(老实说,我不确定你应该如何调用 getProgress() 方法,因为线程将在您可能想要调用 getProgress() 的整个过程中一直在 httpclient 的代码中执行!)。
请注意,您需要为要使用的每种实体类型扩展实体类,并且在编写 HttpClient 代码时,您需要创建该新类型的实体。
我编写了一个非常基本的写监听器,它实现了 WriteListener 接口。在这里,您将添加逻辑以对来自 OutputStreamProgress 的写入报告执行某些操作,例如更新进度条 :)
非常感谢 kilaka 使用装饰器的想法潜入计数外流。
WriteLisener.java
public interface WriteListener
void registerWrite(long amountOfBytesWritten);
OutputStreamProgress.java
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamProgress extends OutputStream
private final OutputStream outstream;
private long bytesWritten=0;
private final WriteListener writeListener;
public OutputStreamProgress(OutputStream outstream, WriteListener writeListener)
this.outstream = outstream;
this.writeListener = writeListener;
@Override
public void write(int b) throws IOException
outstream.write(b);
bytesWritten++;
writeListener.registerWrite(bytesWritten);
@Override
public void write(byte[] b) throws IOException
outstream.write(b);
bytesWritten += b.length;
writeListener.registerWrite(bytesWritten);
@Override
public void write(byte[] b, int off, int len) throws IOException
outstream.write(b, off, len);
bytesWritten += len;
writeListener.registerWrite(bytesWritten);
@Override
public void flush() throws IOException
outstream.flush();
@Override
public void close() throws IOException
outstream.close();
BasicWriteListener
public class BasicWriteListener implements WriteListener
public BasicWriteListener()
// TODO Auto-generated constructor stub
public void registerWrite(long amountOfBytesWritten)
System.out.println(amountOfBytesWritten);
MultipartEntityWithProgressBar
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
public class MultipartEntityWithProgressBar extends MultipartEntity
private OutputStreamProgress outstream;
private WriteListener writeListener;
@Override
public void writeTo(OutputStream outstream) throws IOException
this.outstream = new OutputStreamProgress(outstream, writeListener);
super.writeTo(this.outstream);
public MultipartEntityWithProgressBar(WriteListener writeListener)
super();
this.writeListener = writeListener;
public MultipartEntityWithProgressBar(HttpMultipartMode mode, WriteListener writeListener)
super(mode);
this.writeListener = writeListener;
public MultipartEntityWithProgressBar(HttpMultipartMode mode, String boundary, Charset charset, WriteListener writeListener)
super(mode, boundary, charset);
this.writeListener = writeListener;
// Left in for clarity to show where I took from kilaka's answer
// /**
// * Progress: 0-100
// */
// public int getProgress()
// if (outstream == null)
// return 0;
//
// long contentLength = getContentLength();
// if (contentLength <= 0) // Prevent division by zero and negative values
// return 0;
//
// long writtenLength = outstream.getWrittenLength();
// return (int) (100*writtenLength/contentLength);
//
【讨论】:
在 OutputStreamProgress 中,您能解释一下为什么 bytesWritten 变量被声明为 volatile 吗?这个article 说它在与 '++' 和 '+=' 一起使用时可能不是线程安全的,在这种情况下这是一个潜在的错误吗?谢谢 @bmeding 我不确定 volatile 关键字是如何进入其中的!我很久以前写过这门课,但我已经编辑过从我的答案中删除它,我认为它根本没有必要。【参考方案4】:大家好!
我自己解决了这个问题,并做了一个简单的例子。 如果有任何问题,请随时提出。
我们开始吧!
ApplicationView.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
public class ApplicationView implements ActionListener
File file = new File("C:/Temp/my-upload.avi");
JProgressBar progressBar = null;
public ApplicationView()
super();
public void createView()
JFrame frame = new JFrame("File Upload with progress bar - Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 300, 200);
frame.setVisible(true);
progressBar = new JProgressBar(0, 100);
progressBar.setBounds(20, 20, 200, 30);
progressBar.setStringPainted(true);
progressBar.setVisible(true);
JButton button = new JButton("upload");
button.setBounds(progressBar.getX(),
progressBar.getY() + progressBar.getHeight() + 20,
100,
40);
button.addActionListener(this);
JPanel panel = (JPanel) frame.getContentPane();
panel.setLayout(null);
panel.add(progressBar);
panel.add(button);
panel.setVisible(true);
public void actionPerformed(ActionEvent e)
try
sendFile(this.file, this.progressBar);
catch (Exception ex)
System.out.println(ex.getLocalizedMessage());
private void sendFile(File file, JProgressBar progressBar) throws Exception
String serverResponse = null;
HttpParams params = new BasicHttpParams();
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, true);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpClient client = new DefaultHttpClient(params);
HttpPut put = new HttpPut("http://localhost:8080/" + file.getName());
ProgressBarListener listener = new ProgressBarListener(progressBar);
FileEntityWithProgressBar fileEntity = new FileEntityWithProgressBar(file, "binary/octet-stream", listener);
put.setEntity(fileEntity);
HttpResponse response = client.execute(put);
HttpEntity entity = response.getEntity();
if (entity != null)
serverResponse = EntityUtils.toString(entity);
System.out.println(serverResponse);
FileEntityWithProgressBar.java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.http.entity.AbstractHttpEntity;
/**
* File entity which supports a progress bar.<br/>
* Based on "org.apache.http.entity.FileEntity".
* @author Benny Neugebauer (www.bennyn.de)
*/
public class FileEntityWithProgressBar extends AbstractHttpEntity implements Cloneable
protected final File file;
private final ProgressBarListener listener;
private long transferredBytes;
public FileEntityWithProgressBar(final File file, final String contentType, ProgressBarListener listener)
super();
if (file == null)
throw new IllegalArgumentException("File may not be null");
this.file = file;
this.listener = listener;
this.transferredBytes = 0;
setContentType(contentType);
public boolean isRepeatable()
return true;
public long getContentLength()
return this.file.length();
public InputStream getContent() throws IOException
return new FileInputStream(this.file);
public void writeTo(final OutputStream outstream) throws IOException
if (outstream == null)
throw new IllegalArgumentException("Output stream may not be null");
InputStream instream = new FileInputStream(this.file);
try
byte[] tmp = new byte[4096];
int l;
while ((l = instream.read(tmp)) != -1)
outstream.write(tmp, 0, l);
this.transferredBytes += l;
this.listener.updateTransferred(this.transferredBytes);
outstream.flush();
finally
instream.close();
public boolean isStreaming()
return false;
@Override
public Object clone() throws CloneNotSupportedException
return super.clone();
ProgressBarListener.java
import javax.swing.JProgressBar;
public class ProgressBarListener
private int transferedMegaBytes = 0;
private JProgressBar progressBar = null;
public ProgressBarListener()
super();
public ProgressBarListener(JProgressBar progressBar)
this();
this.progressBar = progressBar;
public void updateTransferred(long transferedBytes)
transferedMegaBytes = (int) (transferedBytes / 1048576);
this.progressBar.setValue(transferedMegaBytes);
this.progressBar.paint(progressBar.getGraphics());
System.out.println("Transferred: " + transferedMegaBytes + " Megabytes.");
编码愉快!
【讨论】:
这属于 NetBeans 为我所做的自动生成。 :) 为什么进度条的完成时间是实际上传时间的三分之一?以上是关于如何使用 Apache HttpClient 4 获取文件上传的进度条?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Apache HttpClient 4.3 处理 Cookie
如何在 Apache HttpClient 4.1 中处理会话
使用Apache HttpClient 4.x发送Json数据
android 使用Apache httpclient 4.3 出错