All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.tophe.ion.HttpEngineIon Maven / Gradle / Ivy

package co.tophe.ion;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;

import org.apache.http.protocol.HTTP;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.DataSink;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.TransformFuture;
import com.koushikdutta.async.http.ConnectionClosedException;
import com.koushikdutta.async.http.filter.PrematureDataEndException;
import com.koushikdutta.async.parser.AsyncParser;
import com.koushikdutta.async.parser.ByteBufferListParser;
import com.koushikdutta.async.parser.JSONArrayParser;
import com.koushikdutta.async.parser.JSONObjectParser;
import com.koushikdutta.async.parser.StringParser;
import com.koushikdutta.async.stream.ByteBufferListInputStream;
import com.koushikdutta.ion.*;
import com.koushikdutta.ion.builder.Builders;
import com.koushikdutta.ion.builder.LoadBuilder;
import com.koushikdutta.ion.future.ResponseFuture;

import co.tophe.AbstractHttpEngine;
import co.tophe.HttpConfig;
import co.tophe.HttpException;
import co.tophe.HttpResponse;
import co.tophe.HttpTimeoutException;
import co.tophe.ServerException;
import co.tophe.UploadProgressListener;
import co.tophe.body.HttpBodyJSON;
import co.tophe.body.HttpBodyMultiPart;
import co.tophe.body.HttpBodyParameters;
import co.tophe.body.HttpBodyString;
import co.tophe.body.HttpBodyUrlEncoded;
import co.tophe.ion.internal.HttpResponseIon;
import co.tophe.ion.internal.IonBody;
import co.tophe.ion.internal.IonHttpBodyJSON;
import co.tophe.ion.internal.IonHttpBodyMultiPart;
import co.tophe.ion.internal.IonHttpBodyString;
import co.tophe.ion.internal.IonHttpBodyUrlEncoded;
import co.tophe.log.LogManager;
import co.tophe.parser.ParserException;
import co.tophe.parser.Utils;
import co.tophe.parser.XferTransform;
import co.tophe.parser.XferTransformChain;
import co.tophe.parser.XferTransformInputStreamString;
import co.tophe.parser.XferTransformResponseInputStream;
import co.tophe.parser.XferTransformStringJSONArray;
import co.tophe.parser.XferTransformStringJSONObject;

/**
 * An {@link co.tophe.HttpEngine} that uses Ion to process the data.
 *
 * @param   type of the data read from the HTTP response
 * @param  type of the exception raised when there's a server generated error.
 */
public class HttpEngineIon extends AbstractHttpEngine> {
	public final Builders.Any.B requestBuilder;
	private static final String ENGINE_SIGNATURE = "Ion-"+ com.koushikdutta.ion.BuildConfig.VERSION_CODE+"+AndroidAsync-"+ com.koushikdutta.async.BuildConfig.VERSION_CODE;

	protected HttpEngineIon(Builder builder, Ion ion) {
		super(builder);

		final LoadBuilder ionLoadBuilder = ion.build(ion.getContext());
		this.requestBuilder = ionLoadBuilder.load(request.getHttpMethod(), null==request.getUri() ? null : request.getUri().toString());

		final HttpBodyParameters sourceBody = request.getBodyParameters();
		final IonBody ionBody;
		if (sourceBody instanceof HttpBodyMultiPart)
			ionBody = new IonHttpBodyMultiPart((HttpBodyMultiPart) sourceBody);
		else if (sourceBody instanceof HttpBodyJSON)
			ionBody = new IonHttpBodyJSON((HttpBodyJSON) sourceBody);
		else if (sourceBody instanceof HttpBodyUrlEncoded)
			ionBody = new IonHttpBodyUrlEncoded((HttpBodyUrlEncoded) sourceBody);
		else if (sourceBody instanceof HttpBodyString)
			ionBody = new IonHttpBodyString((HttpBodyString) sourceBody);
		else if (sourceBody != null)
			throw new IllegalStateException("Unknown body type "+sourceBody);
		else
			ionBody = null;

		if (null != ionBody) {
			ionBody.setOutputData(requestBuilder);

			final UploadProgressListener progressListener = request.getProgressListener();
			if (null != progressListener) {
				requestBuilder.progress(new ProgressCallback() {
					@Override
					public void onProgress(long downloaded, long total) {
						progressListener.onParamUploadProgress(request, null, (int) ((100 * downloaded) / total));
					}
				});
			}
		}
	}

	@Override
	protected String getEngineSignature() {
		return ENGINE_SIGNATURE;
	}

	@Override
	protected void setContentLength(long contentLength) {
		if (0L == contentLength)
			super.setContentLength(contentLength);
	}

	@Override
	public void setHeadersAndConfig() {
		if (request.getBodyParameters() instanceof HttpBodyMultiPart) {
			setHeader(HTTP.CONTENT_TYPE, null);
		}

		for (Entry entry : requestHeaders.entrySet()) {
			requestBuilder.setHeader(entry.getKey(), entry.getValue());
		}

		if (null != responseHandler.followsRedirect()) {
			requestBuilder.followRedirect(responseHandler.followsRedirect());
		}

		HttpConfig httpConfig = request.getHttpConfig();
		if (null != httpConfig) {
			int readTimeout = httpConfig.getReadTimeout(request);
			if (readTimeout >= 0)
				requestBuilder.setTimeout(readTimeout);
		}
	}

	@Override
	protected HttpResponseIon queryResponse() throws SE, HttpException {
		XferTransform errorParser = responseHandler.errorParser;
		XferTransform commonTransforms = Utils.getCommonXferTransform(responseHandler.contentParser, errorParser, true);
		AsyncParser parser = getXferTransformParser(commonTransforms);
		ResponseFuture req = requestBuilder.as(parser);
		Future> withResponse = req.withResponse();
		try {
			Response response = withResponse.get();
			HttpResponseIon ionResponse = new HttpResponseIon(response, commonTransforms);
			setRequestResponse(ionResponse);

			Exception e = response.getException();
			if (null != e) {
				throw exceptionToHttpException(e).build();
			}

			if (isHttpError(ionResponse)) {
				Object data = response.getResult();
				XferTransform transformToResult = Utils.skipCommonTransforms(errorParser, commonTransforms);
				SE errorData;
				if (null == transformToResult)
					errorData = (SE) data;
				else
					errorData = (SE) transformToResult.transformData(data, this);
				throw errorData;
			}

			return ionResponse;

		} catch (InterruptedException e) {
			throw exceptionToHttpException(e).build();

		} catch (ExecutionException e) {
			throw exceptionToHttpException(e).build();

		} catch (ParserException e) {
			throw exceptionToHttpException(e).build();

		} catch (IOException e) {
			throw exceptionToHttpException(e).build();

		}
	}

	@Override
	protected T responseToResult(HttpResponseIon response) throws ParserException, IOException {
		Object data = response.getResult();
		XferTransform transformToResult = Utils.skipCommonTransforms(responseHandler.contentParser, response.getCommonTransform());
		if (null == transformToResult)
			return (T) data;

		return (T) transformToResult.transformData(data, this);
	}

	@Override
	protected HttpException.AbstractBuilder exceptionToHttpException(Exception e) throws HttpException {
		if (e instanceof IllegalArgumentException && e.getMessage().contains("bytesConsumed is negative")) {
			// TODO only check for a Play Services error when the Exception stack trace comes from "com.google.android.gms.org.conscrypt"
			Context context = IonHttpEngineFactory.getInstance(null).getDefaultIon().getContext();
			PackageManager pm = context.getPackageManager();
			String playServicesVersion = "";
			try {
				PackageInfo pI = pm.getPackageInfo("com.google.android.gms", 0);
				if (pI != null) {
					playServicesVersion = String.valueOf(pI.versionCode) + '-' + pI.versionName;
				}
			} catch (PackageManager.NameNotFoundException ignored) {
			}
			LogManager.getLogger().e("Issue #99698 detected on PS:"+playServicesVersion, e);
		}

		if (e instanceof ConnectionClosedException && e.getCause() instanceof Exception) {
			return exceptionToHttpException((Exception) e.getCause());
		}

		if (e instanceof PrematureDataEndException) {
			LogManager.getLogger().d("timeout for "+request);
			HttpTimeoutException.Builder builder = new HttpTimeoutException.Builder(request, httpResponse);
			builder.setErrorMessage("Timeout error " + e.getMessage());
			builder.setCause(e);
			return builder;
		}

		return super.exceptionToHttpException(e);
	}

	private static final AsyncParser INPUT_STREAM_ASYNC_PARSER = new AsyncParser() {
		@Override
		public Future parse(DataEmitter emitter) {
			return new ByteBufferListParser().parse(emitter)
					.then(new TransformFuture() {
						@Override
						protected void transform(ByteBufferList result) throws Exception {
							setComplete(new ByteBufferListInputStream(result));
						}
					});
		}

		@Override
		public void write(DataSink sink, InputStream value, CompletedCallback completed) {
			throw new AssertionError("not implemented");
		}
	};
	private static final AsyncParser STRING_ASYNC_PARSER = new StringParser();
	private static final AsyncParser JSON_OBJECT_ASYNC_PARSER = new JSONObjectParser();
	private static final AsyncParser JSON_ARRAY_ASYNC_PARSER = new JSONArrayParser();

	private 

AsyncParser

getXferTransformParser(XferTransform transform) { if (transform == XferTransformResponseInputStream.INSTANCE) { return (AsyncParser

) INPUT_STREAM_ASYNC_PARSER; } if (transform instanceof XferTransformChain) { final XferTransformChain chain = (XferTransformChain) transform; if (chain.transforms.length != 0) { if (chain.transforms[0] == XferTransformResponseInputStream.INSTANCE) { if (chain.transforms.length == 1) { return (AsyncParser

) INPUT_STREAM_ASYNC_PARSER; } if (chain.transforms[1] == XferTransformInputStreamString.INSTANCE) { if (chain.transforms.length == 2) { return (AsyncParser

) STRING_ASYNC_PARSER; } if (chain.transforms[2] == XferTransformStringJSONObject.INSTANCE) { if (chain.transforms.length == 3) { return (AsyncParser

) JSON_OBJECT_ASYNC_PARSER; } } if (chain.transforms[2] == XferTransformStringJSONArray.INSTANCE) { if (chain.transforms.length == 3) { return (AsyncParser

) JSON_ARRAY_ASYNC_PARSER; } } } return new AsyncParser

() { @Override public Future

parse(DataEmitter emitter) { Future inputStreamFuture = INPUT_STREAM_ASYNC_PARSER.parse(emitter); return inputStreamFuture.then(new TransformFuture() { @Override protected void transform(InputStream result) throws Exception { setComplete((P) chain.skipFirstTransform().transformData(result, HttpEngineIon.this)); } }); } @Override public void write(DataSink sink, P value, CompletedCallback completed) { } }; } } } throw new IllegalStateException(); } }