• commons-httpclient直接发送内存中byte[]文件


    项目中需要生成图像文件,并上传到第三方平台。第三方平台提供的接口是http接口。并提供了比较全面的接口文档。

    private static final String username = "admin";
    private static final String password = "123456";
    public static void create(){
        String auth = encodeBase64(username+":"+password);
        HttpClient httpClient = HttpClients.createDefault();
        String url = "http://yourdomain/example-url";
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Authorization", "Basic " + auth);
        //添加认证消息头
        try {
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
            //添加要上传的文件
            multipartEntityBuilder.addBinaryBody("FILE", new File("E://111.jpg")).setMode(HttpMultipartMode.RFC6532);
            //传入参数
            multipartEntityBuilder.addPart("IMAGE_TYPE", new StringBody("111",ContentType.APPLICATION_JSON));
            multipartEntityBuilder.addPart("PAGE_NUM", new StringBody("1",ContentType.APPLICATION_JSON));
            multipartEntityBuilder.addPart("CREATE_TIME", new StringBody("2018-3-8 1:38:56",ContentType.APPLICATION_JSON));
            httpPost.setEntity(multipartEntityBuilder.build());
            HttpResponse httpResponse = httpClient.execute(httpPost);
            int code = httpResponse.getStatusLine().getStatusCode();
            if (code == 200) {
                String strResult = EntityUtils.toString(httpResponse.getEntity());
                System.out.println(strResult);
            } else{
                HttpEntity httpEntity = httpResponse.getEntity();
                String content = EntityUtils.toString(httpEntity);
                System.out.println(content);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception {
        create();
    }
    

    文档中基于httpClient的方式进行调用,并且上传文件的做法都是上传本地File。
    项目中已经使用了比较老的commons-httpclient。

    而且我的文件是已经生成好在内存中的byte[]数据。比较直接的做法是先把byte[]数据保存到一个临时目录。在通过new File读取文件并上传。但是作为一个强迫症,这样多此一举是不能接受的。所以需要探索下commons-httpclient如何直接上传byte[]格式的文件。

    当然首先是要看下commons-httpclient如何上传文件。

    刚开始找到一个例子是:

    MultipartPostMethod filePost = new MultipartPostMethod(targetURL);
    filePost.addParameter( "fileName" , targetFilePath);
    HttpClient client = new HttpClient();
     
    // 由于要上传的文件可能比较大 , 因此在此设置最大的连接超时时间
    client.getHttpConnectionManager(). getParams().setConnectionTimeout(5000);
    int status = client.executeMethod(filePost);
    

    但是项目中使用的是commons-httpclient-3.0,MultipartPostMethod 已经被废弃。而且也是直接addParameter( "fileName" , targetFilePath);看了下源码不好调整。

    最后找到官网的例子(地址:http://svn.apache.org/viewvc/httpcomponents/oac.hc3x/trunk/src/examples/MultipartFileUploadApp.java?view=co)

    对了,commons-httpclient已经停止维护,推荐使用httpClient。

    示例代码有些调整。

    PostMethod filePost = new PostMethod(targetURL);
    File targetFile = new File("E:/111.jpg");
    try {
        System.out.println("Uploading " + targetFile.getName() + " to " + targetURL);
        Part[] parts = {
                new FilePart(targetFile.getName(), targetFile)
        };
        filePost.setRequestEntity(
                new MultipartRequestEntity(parts, filePost.getParams())
        );
        HttpClient client = new HttpClient();
        client.getHttpConnectionManager().
                getParams().setConnectionTimeout(5000);
        int status = client.executeMethod(filePost);
        if (status == HttpStatus.SC_OK) {
            System.out.println(
                    "Upload complete, response=" + filePost.getResponseBodyAsString()
            );
        } else {
            System.out.println(
                    "Upload failed, response=" + HttpStatus.getStatusText(status)
            );
        }
    } catch (Exception ex) {
        System.out.println("ERROR: " + ex.getClass().getName() + " " + ex.getMessage());
        ex.printStackTrace();
    } finally {
        filePost.releaseConnection();
    }
    

    可以看到主要参数就在parts 里面。其中FilePart就是要上传的文件参数。FilePart的构造函数第一个参数为参数名,第二个是文件对象。
    查看FilePart源码,该构造函数为:

    public FilePart(String name, File file)
            throws FileNotFoundException {
        this(name, ((PartSource) (new FilePartSource(file))), null, null);
    }
    

    可以看到,将File对象封装成FilePartSource。
    另外FilePart的sendData方法如下

    protected void sendData(OutputStream out) throws IOException {
        LOG.trace("enter sendData(OutputStream out)");
        if (lengthOfData() == 0L) {
            LOG.debug("No data to send.");
            return;
        }
        byte tmp[] = new byte[4096];
        InputStream instream = source.createInputStream();
        int i;
        try {
            while ((i = instream.read(tmp)) >= 0) out.write(tmp, 0, i);
        } finally {
            instream.close();
        }
    }
    

    可以猜到这个就是拿到http链接的OutputStream ,往里面写数据。写的是从source拿到的InputStream 里面的内容。这个source就是前面的FilePartSource。
    查看FilePartSource源码,相当于是对File的一层封装。主要方法就是实现的接口PartSource的几个方法。

    public interface PartSource {
    
        public abstract long getLength();
    
        public abstract String getFileName();
    
        public abstract InputStream createInputStream()
                throws IOException;
    }
    

    联想到sendData方法里面调用的source.createInputStream();如果这个地方获取的InputStream 如果不是FileInputStream 而是ByteArrayInputStream不就可以了吗?也就是说自己写个BytesFilePartSource 类实现PartSource接口,但是不封装File,而是封装byte[]不就可以了。然后构建FilePart时传入自己写的BytesFilePartSource 。根据FilePartSource写了自己的BytesFilePartSource 。

    public class BytesFilePartSource implements PartSource {
    
        private byte[] bytes;
        private String fileName;
    
        public BytesFilePartSource(String fileName, byte[] bytes)
                throws FileNotFoundException {
            this.fileName = fileName;
            this.bytes = bytes;
    
        }
    
        @Override
        public long getLength() {
            if (bytes != null)
                return bytes.length;
            else
                return 0L;
        }
    
        @Override
        public String getFileName() {
            return fileName != null ? fileName : "noname";
        }
    
        @Override
        public InputStream createInputStream() throws IOException {
            if (bytes != null)
                return new ByteArrayInputStream(bytes);
            else
                return new ByteArrayInputStream(new byte[0]);
        }
    
    }
    

    其实找找代码发现jar中已经有一个类实现了这样的功能:ByteArrayPartSource。所以使用这个类就行了,就不要重复造轮子了。

    主要代码如下:

    HttpClient httpClient = new HttpClient();
    httpClient.getHttpConnectionManager().
            getParams().setConnectionTimeout(5000);
    PostMethod postMethod = new PostMethod(url);
    // 添加认证消息头
    postMethod.setRequestHeader("Authorization", "Basic " + auth);
    try {
        Part[] parts = {
                new FilePart("FILE", new ByteArrayPartSource("111.jpg", imageBytes)),
                new StringPart("IMAGE_TYPE", imageType, "GBK"),
                new StringPart("SEQ_NUMBER", pageNum + "", "GBK"),
                new StringPart("PAGE_NUM", time, "GBK"),
                new StringPart("CREATE_TIME", time, "GBK")
        };
        postMethod.setRequestEntity(new MultipartRequestEntity(parts,
                postMethod.getParams()));
        int status = httpClient.executeMethod(postMethod);
        log.info("status:" + status + "Result:" + strResult);
    } catch (Exception e) {
        e.printStackTrace();
        contentId = "";
    } finally {
        postMethod.releaseConnection();
    }
    

    最后,其实最新的httpclient也是支持直接发送二进制数据的,没必要先保存数据到磁盘再读取。

  • 相关阅读:
    FLUSH TABLES WITH READ LOCK 锁全局
    第三届中国云计算用户大会笔记和心得
    第三届中国云计算用户大会笔记和心得
    报表或BI的价值在哪?
    ZooKeeper
    TypeError:First argument must be file descriptor
    org.eclipse.core.runtime.OperationCanceledException
    perl 安装 ZooKeeper模块
    2、Zookeeper集群搭建、命令行Client操作
    1、Zookeeper熟悉和用途综述
  • 原文地址:https://www.cnblogs.com/jimmyfan/p/11348700.html
Copyright © 2020-2023  润新知