How to abort large file upload with OkHttp? - java

I am using OkHttp 3.1.2.
I've created file upload similar to the original recipe which is found here: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/PostMultipart.java
I can't find example how to abort an upload of large file upon user request. I mean not how to get the user request but how to tell the OkHttp to stop sending data.
So far the only solution that I can imagine is to use custom RequestBody, add an abort() method and override the writeTo() method like this:
public void abort() {
aborted = true;
}
#Override
public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(mFile);
long transferred = 0;
long read;
while (!aborted && (read = source.read(sink.buffer(), SEGMENT_SIZE)) != -1) {
transferred += read;
sink.flush();
mListener.transferredSoFar(transferred);
}
} finally {
Util.closeQuietly(source);
}
}
Is there any other way?

It turns out it is quite easy:
Just hold reference to the Call object and cancel it when needed like this:
private Call mCall;
private void executeRequest (Request request) {
mCall = mOkHttpClient.newCall(request);
try {
Response response = mCall.execute();
...
} catch (IOException e) {
if (!mCall.isCanceled()) {
mLogger.error("Error uploading file: {}", e);
uploadFailed(); // notify whoever is needed
}
}
}
public void abortUpload() {
if (mCall != null) {
mCall.cancel();
}
}
Please note that when you cancel the Call while uploading an IOException will be thrown so you have to check in the catch if it is cancelled (as shown above) otherwise you will have false positive for error.
I think the same approach can be used for aborting download of large files.

Related

Downloading file using Spring Webclient and AsynchronousFileChannel, file is empty

I want to download a few photos by URL using Webflux and AsynchronousFileChannel, and all files are created but empty.
Here is my code:
public void downloadFilesFromUrl() throws IOException {
List<Photo> notDownloadedFiles = //get photos with name and URL;
for (Photo photo : notDownloadedFiles) {
Path path = Paths.get(pathToFiles + File.separator + photo.getPhotoName());
WebClient client = WebClient.builder().baseUrl(photo.getLoadSource()).build();
Flux<DataBuffer> dataBufferFlux = client
.get().accept(MediaType.APPLICATION_OCTET_STREAM)
.retrieve()
.bodyToFlux(DataBuffer.class);
saveFileOnComputer(path, dataBufferFlux);
}
}
private void saveFileOnComputer(Path path, Flux<DataBuffer> dataBufferFlux) throws IOException {
AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(path, CREATE, WRITE);
DataBufferUtils.write(dataBufferFlux, asynchronousFileChannel)
.doOnNext(DataBufferUtils.releaseConsumer())
.doAfterTerminate(() -> {
try {
asynchronousFileChannel.close();
} catch (IOException ignored) { }
}).then();
}
If I try to use
DataBufferUtils.write(dataBufferFlux, path, StandardOpenOption.CREATE).block();
instead of calling saveFileOnServer(..) method, everything is fine. But I want to use exactly AsynchronousFileChannel.
Okay, I think I fixed it.
private void saveFileOnServer(Path path, Flux<DataBuffer> dataBufferFlux) throws IOException {
AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(path, CREATE, WRITE);
DataBufferUtils.write(dataBufferFlux, asynchronousFileChannel).subscribe();
}
The official documentation says "Note that the writing process does not start until the returned Flux is subscribed to".

CompletionException: NPE trying to read inputStream async

I am trying to read, in an async way, a file that i receive from the client. The idea is to recipt the file, validate it and if the validations are ok, send a response to the client saying that all it's ok, and process the file in background, so the user doesn't need to wait until the file is procesed.
For that I receive the file in my resource like an inputStrem:
#Override
#POST
#Path("/bulk")
#Consumes("text/csv")
#Produces(MediaType.APPLICATION_JSON)
public Response importEmployees(InputStream inputStream) {
if(fileIsNotValid(inputStream)){
throw exceptionFactoryBean.createBadRequestException("there was an error with the file");
}
try {
CompletableFuture.runAsync(() -> {
employeeService.importEmployees(inputStream);
}).exceptionally(e -> {
LOG.error(format(ERROR_IMPORTING_FILE, e.getMessage()));
return null;
});
} catch (RuntimeException e) {
LOG.error(format(ERROR_SENDING_EMAIL, e.getMessage()));
throw exceptionFactoryBean.createServiceException("payment-method.export.installment-schema.error");
}
return Response.ok().build();
}
For the async part I used the runAsync() method of CompletableFuture.
However, inside of my employeeService.importEmployees() method I tried to read the inputStream and I am getting a java.util.concurrent.CompletionException: java.lang.NullPointerException
public List<ImportResult> importEmployees(final InputStream inputStream) {
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
baos.write(buffer, NumberUtils.INTEGER_ZERO, len);
}
The inputStream is not null. And debuging in a low level, I can see that the wrapper of the class Http11InputBuffer is null when i try to read the inputStream.
Do you can see what errors i have or how i can set the wrapper attribute of the Http11InputBuffer previous to read the inputStream
You are not waiting for the result. So it will return the Responsebefore importEmployees is executed. You need to wait with a join/get before returning the response:
public Response importEmployees(InputStream inputStream) {
...
CompletableFuture.runAsync(() -> { ... }).get();
...
return Response.ok().build();
}
There is probably no point in making this code reactive, though.

Verify if the deleteObject has actually deleted the object in AWS S3 Java sdk

I have the following method, which deletes a file from AWS S3 Bucket, however,
there is no exception thrown if the file doesn't exist
there is no success code or flag to see if the file has been deleted successfully
is there any workaround to deal with this situation.
#Override
public void deleteFile(String fileName) {
try {
this.client.deleteObject(builder ->
builder
.bucket(this.bucketName).key(fileName)
.build());
} catch (S3Exception ex) {
ex.printStackTrace();
}
}
If your request succeeded then your object is deleted. Note, that due to eventual consistency, the object is not guaranteed to disappear immediately. You need to check on the HTTP status code.
AmazonS3 as3 = new AmazonS3();
Status myStatus = as3.DeleteObject(<fill in paramters here>);
if (myStatus.Code >= 200 && myStatus.Code < 300)
{
// Success
}
else
{
// Delete Failed
// Handle specific Error Codes below
if (myStatus.Description == "AllAccessDisabled")
{
// Do something
}
if (myStatus.Description == "NoSuchKey")
{
// Do something
}
}
Also, there is an api available to check if the Object exists in S3
doesObjectExist
https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html#doesObjectExist-java.lang.String-java.lang.String-

Retrofit2 - Unable to create string from non-null response body

I'm attempting to make an API call then use it's response as a string. Thus far I am able to successfully get and log the response... however the string I'm attempting to create using the response is empty and I'm unsure why this might be happening.
Any suggestions are appreciated:
api.getUser().enqueue(new API.SimpleCallback<ResponseBody>() {
#Override
public void onResponse(ResponseBody data) {
try {
Log.d("RAW BODY", data.string());
final SharedPreferences.Editor editor = App.sharedPrefs.edit();
String responseString = data.string().toString();
editor.putString(NOTIFICATION_PREFERENCES_ENABLED_STATUS,responseString);
editor.apply();
} catch (IOException e) {
e.printStackTrace();
}
}
});
The response body can be consumed only once. https://square.github.io/okhttp/3.x/okhttp/okhttp3/ResponseBody.html
In your code, you are attempting to consume the body multiple times. (once when you log it, and another time when you try to put it in the shared preferences).
Instead do something like:
#Override
public void onResponse(ResponseBody responseBody) {
String data = responseBody.string();
Log.d("RAW_DATA", data);
sharedPreferences.edit().putString(myKey, data);
}

Execute multiple threads each one for a certain amount of time in java

I'm sending files to my local server that creates a file back. My problem occurs when the user perform multiple actions one after another and I need to show an error message if one of the requests don't get a feedback file in 5 min.
How can I handle all these requests? I used newSingleThreadScheduledExecutor to check if the feedback file is there every minute but I don't know how to handle multiple ones and keep the countdown to each request for the 5 min case.
My try:
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(listPrinter.size()));
for(int i=0;i<list.size();i++){
try {
final File retrievedFile = new File("/home/"+list.get(i)+".csv");
ListenableFuture<File> future = executor.submit(new Callable<File>() {
public File call() {
// Actually send the file to your local server
// and retrieve a file back
if(retrievedFile.exists())
{
new Notification("file exits").show(Page.getCurrent());
}
else{
new Notification("file no exits").show(Page.getCurrent());
}
return retrievedFile;
}
});
future.get(5, TimeUnit.MINUTES);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} catch (ExecutionException ex) {
Exceptions.printStackTrace(ex);
} catch (TimeoutException ex) {
Exceptions.printStackTrace(ex);
new Notification("Time out").show(Page.getCurrent());
}
}
But it just get executed at the beginning and that's it but when the file is added nothing happens.
Is it possible to do this with watchService? It works pretty well for me but I didn't know about the 5 min case
Take a look to the Future interface:
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html
should fit perfectly to your problem.
When you run a thread, the result could be a Future, it is the result of a asyncronous task, and you can have one Future per asyncronous task that you are launching.
Future<File> sendReceiveFile(File inputFile) {
final Future<File> future = new YourFuture<File>(...);
new Thread() {
#Override
public void run() {
File outputFile = null;
try {
outputFile = SendFileToServer(inputFile);
} catch (final Exception e) {
// do something
} finally {
future.setValue(fileOutput);
}
}
}.start();
return future;
}
And in your main:
Future<File> future = sendReceiveFile(myFile);
File outputFile = null;
try {
outputFile = future.get(1, TimeUnit.MINUTE);
} catch(TimeOutException e) {
// do something
}
You could do this manually, but using Guava ListenableFuture would be much better:
// Here we create a fixed thread pool with 10 threads and an inifinite-capacity queue
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
final File fileToSend = ...; //
ListenableFuture<File> future = executor.submit(new Callable<File>() {
public File call() {
// Actually send the file to your local server
// and retrieve a file back
File retrievedFile = YourLocalServer.sendAndRetrieve(fileToSend);
return retrievedFile;
}
});
Futures.addCallback(future, new FutureCallback<File>() {
public void onSuccess(File retrievedFile) {
// Handle the successfully retrieved file when it returns
}
public void onFailure(Throwable thrown) {
// Handle the error
}
});
By sending the file asynchronously, you can send and retrieve many files at any given time. Then, when the server responds (either with a retrieved file or with an error), you can handle the response (retrieved file or exception) just when it comes back, without needing to wait for it. This means that the onSuccess() or onFailure() methods will be automatically executed when there's a response available from your local server.
I solved the problem by using a Timer that is executed every 5 minutes getting all the db transactions that happened for the last 5 minutes and didn't get any response and show my error code. It works pretty good. Thanks everyone for the help

Categories