抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

HttpURLConnection

HttpURLConnection 是 Java 标准库中用于进行 HTTP 请求的类,它提供了一种简便的方式来通过 HTTP 协议与服务器进行通信。HttpURLConnection 属于 java.net 包,可以处理各种 HTTP 请求方法(如 GET、POST、PUT、DELETE 等),并支持 HTTPS 等功能。

基本概念和工作原理

HttpURLConnection 是一个抽象类,继承自 URLConnection,它专门用于发起 HTTP 请求和接收响应。通过 HttpURLConnection,你可以发送请求到指定的 URL,并读取服务器返回的响应。

基本工作流程:

  1. 建立连接:创建 HttpURLConnection 对象,指定请求的 URL。

  2. 配置请求:设置 HTTP 请求方法(如 GET、POST)、请求头等。

  3. 发送请求:打开连接,发送请求内容。

  4. 处理响应:读取服务器的响应状态码和响应内容。

  5. 关闭连接:释放资源,关闭连接。

    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
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;

    public class HttpURLConnectionExample {
    public static void main(String[] args) {
    try {
    // 1. 创建 URL 对象
    URL url = new URL("https://api.example.com/data");

    // 2. 打开连接
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    // 3. 设置请求方法 (GET/POST/PUT/DELETE)
    connection.setRequestMethod("GET");

    // 4. 设置请求头(可选)
    connection.setRequestProperty("User-Agent", "Mozilla/5.0");
    connection.setRequestProperty("Accept-Language", "en-US,en;q=0.5");

    // 5. 发送请求并获取响应码
    int responseCode = connection.getResponseCode();
    System.out.println("Response Code: " + responseCode);

    // 6. 读取响应内容
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
    String inputLine;
    StringBuffer response = new StringBuffer();

    while ((inputLine = in.readLine()) != null) {
    response.append(inputLine);
    }
    in.close();

    // 7. 打印响应内容
    System.out.println(response.toString());

    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

HttpsURLConnection 的 GET 和 POST 请求

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.xiaojianbang.test;

import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Proxy;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;


public class HttpsUtils { //工具类,主要用于处理 HTTPS 请求
public static void doRequest(){ //创建一个新线程来执行请求,避免在主线程中进行网络操作
new Thread(){
public void run(){
String result = HttpsRequest("POST", "https://www.baidu.com/", "user"); //发起 POST 请求
Log.d("xiaojianbang","" + result);
}
}.start();
}

private static SSLContext getSSLContext() { //始化一个 SSLContext 对象,使用 TLS 协议
SSLContext sslContext = null;
try { //在异常捕获中打印堆栈跟踪,便于调试
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null,null, null);
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}

public static String HttpsRequest(String method, String url, String outputStr) {
//这是实际发送请求的核心方法,接受 HTTP 方法、URL 和输出字符串作为参数
try {
SSLContext sslContext = getSSLContext();
if (sslContext != null) {
URL u = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) u.openConnection(Proxy.NO_PROXY);

conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setConnectTimeout(30000);

if(method.equals("POST")){
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
}//对于 POST 请求,设置 Content-Type 为 application/x-www-form-urlencoded

if (null != outputStr) { //如果有输出字符串(仅在 POST 请求中),通过输出流发送数据
OutputStream outputStream = conn.getOutputStream();
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}

conn.connect();
//使用输入流读取服务器响应,并使用 BufferedReader 按行读取数据,将其存入 StringBuffer
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
} //关闭输入流和读取器,断开连接
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
}//返回服务器的响应字符串,如果发生异常则返回 null
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

}

HttpsURLConnection 的自吐

先开启 objection 的 hook

1
objection -g com.example.network explore
1
android hooking search classes HttpsURLConnection

1
android hooking watch class com.android.okhttp.internal.huc.HttpURLConnectionImpl

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
function hook_HttpUrlConnection(){

Java.perform(function(){
// java.net.URL.URL ($init) (得到 URL)

Java.use("java.net.URL").$init.overload('java.lang.String').implementation = function (str){
var result = this.$init(str)
console.log("result , str => ",result,str);
return result;
}

//HttpURLConnection setRequestProperty 得到各种请求头、属性等
Java.use("com.android.okhttp.internal.huc.HttpURLConnectionImpl").setRequestProperty.implementation = function(str1,str2){
var result = this.setRequestProperty(str1,str2);
console.log(".setRequestProperty result,str1,str2->",result,str1,str2);
return result;
}

Java.use("com.android.okhttp.internal.huc.HttpURLConnectionImpl").setRequestMethod.implementation = function(str1){
var result = this.setRequestMethod(str1);
console.log(".setRequestMethod result,str1,str2->",result,str1);
return result;
}

//

})
}
setImmediate(hook_HttpUrlConnection)

okhttp3

基础

OkHttp 是一个高效的 HTTP 和 HTTP/2 客户端,广泛用于 Android 和 Java 应用程序中。它由 Square 开发,提供了许多强大的功能和灵活的 API,适用于各种网络请求场景。

添加依赖

build.gradle 文件中添加 OkHttp 的依赖:

1
implementation 'com.squareup.okhttp3:okhttp:4.10.0'

创建 OkHttpClient

1
OkHttpClient client = new OkHttpClient();
1
2
3
4
java复制代码Interceptor loggingInterceptor = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build();

发送 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();

client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}

@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseData = response.body().string();
// 处理响应数据
}
}
});

发送 POST 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RequestBody requestBody = new FormBody.Builder()
.add("key1", "value1")
.add("key2", "value2")
.build();

Request request = new Request.Builder()
.url("https://api.example.com/submit")
.post(requestBody)
.build();

client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}

@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String responseData = response.body().string();
// 处理响应数据
}
}
});

添加拦截器

1
2
3
4
Interceptor loggingInterceptor = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build();

OkHttp3 的拦截器是一种可以在网络请求和响应过程中拦截、修改和处理 HTTP 请求和响应的机制。在 OkHttp 中,拦截器类似于一个过滤器,允许开发者在请求发出前或响应返回后执行自定义逻辑,从而实现日志记录、添加认证头、处理重试机制等功能。

拦截器主要分为两类:

  1. 应用拦截器(Application Interceptors)
  2. 网络拦截器(Network Interceptors)

应用拦截器(Application Interceptors)

应用拦截器会在整个请求和响应链条的顶端工作,并且只会被调用一次。它们可以用于修改最终请求和响应,还可以根据需要对请求进行重试或短路。应用拦截器不会处理来自缓存的响应,应用更适合用于日志记录、全局添加 Header、错误重试等场景。

添加应用拦截器示例:

1
2
3
4
5
6
7
8
val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.header("Authorization", "Bearer token")
.build()
chain.proceed(request)
}
.build()

网络拦截器(Network Interceptors)

网络拦截器在网络请求的过程中生效,每次 HTTP 请求都会调用它们,包括从缓存读取的请求。网络拦截器可以访问网络的实际响应状态、头信息等,更适用于监控和调试网络流量、处理缓存逻辑等。

添加网络拦截器示例:

1
2
3
4
5
6
7
val client = OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
// 在这里可以对响应数据进行处理或记录日志
response
}
.build()

如何使用多个拦截器

OkHttp 支持链式调用多个拦截器,拦截器按添加顺序依次执行。一个典型的应用是将应用拦截器用于预处理请求,而将网络拦截器用于网络流量的分析。

1
2
3
4
val client = OkHttpClient.Builder()
.addInterceptor(LoggingInterceptor()) // 应用拦截器,用于日志记录
.addNetworkInterceptor(CachingInterceptor()) // 网络拦截器,用于缓存处理
.build()

hook

自定义拦截器 hook

用 spawn 方式启动 app
frida -U –no-pause -f xxx -l hook.js

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
function hook_okhttp3() {
// 1. frida Hook java 层的代码必须包裹在 Java.perform 中,Java.perform 会将 Hook Java 相关 API 准备就绪。
Java.perform(function () {

// 2. 准备相应类库,用于后续调用,前两个库是 Android 自带类库,后三个是使用 Okhttp 网络库的情况下才有的类
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
var Buffer = Java.use("com.android.okhttp.okio.Buffer");
var Interceptor = Java.use("okhttp3.Interceptor");
var ArrayList = Java.use("java.util.ArrayList");
var OkHttpClient = Java.use("okhttp3.OkHttpClient");

// 注册一个 Java 类
var MyInterceptor = Java.registerClass({
name: "okhttp3.MyInterceptor",
implements: [Interceptor],
methods: {
intercept: function (chain) {
var request = chain.request();
try {
console.log("MyInterceptor.intercept onEnter:", request, "\nrequest headers:\n", request.headers());
var requestBody = request.body();
var contentLength = requestBody ? requestBody.contentLength() : 0;
if (contentLength > 0) {
var BufferObj = Buffer.$new();
requestBody.writeTo(BufferObj);
try {
console.log("\nrequest body String:\n", BufferObj.readString(), "\n");
} catch (error) {
try {
console.log("\nrequest body ByteString:\n", ByteString.of(BufferObj.readByteArray()).hex(), "\n");
} catch (error) {
console.log("error 1:", error);
}
}
}
} catch (error) {
console.log("error 2:", error);
}
var response = chain.proceed(request);
try {
console.log("MyInterceptor.intercept onLeave:", response, "\nresponse headers:\n", response.headers());
var responseBody = response.body();
var contentLength = responseBody ? responseBody.contentLength() : 0;
if (contentLength > 0) {
console.log("\nresponsecontentLength:", contentLength, "responseBody:", responseBody, "\n");

var ContentType = response.headers().get("Content-Type");
console.log("ContentType:", ContentType);
if (ContentType.indexOf("video") == -1) {
if (ContentType.indexOf("application") == 0) {
var source = responseBody.source();
if (ContentType.indexOf("application/zip") != 0) {
try {
console.log("\nresponse.body StringClass\n", source.readUtf8(), "\n");
} catch (error) {
try {
console.log("\nresponse.body ByteString\n", source.readByteString().hex(), "\n");
} catch (error) {
console.log("error 4:", error);
}
}
}
}

}

}

} catch (error) {
console.log("error 3:", error);
}
return response;
}
}
});

OkHttpClient.$init.overload('okhttp3.OkHttpClient$Builder').implementation = function (Builder) {
console.log("OkHttpClient.$init:", this, Java.cast(Builder.interceptors(), ArrayList));
this.$init(Builder);
};

var MyInterceptorObj = MyInterceptor.$new();
var Builder = Java.use("okhttp3.OkHttpClient$Builder");
console.log(Builder);
Builder.build.implementation = function () {
this.interceptors().clear();
this.interceptors().add(MyInterceptorObj);
var result = this.build();
return result;
};

Builder.addInterceptor.implementation = function (interceptor) {
this.interceptors().clear();
this.interceptors().add(MyInterceptorObj);
return this;
};

console.log("hook_okhttp3...");
});
}

//hook_okhttp3();
setImmediate(hook_okhttp3);

Frida 提供了如下 API 用于将 DEX 加载进内存,从而使用 DEX 中的方法和类,因为 DEX 是外来之物,因此称为天外飞仙。(需要注意的是,无法加载 JAR 包):

1
Java.openClassFile(dexPath).load();

(1).解压 app-debug.apk 取出 classes1.dex 文件(其中有目标类)

(2).push 到/data/local/tmp 下

(3).修改 Frida Hook 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function hook_okhttp3() {
// 1. frida Hook java 层的代码必须包裹在 Java.perform 中,Java.perform 会将 Hook Java 相关 API 准备就绪。
Java.perform(function () {

Java.openClassFile("/data/local/tmp/okhttp3logging.dex.dex").load();
// 只修改了这一句,换句话说,只是使用不同的拦截器对象。
var MyInterceptor = Java.use("com.roysue.octolesson2ok3.okhttp3Logging");

var MyInterceptorObj = MyInterceptor.$new();
var Builder = Java.use("okhttp3.OkHttpClient$Builder");
console.log(Builder);
Builder.build.implementation = function () {
this.networkInterceptors().add(MyInterceptorObj);
console.log("hook Build.build successfully !")
return this.build();
};
console.log("hooking_okhttp3...");
});
}

hook_okhttp3();

(4).Frida Hook,在此之前将 DEMO 中 OKhttpclient 的添加拦截器代码注销,以防干扰,查看 Android Studio 日志,拦截器是否生效。

官方拦截器:(日志)

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package com.r0ysue.learnokhttp;

/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import android.util.Log;

import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;


public final class okhttp3Logging implements Interceptor {
private static final String TAG = "okhttpGET";

private static final Charset UTF8 = Charset.forName("UTF-8");

@Override public Response intercept(Chain chain) throws IOException {

Request request = chain.request();

RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;

Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url();
Log.e(TAG, requestStartMessage);

if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
Log.e(TAG, "Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
Log.e(TAG, "Content-Length: " + requestBody.contentLength());
}
}

Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
Log.e(TAG, name + ": " + headers.value(i));
}
}

if (!hasRequestBody) {
Log.e(TAG, "--> END " + request.method());
} else if (bodyHasUnknownEncoding(request.headers())) {
Log.e(TAG, "--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);

Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}

Log.e(TAG, "");
if (isPlaintext(buffer)) {
Log.e(TAG, buffer.readString(charset));
Log.e(TAG, "--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
Log.e(TAG, "--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}


long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
Log.e(TAG, "<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);

ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
Log.e(TAG, "<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + response.message())
+ ' ' + response.request().url()
+ " (" + tookMs + "ms" + (", " + bodySize + " body:" + "") + ')');

Headers myheaders = response.headers();
for (int i = 0, count = myheaders.size(); i < count; i++) {
Log.e(TAG, myheaders.name(i) + ": " + myheaders.value(i));
}

if (!HttpHeaders.hasBody(response)) {
Log.e(TAG, "<-- END HTTP");
} else if (bodyHasUnknownEncoding(response.headers())) {
Log.e(TAG, "<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();

Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(myheaders.get("Content-Encoding"))) {
gzippedLength = buffer.size();
GzipSource gzippedResponseBody = null;
try {
gzippedResponseBody = new GzipSource(buffer.clone());
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
} finally {
if (gzippedResponseBody != null) {
gzippedResponseBody.close();
}
}
}

Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}

if (!isPlaintext(buffer)) {
Log.e(TAG, "");
Log.e(TAG, "<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}

if (contentLength != 0) {
Log.e(TAG, "");
Log.e(TAG, buffer.clone().readString(charset));
}

if (gzippedLength != null) {
Log.e(TAG, "<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)");
} else {
Log.e(TAG, "<-- END HTTP (" + buffer.size() + "-byte body)");
}
}

return response;
}

/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}

private boolean bodyHasUnknownEncoding(Headers myheaders) {
String contentEncoding = myheaders.get("Content-Encoding");
return contentEncoding != null
&& !contentEncoding.equalsIgnoreCase("identity")
&& !contentEncoding.equalsIgnoreCase("gzip");
}
}

Retrofit

Retrofit 是由 Square 公司开发的一个类型安全的 HTTP 客户端库,主要用于 Android 和 Java 应用程序,通过将 REST API 转化为 Java 接口,大大简化了网络请求的实现过程。Retrofit 使用动态代理、注解等机制,让网络请求的定义更直观,并通过与 Gson、Moshi 等序列化工具的集成,支持数据的自动解析与转换。Retrofit 就是 Ok3
所有搞 Ok3 的都可以搞 Retrofit

Retrofit 的核心工作原理

  • 接口设计:在 Retrofit 中,每一个 HTTP 请求都通过一个 Java 接口的方法来定义。每个方法代表一个 API 端点,并通过注解来指定请求类型(如 GET、POST)和 URL 路径。
  • 动态代理:Retrofit 使用动态代理模式实现接口定义的网络请求,简化了实际代码量。开发者仅需关注接口定义,无需手动实现网络请求。
  • 数据解析与转换:Retrofit 支持多种序列化工具(如 Gson、Moshi)将 API 响应的 JSON 数据自动转换成 Java 对象,提升了代码的可维护性。

主要注解类型

  • @GET@POST@PUT@DELETE 等:指定 HTTP 请求方法类型。
  • @Path:用于 URL 路径参数的动态替换,例如 /users/{userId}
  • @Query:用于在 URL 中添加查询参数,例如 ?key=value
  • @Body:用于 POST 请求的请求体数据。
  • @FormUrlEncoded@Field:用于表单编码请求(application/x-www-form-urlencoded)。
  • @Multipart@Part:用于多部分请求,适用于文件上传等场景。
  • @Headers:用于指定静态的请求头信息。

构建 Retrofit 实例

使用 Retrofit.Builder 设置基础配置并生成 Retrofit 实例:

1
2
3
4
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/") // 设置基础 URL
.addConverterFactory(GsonConverterFactory.create()) // JSON 解析器
.build();
  • **baseUrl()**:设置基础 URL,所有接口定义的相对 URL 会以此为基础。
  • **addConverterFactory()**:添加解析器(如 Gson)以处理响应数据的解析。
  • **client()**:可设置自定义的 OkHttpClient,用来配置超时、缓存、日志拦截等。

API 接口定义

例如,假设我们需要从 https://api.example.com/users/{id} 获取用户信息,可以定义如下接口:

1
2
3
4
public interface ApiService {
@GET("users/{id}")
Call<User> getUser(@Path("id") int userId);
}

然后使用 Retrofit 创建实例并调用方法:

1
2
ApiService apiService = retrofit.create(ApiService.class);
Call<User> call = apiService.getUser(1);

异步与同步请求

  • 同步请求:通过 Call.execute() 来实现,但会阻塞当前线程,通常不推荐在主线程使用。
  • 异步请求:通过 Call.enqueue() 实现,传入回调函数处理响应或错误,避免阻塞主线程。

数据解析

Retrofit 支持多种数据转换工厂(Converter Factory),常用的包括:

  • GsonConverterFactory:将 JSON 响应转换成 Java 对象。
  • MoshiConverterFactory:支持 Moshi 作为数据解析工具。
  • ScalarsConverterFactory:直接将原始数据作为 Stringint 类型返回。

Retrofit 与 OkHttp

Retrofit 基于 OkHttp 构建,允许与 OkHttp 的拦截器、缓存机制、连接池等进行集成和优化。开发者可以通过自定义 OkHttpClient 来增强 Retrofit 的功能,比如添加日志拦截器、身份验证拦截器等。

Retrofit 基于 OkHttp 构建,允许与 OkHttp 的拦截器、缓存机制、连接池等进行集成和优化。开发者可以通过自定义 OkHttpClient 来增强 Retrofit 的功能,比如添加日志拦截器、身份验证拦截器等。

错误处理

Retrofit 提供 onFailure() 回调捕获请求异常。同时,响应对象的 isSuccessful() 方法可以判断请求是否成功,通常结合 try-catch 进行更细致的错误处理。

hook

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
function hookbaseurl (){
Java.perform(function(){
Java.use("retrofit2.Retrofit$Builder").baseUrl.overload('java.lang.String').implementation = function(str){
console.log("Entering 1")
var result = this.baseUrl(str);
console.log("result,str=>",result,str)
return result;
}
Java.use("retrofit2.Retrofit$Builder").baseUrl.overload('okhttp3.HttpUrl').implementation = function(str){
console.log("Entering 1")
var result = this.baseUrl(str);
console.log("result,str=>",result,str)
return result;
}

})
}


function hook_DailyNews(){
Java.perform(function(){
console.log("Entering java perform ")
Java.use("ganhuo.ly.com.ganhuo.mvp.entity.DailyNews").getDailyTitle.implementation = function(){
console.log("Entering getDailyNews")
var result = this.getDailyTitle()
console.log("result,str=>",result)
return result;
}
})
}
setImmediate(hook_DailyNews)

SOCKET 自吐

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
function hook_socket(){
Java.perform(function(){
var ByteString = Java.use("com.android.okhttp.okio.ByteString");// 使用 ByteString 类来将数据转换为十六进制格式,以便更容易查看。
//代码重写了 java.net.SocketOutputStream 中的 socketWrite0 方法。
Java.use("java.net.SocketOutputStream").socketWrite0.implementation = function(fd,bytearray,int1,int2){
var result = this.socketWrite0(fd,bytearray,int1,int2);
console.log("socketWrite0 fd,bytearray,int1,int2,result => ",fd,bytearray,int1,int2,result)
// console.log(ByteString.of(bytearray).hex());
return result;
}//java.net.SocketInputStream 的 socketRead0 方法被重写。
Java.use("java.net.SocketInputStream").socketRead0.implementation = function(fd,bytearray,int1,int2,int3){
var result = this.socketRead0(fd,bytearray,int1,int2,int3)
console.log("socketRead0 fd,bytearray,int1,int2,int3,result =>",fd,bytearray,int1,int2,int3,result)
console.log(ByteString.of(bytearray).hex());//ByteString.of(bytearray).hex() 将读取的字节数组以十六进制显示,从而观察接收的数据
return result;
}
})
}

function hook_sslsocket(){/该函数用于拦截 SSL socket 传输中的读写操作。SSL socket 是加密传输,通常用于 HTTPS 等场景。
Java.perform(function(){ //com.android.org.conscrypt.NativeCrypto 的 SSL_write 方法被重写。
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
Java.use("com.android.org.conscrypt.NativeCrypto").SSL_write.implementation = function(long,NS,fd,NC,bytearray,int1,int2,int3){
var result = this .SSL_write(long,NS,fd,NC,bytearray,int1,int2,int3);
console.log("SSL_write(long,NS,fd,NC,bytearray,int1,int2,int3),result=>",long,NS,fd,NC,bytearray,int1,int2,int3,result)
console.log(ByteString.of(bytearray).hex());
return result;
}
Java.use("com.android.org.conscrypt.NativeCrypto").SSL_read.implementation = function(long,NS,fd,NC,bytearray,int1,int2,int3){
var result = this .SSL_read(long,NS,fd,NC,bytearray,int1,int2,int3);
console.log("SSL_read(long,NS,fd,NC,bytearray,int1,int2,int3),result=>",long,NS,fd,NC,bytearray,int1,int2,int3,result)
console.log(ByteString.of(bytearray).hex());
return result;
}
})
}

setImmediate(hook_sslsocket)

评论