package com.openfeint.internal.request; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.concurrent.Future; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.AbstractHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import com.openfeint.api.R; import com.openfeint.internal.OpenFeintInternal; import com.openfeint.internal.resource.ServerException; public abstract class BaseRequest { private static int DEFAULT_RETRIES = 2; private static long DEFAULT_TIMEOUT = 20 * 1000; protected static String TAG = "Request"; protected OrderedArgList mArgs; private HttpUriRequest mRequest; private byte[] mResponseBody; private boolean mResponded = false; private String mResponseEncoding = null; private String mResponseType = null; protected String getResponseEncoding() { return mResponseEncoding; } protected String getResponseType() { return mResponseType; } private int mResponseCode; private static String sBaseServerURL = null; private long mSecondsSinceEpoch; private String mSignature = null; private String mKey = null; private int mRetriesLeft = 0; public int numRetries() { return DEFAULT_RETRIES; } public long timeout() { return DEFAULT_TIMEOUT; } private String mCurrentURL = null; protected String currentURL() { return mCurrentURL != null ? mCurrentURL : url(); } private Future<?> mFuture = null; private HttpParams mHttpParams = null; public void setFuture(Future<?> future) { mFuture = future; } public Future<?> getFuture() { return mFuture; } protected HttpParams getHttpParams() { if (mHttpParams == null) { mHttpParams = new BasicHttpParams(); } return mHttpParams; } // If we know a request will fail if we're not logged in, override // wantsLogin() to return true. This way, if we can log in and we're not // already logged in, we'll issue a login request and queue the given request // after that. public boolean wantsLogin() { return false; } // By default, requests are signed - we turn it off as necessary to save cpu. public boolean signed() { return true; } // Almost all requests need the device session. The exception are requests that aren't signed // (i.e., aren't going to (sBaseServerURL + "/xp/" + x), and of course the device session itself. // This exists primarily to keep requests from issuing in parallel with the session and stomping // on cookies. public boolean needsDeviceSession() { return signed(); } public BaseRequest() { super(); } public BaseRequest(OrderedArgList args) { super(); setArgs(args); } public abstract String method(); public abstract String path(); public String url() { if (sBaseServerURL == null) { sBaseServerURL = OpenFeintInternal.getInstance().getServerUrl(); } return sBaseServerURL + path(); } public final void sign(Signer authority) { if (mArgs==null) mArgs = new OrderedArgList(); if (signed()) { mSecondsSinceEpoch = System.currentTimeMillis()/1000; mSignature = authority.sign(path(), method(), mSecondsSinceEpoch, mArgs); mKey = authority.getKey(); } } public final void setArgs(OrderedArgList args) { mArgs = args; } protected HttpUriRequest generateRequest() { HttpUriRequest retval = null; String meth = method(); if (meth.equals("GET") || meth.equals("DELETE")) { String url = url(); String argString = mArgs.getArgString(); if (argString != null) { url += "?" + argString; } if (meth.equals("GET")) retval = new HttpGet (url); else if (meth.equals("DELETE")) retval = new HttpDelete(url); } else { HttpEntityEnclosingRequestBase postReq = null; if (meth.equals("POST")) postReq = new HttpPost(url()); else if (meth.equals("PUT")) postReq = new HttpPut(url()); else throw new RuntimeException("Unsupported HTTP method: "+ meth); try { String encoding = "UTF-8"; UrlEncodedFormEntity entity = new UrlEncodedFormEntity(mArgs.getArgs(), encoding); entity.setContentType("application/x-www-form-urlencoded; charset=" + encoding); postReq.setEntity(entity); } catch (UnsupportedEncodingException e) { OpenFeintInternal.log(TAG, "Unable to encode request."); e.printStackTrace(System.err); } retval = postReq; } if (signed() && mSignature != null && mKey != null) { retval.addHeader("X-OF-Signature", mSignature); retval.addHeader("X-OF-Key", mKey); } addParams(retval); return retval; } protected boolean shouldRedirect(String url) { return true; } protected final void addParams(HttpUriRequest retval) { if (mHttpParams != null) { retval.setParams(mHttpParams); } } private HttpResponse response_; public final void exec() { mRequest = generateRequest(); mRetriesLeft = numRetries(); mResponseBody = null; while (mResponseBody == null) { try { final AbstractHttpClient client = OpenFeintInternal.getInstance().getClient(); final HttpContext context = new BasicHttpContext(); final ResponseHandler<Object> handler = new ResponseHandler<Object>() { public Object handleResponse(HttpResponse response) throws ClientProtocolException, IOException { final HttpUriRequest currentReq = (HttpUriRequest) context.getAttribute( ExecutionContext.HTTP_REQUEST); final HttpHost currentHost = (HttpHost) context.getAttribute( ExecutionContext.HTTP_TARGET_HOST); mCurrentURL = currentHost.toURI() + currentReq.getURI(); HttpEntity entity = response.getEntity(); // Failure by default mResponseBody = new byte[0]; mResponseCode = response.getStatusLine().getStatusCode(); if (entity != null) { final Header contentEncoding = entity.getContentEncoding(); if (contentEncoding != null) { mResponseEncoding = contentEncoding.getValue(); } final Header contentType = entity.getContentType(); if (contentType != null) { mResponseType = contentType.getValue(); } mResponseBody = EntityUtils.toByteArray(entity); if (entity.getContentLength() >= 0 && entity.getContentLength() != mResponseBody.length) { // Content-Length mismatch with content, consider this a failure OpenFeintInternal.log(TAG, "Content-Length mismatch with content - " + mRequest.getURI().toASCIIString()); mResponseCode = 0; } } response_ = response; return null; } }; client.execute(mRequest, handler, context); mRequest = null; } catch (Exception e) { OpenFeintInternal.log(TAG, "Error executing request '" + path() + "'."); e.printStackTrace(System.err); // Ensure that we don't count this as a success (i.e. we'll retry if allowed). mResponseBody = null; mResponseCode = 0; response_ = null; if (--mRetriesLeft < 0) { // build up a fake exception body. ServerException se = new ServerException(); se.exceptionClass = e.getClass().getName(); se.message = e.getMessage(); if (se.message == null) { se.message = OpenFeintInternal.getRString(R.string.of_unknown_server_error); } String exceptionBody = se.generate(); mResponseBody = exceptionBody.getBytes(); mResponseCode = 0; break; } } } onResponseOffMainThread(mResponseCode, mResponseBody); } /* * this only valid during life time of onResponse */ public HttpResponse getResponse() { return response_;} // By default do nothing, but allow derived classes to do stuff off main thread protected void onResponseOffMainThread(int responseCode, byte[] body) {} public abstract void onResponse(int responseCode, byte[] body); public final void onResponse() { if (!mResponded) { mResponded = true; onResponse(mResponseCode, mResponseBody); response_ = null; } } public void launch() { OpenFeintInternal.makeRequest(this); } public void postTimeoutCleanup() { final HttpUriRequest req = mRequest; mRequest = null; if (null != req) { try { req.abort(); } catch (UnsupportedOperationException e) { // well, we tried } } ServerException se = new ServerException(); se.exceptionClass = "Timeout"; se.message = OpenFeintInternal.getRString(R.string.of_timeout); mResponseBody = se.generate().getBytes(); mResponseCode = 0; } }