背景
最近在項(xiàng)目測試,經(jīng)常出現(xiàn) qa 發(fā)現(xiàn)客戶端有 bug 后晃虫,由于沒有鏈接 charles 代理皆撩,無法定位是客戶端問題還是服務(wù)端問題。所以最近做了一款輕量的網(wǎng)絡(luò)請求抓取 應(yīng)用哲银,用于抓取網(wǎng)絡(luò)請求所有數(shù)據(jù)以及后續(xù)可實(shí)現(xiàn)請求數(shù)據(jù)的修改以及過濾扛吞。
實(shí)現(xiàn)方案
使用
- 在項(xiàng)目中添加依賴
// littleproxy及其依賴
implementation('net.lightbody.bmp:littleproxy:1.1.0-beta-bmp-17') {
exclude group: 'io.netty'
}
implementation files('libs/netty-all-android-4.0.44.Final.jar')
- 核心代碼
啟動(dòng)代理類CustomProxyServer.java
package e.lengku8e.myapplication.catchnet;
import org.littleshoot.proxy.HttpFilters;
import org.littleshoot.proxy.HttpFiltersSource;
import org.littleshoot.proxy.HttpProxyServer;
import org.littleshoot.proxy.HttpProxyServerBootstrap;
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
import java.util.ArrayList;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpRequest;
public class CustomProxyServer {
private static volatile CustomProxyServer mInstance;
private CustomProxyServer() {
}
private HttpProxyServer httpProxyServer;
public static CustomProxyServer getInstance() {
if (mInstance == null) {
synchronized (CustomProxyServer.class) {
if (mInstance == null) {
mInstance = new CustomProxyServer();
}
}
}
return mInstance;
}
public ArrayList<MyResponse> responseArrayList = new ArrayList<>();
public void start(int port) {
try {
HttpProxyServerBootstrap bootstrap = DefaultHttpProxyServer.bootstrap().withPort(port)
.withFiltersSource(new HttpFiltersSource() {
@Override
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext channelHandlerContext) {
return new MyHttpFiltersAdapter(CustomProxyServer.this, originalRequest, channelHandlerContext, responseArrayList);
}
@Override
public int getMaximumRequestBufferSizeInBytes() {
return 20 * 1024 * 1024; // 每個(gè)網(wǎng)絡(luò)請求和響應(yīng)都是分次傳輸?shù)腂uf容量)
}
@Override
public int getMaximumResponseBufferSizeInBytes() {
return 20 * 1024 * 1024;
}
});
httpProxyServer = bootstrap.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
if (httpProxyServer != null) {
httpProxyServer.stop();
}
}
}
請求 filter adapter 類
MyHttpFiltersAdapter.java
public class MyHttpFiltersAdapter extends HttpFiltersAdapter {
@Override
public HttpObject proxyToClientResponse(HttpObject httpObject) {
Log.d("MyHttpFiltersAdapter", "proxyToClientResponse");
return super.proxyToClientResponse(httpObject);
}
@Override
public HttpResponse proxyToServerRequest(HttpObject httpObject) {
Log.d("MyHttpFiltersAdapter", "proxyToServerRequest");
return super.proxyToServerRequest(httpObject);
}
@Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
Log.d("MyHttpFiltersAdapter", "clientToProxyRequest");
return null;
}
@Override
public HttpObject serverToProxyResponse(HttpObject httpObject) {
Log.d("MyHttpFiltersAdapter", "serverToProxyResponse");
return httpObject;
}
這里只介紹下HttpFiltersAdapter的4個(gè)關(guān)鍵性函數(shù)的作用
-
clientToProxyRequest
這個(gè)函數(shù)中可以獲取請求的uri以及header以及 post 中的參數(shù), 默認(rèn)返回null荆责,表示不攔截滥比,若返回?cái)?shù)據(jù),則不再走proxyToClientResponse
和serverToProxyResponse
,這個(gè)函數(shù)中可以修改返回的數(shù)據(jù) -
proxyToServerRequest
這個(gè)函數(shù)和上邊的函數(shù)類似 -
serverToProxyResponse
這里默認(rèn)返回傳入?yún)?shù)做院,這里可以對返回值做一定的修改 -
proxyToClientResponse
這里默認(rèn)返回傳入?yún)?shù)盲泛,可以做一定的修改
發(fā)送一條網(wǎng)絡(luò)請求并接收返回結(jié)構(gòu),打印調(diào)用的日志如下
06-18 15:50:37.118 28402 28560 D MyHttpFiltersAdapter: clientToProxyRequest
06-18 15:50:37.120 28402 28560 D MyHttpFiltersAdapter: proxyToServerRequest
06-18 15:50:37.159 28402 28517 D MyHttpFiltersAdapter: serverToProxyResponse
06-18 15:50:37.160 28402 28517 D MyHttpFiltersAdapter: proxyToClientResponse
遺留問題
- 上邊的實(shí)現(xiàn)目前只支持了 http 請求的抓取键耕,如果想要抓 https則還需要對 https 解包寺滚,然后再生成自己的ssl正數(shù)進(jìn)行加密簽名。https 的抓取后續(xù)會繼續(xù)進(jìn)行研究屈雄。