提交 3f1c5ab4 编写于 作者: J Jake Wharton

Add synchronous method invocation.

上级 2efc1c33
......@@ -19,7 +19,7 @@ instance of that API handler, which you can then store and use throughout your a
example interface:
```java
public interface DummyService {
public interface DummyServiceAsync {
// Produces a url like "foo/bar?id=idValue".
@GET("foo/bar")
void normalGet(@Named("id") String id, Callback<SimpleResponse> callback);
......@@ -38,13 +38,21 @@ public interface DummyService {
}
```
Note that each method _must_ have a `Callback` object at the end of the parameter list. This is how
your application will handle the results of your network calls: errors and successful responses are
both handled by the `Callback` interface.
Each method has a `Callback` type specified at the end of the parameter list. This is how your
application will handle the results of your network calls asynchronously: errors and successful
responses are both handled by the `Callback` interface.
If you want to use the `@SingleEntity` method of specifying request body (see `singleEntityPost` above),
your `MyJsonObject` will need to implement `TypedBytes`. For convenience, you can extend
`GsonRequestEntity` if you're just trying to send a JSON string in the request body.
your `MyJsonObject` will need to implement `TypedBytes`.
For synchronous execution, omit the `Callback` parameter and specify the response as the return type.
```java
public interface DummyServiceSync {
@GET("foo/bar")
SimpleResponse normalGet(@Named("id") String id);
}
```
Also worth noting: for POST/PUT requests using default form encoding for the request entity (see
normalPost), any path parameters are also included in the request body. This is different from the
......
......@@ -58,11 +58,22 @@ public interface Callback<T> {
/** JSON object for parsing server error responses. */
static class ServerError {
static final class ServerError {
public final String message;
public ServerError(String message) {
this.message = message;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerError that = (ServerError) o;
return message == null ? that.message == null : message.equals(that.message);
}
@Override public int hashCode() {
return message != null ? message.hashCode() : 0;
}
}
}
// Copyright 2012 Square, Inc.
package retrofit.http;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.ResponseHandler;
import retrofit.http.Callback.ServerError;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Support for response handlers that invoke {@link Callback}.
*
* @author Bob Lee (bob@squareup.com)
* @author Jake Wharton (jw@squareup.com)
*/
class CallbackResponseHandler<R> implements ResponseHandler<Void> {
private static final Logger LOGGER = Logger.getLogger(CallbackResponseHandler.class.getName());
private final Callback<R> callback;
private final Type callbackType;
private final Converter converter;
private final String requestUrl; // Can be null.
private final Date start;
private final ThreadLocal<DateFormat> dateFormat;
protected CallbackResponseHandler(Callback<R> callback, Type callbackType, Converter converter, String requestUrl,
Date start, ThreadLocal<DateFormat> dateFormat) {
this.callback = callback;
this.callbackType = callbackType;
this.converter = converter;
this.requestUrl = requestUrl;
this.start = start;
this.dateFormat = dateFormat;
}
/**
* Parses the HTTP entity and creates an object that will be passed to
* {@link Callback#call(R)}. Invoked in background thread.
*
* @param entity HTTP entity to read and parse, not null
* @param type destination object type which is guaranteed to match <T>
* @return parsed response
* @throws ConversionException if the server returns an unexpected response
*/
private Object parse(HttpEntity entity, Type type) throws ConversionException {
if (LOGGER.isLoggable(Level.FINE)) {
try {
entity = HttpClients.copyAndLog(entity, requestUrl, start, dateFormat.get());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return converter.to(entity, type);
}
@SuppressWarnings("unchecked") // Type is extracted from generic properties so cast is safe.
public Void handleResponse(HttpResponse response) throws IOException {
// Note: An IOException thrown from here (while downloading the HTTP
// entity, for example) will propagate to the caller and be reported as a
// network error.
//
// Callback methods actually execute in the main thread, so we don't
// have to worry about unhandled exceptions thrown by them.
HttpEntity entity = response.getEntity();
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
LOGGER.fine("Session expired. Request url " + requestUrl);
ServerError error = null;
try {
error = (ServerError) parse(entity, ServerError.class);
LOGGER.fine("Server returned " + HttpStatus.SC_UNAUTHORIZED + ", " + statusLine.getReasonPhrase() + ". Body: "
+ error + ". Request url " + requestUrl);
} catch (ConversionException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
callback.sessionExpired(error);
return null;
}
// 2XX == successful request
if (statusCode >= 200 && statusCode < 300) {
if (entity == null) {
LOGGER.fine("Missing entity for " + statusCode + " response. Url " + requestUrl);
callback.serverError(null, statusCode);
return null;
}
try {
R result = (R) parse(entity, callbackType);
callback.call(result);
} catch (ConversionException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
callback.serverError(null, statusCode);
}
return null;
}
// 5XX == server error
if (statusCode >= 500) {
ServerError error = null;
try {
error = (ServerError) parse(entity, ServerError.class);
LOGGER.fine("Server returned " + statusCode + ", " + statusLine.getReasonPhrase() + ". Body: " + error
+ ". Request url " + requestUrl);
} catch (ConversionException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
callback.serverError(error, statusCode);
return null;
}
// 4XX error
if (entity != null) {
R error = null;
try {
error = (R) parse(entity, callbackType);
LOGGER.fine("Server returned " + statusCode + ", " + statusLine.getReasonPhrase() + ". Body: " + error
+ ". Request url " + requestUrl);
} catch (ConversionException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
callback.clientError(error, statusCode);
return null;
}
LOGGER.fine("Server returned " + statusCode + ", " + statusLine.getReasonPhrase() + ". Request url " + requestUrl);
callback.clientError(null, statusCode);
return null;
}
}
// Copyright 2012 Square, Inc.
package retrofit.http;
import retrofit.http.Callback.ServerError;
import retrofit.http.RestException.ClientHttpException;
import retrofit.http.RestException.NetworkException;
import retrofit.http.RestException.ServerHttpException;
import retrofit.http.RestException.UnauthorizedHttpException;
import retrofit.http.RestException.UnexpectedException;
/**
* A {@link Runnable} executed on a background thread to invoke {@link #obtainResponse()} which performs an HTTP
* request. The response of the request, whether it be an object or exception, is then marshaled to the supplied
* {@link MainThread} in the form of a method call on a {@link Callback}.
*/
abstract class CallbackRunnable<T> implements Runnable {
private final Callback<T> callback;
private final MainThread mainThread;
CallbackRunnable(Callback<T> callback, MainThread mainThread) {
this.callback = callback;
this.mainThread = mainThread;
}
@SuppressWarnings("unchecked")
@Override public final void run() {
try {
final Object response = obtainResponse();
mainThread.execute(new Runnable() {
@Override public void run() {
callback.call((T) response);
}
});
} catch (final ClientHttpException ce) {
mainThread.execute(new Runnable() {
@Override public void run() {
callback.clientError((T) ce.getResponse(), ce.getStatus());
}
});
} catch (final ServerHttpException se) {
mainThread.execute(new Runnable() {
@Override public void run() {
callback.serverError((ServerError) se.getResponse(), se.getStatus());
}
});
} catch (final UnauthorizedHttpException ue) {
mainThread.execute(new Runnable() {
@Override public void run() {
callback.sessionExpired((ServerError) ue.getResponse());
}
});
} catch (final NetworkException ne) {
mainThread.execute(new Runnable() {
@Override public void run() {
callback.networkError();
}
});
} catch (final UnexpectedException ue) {
mainThread.execute(new Runnable() {
@Override public void run() {
callback.unexpectedError(ue.getCause());
}
});
} catch (final Throwable t) {
mainThread.execute(new Runnable() {
@Override public void run() {
callback.unexpectedError(t);
}
});
}
}
public abstract Object obtainResponse();
}
// Copyright 2012 Square, Inc.
package retrofit.http;
import org.apache.http.HttpEntity;
import retrofit.io.TypedBytes;
import java.lang.reflect.Type;
......@@ -15,13 +14,14 @@ public interface Converter {
/**
* Convert an HTTP response body to a concrete object of the specified type.
*
* @param entity HTTP response body.
* @param body HTTP response body.
* @param type Target object type.
* @return Instance of {@code type} which will be cast by the caller.
* @throws ConversionException If conversion was unable to complete. This will trigger a call to
* {@link Callback#serverError(retrofit.http.Callback.ServerError, int)}.
* {@link Callback#serverError(retrofit.http.Callback.ServerError, int)} or throw a
* {@link retrofit.http.RestException.ServerHttpException}.
*/
Object to(HttpEntity entity, Type type) throws ConversionException;
Object to(byte[] body, Type type) throws ConversionException;
/**
* Convert and object to appropriate representation for HTTP transport.
......
......@@ -3,10 +3,10 @@ package retrofit.http;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import org.apache.http.HttpEntity;
import retrofit.io.MimeType;
import retrofit.io.TypedBytes;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
......@@ -24,10 +24,10 @@ public class GsonConverter implements Converter {
this.gson = gson;
}
@Override public Object to(HttpEntity entity, Type type) throws ConversionException {
@Override public Object to(byte[] body, Type type) throws ConversionException {
try {
// TODO use actual encoding
InputStreamReader isr = new InputStreamReader(entity.getContent(), "UTF-8");
InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(body), "UTF-8");
return gson.fromJson(isr, type);
} catch (IOException e) {
throw new ConversionException(e);
......
// Copyright 2010 Square, Inc.
package retrofit.http;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.entity.ByteArrayEntity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility methods for dealing with HttpClient.
*
* @author Bob Lee (bob@squareup.com)
*/
public class HttpClients {
private static final Logger LOGGER =
Logger.getLogger(HttpClients.class.getName());
/**
* Converts an HttpEntity to a byte[].
*
* @throws NullPointerException if the entity is null
*/
public static byte[] entityToBytes(HttpEntity entity) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
entity.writeTo(bout);
return bout.toByteArray();
}
/**
* Converts an HTTP response to an IOException.
*/
public static IOException responseToException(HttpResponse response) {
StatusLine statusLine = response.getStatusLine();
String body = null;
try {
// TODO: Ensure entity is text-based and specify encoding.
HttpEntity entity = response.getEntity();
if (entity != null) body = new String(entityToBytes(entity));
} catch (Throwable t) {
// The original error takes precedence.
LOGGER.log(Level.WARNING, "Response entity to String conversion.", t);
}
return new IOException("Unexpected response."
+ " Code: " + statusLine.getStatusCode()
+ ", Reason: " + statusLine.getReasonPhrase()
+ ", Body: " + body);
}
/**
* Copies a response (so we can read it a second time) and logs it.
*/
public static HttpEntity copyAndLog(HttpEntity entity, String url, Date start, DateFormat dateFormat)
throws IOException {
byte[] bytes = entityToBytes(entity);
// TODO: Use correct encoding.
if (LOGGER.isLoggable(Level.FINE)) {
final int chunkSize = 4000;
long msElapsed = System.currentTimeMillis() - start.getTime();
final String startTime = dateFormat.format(start);
LOGGER.fine("----Response from " + url + " at " + startTime + " (" + msElapsed + "ms):");
for (int i = 0; i < bytes.length; i += chunkSize) {
int end = i + chunkSize;
LOGGER.fine(((end > bytes.length) ? new String(bytes, i, bytes.length - i)
: new String(bytes, i, chunkSize)));
}
LOGGER.fine("----end response.");
}
return new ByteArrayEntity(bytes);
}
}
......@@ -33,6 +33,7 @@ final class HttpRequestBuilder {
private final Converter converter;
private Method javaMethod;
private boolean isSynchronous;
private Object[] args;
private String apiUrl;
private String replacedRelativePath;
......@@ -45,8 +46,9 @@ final class HttpRequestBuilder {
this.converter = converter;
}
HttpRequestBuilder setMethod(Method method) {
HttpRequestBuilder setMethod(Method method, boolean isSynchronous) {
this.javaMethod = method;
this.isSynchronous = isSynchronous;
requestLine = RequestLine.fromMethod(method);
return this;
}
......@@ -106,7 +108,10 @@ final class HttpRequestBuilder {
/** Converts all but the last method argument to a list of HTTP request parameters. */
private List<NameValuePair> createParamList() {
Annotation[][] parameterAnnotations = javaMethod.getParameterAnnotations();
int count = parameterAnnotations.length - 1;
int count = parameterAnnotations.length;
if (!isSynchronous) {
count -= 1;
}
List<NameValuePair> params = new ArrayList<NameValuePair>(count);
......
// Copyright 2012 Square, Inc.
package retrofit.http;
import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Sends server call times and response status codes to {@link retrofit.http.HttpProfiler}. */
class ProfilingResponseHandler<T> implements ResponseHandler<Void> {
private static final Logger LOGGER = Logger.getLogger(ProfilingResponseHandler.class.getSimpleName());
private final ResponseHandler<Void> delegate;
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. */
ProfilingResponseHandler(ResponseHandler<Void> delegate, HttpProfiler<T> profiler,
HttpProfiler.RequestInformation requestInfo, long startTime) {
this.delegate = delegate;
this.profiler = profiler;
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();
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);
}
}
......@@ -2,10 +2,20 @@ package retrofit.http;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
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 org.apache.http.util.EntityUtils;
import retrofit.http.Callback.ServerError;
import retrofit.http.HttpProfiler.RequestInformation;
import retrofit.http.RestException.ClientHttpException;
import retrofit.http.RestException.HttpException;
import retrofit.http.RestException.NetworkException;
import retrofit.http.RestException.ServerHttpException;
import retrofit.http.RestException.UnauthorizedHttpException;
import retrofit.http.RestException.UnexpectedException;
import javax.inject.Provider;
import java.io.IOException;
......@@ -15,27 +25,24 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
/**
* Converts Java method calls to Rest calls.
*
* @author Bob Lee (bob@squareup.com)
* @author Jake Wharton (jw@squareup.com)
*/
public class RestAdapter {
private static final Logger LOGGER = Logger.getLogger(RestAdapter.class.getName());
static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>() {
@Override protected DateFormat initialValue() {
return new SimpleDateFormat("HH:mm:ss");
}
};
private final Server server;
private final Provider<HttpClient> httpClientProvider;
......@@ -57,23 +64,29 @@ public class RestAdapter {
}
/**
* Adapts a Java interface to a REST API. HTTP requests happen on the provided {@link Executor} thread
* and callbacks happen on the provided {@link MainThread}.
*
* <p>Gets the relative path for a given method from a {@link GET}, {@link POST}, {@link PUT}, or
* {@link DELETE} annotation on the method. Gets the names of URL parameters from {@link
* javax.inject.Named} annotations on the method parameters.
*
* <p>The last method parameter should be of type {@link Callback}. The JSON HTTP response will be
* converted to the callback's parameter type using the specified {@link Converter}. If the callback
* parameter type uses a wildcard, the lower bound will be used as the conversion type.
*
* <p>For example:
*
* Adapts a Java interface to a REST API.
* <p/>
* The relative path for a given method is obtained from a {@link GET}, {@link POST}, {@link PUT}, or {@link DELETE}
* annotation on the method. Gets the names of URL parameters from {@link javax.inject.Named} annotations on the
* method parameters.
* <p/>
* HTTP requests happen in one of two ways:
* <ul>
* <li>On the provided {@link Executor} thread with callbacks marshaled to the provided {@link MainThread}. The last
* method parameter should be of type {@link Callback}. The HTTP response will be converted to the callback's
* parameter type using the specified {@link Converter}. If the callback parameter type uses a wildcard, the lower
* bound will be used as the conversion type.</li>
* <li>On the current thread returning the response or throwing a {@link RestException}. The HTTP response will be
* converted to the method's return type using the specified {@link Converter}.</li>
* </ul>
* <p/>
* For example:
* <pre>
* public interface MyApi {
* &#64;POST("go") public void go(@Named("a") String a, @Named("b") int b,
* Callback&lt;? super MyResult> callback);
* &#64;POST("go") // Asynchronous execution.
* public void go(@Named("a") String a, @Named("b") int b, Callback&lt;? super MyResult> callback);
* &#64;POST("go") // Synchronous execution.
* public MyResult go(@Named("a") String a, @Named("b") int b);
* }
* </pre>
*
......@@ -81,31 +94,43 @@ public class RestAdapter {
*/
@SuppressWarnings("unchecked")
public <T> T create(Class<T> type) {
return (T) Proxy.newProxyInstance(type.getClassLoader(),
new Class<?>[] {type}, new RestHandler());
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] {type}, new RestHandler());
}
private class RestHandler implements InvocationHandler {
private final Map<Method, Type> responseTypeCache = new HashMap<Method, Type>();
@SuppressWarnings("unchecked")
@Override public Object invoke(Object proxy, final Method method, final Object[] args) {
// Determine whether or not the execution will be synchronous.
boolean isSynchronousInvocation = methodWantsSynchronousInvocation(method);
if (isSynchronousInvocation) {
// TODO support synchronous invocations!
throw new UnsupportedOperationException("Synchronous invocation not supported.");
if (methodWantsSynchronousInvocation(method)) {
return invokeRequest(method, args, true);
} else {
executor.execute(new CallbackRunnable(obtainCallback(args), mainThread) {
@Override public Object obtainResponse() {
return invokeRequest(method, args, false);
}
});
return null; // Asynchronous methods should have return type of void.
}
}
// Construct HTTP request.
final Callback<?> callback =
UiCallback.create((Callback<?>) args[args.length - 1], mainThread);
/**
* Execute an HTTP request.
*
* @return HTTP response object of specified {@code type}.
* @throws ClientHttpException if HTTP 4XX error occurred.
* @throws UnauthorizedHttpException if HTTP 401 error occurred.
* @throws ServerHttpException if HTTP 5XX error occurred.
* @throws NetworkException if the {@code request} URL was unreachable.
* @throws UnexpectedException if an unexpected exception was thrown while processing the request.
*/
private Object invokeRequest(Method method, Object[] args, boolean isSynchronousInvocation) {
long start = System.nanoTime();
String url = server.apiUrl();
String startTime = "NULL";
try {
// Build the request and headers.
final HttpUriRequest request = new HttpRequestBuilder(converter) //
.setMethod(method)
.setMethod(method, isSynchronousInvocation)
.setArgs(args)
.setApiUrl(server.apiUrl())
.setHeaders(headers)
......@@ -119,78 +144,83 @@ public class RestAdapter {
responseTypeCache.put(method, type);
}
LOGGER.fine("Sending " + request.getMethod() + " to " + request.getURI());
final Date start = new Date();
startTime = DATE_FORMAT.get().format(start);
Object profilerObject = null;
if (profiler != null) {
profilerObject = profiler.beforeCall();
}
ResponseHandler<Void> rh = new CallbackResponseHandler(callback, type, converter, url, start, DATE_FORMAT);
LOGGER.fine("Sending " + request.getMethod() + " to " + request.getURI());
HttpResponse response = httpClientProvider.get().execute(request);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
// Optionally wrap the response handler for server call profiling.
if (profiler != null) {
rh = createProfiler(rh, (HttpProfiler<?>) profiler, getRequestInfo(method, request), start);
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
RequestInformation requestInfo = getRequestInfo(server, method, request);
profiler.afterCall(requestInfo, elapsedTime, statusCode, profilerObject);
}
// Execute HTTP request in the background.
final String finalUrl = url;
final String finalStartTime = startTime;
final ResponseHandler<Void> finalResponseHandler = rh;
executor.execute(new Runnable() {
@Override public void run() {
invokeRequest(request, finalResponseHandler, callback, finalUrl, finalStartTime);
HttpEntity entity = response.getEntity();
byte[] body = null;
if (entity != null) {
body = EntityUtils.toByteArray(entity);
}
try {
if (statusCode >= 200 && statusCode < 300) { // 2XX == successful request
return converter.to(body, type);
} else if (statusCode == SC_UNAUTHORIZED) { // 401 == unauthorized user
ServerError serverError = (ServerError) converter.to(body, ServerError.class);
throw new UnauthorizedHttpException(url, statusLine.getReasonPhrase(), serverError);
} else if (statusCode >= 500) { // 5XX == server error
ServerError serverError = (ServerError) converter.to(body, ServerError.class);
throw new ServerHttpException(url, statusCode, statusLine.getReasonPhrase(), serverError);
} else { // 4XX == client error
Object clientError = converter.to(body, type);
throw new ClientHttpException(url, statusCode, statusLine.getReasonPhrase(), clientError);
}
});
} catch (ConversionException e) {
LOGGER.log(WARNING, e.getMessage() + " from " + url, e);
throw new ServerHttpException(url, statusCode, statusLine.getReasonPhrase(), e);
}
} catch (HttpException e) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Sever returned " + e.getStatus() + ", " + e.getMessage() + ". Body: " + e.getResponse()
+ ". Url: " + e.getUrl());
}
throw e; // Allow any rest-related exceptions to pass through.
} catch (IOException e) {
LOGGER.log(WARNING, e.getMessage() + " from " + url, e);
throw new NetworkException(url, e);
} catch (Throwable t) {
LOGGER.log(Level.WARNING, t.getMessage() + " from " + url + " at " + startTime, t);
callback.unexpectedError(t);
LOGGER.log(WARNING, t.getMessage() + " from " + url, t);
throw new UnexpectedException(url, t);
}
// Methods should return void.
return null;
}
}
private HttpProfiler.RequestInformation getRequestInfo(Method method, HttpUriRequest request) {
RequestLine requestLine = RequestLine.fromMethod(method);
HttpMethodType httpMethod = requestLine.getHttpMethod();
HttpProfiler.Method profilerMethod = httpMethod.profilerMethod();
private static Callback<?> obtainCallback(Object[] args) {
return (Callback<?>) args[args.length - 1];
}
long contentLength = 0;
String contentType = null;
if (request instanceof HttpEntityEnclosingRequestBase) {
HttpEntityEnclosingRequestBase entityReq = (HttpEntityEnclosingRequestBase) request;
HttpEntity entity = entityReq.getEntity();
contentLength = entity.getContentLength();
private static HttpProfiler.RequestInformation getRequestInfo(Server server, Method method, HttpUriRequest request) {
RequestLine requestLine = RequestLine.fromMethod(method);
HttpMethodType httpMethod = requestLine.getHttpMethod();
HttpProfiler.Method profilerMethod = httpMethod.profilerMethod();
Header entityContentType = entity.getContentType();
contentType = entityContentType != null ? entityContentType.getValue() : null;
}
long contentLength = 0;
String contentType = null;
if (request instanceof HttpEntityEnclosingRequestBase) {
HttpEntityEnclosingRequestBase entityReq = (HttpEntityEnclosingRequestBase) request;
HttpEntity entity = entityReq.getEntity();
contentLength = entity.getContentLength();
return new HttpProfiler.RequestInformation(profilerMethod, server.apiUrl(), requestLine.getRelativePath(),
contentLength, contentType);
Header entityContentType = entity.getContentType();
contentType = entityContentType != null ? entityContentType.getValue() : null;
}
private void invokeRequest(HttpUriRequest request, ResponseHandler<Void> rh,
Callback<?> callback, String url, String startTime) {
try {
httpClientProvider.get().execute(request, rh);
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage() + " from " + url + " at " + startTime, e);
callback.networkError();
} catch (Throwable t) {
LOGGER.log(Level.WARNING, t.getMessage() + " from " + url + " at " + startTime, t);
callback.unexpectedError(t);
}
}
/** Wraps a {@code GsonResponseHandler} with a {@code ProfilingResponseHandler}. */
private <T> ProfilingResponseHandler<T> createProfiler(ResponseHandler<Void> handlerToWrap,
HttpProfiler<T> profiler, HttpProfiler.RequestInformation requestInfo, Date start) {
ProfilingResponseHandler<T> responseHandler = new ProfilingResponseHandler<T>(handlerToWrap, profiler,
requestInfo, start.getTime());
responseHandler.beforeCall();
return responseHandler;
}
return new HttpProfiler.RequestInformation(profilerMethod, server.apiUrl(), requestLine.getRelativePath(),
contentLength, contentType);
}
/**
......@@ -253,9 +283,12 @@ public class RestAdapter {
* <ul>
* <li>{@link #setServer(Server)}</li>
* <li>{@link #setClient(javax.inject.Provider)}</li>
* <li>{@link #setConverter(Converter)}</li>
* </ul>
* If you are using asynchronous execution (i.e., with {@link Callback Callbacks}) the following are also required:
* <ul>
* <li>{@link #setExecutor(java.util.concurrent.Executor)}</li>
* <li>{@link #setMainThread(MainThread)}</li>
* <li>{@link #setConverter(Converter)}</li>
* </ul>
*/
public static class Builder {
......@@ -319,10 +352,6 @@ public class RestAdapter {
if (clientProvider == null) throw new NullPointerException("clientProvider");
if (converter == null) throw new NullPointerException("converter");
// TODO Remove the following two when we support synchronous invocation as they will be allowed to be null.
if (executor == null) throw new NullPointerException("executor");
if (mainThread == null) throw new NullPointerException("mainThread");
return new RestAdapter(server, clientProvider, executor, mainThread, headers, converter, profiler);
}
}
......
// Copyright 2012 Square, Inc.
package retrofit.http;
import java.io.IOException;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
public abstract class RestException extends RuntimeException {
private final String url;
protected RestException(String url, String message) {
super(message);
this.url = url;
}
protected RestException(String url, Throwable t) {
super(t);
this.url = url;
}
protected RestException(String url, String message, Throwable t) {
super(message, t);
this.url = url;
}
public String getUrl() {
return url;
}
/** An exception that is the result of an HTTP response. */
public static abstract class HttpException extends RestException {
private final int status;
private final Object response;
protected HttpException(String url, int status, String message, Object response) {
super(url, message);
this.status = status;
this.response = response;
}
protected HttpException(String url, int status, String message, ConversionException cause) {
super(url, message, cause);
this.status = status;
this.response = null;
}
public int getStatus() {
return status;
}
public Object getResponse() {
return response;
}
}
/**
* The server returned a client error. In most cases, this is a programming error, but it can also signify a user
* input error.
*/
public static class ClientHttpException extends HttpException {
public ClientHttpException(String url, int status, String message, Object response) {
super(url, status, message, response);
}
}
/**
* We reached the server, but it encountered an error (5xx) or its response was unparseable. Please try again later.
*/
public static class ServerHttpException extends HttpException {
public ServerHttpException(String url, int status, String message, Object response) {
super(url, status, message, response);
}
public ServerHttpException(String url, int status, String message, ConversionException cause) {
super(url, status, message, cause);
}
}
/** The session expired or the account has been disabled. Prompt the user to log in again. */
public static class UnauthorizedHttpException extends HttpException {
public UnauthorizedHttpException(String url, String message, Object response) {
super(url, SC_UNAUTHORIZED, message, response);
}
}
/** Couldn't reach the server. Check network settings and try again. */
public static class NetworkException extends RestException {
public NetworkException(String url, IOException e) {
super(url, e);
}
}
/**
* An unexpected error occurred. Called if the framework throws an unexpected exception or if the server returns a 400
* (Bad Request) error. In either case, the client software likely contains a bug; otherwise, the error would have
* been caught sooner. The user should try updating their client.
*/
public static class UnexpectedException extends RestException {
public UnexpectedException(String url, Throwable t) {
super(url, t);
}
}
}
// Copyright 2010 Square, Inc.
package retrofit.http;
/**
* Executes callback methods in the UI thread.
*
* @author Bob Lee (bob@squareup.com)
*/
final class UiCallback<T> implements Callback<T> {
final Callback<T> delegate;
final MainThread mainThread;
UiCallback(Callback<T> delegate, MainThread mainThread) {
this.delegate = delegate;
this.mainThread = mainThread;
}
public static <T> UiCallback<T> create(Callback<T> delegate,
MainThread mainThread) {
return new UiCallback<T>(delegate, mainThread);
}
public void call(final T t) {
mainThread.execute(new Runnable() {
public void run() {
delegate.call(t);
}
});
}
public void sessionExpired(final ServerError error) {
mainThread.execute(new Runnable() {
public void run() {
delegate.sessionExpired(error);
}
});
}
public void networkError() {
mainThread.execute(new Runnable() {
public void run() {
delegate.networkError();
}
});
}
public void clientError(final T response, final int statusCode) {
mainThread.execute(new Runnable() {
public void run() {
delegate.clientError(response, statusCode);
}
});
}
public void serverError(final ServerError error, final int statusCode) {
mainThread.execute(new Runnable() {
public void run() {
delegate.serverError(error, statusCode);
}
});
}
public void unexpectedError(final Throwable t) {
mainThread.execute(new Runnable() {
public void run() {
delegate.unexpectedError(t);
}
});
}
}
......@@ -197,7 +197,7 @@ public class HttpRequestBuilderTest {
private HttpUriRequest build(Method method, Object[] args) throws URISyntaxException {
return new HttpRequestBuilder(new GsonConverter(GSON)) //
.setMethod(method)
.setMethod(method, false)
.setArgs(args)
.setApiUrl(API_URL)
.build();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册