Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
wd1105040417
retrofit
提交
3f1c5ab4
R
retrofit
项目概览
wd1105040417
/
retrofit
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
retrofit
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
3f1c5ab4
编写于
10月 23, 2012
作者:
J
Jake Wharton
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add synchronous method invocation.
上级
2efc1c33
变更
14
展开全部
隐藏空白更改
内联
并排
Showing
14 changed file
with
659 addition
and
561 deletion
+659
-561
README.md
README.md
+14
-6
http/src/main/java/retrofit/http/Callback.java
http/src/main/java/retrofit/http/Callback.java
+12
-1
http/src/main/java/retrofit/http/CallbackResponseHandler.java
.../src/main/java/retrofit/http/CallbackResponseHandler.java
+0
-143
http/src/main/java/retrofit/http/CallbackRunnable.java
http/src/main/java/retrofit/http/CallbackRunnable.java
+74
-0
http/src/main/java/retrofit/http/Converter.java
http/src/main/java/retrofit/http/Converter.java
+4
-4
http/src/main/java/retrofit/http/GsonConverter.java
http/src/main/java/retrofit/http/GsonConverter.java
+3
-3
http/src/main/java/retrofit/http/HttpClients.java
http/src/main/java/retrofit/http/HttpClients.java
+0
-78
http/src/main/java/retrofit/http/HttpRequestBuilder.java
http/src/main/java/retrofit/http/HttpRequestBuilder.java
+7
-2
http/src/main/java/retrofit/http/ProfilingResponseHandler.java
...src/main/java/retrofit/http/ProfilingResponseHandler.java
+0
-53
http/src/main/java/retrofit/http/RestAdapter.java
http/src/main/java/retrofit/http/RestAdapter.java
+128
-99
http/src/main/java/retrofit/http/RestException.java
http/src/main/java/retrofit/http/RestException.java
+103
-0
http/src/main/java/retrofit/http/UiCallback.java
http/src/main/java/retrofit/http/UiCallback.java
+0
-71
http/src/test/java/retrofit/http/HttpRequestBuilderTest.java
http/src/test/java/retrofit/http/HttpRequestBuilderTest.java
+1
-1
http/src/test/java/retrofit/http/RestAdapterTest.java
http/src/test/java/retrofit/http/RestAdapterTest.java
+313
-100
未找到文件。
README.md
浏览文件 @
3f1c5ab4
...
...
@@ -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
DummyService
Async
{
// 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
...
...
http/src/main/java/retrofit/http/Callback.java
浏览文件 @
3f1c5ab4
...
...
@@ -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
;
}
}
}
http/src/main/java/retrofit/http/CallbackResponseHandler.java
已删除
100644 → 0
浏览文件 @
2efc1c33
// 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
;
}
}
http/src/main/java/retrofit/http/CallbackRunnable.java
0 → 100644
浏览文件 @
3f1c5ab4
// 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
();
}
http/src/main/java/retrofit/http/Converter.java
浏览文件 @
3f1c5ab4
// 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
entit
y HTTP response body.
* @param
bod
y 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
entit
y
,
Type
type
)
throws
ConversionException
;
Object
to
(
byte
[]
bod
y
,
Type
type
)
throws
ConversionException
;
/**
* Convert and object to appropriate representation for HTTP transport.
...
...
http/src/main/java/retrofit/http/GsonConverter.java
浏览文件 @
3f1c5ab4
...
...
@@ -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
entit
y
,
Type
type
)
throws
ConversionException
{
@Override
public
Object
to
(
byte
[]
bod
y
,
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
);
...
...
http/src/main/java/retrofit/http/HttpClients.java
已删除
100644 → 0
浏览文件 @
2efc1c33
// 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
);
}
}
http/src/main/java/retrofit/http/HttpRequestBuilder.java
浏览文件 @
3f1c5ab4
...
...
@@ -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
);
...
...
http/src/main/java/retrofit/http/ProfilingResponseHandler.java
已删除
100644 → 0
浏览文件 @
2efc1c33
// 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
);
}
}
http/src/main/java/retrofit/http/RestAdapter.java
浏览文件 @
3f1c5ab4
...
...
@@ -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 {
* @POST("go") public void go(@Named("a") String a, @Named("b") int b,
* Callback<? super MyResult> callback);
* @POST("go") // Asynchronous execution.
* public void go(@Named("a") String a, @Named("b") int b, Callback<? super MyResult> callback);
* @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
);
}
}
...
...
http/src/main/java/retrofit/http/RestException.java
0 → 100644
浏览文件 @
3f1c5ab4
// 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
);
}
}
}
http/src/main/java/retrofit/http/UiCallback.java
已删除
100644 → 0
浏览文件 @
2efc1c33
// 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
);
}
});
}
}
http/src/test/java/retrofit/http/HttpRequestBuilderTest.java
浏览文件 @
3f1c5ab4
...
...
@@ -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
();
...
...
http/src/test/java/retrofit/http/RestAdapterTest.java
浏览文件 @
3f1c5ab4
此差异已折叠。
点击以展开。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录