Android 浅析 okHttp WebSocket (二) MockWebServer

前言

Linus Benedict Torvalds : RTFSC – Read The Funning Source Code

概括

MockWebServer, okhttp 的虚拟服务端,通过它可以在测试的时候搭建本地服务端。学习它的方法可以理解本的的服务端的搭建构造。

使用

首先我们再来详细了解 MockWebServer 的用法,加深我们对服务器的认识。

官方例子

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
40
41
42
43
44
45
46
// 引入 gradle
compile 'com.squareup.okhttp3:mockwebserver:3.8.0'
public void test() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new instance for every unit test.
MockWebServer server = new MockWebServer();
// Schedule some responses.
server.enqueue(new MockResponse().setBody("hello, world!"));
server.enqueue(new MockResponse().setBody("sup, bra?"));
server.enqueue(new MockResponse().setBody("yo dog"));
// Start the server.
server.start();
// Ask the server for its URL. You'll need this to make HTTP requests.
HttpUrl baseUrl = server.url("/v1/chat/");
// Exercise your application code, which should make those HTTP requests.
// Responses are returned in the same order that they are enqueued.
Chat chat = new Chat(baseUrl);
chat.loadMore();
assertEquals("hello, world!", chat.messages());
chat.loadMore();
chat.loadMore();
assertEquals(""
+ "hello, world!\n"
+ "sup, bra?\n"
+ "yo dog", chat.messages());
// Optional: confirm that your app made the HTTP requests you were expecting.
RecordedRequest request1 = server.takeRequest();
assertEquals("/v1/chat/messages/", request1.getPath());
assertNotNull(request1.getHeader("Authorization"));
RecordedRequest request2 = server.takeRequest();
assertEquals("/v1/chat/messages/2", request2.getPath());
RecordedRequest request3 = server.takeRequest();
assertEquals("/v1/chat/messages/3", request3.getPath());
// Shut down the server. Instances cannot be reused.
server.shutdown();
}

这段代码写的非常清楚,主要疑惑的地方在于Chat类,这个是需要自己去定义生成的。生成一个能够发送这个请求的类和接受请求结果。
我们可以简单的利用Volley来实现一个chat

1
2
3
4
5
6
7
8
9
10
Volley.newRequestQueue(context)
.add(new StringRequest(baseUrl.url().toString(), new com.android.volley.Response.Listener<String>() {
@Override
public void onResponse(String response) {
}
}, new com.android.volley.Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}));

MockResponse

MockResponse默认是状态码200,body为空,所有的协议内容都可以定制:

1
2
3
4
5
6
7
MockResponse response = new MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setBody("{}");
// 可以通过这个指令来模仿网速很慢的情况。
response.throttleBody(1024, 1, TimeUnit.SECONDS);

Dispatcher

Dispatcher通过dispatcher可以定制不同的url回传不同的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final Dispatcher dispatcher = new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
if (request.getPath().equals("/v1/login/auth/")){
return new MockResponse().setResponseCode(200);
} else if (request.getPath().equals("v1/check/version/")){
return new MockResponse().setResponseCode(200).setBody("version=9");
} else if (request.getPath().equals("/v1/profile/info")) {
return new MockResponse().setResponseCode(200).setBody(
"{\\\"info\\\":{\\\"name\":\"Lucas Albuquerque\",\"age\":\"21\",\"gender\":\"male\"}}");
}
return new MockResponse().setResponseCode(404);
}
};
server.setDispatcher(dispatcher);

小结

通过简单而全面的例子了解了MockWebServer的应用。接下来通过一步步深入我们来详尽的了解虚拟服务器。

结构

MockWebServer 内部结构非常简单,主要功能大部分还是依托于 okhttp 来实现。
大致可以分为三个部分:

  1. Dispatcher.java/QueueDispatcher.java
  2. MockResponse.java/RecordedRequest.java
  3. MockWebServer.java

其它的部分为使用的状态值和一些常量。

Part 1

Dispatcher 为虚拟服务器的任务分发类,通过生产者消费者的设计模式设计,结构简单明确。

Dispatcher

这是一个抽象类,封装了三个操作类:MockResponse dispatch()/MockResponse peek()/void shutdown()。

QueueDispatcher

Default dispatcher that processes a script of responses.它是一个基于队列的调度系统。它也是处理响应脚本的默认调度程序。

QueueDispatcher 的核心数据结构是使用了BlockingQueue实现。

1
protected final BlockingQueue<MockResponse> responseQueue = new LinkedBlockingQueue<>();

利用BlockingQueue可以省略很多线程等待的操作。

结束的标记是private static final MockResponse DEAD_LETTER = new MockResponse().setStatus("HTTP/1.1 " + 503 + " shutting down");,注意:在关机时启动,释放等待dispatch的线程。由于在返回此响应之前关闭连接,因此不会发送此响应。

Part 2

MockResponse 和 RecordedRequest 分别是服务器对接收的请求和回复的类。所有的请求和回复都基于这两个类来做应答。

MockResponse

A scripted response to be replayed by the mock web server. 一个脚本响应用来回复对虚拟服务器的请求。

默认的情况下会对请求者回复"HTTP/1.1 200 OK"
MockResponse 里面的作用基本上都是用来设置响应值,例如头部,内容等等,在里面比较关注的是设置了WebSocket的参数:public MockResponse withWebSocketUpgrade(WebSocketListener listener)。注意的是设置了这个参数会把所有先前设置的状态或内容改变。

SocketPolicy

对传入的套接字做什么的类型类。

在使用DISCONNECT_AT_ENDSHUTDOWN_INPUT_AT_ENDSHUTDOWN_OUTPUT_AT_END之类的值(响应后关闭套接字,以及后续请求)时,请务必小心。 客户端已解除阻止,并在收到完整响应正文后立即自由继续。 如果并且当客户端使用池套接字发出后续请求时,服务器可能没有时间关闭套接字。 在第二次请求之前或期间,套接字将在不确定的点关闭。 它可以在客户端开始发送请求正文后关闭。 如果请求主体不可重试,则客户端可能使请求失败,使得客户端行为是非确定性的。 在客户端中添加延迟,以提高服务器在执行后续请求之前关闭套接字的机会。

MockResponse 大致的用法就如前面提到的那样。里面封装了一套Buffer body,当客户端发起请求是将body传回过去。如果是使用WebSocket的方法则会通过listener的方式回调。

RecordedRequest

An HTTP request that came into the mock web server. 一个传入到 mockwebserver 的 Http 请求。

内部包含了请求的头部、正文和一些网络请求的信息。作为内部传输信息的请求类。包括了获取头部、主体和一些信息的接口。

Part 3

MockWebServer

A scriptable web server. Callers supply canned responses and the server replays them upon request in sequence. 这个是MockWebServer的核心处理类。主要就是负责开启一个虚拟的服务器来工作。

1. start()

MockWebServer 的入口从start函数开始。在里面会创建一条工作线程来进行等待,接着通过 ServerSocketFactory 提供的服务端类创建一个本地服务器。通过不断轮询的方法不停的接受客户端传进来的请求并处理后返回出去。最后在处理完成后通过强制结束来停止轮询并析构所有工作。

2. shutdown()

服务器的停止也比较暴力,直接将serverSocket 关闭,在主线程就会收到一个异常,通过异常直接来停止循环。

3. enqueue(MockResponse response)

服务器的回复可以通过在外部设置来添加特定的应答,这些应答会保存到 QueueDispatcher 里并且通过需要的时候传回给客户端。

4. acceptConnections()

主体操作函数,里面包含了所有对请求的处理和对请求的答复,过程复杂也相当枯燥,应为我们并不是全服务端解析,这章暂时略过…