如何使用 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 出错

忽略 Apache HttpClient 4.3 中的 SSL 证书

Apache HttpClient 4.3 和 x509 客户端证书进行身份验证