Last active
February 15, 2016 11:35
-
-
Save Nilzor/234c2374f6f0d93bcdf3 to your computer and use it in GitHub Desktop.
A Retrofit 1.x HTTP Client using Ion and its AndroidAsync library.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This class is an adapter for Retrofit allowing you to use ion/andorid-async with Retrofix 1.x. | |
* Since retrofit is inherently synchronous under the hood, adding this adapter unfortunately does not | |
* yield a smaller thread footprint. -Notice the .get() blocking call in the code below. | |
* So why would you use this? My use case is that we rely on third party libraries that requre Retrofit. | |
* To get all our HTTP comms to go through the same logic we use this adapter. | |
* That gives us zero dependencies to apache-http-client or okhttp. | |
* | |
* ion home: https://github.com/koush/ion | |
*/ | |
import android.content.Context; | |
import com.koushikdutta.async.http.Headers; | |
import com.koushikdutta.ion.HeadersResponse; | |
import com.koushikdutta.ion.Ion; | |
import com.koushikdutta.ion.builder.Builders; | |
import com.koushikdutta.ion.future.ResponseFuture; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.PipedInputStream; | |
import java.io.PipedOutputStream; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.concurrent.ExecutionException; | |
import retrofit.client.Client; | |
import retrofit.client.Header; | |
import retrofit.client.Request; | |
import retrofit.client.Response; | |
import retrofit.mime.TypedInput; | |
import retrofit.mime.TypedOutput; | |
/** | |
* A Retrofit HTTP Client using Ion and its AndroidAsync library. | |
*/ | |
public class RetrofitIonClient implements Client { | |
private static final String HEADER_CONTENT_LENGTH = "Content-Length"; | |
private static final String HEADER_CONTENT_TYPE = "Content-Type"; | |
private Ion mIon; | |
private Context mContext; | |
public RetrofitIonClient(Context context) { | |
this(context, null); | |
} | |
public RetrofitIonClient(Context context, Ion ionInstance) { | |
if (context != context.getApplicationContext()) { | |
throw new IllegalArgumentException("Context must be appContext to avoid activitiy leaks"); | |
} | |
mContext = context; | |
mIon = ionInstance; | |
if (mIon == null) { | |
mIon = Ion.getDefault(context); | |
} | |
} | |
@Override | |
public Response execute(Request request) throws IOException { | |
try { | |
Map<String, List<String>> headers = convertToIonHeaders(request.getHeaders()); | |
String url = request.getUrl(); | |
Builders.Any.B builder = mIon.build(mContext) | |
.load(request.getMethod(), url) | |
.addHeaders(headers); | |
ResponseFuture<InputStream> future = setPostData(builder, request.getBody()).asInputStream(); | |
// Execute | |
com.koushikdutta.ion.Response<InputStream> ionResponse = future.withResponse().get(); | |
// Parse response | |
HeadersResponse ionHeadersObj = ionResponse.getHeaders(); | |
TypedInput retroBody = createTypedInput(ionResponse); | |
ArrayList<Header> retroHeaders = convertToRetrofitHeaders(ionHeadersObj.getHeaders()); | |
Response retroResponse = new Response(url, ionHeadersObj.code(), ionHeadersObj.message(), retroHeaders, retroBody); | |
return retroResponse; | |
} catch (InterruptedException e) { | |
throw new RuntimeException("Interrupted", e); | |
} catch (ExecutionException e) { | |
throw new RuntimeException("Execution failed", e); | |
} | |
} | |
private TypedInput createTypedInput(com.koushikdutta.ion.Response<InputStream> ionResponse) { | |
Headers headers = ionResponse.getHeaders().getHeaders(); | |
return new TypedInput() { | |
@Override | |
public String mimeType() { | |
return headers.get(HEADER_CONTENT_TYPE); | |
} | |
@Override | |
public long length() { | |
String length = headers.get(HEADER_CONTENT_LENGTH); | |
try { | |
return Long.parseLong(length); | |
} catch (Exception ignored) { | |
} | |
return 0; | |
} | |
@Override | |
public InputStream in() throws IOException { | |
return ionResponse.getResult(); | |
} | |
}; | |
} | |
private Builders.Any.F setPostData(Builders.Any.B builder, TypedOutput typedOutput) throws IOException { | |
Builders.Any.F retVal = builder; | |
if (typedOutput != null) { | |
// This method of passing on the POST body will result in a buffer at the full size of the post body being allocated | |
// Consider implementing with use of threading instead, where typedOuptut.write is done on a separate thread. | |
// What gives the best user experience? | |
PipedOutputStream outStream = new PipedOutputStream(); | |
PipedInputStream inStream = new PipedInputStream(outStream, (int) typedOutput.length()); | |
retVal = builder.setStreamBody(inStream, (int) typedOutput.length()); | |
builder.setHeader(HEADER_CONTENT_TYPE, typedOutput.mimeType()); | |
typedOutput.writeTo(outStream); | |
} | |
return retVal; | |
} | |
private HashMap<String, List<String>> convertToIonHeaders(List<Header> headers) { | |
HashMap<String, List<String>> outHeaders = new HashMap<>(); | |
for (Header header: headers) { | |
ArrayList<String> outValues = new ArrayList<>(1); | |
outValues.add(header.getValue()); | |
outHeaders.put(header.getName(), outValues); | |
} | |
return outHeaders; | |
} | |
private ArrayList<Header> convertToRetrofitHeaders(Headers headers) { | |
ArrayList<Header> retroHeaders = new ArrayList<>(); | |
Set<String> keys = headers.getMultiMap().keySet(); | |
// This method does not support the use case where one header is sent multiple times | |
// because Retrofit1 doesn't support it | |
for (String headerKey : keys) { | |
String value = headers.get(headerKey); | |
retroHeaders.add(new Header(headerKey, value)); | |
} | |
return retroHeaders; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment