項目中需要生成圖像文件,並上傳到第三方平台。第三方平台提供的接口是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也是支持直接發送二進制數據的,沒必要先保存數據到磁盤再讀取。