OkCurl分析模块
模块概述
OkCurl是一个基于OkHttp的curl克隆工具,使用Kotlin编写。它提供了类似curl的命令行接口,但使用OkHttp作为底层HTTP客户端,从而能够利用OkHttp的HTTP引擎(包括HTTP/2)来测试Web服务器。
项目结构
OkCurl项目的主要代码结构如下:
okcurl/
├── src/
│ ├── main/
│ │ └── kotlin/
│ │ └── okhttp3/
│ │ └── curl/
│ │ ├── logging/
│ │ │ ├── LoggingUtil.kt # 日志配置工具
│ │ │ ├── MessageFormatter.kt # 消息格式化器
│ │ │ └── OneLineLogFormat.kt # 单行日志格式
│ │ ├── internal/
│ │ │ └── -MainCommon.kt # Main类的通用方法实现
│ │ ├── Main.kt # 主类,处理命令行参数和执行请求
│ │ └── MainCommandLine.kt # 程序入口点
│ └── test/
│ └── kotlin/
│ └── okhttp3/
│ └── curl/
│ └── MainTest.kt # Main类的单元测试
└── build.gradle.kts # 构建配置文件
核心功能
OkCurl提供了以下核心功能:
-
命令行参数处理:支持类似curl的命令行选项,如:
-X:指定HTTP方法(GET、POST、PUT等)-d:指定POST数据-H:添加HTTP请求头-A:设置User-Agent-e:设置Referer- 以及其他超时、重定向、SSL设置等选项
-
HTTP请求执行:
- 创建和配置OkHttpClient
- 构建HTTP请求
- 执行请求并处理响应
- 输出响应状态、响应头和响应体
-
日志记录:
- 支持详细的HTTP请求和响应日志
- 支持HTTP/2帧的日志
- 支持SSL调试日志
-
安全选项:
- 支持配置SSL证书验证
- 支持配置主机名验证
实现细节
命令行处理
OkCurl使用Clikt库处理命令行参数。在Main.kt中,各种命令行选项被定义为属性,并使用Clikt的DSL进行配置:
private val method by option("-X", "--request", help = "Specify request command to use")
private val data by option("-d", "--data", help = "HTTP POST data")
private val headers by option("-H", "--header", help = "Custom header to pass to server").multiple()
// 更多选项...
HTTP客户端创建
在Main.kt的createClient方法中,根据命令行选项创建和配置OkHttpClient:
fun createClient(): OkHttpClient {
val builder = OkHttpClient.Builder()
// 配置超时
if (connectTimeout != null) {
builder.connectTimeout(connectTimeout!!)
}
// 配置重定向
if (followRedirects) {
builder.followRedirects(true)
builder.followSslRedirects(true)
}
// 配置SSL
if (insecure) {
builder.sslSocketFactory(createInsecureSslSocketFactory(), createInsecureTrustManager())
builder.hostnameVerifier(createInsecureHostnameVerifier())
}
// 配置日志
if (verbose) {
val logging = HttpLoggingInterceptor(logger)
logging.level = HttpLoggingInterceptor.Level.HEADERS
builder.addNetworkInterceptor(logging)
}
return builder.build()
}
请求创建
在-MainCommon.kt的commonCreateRequest方法中,根据命令行选项创建HTTP请求:
fun commonCreateRequest(): Request {
// 创建请求构建器
val requestBuilder = Request.Builder()
// 设置URL
requestBuilder.url(url!!)
// 设置请求方法和请求体
var requestMethod = method
if (requestMethod == null) {
requestMethod = if (data != null) "POST" else "GET"
}
// 设置请求头
for (header in headers) {
val parts = header.split(':', limit = 2)
val name = parts[0].trim()
val value = parts[1].trim()
if (!isSpecialHeader(name)) {
requestBuilder.header(name, value)
}
}
// 设置特殊请求头
if (referer != null) {
requestBuilder.header("Referer", referer!!)
}
if (userAgent != null) {
requestBuilder.header("User-Agent", userAgent!!)
} else {
requestBuilder.header("User-Agent", versionString())
}
// 创建请求体
if (data != null) {
val contentType = mediaType()
val requestBody = RequestBody.create(contentType, data!!)
requestBuilder.method(requestMethod, requestBody)
} else {
requestBuilder.method(requestMethod, null)
}
return requestBuilder.build()
}
请求执行和响应处理
在-MainCommon.kt的commonRun方法中,执行请求并处理响应:
fun commonRun(): Int {
// 创建HTTP客户端
val client = createClient()
// 创建请求
val request = createRequest()
// 执行请求
val response = client.newCall(request).execute()
// 处理响应
if (showHeaders) {
println("${response.protocol} ${response.code} ${response.message}")
for ((name, value) in response.headers) {
println("$name: $value")
}
println()
}
// 输出响应体
response.body!!.source().use { source ->
val sink = Okio.buffer(Okio.sink(System.out))
sink.writeAll(source)
sink.emit()
}
// 关闭资源
response.body!!.close()
client.close()
return 0
}
日志记录
在LoggingUtil.kt中,配置日志记录:
fun configureLogging(debug: Boolean, showHttp2Frames: Boolean, sslDebug: Boolean) {
if (debug) {
val rootLogger = getLogger("")
rootLogger.level = Level.FINE
val handler = ConsoleHandler()
handler.formatter = OneLineLogFormat()
handler.level = Level.FINE
rootLogger.addHandler(handler)
}
if (showHttp2Frames) {
val http2Logger = getLogger("okhttp3.internal.http2")
http2Logger.level = Level.FINE
val handler = ConsoleHandler()
handler.formatter = MessageFormatter
handler.level = Level.FINE
http2Logger.addHandler(handler)
}
if (sslDebug) {
val sslLogger = getLogger("javax.net.ssl")
sslLogger.level = Level.FINE
val handler = ConsoleHandler()
handler.formatter = MessageFormatter
handler.level = Level.FINE
sslLogger.addHandler(handler)
}
}
依赖关系
OkCurl项目的主要依赖包括:
- OkHttp:作为底层HTTP客户端
- Okio:用于I/O操作
- Clikt:用于命令行解析
- Kotlin标准库:Kotlin语言支持
构建配置
OkCurl使用Gradle构建系统,主要配置包括:
-
插件:
- Kotlin JVM插件
- Dokka(Kotlin文档生成工具)
- Maven发布插件
- Shadow JAR插件(用于创建包含所有依赖的可执行JAR)
-
任务:
- 复制资源模板文件,并替换变量(如项目版本)
-
JAR配置:
- 设置模块名为okhttp3.curl
- 设置主类为okhttp3.curl.MainCommandLineKt
-
GraalVM配置(如果Java版本>=11):
- 配置本地镜像名称为okcurl
- 设置主类为okhttp3.curl.MainCommandLineKt
测试方法
OkCurl使用JUnit和assertk库进行单元测试。测试用例包括:
- 简单GET请求:验证默认请求方法和URL
- PUT请求:验证PUT方法和请求体
- 带数据的POST请求:验证POST方法、Content-Type和请求体
- 带数据的PUT请求:验证PUT方法、Content-Type和请求体
- 带Content-Type头的请求:验证自定义Content-Type
- 带Referer头的请求:验证Referer头
- 带User-Agent头的请求:验证User-Agent头
- 默认User-Agent:验证默认User-Agent头
- 带日期格式的头:验证带日期格式的头
总结
OkCurl是一个功能完善的curl克隆工具,它利用OkHttp的强大功能提供了类似curl的命令行接口。它支持各种HTTP方法、请求头、请求体、超时设置、重定向、SSL设置等功能,并提供了详细的日志记录功能。通过使用Kotlin语言和现代库(如Clikt、OkHttp、Okio),OkCurl提供了一个简洁、高效的HTTP客户端工具。