I am currently trying to upload an image captured on an ESP32 Cam board to a Spring server via HTTP POST. However, I keep getting an error saying
Required request part 'image' is not present
I have tried multiple solutions across the web but with no success. Here's the arduino code:
String serverUrl = "192.168.100.4";
String serverPath = "/identify";
const int serverPort = 8080;
String contentLengthStr = String("Content-Length=");
String crLf = String("\r\n");
String bodyStart = "–-claudiu\r\nContent-Disposition: form-data; name=\"image\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
String bodyEnd = "\r\n–-claudiu–-\r\n";
String sendPhoto() {
camera_fb_t * frameBuffer = NULL;
frameBuffer = esp_camera_fb_get();
if(!frameBuffer) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
}
Serial.println("Connecting to server: " + serverUrl);
if (client.connect(serverUrl.c_str(), serverPort)) {
Serial.println("Connection successful!");
uint32_t imageLength = frameBuffer->len;
uint32_t contentLength = bodyStart.length() + imageLength + bodyEnd.length();
Serial.print("Content length: ");
Serial.println(String(contentLength));
Serial.println("Posting image");
client.println("POST " + serverPath + " HTTP/1.1");
client.println("Host: " + serverUrl);
client.println("Content-Length: " + String(contentLength));
client.println("Content-Type: multipart/form-data; boundary=claudiu\r\n");
client.print(bodyStart);
uint8_t *fbBuf = frameBuffer->buf;
size_t fbLen = frameBuffer->len;
for (size_t n=0; n<fbLen; n=n+1024) {
if (n+1024 < fbLen) {
client.write(fbBuf, 1024);
fbBuf += 1024;
}
else if (fbLen%1024>0) {
size_t remainder = fbLen%1024;
client.write(fbBuf, remainder);
}
}
client.print(bodyEnd);
esp_camera_fb_return(frameBuffer);
Serial.println("Response:");
while (client.connected()) {
if (client.available()) {
String response = client.readStringUntil('\n');
Serial.println(response.c_str());
}
}
client.stop();
delay(50);
esp_camera_fb_return(frameBuffer);
}
else {
Serial.println("Connection to " + serverUrl + " failed.");
}
return "DONE";
}
My Spring Controller looks like this:
#RestController
public class RequestController {
#PostMapping(value = "/identify", consumes = "multipart/form-data")
public String identify(#RequestParam("imageFile") MultipartFile imageFile) throws IOException {
System.out.println(imageFile.getOriginalFilename());
FileUtil.saveFile("/images/", imageFile.getOriginalFilename(), imageFile);
return "WORKS";
}
}
The connection is successful but I can not get a proper response from the server.
Could you please help me find the problem? I have been looking for the past few hours but I can't find anything.
I am not familiar with arduino or embedded systems code at all but I have some spring boot experience try something like this on the api resource code I hope it helps and let me know if it works
#RestController
public class RequestController {
#PostMapping(value = "/identify", consumes = "multipart/form-data")
public ResponseEntity<String> identify(#RequestParam("imageFile") MultipartFile
imageFile) throws IOException {
System.out.println(imageFile.getOriginalFilename());
FileUtil.saveFile("/images/", imageFile.getOriginalFilename(), imageFile);
return ResponseEntity.ok("WORKS");
}
}
Related
I have the following class:
class MyClass {
private OkHttpClient httpClient;
private String session_id;
public MyClass() {
this.setHttpClient(new OkHttpClient());
}
public String getSessionId() {
return session_id;
}
public void setHttpClient(OkHttpClient httpClient) {
this.htttpClient = httpClient;
}
public String retrieveUrlContents(String url, String csrfToken) throws Exception {
url = this.url.replaceAll("/$", "") + "/" + url.replaceAll("^/", "");
csrfToken = (csrfToken == null) ? "" : csrfToken;
if (!csrfToken.equals("")) {
long unixtime = System.currentTimeMillis() / 1000L;
// AJAX Calls also require to offer the _ with a unix timestamp alongside csrf token
url += "?_=" + unixtime + "&csrf_token=" + csrfToken;
}
Request.Builder request = new Request.Builder()
.url(url)
.header("User-Agent", "Mozila/5.0 (X11;Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0");
String session_id = this.getSessionId();
session_id = session_id == null ? "" : session_id;
if (!session_id.equals("")) {
request.header("Set-Cookie", "session_id=" + session_id + ";login_uid=" + Math.random());
}
Response response = this.httpClient.newCall(request.build()).execute();
int code = response.code();
if (code != 200) {
throw new Exception("The url " + url + " returned code " + code);
}
String responseBody = response.body().string();
return responseBody;
}
}
And I want to unit-test the case that if getSessionId actually return a non-null or a non-empty string then the httpCall is performed:
class MyClassTest {
private static OkHttpClient mockHttpClient(final String serializedBody, final boolean json, int code) throws IOException {
final OkHttpClient okHttpClient = mock(OkHttpClient.class);
final Call remoteCall = mock(Call.class);
code = code < 0 ? 200 : code;
final Response response = new Response.Builder()
.request(new Request.Builder().url("http://url.com").build())
.protocol(Protocol.HTTP_1_1)
.code(code).message("").body(
ResponseBody.create(
MediaType.parse(json ? "application/json" : "text/html"),
serializedBody
))
.build();
when(remoteCall.execute()).thenReturn(response);
when(okHttpClient.newCall(any())).thenReturn(remoteCall);
return okHttpClient;
}
#Test
public void retrieveUrlContentsIsRetrievedWithSessionId() {
File file = (new File("src/test/resources/csrfInvalid.html")).getAbsoluteFile();
String path = file.getPath();
Scanner fileReader = new Scanner(file);
String contents = fileReader.useDelimiter("\\Z").next();
OkHttpClient client = this.mockHttpClient(contents, false, 200);
final Η300sCredentialsRetriever retriever = spy(Η300sCredentialsRetriever.class);
doReturn("Hello").when(retriever).getSessionId();
retriever.setUrl("192.168.2.1");
retriever.setHttpClient(client);
String response = retriever.retrieveUrlContents("/example.html");
// Test that http call is permormed with SessionId
// Rest of Assertions
}
}
What I want is to Assert that OkHttp3 is performing an HttpCall with the appropriate Cookie Header. But I do not know how I can assert that the HttpCall is performed with this header. Do you have any ideas how I can test that?
One option is MockWebServer, look at RecordedRequest which can confirm the headers you sent.
https://github.com/square/okhttp/tree/master/mockwebserver
https://www.baeldung.com/spring-mocking-webclient#mockwebserverchecking
RecordedRequest request1 = server.takeRequest();
assertEquals("/v1/chat/messages/", request1.getPath());
assertNotNull(request1.getHeader("Authorization"));
#RequestMapping(value = "/video/{clientID}/{fileName}", method = RequestMethod.GET)
public ResponseEntity<StreamingResponseBody> getClientVideo(#PathVariable(value = "clientID") Integer clientID, #PathVariable(value = "fileName") final String fileName) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
String absolutePath = new File(".").getAbsolutePath();
File file = new File(Paths.get(absolutePath).getParent() + "/" + clientID);
if (null != file) {
FilenameFilter beginswithm = new FilenameFilter() {
public boolean accept(File directory, String filename) {
return filename.contains("ClientVideo_"+fileName);
}
};
File[] files = file.listFiles(beginswithm);
if (null != files && files.length > 0) {
Resource resource = null;
for (final File f : files) {
headers.set("Content-Disposition", "inline; filename=" + f.getName());
StreamingResponseBody responseBody = new StreamingResponseBody() {
#Override
public void writeTo(OutputStream out) throws IOException {
out.write(Files.readAllBytes(f.toPath()));
out.flush();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
return ResponseEntity.ok().headers(headers).contentType(MediaType.APPLICATION_OCTET_STREAM).body(responseBody); //(responseBody, headers, HttpStatus.OK);
}
}
}
RecruiterResponseBean resBean = new RecruiterResponseBean();
resBean.setStatusMessage("Video is not present : " + Constants.FAILED);
resBean.setStatusCode(Constants.FAILED_CODE);
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
I am using Spring MVC, I tried many methods but all are downloading the video. Above code is streaming the video, the problem is: in firefox it is streaming but in crome it is downloading. I want to play streaming video in crome also like below ex.
[Ex streaming video: http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4]
Do I need to add more Headers? [if so, which headers need to add?]
Or Did i miss anything?
I am using Okhttp lib to send data to server. I want to set text and images in RequestBody. For uploading multiple image to server using Okhttp i follow this link. I have implemented that type of code in my app in another activity class and its work fine. I have checked this question that how to pass array in RequestBody.
My arrayList format is like this
blockList
{
block0
{
description0 = First block
image0 = {image1, image2}
video0 = videolink
disp_order0 = 0
block0 = 0
}
block1
{
description1 = second block
image1 = {image1,image2,image2}
video1 = videolink
disp_order1 = 1
block1 = 1
}
.....
}
My Requirement :-
Now i want to send multiple images as an array in single parameter. When i send first block then parameter names are description0,image0[],video1,disp_order and block0 and image0[] will contain first block images as array and same for other.
API is working fine because when i test in postman then i receive the data in server side. You can see in below..
Here is my java function that set the data in RequestBody and make a call to send that data on sever.
ProgressDialog pd;
private OkHttpClient client;
private void saveCastBoxOnServer(String castBoxTitle, String selectedCastBoxId, String selectedCategoryId,
String userId, String action, ArrayList<CastBoxBlock> blockList)
{
try
{
client = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build();
ArrayList<CastBoxBlock> blockArrayList = blockList;
int blockSize = blockArrayList.size();
MultipartBody.Builder multipartBuilderNew = new MultipartBody.Builder().setType(MultipartBody.FORM);
for (int i = 0; i < blockSize; i++)
{
String description = blockArrayList.get(i).getBlockDescription();
String descriptionField = "description"+i;
multipartBuilderNew.addFormDataPart(descriptionField, description);
/**This is used for distribution of images and videos. After that set that
* Images and video in multipartBuilder.
**/
CastBoxBlock model = blockArrayList.get(i);
ArrayList<SelectedMediaModel> mediaModels = model.getSelectedMediaModelArrayList();
int mediaModelsSize = mediaModels.size();
String passingVideoUri = "";
String videoUri = "";
for (int j = 0; j < mediaModelsSize; j++)
{
String mediaType = mediaModels.get(j).getMediaType();
if (mediaType.equals(StringKeyConstant.mediaVideo))
{
videoUri = mediaModels.get(j).getMediaPath();
if (passingVideoUri.trim().length()==0){
passingVideoUri = videoUri;
}else{
passingVideoUri = passingVideoUri + "," + videoUri;
}
}
else if (mediaType.equals(StringKeyConstant.mediaImage))
{
String imagePath = mediaModels.get(j).getMediaPath();
File sourceFile = new File(imagePath);
/**Changes whether JPEG or PNG**/
final MediaType MEDIA_TYPE = MediaType.parse(
constant.getFileExt(imagePath).endsWith("png") ? "image/png" : "image/jpeg");
String imageName = System.currentTimeMillis() + j + "_block_img.jpg";
String imageField = "image"+i+"["+j+"]";
multipartBuilderNew.addFormDataPart(imageField,imageName,
RequestBody.create(MEDIA_TYPE, sourceFile));
}
}
/**This is used to set the {#videoUri} block of videos and send to sever**/
String videoField = "video"+i;
multipartBuilderNew.addFormDataPart(videoField, passingVideoUri);
/**This will set the {#display_order} in multipartBuilder**/
String displayOrderField = "disp_order"+i;
String displayOrder = blockArrayList.get(i).getBlockIndex();
multipartBuilderNew.addFormDataPart(displayOrderField, displayOrder);
/**This will set the {#block} value in multipartBuilder**/
String blockField = "block"+i;
String block = ""+i;
multipartBuilderNew.addFormDataPart(blockField, block);
}
pd = new ProgressDialog(activity);
pd.setCancelable(false);
pd.setMessage(getResources().getString(R.string.please_wait));
pd.show();
RequestBody formBody = multipartBuilderNew
.addFormDataPart("cast_title", castBoxTitle)
.addFormDataPart("user_id", userId)
.addFormDataPart("cast_box", selectedCastBoxId)
.addFormDataPart("category", selectedCategoryId)
.addFormDataPart("action", action)
.build();
Request request = new Request.Builder()
.url(ApiUtils.ADD_FETCH_USER_CAST)
.post(formBody)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
Log.e(TAG, "Get Api credential fail."+call.toString());
e.printStackTrace();
}
#Override
public void onResponse(Call call, Response response) throws IOException
{
try
{
if (pd != null){
pd.cancel();
pd.dismiss();
pd = null;
}
String castSavedResponse = response.body().string();
Log.i(TAG, "castSavedResponse = " + castSavedResponse);
}
catch (Exception e){
Log.e(TAG, "***Error : onResponse() method");
e.printStackTrace();
}
}
});
}
catch (Exception e){
Log.e(TAG, "***Error : saveCastBoxOnServer()");
e.printStackTrace();
}
}
Thanks in advance. If any one will help it would be very appreciate.
When i check your code then i note one things. I am no sure but i think you change your code on image[] place. you can just change your code as below. I hope this will help you and you got the solution.
Use this code
String imageField = "image"+i+"[]";
Instead of
String imageField = "image"+i+"["+j+"]";
when i passed simple images as an array in Okhttp then i do code as above. For uploading multiple images on server using Okhttp, i also follow this link as you follow.
My web service publish push notification to APNs and APNs send to destination IOS device.
When apns contain Unicode emoji on alert body push notification and Iphone os can't decode my Unicode emoji '\uD83D\uDE0A' app already kill.
Push notification show same '\uD83D\uDE0A', No emoji shown on banner notification bar on top.
Android application works fine by GCM dispatches push notification But IOS not support.
Iphone-Ios supports only this format '\ue415'
Here code that from ActiveMQ subscribe chat payload get into web-service
public void onPublish(UTF8Buffer topic, Buffer msg, Runnable ack) {
try {
String body = msg.utf8().toString();
if (logger.isInfoEnabled()) {
logger.info("MQTT connection.listener.onPublish(), msg Received ["
+ body + "]");
}
if (body.contains("\"cmd\":\"chat\"")
&& body.contains("\"is_sender_msg\":true")) {
QueueMgr.addToChatQueue(body); //Changed true to false
}
else if(body.contains("\"cmd\":\"msg_seen\"")){
QueueMgr.addToChatReadSeenQueue(body);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
ack.run();
}
}
My code for create push notification on java
public static JSONObject constructePushJson(JSONObject jsonObject,String[] cloudkeyWithDevice) throws JSONException {
if(cloudkeyWithDevice[0] != null){
JSONObject pnAPIdata = new JSONObject();
if(cloudkeyWithDevice[1].equals("a") || cloudkeyWithDevice[1].equals("d")){
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_CMD, com.anyorg.constants.AppConstants.CMD_ANDROID_PUSH);
}
else{
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_CMD, com.anyorg.constants.AppConstants.CMD_IOS_PUSH);
}
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_APP_TOKEN, com.anyorg.constants.AppConstants.DEFAULT_APP_TOKEN);
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_DEVICE_TOKEN, cloudkeyWithDevice[0]);
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_USER_ID, jsonObject.getInt(com.anyorg.constants.AppConstants.FLD_TO_USER_ID));
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_DEVICE_ID, 0);
String alertMsg=StringEscapeUtils.unescapeJava(jsonObject.getString(com.anyorg.constants.AppConstants.FLD_BODY));
jsonObject.put(com.anyorg.constants.AppConstants.FLD_BODY,alertMsg);
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_ALERT_MSG, "AryaConnect: "+alertMsg);//(jsonObject.isNull("body")) ? jsonObject.getString("from_user_name")+": Sent a file" : jsonObject.getString("from_user_name")+": "+jsonObject.getString("body")
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_MSG, jsonObject);//jsonObject.getString(com.anyorg.constants.AppConstants.FLD_BODY)
pnAPIdata.put(com.anyorg.constants.AppConstants.FLD_CALLBACK_URL, callbackUrl);
pnAPIdata.put(com.anyorg.constants.AppConstants.MAC_ADDRESS_ID, jsonObject.getString("mobile_rec_id"));
return pnAPIdata;
}
else{
return null;
}
}
Publish to APNs code
public class ANSNotificationDispatcher implements NotificationDispatcher {
protected static final Logger logger = Logger
.getLogger(ANSNotificationDispatcher.class);
public static final String OS_NAME = AppConstants.OS_TYPE_IPHONE;
String keystore;
String password;
boolean production;
public ANSNotificationDispatcher() {
try {
keystore = AppConfig.getAPNKeystore();
password = AppConfig.getAPNKeystorePassword();
PushyAPNMgr.init(keystore, password, AppConfig.isAPNProdcution());
} catch (Throwable e) {
e.printStackTrace();
}
}
private void push(Payload payload, String token, String userId,
String deviceId) throws ConfigurationException,
DeviceUnregisteredException {
// QueueManager.addToIOsQueue(payLoad, token, userId, ivUserDeviceId);
long stime = System.currentTimeMillis();
try {
PushyAPNMgr.push(token, payload.toString());
if (logger.isInfoEnabled())
logger.info("push(): APN PN userId [" + userId
+ "], device id [" + deviceId + "] payoad [" + payload
+ "] Response time ["
+ (System.currentTimeMillis() - stime) + "]ms");
} catch (Exception e) {
e.printStackTrace();
throw new ConfigurationException();
}
}
public static Payload createComplexPayload(JSONObject jsonObject) {
PushNotificationPayload complexPayload = null;
try {
complexPayload = createPayload(jsonObject);
String msg = Common.getStringAsNull(jsonObject,
AppConstants.FLD_MSG);
if (!Common.isEmpty(msg)) {
complexPayload.addCustomDictionary(AppConstants.FLD_MSG, msg);
}
if (logger.isInfoEnabled()) {
logger.info("createComplexPayloadV2(): payload ["
+ complexPayload.getPayload().toString() + "]");
}
} catch (JSONException e) {
e.printStackTrace();
}
return complexPayload;
}
public void dispatch(JSONObject jsonObject, String jsonData)
throws NotificationException, DeviceUnregisteredException,
MultipleRegistartionIdException, ConfigurationException {
String deviceToken = Common.getStringAsNull(jsonObject,
AppConstants.FLD_DEVICE_TOKEN);
if (Common.isEmpty(deviceToken)) {
logger.error("dispatch(): device token is null, cmd [" + jsonData
+ "]");
return;
}
Payload payload = createComplexPayload(jsonObject);
String userId = Common.getStringAsNull(jsonObject,
AppConstants.FLD_USER_ID);
String deviceId = Common.getStringAsNull(jsonObject,
AppConstants.FLD_DEVICE_ID);
push(payload, deviceToken, userId, deviceId);
}
public static void handleInvalidTokeException(String token) {
}
public static void handleDeviceUnregisteredException(String token) {
}
}
Ios push notification managed by Ios OS
My Apache Catalina log
I am a web service cloud developer faceing this issue last one days for only Ios app. So please, if some body have knowledge or done before. please advise and refer me some idea.
Emoji in my push notifications link.
https://mixpanel.com/help/questions/articles/how-do-i-send-custom-parameters-like-emoji-in-my-push-notifications
Thanks
Finally, APNs issue resolve in (Ios app) on java web service code by this Unicode encode and decode process. (unescapeJava and escapeJava) from lib commons-lang-2.6.jar and class org.apache.commons.lang.StringEscapeUtils
emojiBytes = alertMsg.getBytes("UTF-8"); text = new String(emojiBytes, "UTF-8");
private static PushNotificationPayload createPayload(JSONObject jsonObject)
throws JSONException {
String alertMsg = Common.getStringUnicodeAsNull1(jsonObject,
AppConstants.FLD_ALERT_MSG);
byte[] emojiBytes=null;
String text=null;
try {
emojiBytes = alertMsg.getBytes("UTF-8");
text = new String(emojiBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//String emojiAsString = new String(emojiBytes, Charset.forName("UTF-8"));
//System.out.println("#####alertMsg: "+text);
Integer badgeCnt;
if (jsonObject.has(AppConstants.FLD_BADGE_CNT)){
badgeCnt = Common.getIntegerAsNull(jsonObject,
AppConstants.FLD_BADGE_CNT);
}else{
badgeCnt = AppConstants.VAL_ZERO;
}
PushNotificationPayload payload = createPayload(badgeCnt, text);
return payload;
}
String alertMsg = Common.getStringUnicodeAsNull1(jsonObject,
AppConstants.FLD_ALERT_MSG);
public static String getStringUnicodeAsNull1(JSONObject jsonObject,
String key) {
try {
if(jsonObject.isNull(key))
return null;
else
return StringEscapeUtils.unescapeJava(jsonObject.getString(key));
} catch (JSONException je) {
return null;
}
}
Respected sir and ma'am, If there is any other solution of java APNs emoji Unicode on IOS Push Notification.
Then please give me some hints.Thanks
you shouldn't need to mess about with html decoding. As you say the code point for smiling face is \u263A. In PHP you can represent that in a UTF8-encoded string as "\xE2\x98\xBA"
Lightning bolt (actually 'high voltage sign') is \u26A1 or "\xE2\x9A\xA1" in UTF-8.
Both these characters are present in some non-emoji fonts as regular Unicode symbols. You can see with:
<?php
header('Content-type: text/html; charset=utf-8');
echo "\xE2\x9A\xA1";
echo "\xE2\x98\xBA";
or other things...
for the googlers. json_encode() adds double \
$message = "\ue04a";
$body['aps'] = array(
'alert' => $message,
'sound' => 'default',
'type' => $type,
'param' => $param
);
$payload = json_encode($body);
$payload = str_replace("\", "\\", $payload);
Please check with this two way... i think this is helpfull for you.
I am working on a file downloader that submits get requests for around thousand files. I came across this article that would aid in submitting a lot of requests using the executor framework. I tried running a smaller number of files (around a hundred), it was working. However, the large number of files that I ran resulted in ConnectionClosedException.
This is the download code that submits the requests:
void download(String sObjname, List<FileMetadata> blobList) throws IOException, InterruptedException
{
long totalSize = 0;
this.sObjname = sObjname;
for (FileMetadata doc : blobList)
{
totalSize += doc.getSize();
doc.setStatus(JobStatus.INIT_COMPLETE);
}
totalFileSize = new AtomicLong(totalSize);
// Async client definiton; MAX_CONN around 5-15
try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom().setMaxConnPerRoute(MAX_CONN)
.setMaxConnTotal(MAX_CONN).build())
{
httpclient.start();
// Define the callback for handling the response and marking the status
FutureCallback<String> futureCallback = new FutureCallback<String>() {
#Override
public void cancelled()
{
logger.error("Task cancelled in the rest client.");
shutdownLatch.countDown();
}
#Override
public void completed(String docPath)
{
FileMetadata doc = futureMap.get(docPath);
logger.info(doc.getPath() + " download completed");
totalFileSize.addAndGet(-1 * doc.getSize());
doc.setStatus(JobStatus.WRITE_COMPLETE);
shutdownLatch.countDown();
}
#Override
public void failed(Exception e)
{
shutdownLatch.countDown();
logger.error("Exception caught under failed for " + sObjname + " " + e.getMessage(), e);
Throwable cause = e.getCause();
if (cause != null && cause.getClass().equals(ClientProtocolException.class))
{
String message = cause.getMessage();
// TODO Remove this
logger.error("Cause message: " + message);
String filePath = message.split("Unable to download the file ")[1].split(" ")[0];
futureMap.get(filePath).setStatus(JobStatus.WRITE_FAILED);
}
}
};
// Submit the get requests here
String folderPath = SalesforceUtility.getFolderPath(sObjname);
new File(new StringBuilder(folderPath).append(File.separator).append(Constants.FILES).toString()).mkdir();
String body = (sObjname.equals(Constants.contentVersion)) ? "/VersionData" : "/body";
shutdownLatch = new CountDownLatch(blobList.size());
for (FileMetadata doc : blobList)
{
String uri = baseUri + "/sobjects/" + sObjname + "/" + doc.getId() + body;
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader(oauthHeader);
doc.setStatus(JobStatus.WRITING);
// Producer definition
HttpAsyncRequestProducer producer = HttpAsyncMethods.create(httpGet);
// Consumer definition
File docFile = new File(doc.getPath());
HttpAsyncResponseConsumer<String> consumer = new ZeroCopyConsumer<String>(docFile) {
#Override
protected String process(final HttpResponse response, final File file,
final ContentType contentType) throws Exception
{
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
{
throw new ClientProtocolException("Unable to download the file " + file.getAbsolutePath()
+ ". Error code: " + response.getStatusLine().getStatusCode() + "; Error message: "
+ response.getStatusLine());
}
return file.getAbsolutePath();
}
};
// Execute the request
logger.info("Submitted download for " + doc.getPath());
httpclient.execute(producer, consumer, futureCallback);
futureMap.put(doc.getPath(), doc);
}
if (futureMap.size() > 0)
schedExec.scheduleAtFixedRate(timerRunnable, 0, 5, TimeUnit.MINUTES);
logger.debug("Waiting for download results for " + sObjname);
shutdownLatch.await();
}
finally
{
schedExec.shutdown();
schedExec.awaitTermination(24, TimeUnit.HOURS);
logger.debug("Finished downloading files for " + sObjname);
}
}
The stacktrace that I received was:
org.apache.http.ConnectionClosedException: Connection closed unexpectedly
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.closed(HttpAsyncRequestExecutor.java:139) [httpcore-nio-4.4.4.jar:4.4.4]
at org.apache.http.impl.nio.client.InternalIODispatch.onClosed(InternalIODispatch.java:71) [httpasyncclient-4.1.1.jar:4.1.1]
at org.apache.http.impl.nio.client.InternalIODispatch.onClosed(InternalIODispatch.java:39) [httpasyncclient-4.1.1.jar:4.1.1]
at org.apache.http.impl.nio.reactor.AbstractIODispatch.disconnected(AbstractIODispatch.java:102) [httpcore-nio-4.4.4.jar:4.4.4]
at org.apache.http.impl.nio.reactor.BaseIOReactor.sessionClosed(BaseIOReactor.java:281) [httpcore-nio-4.4.4.jar:4.4.4]
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processClosedSessions(AbstractIOReactor.java:442) [httpcore-nio-4.4.4.jar:4.4.4]
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:285) [httpcore-nio-4.4.4.jar:4.4.4]
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:106) [httpcore-nio-4.4.4.jar:4.4.4]
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:590) [httpcore-nio-4.4.4.jar:4.4.4]
at java.lang.Thread.run(Unknown Source) [?:1.8.0_72]
for a number of workers.
Thanks to #lucasvc, the default behaviour is explained here. Pertaining to my solution, the code was updated to the following and the issue did not appear.
IOReactorConfig reactorConfig = IOReactorConfig.custom()
.setConnectTimeout(TIMEOUT_5_MINS_IN_MILLIS)
.setSoTimeout(TIMEOUT_5_MINS_IN_MILLIS).build();
try (CloseableHttpAsyncClient asyncClient = HttpAsyncClients.custom()
.setDefaultIOReactorConfig(reactorConfig)
.setDefaultHeaders(Collections.singletonList(oauthHeader))
.setMaxConnPerRoute(MAX_CONN)
.setMaxConnTotal(MAX_CONN).build();) {
// ...
}