使用Retrofit提交Soap请求以及自定义Converter解析数据

HTTP 只负责把数据传送过去,不会管这个数据是 XMLHTML、图片、文本文件或者别的什么。而 SOAP 协议则定义了怎么把一个对象变成 XML 文本,在远程如何调用等。

Web service 一般就是用 SOAP 协议通过 HTTP 来调用它,其实他就是一个 WSDL 文档,客户可以阅读 WSDL 文档来用这个 Web service。客户根据 WSDL 描述文档,会生成一个 SOAP 请求消息。客户端生成的 SOAP 请求会被嵌入在一个 HTTP POST 请求中,发送给 Web 服务器。Web 服务器再把这些请求转发给 Web service 请求处理器。请求处理器解析收到的 SOAP 请求,调用 Web service,然后再生成相应的 SOAP 应答。Web 服务器得到 SOAP 应答后,会再通过 HTTP 应答的方式把它送回到客户端。

Web Service 协议主要包括两个方面,传输协议和数据表示:

  • 传输协议 : 可以是 Http 或其他,现在通用的是 http+soap
  • 数据表示 : 可以是键值对、xml 或其他。

SOAP 简单的理解,就是这样的一个开放协议 SOAP=RPC+HTTP+XML:采用 HTTP 作为底层通讯协议;RPC 作为一致性的调用途径,XML 作为数据传送的格式,允许服务提供者和服务客户经过防火墙在 INTERNET 进行通讯交互。

接口定义

SOAP 由于是传 xml 数据给服务器,所以得使用 @Body + RequestBody 来定义请求接口。

http://192.168.0.1:8030/AwesomeService.asmx 只是一个 Service 地址,而具体的接口 api#Login 则是定义在该 Service 里面的方法;

1
2
3
4
private static final String ServiceName = "AwesomeService.asmx";
@POST(ServiceName)
Observable<User> login(@Body UDRequestBody body);

比如 Login 接口需要客户端传递的 xml

1
2
3
4
<Login xmlns="http://com.sxmlgd.citygrid/">
<UserName>string</UserName>
<Pwd>string</Pwd>
</Login>

生成RequestBody

为了方便其它接口使用,继承 RequestBody 实现一个自定义的 UDRequestBody ,有两个重要的方法:

  • contentType() : 设置请求的 Content-Typeapplication/soap+xml;
  • writeTo() : 将 xml 字符串通过 BufferedSink#write 写入请求体;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class UDRequestBody extends RequestBody {
private String apiName;
private String[] keysAndValues;
public static UDRequestBody create(String apiName, String... keysAndValues) {
return new UDRequestBody(apiName, keysAndValues);
}
private UDRequestBody(String apiName, String... keysAndValues) {
this.apiName = apiName;
int len = keysAndValues.length;
this.keysAndValues = new String[len];
System.arraycopy(keysAndValues, 0, this.keysAndValues, 0, keysAndValues.length);
}
@Override
public MediaType contentType() {
// 定义 MediaType
return MediaType.parse("application/soap+xml");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
// 传递 xml 数据到服务器后台;
String ori = getSoapXml(apiName, keysAndValues);
sink.write(ori.getBytes("utf-8"));
}
/**
* 获取请求内容
*
* @param apiName
* @param nameValues
* @return
*/
private String getSoapXml(String apiName, String... nameValues){
// 在该方法中返回需要传递到服务器后台的 xml 数据;
}
}

自定义ConverterFactory

SOAP 网络协议返回的数据通常也是一段 xml ,而不是标准的 JSON 格式。

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap12:Body>
<LoginResponse xmlns="http://com.sxmlgd.citygrid/">
<LoginResult>{"name":"Michael Cai", "age": 27}</LoginResult>
</LoginResponse>
</soap12:Body>
</soap12:Envelope>

JSON 数据是在最里层的 ApiResult 节点的值,所以可以先获取到这个节点的值,然后再使用 Gson 工具来转换。

参考 GsonConverterFactory 来自定义 SoapConverterFactory:
需要定义三个文件

  • SoapConverterFactory : 定义请求和响应转换器
  • SoapRequestBodyConverter : 指定请求方式,生成请求体
  • SoapResponseBodyConverter : 获取响应数据,使用正则表达式获取 JSON 数据,再使用 Gson 转换成 Entity

SoapConverterFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SoapConverterFactory extends Converter.Factory {
private final Gson gson;
private SoapConverterFactory(Gson gson) {
this.gson = gson;
}
public static SoapConverterFactory create() {
return new SoapJsonFactory(new Gson());
}
public static SoapConverterFactory create(Gson gson) {
return new SoapJsonFactory(gson);
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new SoapResponseBodyConverter<>(adapter);
}
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
return new SoapRequestBodyConverter<>(gson, adapter);
}
}

SoapRequestBodyConverter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SoapRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.parse("application/soap+xml; charset=UTF-8");
// 此处对于 SOAP 请求需要指定 MediaType 为 application/soap+xml;
private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Gson gson;
private final TypeAdapter<T> adapter;
public SoapRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) {
this.gson = gson;
this.adapter = adapter;
}
@Override
public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
}
}

SoapResponseBodyConverter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SoapResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
public SoapResponseBodyConverter(TypeAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
try {
String responseString = value.string();
Pattern pat = Pattern.compile("Result>([\\s\\S]*?)" + "</");
// 使用正则表达式提取服务器返回的 JSON 数据;
Matcher m = pat.matcher(responseString);
String json;
if (m.find()) {
json = m.group(1);
} else {
json = "{\"error\":1,\"msg\":\"Error 001\"}";
}
Log.d("SoapResponseBodyConverter", json);
try {
return adapter.fromJson(json);
} catch (Exception e) {
Log.e("SoapResponseBodyConverter", e.getMessage());
return (T) json;
}
} finally {
value.close();
}
}
}

Over