简介
HTTP-RPC 是一个用于通过方便的 RPC 类似隐喻访问基于 HTTP 的 Web 服务的开源框架。该项目目前包括支持使用 Objective-C、Swift 和 Java 消费服务,并提供了一种基于回调的、一致的 API,使得无论目标平台如何,都能够轻松地与服务交互。还提供了一个可选库,可用于在 Java 中实现服务。
例如,以下代码片段展示了 Swift 客户端如何访问一个简单的返回友好问候语的 Web 服务。
serviceProxy.invoke("GET", path: "/hello") { (result: Any?, error: NSError?) in
if (error == nil) {
print(result!) // Prints "Hello, World!"
}
}
在 Java 中,代码可能如下所示。
serviceProxy.invoke("GET", "/hello", (result, exception) -> {
if (error == null) {
System.out.println(result); // Prints "Hello, World!"
}
});
在两种情况下,请求将会异步执行,并在调用返回时打印结果。
本指南介绍了 HTTP-RPC 框架,并对其关键特性进行了概述。有关更多信息及示例,请参阅 wiki。
反馈
欢迎并鼓励反馈。请随时通过 联系我 提出任何问题、评论或建议。此外,如果您喜欢使用 HTTP-RPC,请考虑 点赞 它!
内容
Objective-C/Swift 客户端
Objective-C/Swift 客户端使得 iOS 和 tvOS 应用能够消费基于 HTTP 的网络服务。它以通用框架的形式发行,包含一个单独的 WSWebServiceProxy
类,以下是更详细的讨论。
iOS 和 tvOS 框架可以在此处下载:https://github.com/gk-brown/HTTP-RPC/releases。它们也可以通过 Cocoa Pods 获取。需要 iOS 10 或 tvOS 10 或更高版本。
WSWebServiceProxy 类
WSWebServiceProxy
类作为网服务的客户端调用代理。内部,它使用 NSURLSession
实例来发出 HTTP 请求。
服务代理通过 initWithSession:serverURL:
方法初始化,该方法接受以下参数:
session
- 一个NSURLSession
实例,用于分发服务请求serverURL
- 服务的基准 URL
通过调用以下方法之一来启动服务操作:
invoke:path:resultHandler
invoke:path:arguments:resultHandler
invoke:path:arguments:responseHandler:resultHandler
这些方法接受以下参数:
method
- 执行的 HTTP 方法path
- 请求资源的路径arguments
- 作为键/值对包含请求参数的可选字典responseHandler
- 用于解码服务器响应的可选回调resultHandler
- 在方法完成后将被调用的回调
这些方法返回一个代表调用请求的 NSURLSessionTask
实例。这允许应用程序在需要时取消任务。
参数
与 HTML 表单类似,参数是通过查询字符串或在请求体中提交的。对于 GET
和 DELETE
请求,参数始终在查询字符串中发送。POST
参数始终在请求体中发送,可以使用标准 W3C URL 编码、多部分表单编码或 JSON 发送。而 PUT
和 PATCH
参数可以是 JSON 或通过查询字符串提交。
请求编码通过服务代理实例的 encoding
属性指定。HTTP-RPC 提供以下常量,代表支持的编码类型:
WSApplicationXWWWFormURLEncoded
WSMultipartFormData
WSApplicationJSON
默认值是 WSMultipartFormData
。
通过查询字符串或使用表单编码方式发送的参数通常通过参数的description
方法转换为参数值。然而,数组实例代表多个值的参数,其行为类似于HTML中的<select multiple>
标签。另外,在多部分表单数据编码中,NSURL
实例代表文件上传,其行为类似于HTML表单中的<input type="file">
标签。URL值数组的操作类似于<input type="file" multiple>
标签。
使用JSON编码时,一个包含整个参数字典的单个JSON对象会被发送到请求体中。字典通过NSJSONSerialization
类转换为JSON。
返回值
操作完成后会调用结果处理器。如果成功,第一个参数将包含服务器返回内容的反序列化表示。否则,第一个参数将为nil
,第二个参数将被填充一个描述所发生问题的NSError
实例。
WSWebServiceProxy
支持以下响应类型
- application/json
- image/*
- text/*
默认情况下,使用NSJSONSerialization
解码JSON响应数据,使用UIImage
解码图像内容。文本内容以字符串形式返回。可以通过可选的响应处理器回调实现自定义反序列化。例如,以下代码使用Swift的JSONDecoder
类返回强类型结果值
serviceProxy.invoke("GET", path: "/example", arguments: [:], responseHandler: { data, contentType in
let decoder = JSONDecoder()
return try? decoder.decode(Example.self, from: data)
}) { (result: Example?, error: NSError?) in
// Handle result
}
请注意,尽管请求通常在后台线程上处理,但结果处理器的执行是在最初调用服务方法的队列上(通常是应用程序的主队列)。这允许结果处理器直接更新用户界面,而不是将单独的更新操作发送到主队列。自定义响应处理器在请求处理器队列上执行,在调用结果处理器之前。
如果服务器返回一个内容类型为"text/plain"的错误响应,则响应体将作为错误参数的本地化描述返回。如果响应类型是"application/json",则响应体作为错误对象的userInfo
属性返回。否则,将返回默认错误消息。
认证
虽然可以使用NSURLSessionDelegate
协议中的URLSession:didReceiveChallenge:completionHandler:
方法对服务请求进行认证,但如果在事先已知用户的凭据的情况下,此方法会需要不必要的往返调用到服务器,这种情况常常发生。
HTTP-RPC提供了一种额外的认证机制,该机制可以在每个代理的基础上进行指定。可以使用authorization
属性将一组用户凭据与代理实例相关联。该属性接受一个标识用户的NSURLCredential
实例。当指定后,凭据会使用基本HTTP认证在每个请求中提交。
重要由于基本认证会以明文形式传输编码的用户名和密码,因此它应该仅用于安全(即HTTPS)连接。
示例
以下代码示例演示了如何使用WSWebServiceProxy
类访问一个假设的数学服务的操作
// Create service proxy
let serviceProxy = WSWebServiceProxy(session: URLSession.shared, serverURL: URL(string: "https://:8443")!)
// Get sum of "a" and "b"
serviceProxy.invoke("GET", path: "/math/sum", arguments: ["a": 2, "b": 4]) { (result: Int?, error: NSError?) in
// result is 6
}
// Get sum of all values
serviceProxy.invoke("GET", path: "/math/sum", arguments: ["values": [1, 2, 3, 4]]) { (result: Int?, error: NSError?) in
// result is 6
}
Java客户端
Java客户端使Java应用程序(包括Android)能够消费基于HTTP的Web服务。它作为一个包含以下类型的JAR文件分发,以下是更详细的讨论:
WebServiceProxy
- Web服务调用代理WebServiceException
- 当服务操作返回错误时生成的异常ResultHandler
- 用于处理服务结果的回调接口
此外,该框架还包括两个类,JSONEncoder
和JSONDecoder
,这些类用于处理JSON数据。然而,这些类是公开的,也可能被应用程序代码使用。
可以通过这里下载Java客户端库。它也可以通过Maven获取。
<dependency>
<groupId>org.httprpc</groupId>
<artifactId>httprpc</artifactId>
<version>...</version>
</dependency>
在Android Studio中
dependencies {
...
compile 'org.httprpc:httprpc:...'
}
需要Java 8或更高版本。
WebServiceProxy类
WebServiceProxy
类作为Web服务的客户端调用代理。内部,它使用一个HttpURLConnection
实例发送和接收数据。
服务代理通过以下参数的构造函数进行初始化
serverURL
- 表示服务基本URL的java.net.URL
实例executorService
- 用于分派服务请求的java.util.concurrent.ExecutorService
实例
通过调用以下方法之一来启动服务操作:
public <V> Future<V> invoke(String method, String path, ResultHandler<V> resultHandler) { ... }
public <V> Future<V> invoke(String method, String path, Map<String, ?> arguments, ResultHandler<V> resultHandler) { ... }
public <V> Future<V> invoke(String method, String path, Map<String, ?> arguments, ResponseHandler<V> responseHandler, ResultHandler<V> resultHandler) { ... }
这些方法接受以下参数:
method
- 执行的 HTTP 方法path
- 请求资源的路径arguments
- 包含请求参数作为键/值对的可选映射responseHandler
- 用于解码服务器响应的可选回调resultHandler
- 完成请求后将被调用的可选回调
方法返回一个代表调用请求的java.util.concurrent.Future
实例。此对象允许调用者取消挂起的请求、获取已完成请求的信息或在等待操作完成时阻塞当前线程。
参数
与 HTML 表单类似,参数是通过查询字符串或在请求体中提交的。对于 GET
和 DELETE
请求,参数始终在查询字符串中发送。POST
参数始终在请求体中发送,可以使用标准 W3C URL 编码、多部分表单编码或 JSON 发送。而 PUT
和 PATCH
参数可以是 JSON 或通过查询字符串提交。
请求编码通过服务代理实例的setEncoding()
方法指定。WebServiceProxy
类提供了以下代表支持的编码类型的常量
APPLICATION_X_WWW_FORM_URLENCODED
MULTIPART_FORM_DATA
APPLICATION_JSON
默认值是MULTIPART_FORM_DATA
。
通过查询字符串或使用表单编码之一发送的参数通常通过参数的toString()
方法转换为参数值。然而,Iterable
值(如列表)表示多值参数,并且类似于HTML中的<select multiple>
标签。此外,在多部分表单数据编码中,java.net.URL
实例表示文件上传,类似于HTML表单中的<input type="file">
标签。URL值的可迭代操作类似于<input type="file" multiple>
标签。
在JSON编码中,完整的参数映射的单一JSON对象作为请求体发送。参数转换为其JSON等效项如下
Number
:数字Boolean
:true/falseCharSequence
:字符串Iterable
:数组java.util.Map
:对象
Map
实现必须使用String
作为键值。支持嵌套结构,但不允许引用循环。
请注意,PATCH
请求可能不被所有平台支持。例如,PATCH
在Android SDK 24上工作正常,但在Oracle Java 8运行时产生ProtocolException
。
参数映射创建
由于显式构建和填充参数映射可能很繁琐,因此WebServiceProxy
提供了以下静态便利方法来帮助简化映射创建
public static <K> Map<K, ?> mapOf(Map.Entry<K, ?>... entries) { ... }
public static <K> Map.Entry<K, ?> entry(K key, Object value) { ... }
使用这些方法,可以将参数映射声明从以下内容
HashMap<String, Object> arguments = new HashMap<>();
arguments.put("a", 2);
arguments.put("b", 4);
减少到以下内容
mapOf(entry("a", 2), entry("b", 4));
声明列表的便利方法也提供了
public static List<?> listOf(Object... elements) { ... }
返回值
在操作完成后调用结果处理程序。ResultHandler
是一个函数式接口,其单一代码execute()
定义如下
public void execute(V result, Exception exception);
如果操作成功,第一个参数将包含服务器返回内容的反序列化表示。否则,第一个参数将为 null
,第二个参数将包含表示错误事件的异常。
WebServiceProxy
支持以下响应类型
- application/json
- image/*
- text/*
JSON值映射到Java等价值如下
- 字符串:
String
- 数字:
Number
- 真/假:
Boolean
- 数组:
java.util.List
- 对象:
java.util.Map
图像数据通过 WebServiceProxy
类的 decodeImageResponse()
方法进行解码。默认实现抛出 UnsupportedOperationException
。然而,子类可以重写此方法以提供自定义图像反序列化行为。例如,Android客户端可能会重写此方法以生成 Bitmap
对象。
@Override
protected Object decodeImageResponse(InputStream inputStream, String imageType) {
return BitmapFactory.decodeStream(inputStream);
}
文本数据通过 decodeTextResponse()
方法进行解码。默认实现简单地将文本内容作为字符串返回。子类可以重写此方法以生成其他表示(例如,将XML文档加载到文档对象模型中)。
可以通过响应处理器回调实现自定义反序列化。例如,以下代码使用 Jackson JSON解析器返回强类型结果值
serviceProxy.invoke("GET", "/example", mapOf(), (inputStream, contentType) -> {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(input, Example.class);
}, (Example result, Exception exception) -> {
// Handle result
});
如果服务器返回包含“text/plain”内容类型的错误响应,则响应体将在异常参数的 message
属性中返回。如果响应类型是“application/json”,则响应体将在异常的 error
属性中返回。否则,将返回默认错误信息。
访问嵌套结构
WebServiceProxy
为通过键路径访问嵌套映射值提供了以下便利方法
public static <V> V valueAt(Map<String, ?> root, String path) { ... }
例如,给定以下JSON响应
{
"foo": {
"bar": 123
}
}
可以使用此方法检索 "foo.bar" 处的值
System.out.println(valueAt(result, "foo.bar")); // Prints 123
此外,WebServiceProxy
还提供以下方法来协助处理 null
值。该方法识别列表中第一个非 null
值
public static <V> V coalesce(V... values) { ... }
例如
System.out.println(coalesce(valueAt(result, "xyz"), "not found")); // Prints "not found"
线程并发考虑
默认情况下,结果处理器在执行远程请求的线程中被调用。在大多数情况下,这将是一个后台线程。然而,用户界面工具包通常要求在主线程上进行更新。因此,处理器通常需要“发布”一条消息回UI线程以更新应用程序的状态。例如,Swing应用程序可能会调用 SwingUtilities#invokeAndWait()
,而Android应用程序可能会调用 Activity#runOnUiThread()
或 Handler#post()
。
虽然这可以在结果处理器本身中完成,但 WebServiceProxy
提供了一个更方便的替代方案。受保护的 dispatchResult()
方法可以被重写以处理结果处理器通知。例如,以下Android特定的代码确保所有结果处理器都会在主UI线程上执行
WebServiceProxy serviceProxy = new WebServiceProxy(serverURL, executorService) {
private Handler handler = new Handler(Looper.getMainLooper());
@Override
protected void dispatchResult(Runnable command) {
handler.post(command);
}
};
命令行应用程序通常可以使用默认的分发器,该分发器简单地在对当前线程的结果处理程序通知进行操作。
请注意,自定义响应处理程序在调用 dispatchResult()
方法之前在后台线程中执行。
身份验证
尽管可以使用 java.net.Authenticator
类来对服务请求进行身份验证,但这个类与多个并发请求或身份验证到不同凭据的多个服务时工作可能会很困难。如果在用户凭据众所周知的情况下,这通常是常见的情况,也需要不必要的服务器往返。
HTTP-RPC 提供了一种额外的基于每个代理的身份验证机制。可以使用 setAuthorization()
方法将一组用户凭据与代理实例相关联。此方法采用一个表示用户的 java.net.PasswordAuthentication
实例。在指定的情况下,凭据会随每个请求一起使用基本 HTTP 身份验证提交。
重要由于基本认证会以明文形式传输编码的用户名和密码,因此它应该仅用于安全(即HTTPS)连接。
示例
以下代码示例演示了如何使用 WebServiceProxy
类访问一个假设的数学服务的操作
// Create service proxy
WebServiceProxy serviceProxy = new WebServiceProxy(new URL("https://:8443"), Executors.newSingleThreadExecutor());
// Get sum of "a" and "b"
serviceProxy.invoke("GET", "/math/sum", mapOf(entry("a", 2), entry("b", 4)), (result, exception) -> {
// result is 6
});
// Get sum of all values
serviceProxy.invoke("GET", "/math/sum", mapOf(entry("values", listOf(1, 2, 3))), (result, exception) -> {
// result is 6
});
Java 服务器
可选 Java 服务器库允许开发者在 Java 中实现基于 HTTP 的 Web 服务。它作为一个包含以下类型的 JAR 文件提供:
DispatcherServlet
- Web 服务的抽象基类RequestMethod
- 将 HTTP 动词与服务方法关联的注解ResourcePath
- 将资源路径与服务方法关联的注解
服务器 JAR 可以在此处下载:https://github.com/gk-brown/HTTP-RPC/releases。它也可以通过 Maven 获取。
<dependency>
<groupId>org.httprpc</groupId>
<artifactId>httprpc-server</artifactId>
<version>...</version>
</dependency>
需要 Java 客户端库和 Java 8 或更高版本。
DispatcherServlet
DispatcherServlet
是 HTTP 基础 Web 服务的抽象基类。服务操作是通过向具体的服务实现中添加公共方法来定义的。
通过提交一个HTTP请求到与servlet实例关联的路径来调用方法。参数可以通过查询字符串或在请求体中提供,就像HTML表单一样。参数也可以以JSON的形式提供。DispatcherServlet
将请求参数转换为期望的参数类型,调用方法,并将返回值以JSON格式写入输出流。
使用RequestMethod
注解将服务方法与HTTP动词(如GET
或POST
)关联起来。可选的ResourcePath
注解可以用来将方法与servlet相对于的特定路径关联起来。如果没有指定,则方法与servlet本身关联。
可能将多个方法与相同的动词和路径关联起来。DispatcherServlet
根据提供的参数值选择最佳方法执行。例如,以下类可能用于实现之前讨论的简单加法操作
@WebServlet(urlPatterns={"/math/*"})
public class MathServlet extends DispatcherServlet {
@RequestMethod("GET")
@ResourcePath("/sum")
public double getSum(double a, double b) {
return a + b;
}
@RequestMethod("GET")
@ResourcePath("/sum")
public double getSum(List<Double> values) {
double total = 0;
for (double value : values) {
total += value;
}
return total;
}
}
以下请求会导致第一个方法被调用
GET /math/sum?a=2&b=4
此请求将调用第二个方法
GET /math/sum?values=1&values=2&values=3
在两种情况下,服务都会返回6这个值作为响应。
请注意,服务类必须使用-parameters
标志进行编译,以便在运行时可用其方法参数名称。
方法参数
方法参数可以是以下类型之一
- 数值原始类型或包装类(例如
int
或Integer
) boolean
或Boolean
String
java.util.List
java.util.Map
java.net.URL
列表参数表示使用W3C表单编码提交的多值参数或以JSON提交的数组结构。映射参数表示以JSON提交的对象结构,必须使用字符串作为键。列表和映射值在可能的情况下会自动转换为它们的声明类型。
URL
参数表示文件上传。它们只能在使用多部分表单数据编码提交的POST
请求中使用。例如
@WebServlet(urlPatterns={"/upload/*"})
@MultipartConfig
public class FileUploadServlet extends DispatcherServlet {
@RequestMethod("POST")
public void upload(URL file) throws IOException {
...
}
@RequestMethod("POST")
public void upload(List<URL> files) throws IOException {
...
}
}
返回值
返回值会转换为前面所述的JSON等效值
Number
:数字Boolean
:true/falseCharSequence
:字符串Iterable
:数组java.util.Map
:对象
方法也可以返回void
或Void
来表示它们不产生值。
例如,以下方法将生成包含三个值的JSON对象
@RequestMethod("GET")
public Map<String, ?> getMap() {
return mapOf(
entry("text", "Lorem ipsum"),
entry("number", 123),
entry("flag", true)
);
}
服务将返回以下内容
{
"text": "Lorem ipsum",
"number": 123,
"flag": true
}
请求和响应属性
DispatcherServlet
为允许服务访问与当前操作关联的请求和响应对象提供了以下方法
protected HttpServletRequest getRequest() { ... }
protected HttpServletResponse getResponse() { ... }
例如,服务可以通过请求获取当前用户的名称,或使用响应返回自定义标头。
响应对象还可以用于生成自定义结果。如果一个服务方法通过向输出流写入来提交响应,则DispatcherServlet
将忽略(如果有的话)返回值。这允许服务返回难以表示为JSON的内容,如图像数据或文本格式。
异常
如果在执行方法期间抛出异常,并且尚未提交响应,则异常消息将以纯文本形式作为响应体返回。这允许服务向调用者提供失败原因的洞察,例如无效参数。
路径变量
路径变量可以通过资源路径中的"?"字符指定。例如
@RequestMethod("GET")
@ResourcePath("/contacts/?/addresses/?")
public List<Map<String, ?>> getContactAddresses() { ... }
getKey()
方法返回与当前请求关联的路径变量的值
protected String getKeys(int index) { ... }
例如,给定以下路径
/contacts/jsmith/addresses/home
索引0处的键的值将是"jsmith",索引1处的值将是"home"。
更多信息
有关更多信息示例,请参阅wiki。