Skip to content

Commit

Permalink
Merge pull request square#35 from square/rdickerson/profiler-data
Browse files Browse the repository at this point in the history
Adding a beforeCall() to HttpProfiler.
  • Loading branch information
pforhan committed Jun 21, 2012
2 parents 2e0be86 + 039eaa6 commit 9f6130d
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 32 deletions.
76 changes: 63 additions & 13 deletions http/src/main/java/retrofit/http/HttpProfiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,27 @@
*
* @author Eric Burke ([email protected])
*/
public interface HttpProfiler {
public interface HttpProfiler<T> {

/**
* Invoked before an HTTP method call. The object returned by this method will be
* passed to {@link #afterCall} when the call returns.
*
* This method gives implementors the opportunity to include information that may
* change during the server call in {@code afterCall} logic.
*/
T beforeCall();

/**
* Invoked after an HTTP method completes. This is called from the
* RestAdapter's background thread.
*
* @param requestInfo information about the originating HTTP request.
* @param elapsedTime time in milliseconds it took the HTTP request to complete.
* @param statusCode response status code.
* @param beforeCallData the data returned by the corresponding {@link #beforeCall()}.
*/
void afterCall(RequestInformation requestInfo, long elapsedTime, int statusCode, T beforeCallData);

/** The HTTP method. */
public enum Method {
Expand All @@ -17,16 +37,46 @@ public enum Method {
PUT
}

/**
* Invoked after an HTTP method completes. This is called from the
* RestAdapter's background thread.
*
* @param method the HTTP method (POST, GET, etc).
* @param baseUrl the URL that was called.
* @param relativePath the path part of the URL.
* @param elapsedTime time in milliseconds.
* @param statusCode response status code.
*/
void called(Method method, String baseUrl, String relativePath,
long elapsedTime, int statusCode);
/** Information about the HTTP request. */
public static final class RequestInformation {
private final Method method;
private final String baseUrl;
private final String relativePath;
private final long contentLength;
private final String contentType;

public RequestInformation(Method method, String baseUrl, String relativePath, long contentLength,
String contentType) {
this.method = method;
this.baseUrl = baseUrl;
this.relativePath = relativePath;
this.contentLength = contentLength;
this.contentType = contentType;
}

/** Returns the HTTP method of the originating request. */
public Method getMethod() {
return method;
}

/** Returns the URL to which the originating request was sent. */
public String getBaseUrl() {
return baseUrl;
}

/** Returns the path relative to the base URL to which the originating request was sent. */
public String getRelativePath() {
return relativePath;
}

/** Returns the number of bytes in the originating request. */
public long getContentLength() {
return contentLength;
}

/** Returns the content type header value of the originating request. */
public String getContentType() {
return contentType;
}
}
}
69 changes: 50 additions & 19 deletions http/src/main/java/retrofit/http/RestAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import retrofit.core.Callback;
import retrofit.core.MainThread;
Expand All @@ -23,6 +25,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -128,7 +131,7 @@ private class RestHandler implements InvocationHandler {

// Optionally wrap the response handler for server call profiling.
final ResponseHandler<Void> rh = (profiler == null) ? gsonResponseHandler
: createProfiler(gsonResponseHandler, profiler, method, server.apiUrl(), start);
: createProfiler(gsonResponseHandler, (HttpProfiler<?>) profiler, getRequestInfo(method, request), start);

// Execute HTTP request in the background.
final String finalUrl = url;
Expand All @@ -147,6 +150,24 @@ private class RestHandler implements InvocationHandler {
return null;
}

private HttpProfiler.RequestInformation getRequestInfo(Method method, HttpUriRequest request) {
RequestLine requestLine = RequestLine.fromMethod(method);
HttpMethodType httpMethod = requestLine.getHttpMethod();
HttpProfiler.Method profilerMethod = httpMethod.profilerMethod();

long contentLength = 0;
String contentType = null;
if (request instanceof HttpEntityEnclosingRequestBase) {
HttpEntityEnclosingRequestBase entityReq = (HttpEntityEnclosingRequestBase) request;
HttpEntity entity = entityReq.getEntity();
contentLength = entity.getContentLength();
contentType = entity.getContentType().getValue();
}

return new HttpProfiler.RequestInformation(profilerMethod, server.apiUrl(), requestLine.getRelativePath(),
contentLength, contentType);
}

private void backgroundInvoke(HttpUriRequest request, ResponseHandler<Void> rh,
UiCallback<?> callback, String url, String startTime) {
try {
Expand All @@ -161,15 +182,15 @@ private void backgroundInvoke(HttpUriRequest request, ResponseHandler<Void> rh,
}

/** Wraps a {@code GsonResponseHandler} with a {@code ProfilingResponseHandler}. */
private ProfilingResponseHandler createProfiler(ResponseHandler<Void> handlerToWrap,
HttpProfiler profiler, Method method, String apiUrl, Date start) {
RequestLine requestLine = RequestLine.fromMethod(method);
private <T> ProfilingResponseHandler<T> createProfiler(ResponseHandler<Void> handlerToWrap,
HttpProfiler<T> profiler, HttpProfiler.RequestInformation requestInfo, Date start) {

HttpMethodType httpMethod = requestLine.getHttpMethod();
HttpProfiler.Method profilerMethod = httpMethod.profilerMethod();

return new ProfilingResponseHandler(handlerToWrap, profiler, profilerMethod, apiUrl,
requestLine.getRelativePath(), start.getTime());
ProfilingResponseHandler<T> responseHandler = new ProfilingResponseHandler<T>(handlerToWrap, profiler,
requestInfo, start.getTime());
responseHandler.beforeCall();

return responseHandler;
}

private static final String NOT_CALLBACK =
Expand Down Expand Up @@ -212,30 +233,40 @@ private IllegalArgumentException notCallback(Method method) {
}

/** Sends server call times and response status codes to {@link HttpProfiler}. */
private static class ProfilingResponseHandler implements ResponseHandler<Void> {
private static class ProfilingResponseHandler<T> implements ResponseHandler<Void> {
private final ResponseHandler<Void> delegate;
private final HttpProfiler profiler;
private final String apiUrl;
private final String relativePath;
private final HttpProfiler.Method method;
private final HttpProfiler<T> profiler;
private final HttpProfiler.RequestInformation requestInfo;
private final long startTime;
private final AtomicReference<T> beforeCallData = new AtomicReference<T>();

/** Wraps the delegate response handler. */
private ProfilingResponseHandler(ResponseHandler<Void> delegate, HttpProfiler profiler,
HttpProfiler.Method method, String apiUrl, String relativePath, long startTime) {
private ProfilingResponseHandler(ResponseHandler<Void> delegate, HttpProfiler<T> profiler,
HttpProfiler.RequestInformation requestInfo, long startTime) {
this.delegate = delegate;
this.profiler = profiler;
this.method = method;
this.apiUrl = apiUrl;
this.relativePath = relativePath;
this.requestInfo = requestInfo;
this.startTime = startTime;
}

public void beforeCall() {
try {
beforeCallData.set(profiler.beforeCall());
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error occurred in HTTP profiler beforeCall().", e);
}
}

@Override public Void handleResponse(HttpResponse httpResponse) throws IOException {
// Intercept the response and send data to profiler.
long elapsedTime = System.currentTimeMillis() - startTime;
int statusCode = httpResponse.getStatusLine().getStatusCode();
profiler.called(method, apiUrl, relativePath, elapsedTime, statusCode);

try {
profiler.afterCall(requestInfo, elapsedTime, statusCode, beforeCallData.get());
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error occurred in HTTP profiler afterCall().", e);
}

// Pass along the response to the normal handler.
return delegate.handleResponse(httpResponse);
Expand Down

0 comments on commit 9f6130d

Please sign in to comment.