在測試HttpClient編寫的圖片上傳程序時拋出以下異常:
1 Exception in thread "main" org.apache.http.client.ClientProtocolException 2 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:909) 3 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805) 4 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784) 5 at TestImageServer.main(TestImageServer.java:75) 6 Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity. The cause lists the reason the original request failed. 7 at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:686) 8 at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:517) 9 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906) 10 ... 3 more 11 Caused by: java.io.IOException: Read error 12 at java.io.FileInputStream.readBytes(Native Method) 13 at java.io.FileInputStream.read(FileInputStream.java:177) 14 at org.apache.http.entity.mime.content.InputStreamBody.writeTo(InputStreamBody.java:69) 15 at org.apache.http.entity.mime.HttpMultipart.doWriteTo(HttpMultipart.java:206) 16 at org.apache.http.entity.mime.HttpMultipart.writeTo(HttpMultipart.java:224) 17 at org.apache.http.entity.mime.MultipartEntity.writeTo(MultipartEntity.java:183) 18 at org.apache.http.entity.HttpEntityWrapper.writeTo(HttpEntityWrapper.java:98) 19 at org.apache.http.impl.client.EntityEnclosingRequestWrapper$EntityWrapper.writeTo(EntityEnclosingRequestWrapper.java:108) 20 at org.apache.http.impl.entity.EntitySerializer.serialize(EntitySerializer.java:122) 21 at org.apache.http.impl.AbstractHttpClientConnection.sendRequestEntity(AbstractHttpClientConnection.java:271) 22 at org.apache.http.impl.conn.ManagedClientConnectionImpl.sendRequestEntity(ManagedClientConnectionImpl.java:197) 23 at org.apache.http.protocol.HttpRequestExecutor.doSendRequest(HttpRequestExecutor.java:257) 24 at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125) 25 at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:712) 26 ... 5 more
根據異常信息"Cannot retry request with a non-repeatable request entity. The cause lists the reason the original request failed."可以確定拋出異常的原因是程序中使用了不可重復的請求實體(non-repeatable request entity)。HttpClient代碼如下:
1 import org.apache.http.HttpEntity; 2 import org.apache.http.HttpResponse; 3 import org.apache.http.client.HttpClient; 4 import org.apache.http.client.methods.HttpPost; 5 import org.apache.http.entity.mime.MultipartEntity; 6 import org.apache.http.entity.mime.content.InputStreamBody; 7 import org.apache.http.entity.mime.content.StringBody; 8 import org.apache.http.impl.client.DefaultHttpClient; 9 import org.apache.http.util.EntityUtils; 10 import org.jdom2.JDOMException; 11 12 import java.io.*; 13 14 /** 15 * Created with IntelliJ IDEA. 16 * User: qutengfei 17 * Date: 12-11-15 18 * Time: 下午2:00 19 * To change this template use File | Settings | File Templates. 20 * 測試 MultipartForm 21 */ 22 public class TestMultipartForm { 23 public static void main(String[] args) throws IOException { 24 HttpClient httpclient = new DefaultHttpClient(); 25 HttpPost httpPost = new HttpPost("http://10.5.13.21/upload/"); 26 27 MultipartEntity multipartEntity = new MultipartEntity(); 28 multipartEntity.addPart("pixel_middle", new StringBody("960,600")); 29 multipartEntity.addPart("pixel_small", new StringBody("192,120")); 30 multipartEntity.addPart("system", new StringBody("01")); 31 multipartEntity.addPart("sort", new StringBody("01")); 32 multipartEntity.addPart("uploader", new StringBody("0283")); 33 multipartEntity.addPart("image", new InputStreamBody(new FileInputStream(new File("D:/httpclient/images/0.jpg");), file.getName())); 34 35 httpPost.setEntity(multipartEntity); 36 HttpResponse response = httpclient.execute(httpPost); 37 try { 38 int statusCode = response.getStatusLine().getStatusCode(); 39 if (200 == statusCode) { 40 HttpEntity entity = response.getEntity(); 41 System.out.println(EntityUtils.toString(entity)); 42 } else { 43 System.out.println("Error " + statusCode + "!"); 44 } 45 } finally { 46 httpPost.releaseConnection(); 47 } 48 } 49 }
於是查看官方HttpClient Tutorial的1.1.4.1. Repeatable entities節,內容如下:
An entity can be repeatable, meaning its content can be read more than once. This is only possible with self contained entities (like ByteArrayEntity
or StringEntity
)
根據上面對Repeatable Entity的介紹,就可以確定問題是因為構建multipartEntity時包含除ByteArrayEntity和StringEntity以外的Entity
。修改代碼如下:
1 import org.apache.http.HttpEntity; 2 import org.apache.http.HttpResponse; 3 import org.apache.http.client.HttpClient; 4 import org.apache.http.client.methods.HttpPost; 5 import org.apache.http.entity.mime.MultipartEntity; 6 import org.apache.http.entity.mime.content.InputStreamBody; 7 import org.apache.http.entity.mime.content.StringBody; 8 import org.apache.http.impl.client.DefaultHttpClient; 9 import org.apache.http.util.EntityUtils; 10 import org.jdom2.JDOMException; 11 12 import java.io.*; 13 14 /** 15 * Created with IntelliJ IDEA. 16 * User: qutengfei 17 * Date: 12-11-15 18 * Time: 下午2:00 19 * To change this template use File | Settings | File Templates. 20 * 測試 MultipartForm 21 */ 22 public class TestMultipartForm { 23 public static void main(String[] args) throws IOException, JDOMException { 24 HttpClient httpclient = new DefaultHttpClient(); 25 HttpPost httpPost = new HttpPost("http://10.5.13.21/upload/"); 26 27 MultipartEntity multipartEntity = new MultipartEntity(); 28 multipartEntity.addPart("pixel_middle", new StringBody("960,600")); 29 multipartEntity.addPart("pixel_small", new StringBody("192,120")); 30 multipartEntity.addPart("system", new StringBody("01")); 31 multipartEntity.addPart("sort", new StringBody("01")); 32 multipartEntity.addPart("uploader", new StringBody("0283")); 33 multipartEntity.addPart("image", new InputStreamBody(new FileInputStream(new File("D:/httpclient/images/0.jpg");), file.getName())); 34 35 // 顯示實體是否可重復 36 System.out.println("Repeatable:" + multipartEntity.isRepeatable()); 37 38 httpPost.setEntity(multipartEntity); 39 HttpResponse response = httpclient.execute(httpPost); 40 try { 41 int statusCode = response.getStatusLine().getStatusCode(); 42 if (200 == statusCode) { 43 HttpEntity entity = response.getEntity(); 44 System.out.println(EntityUtils.toString(entity)); 45 } else { 46 System.out.println("Error " + statusCode + "!"); 47 } 48 } finally { 49 httpPost.releaseConnection(); 50 } 51 } 52 }
編譯執行上述程序,結果如下:
Repeatable:false
注釋第33行代碼,重新編譯執行程序,結果如下:
Repeatable:true
根據上面的執行結果,可以確定InputStreamBody是造成multipartEntity為不可重復實體的元凶。
HttpClient有ByteArrayBody、FileBody、InputStreamBody和StringBody四種Body。經過筆者測試,其中只有用InputStreamBody構建multipartEntity才是non-repeatable entity。
總結:
筆者最早使用InputStreamBody而不是FileBody,是因為業務需要前置系統將圖片上傳到圖片服務器,並保存圖片鏈接地址到前置系統的數據庫。但是前置系統是B/S架構,圖片從頁面上傳到前置的后台是以流的方式,故而才使用InputStreamBody。最后,使用ByteArrayBody代替InputStreamBody。
參考文檔:
本文為筆者原創,轉載請說明出處。