Skip to content

Instantly share code, notes, and snippets.

@dotgc
Last active November 25, 2019 11:22
Show Gist options
  • Select an option

  • Save dotgc/69dae436d9fe36377dd3 to your computer and use it in GitHub Desktop.

Select an option

Save dotgc/69dae436d9fe36377dd3 to your computer and use it in GitHub Desktop.
S3 Async Post uploads for Android/Java using OkHttp which posts the callback to the calling thread if it has a looper. So if you start the async post on the Main Thread, the response callback is also executed on the main thread.
public abstract class BaseUploader {
protected String mBucketUrl;
protected S3Data mData;
protected ResponseCallback mCallback;
public BaseUploader(String bucketUrl, S3Data data, ResponseCallback cb) {
mBucketUrl = bucketUrl;
mData = data;
mCallback = cb;
}
public abstract void upload(final byte[] imageData);
public abstract void upload(final File imageData);
}
public class OkHttpUploader extends BaseUploader {
private static final String TAG = "OkHttpUploader";
public OkHttpUploader(String bucketUrl, S3Data data, ResponseCallback cb) {
super(bucketUrl, data, cb);
}
@Override
public void upload(final byte[] imageData) {
RequestBody requestBody = getS3RequestBody(mData)
.addFormDataPart("file", "upload.jpg", RequestBody.create(MediaType.parse("image/jpeg"), imageData))
.build();
makeMultipartS3Request(requestBody);
}
@Override
public void upload(final File file) {
RequestBody requestBody = getS3RequestBody(mData)
.addFormDataPart("file", "upload.jpg", RequestBody.create(MediaType.parse("image/jpeg"), file))
.build();
makeMultipartS3Request(requestBody);
}
private MultipartBuilder getS3RequestBody(final S3Data data) {
return new MultipartBuilder()
.addFormDataPart("key", data.getKey())
.addFormDataPart("AWSAccessKeyId", data.getAWSAccessKeyId())
.addFormDataPart("acl", data.getAcl())
.addFormDataPart("policy", data.getPolicy())
.addFormDataPart("signature", data.getSignature())
.addFormDataPart("Content-Type", "image/jpeg")
.addFormDataPart("success_action_status", data.getSuccessActionStatus());
}
private void makeMultipartS3Request(final RequestBody requestBody) {
makeMultipartS3RequestHelper(requestBody, 0);
}
private void makeMultipartS3RequestHelper(final RequestBody requestBody, final int currentAttempt) {
final Request request = new Request.Builder()
.url(mBucketUrl)
.post(requestBody)
.build();
Looper threadLooper = Looper.myLooper(); // looper of the calling thread
final Handler threadHandler = threadLooper != null ? new Handler(threadLooper) : null;
OkHttpClient client = new OkHttpClient(); // You'd normally keep just one instance of OkHttpClient.
client.setRetryOnConnectionFailure(true);
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
Log.d(TAG, "Failure doing multipart request" + request.urlString());
if (currentAttempt >= 1) {
parseS3FailureResponse(request, threadHandler);
} else {
Log.d(TAG, "retrying s3 upload " + mBucketUrl);
makeMultipartS3RequestHelper(requestBody, currentAttempt + 1);
}
}
@Override
public void onResponse(Response response) throws IOException {
parseS3Response(response, threadHandler);
}
});
}
private void parseS3FailureResponse(final Request request, Handler threadHandler) {
if (threadHandler != null) {
threadHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onFailure("Network Error: headers: " + request.headers().toString());
}
});
} else {
mCallback.onFailure("Network Error");
}
}
private void parseS3Response(Response response, Handler threadHandler) {
try {
if (response.isSuccessful()) {
onResponseSuccess(response, threadHandler);
} else {
onResponseError(response, threadHandler);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void onResponseSuccess(Response response, Handler threadHandler) throws JSONException {
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
if ("Location".equals(responseHeaders.name(i))) {
String uploadedUrl = responseHeaders.value(i);
try {
uploadedUrl = URLDecoder.decode(uploadedUrl, "utf-8");
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "unsupported encoding exception");
}
Log.d(TAG, "Uploaded: url=" + uploadedUrl);
final JSONObject responseJson = new JSONObject();
responseJson.put("url", uploadedUrl);
if (threadHandler != null) {
threadHandler.post(new Runnable() {
@Override
public void run() {
try {
mCallback.onSuccess(responseJson);
} catch (JSONException e) {
Log.d(TAG, "exce");
}
}
});
} else {
mCallback.onSuccess(responseJson);
}
return;
}
}
}
private void onResponseError(Response response, Handler threadHandler) throws JSONException {
if (threadHandler != null) {
threadHandler.post(new Runnable() {
@Override
public void run() {
mCallback.onError("Upload Failed!");
}
});
} else {
mCallback.onError("Upload Failed!");
}
}
}
/**
Interface to be implemented by the client code
*/
public interface ResponseCallback {
void onSuccess(JSONObject result);
void onError(String msg);
void onFailure(String msg);
}
/**
Add your own constructor to contruct the S3Data object.
*/
public class S3Data {
private String key;
private String AWSAccessKeyId;
private String acl;
private String policy;
private String signature;
private String bucket;
private String bucketUrl;
private String successActionStatus;
public String getAcl() {
return acl;
}
public String getAWSAccessKeyId() {
return AWSAccessKeyId;
}
public String getBucket() {
return bucket;
}
public String getBucketUrl() {
return bucketUrl;
}
public String getKey() {
return key;
}
public String getPolicy() {
return policy;
}
public String getSignature() {
return signature;
}
public String getSuccessActionStatus() {
return successActionStatus;
}
}
Copy link

ghost commented May 10, 2016

Simple solution.

AWSSecretKey is not needed when signing the request? The last time i tried to do something like this was on Ruby-on-rails. I had to sign the request on the server, return the signed url then do the upload on the client. Now looking at this i think is not even necessary but is it safe doing this on the client side?

Also if possible could you please share a sample initialization of the S3Data object.

Thanks in advance.

@dotgc
Copy link
Author

dotgc commented Sep 30, 2016

@dretnan: You hit a server side REST API to get a response that populates the S3Data object. The AWSSecretKey is used on the server side to generate the response. This way, you don't need to store the key on client.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment