From 6052adebd9c41edf7a44e77abdeb5ed01a526e8c Mon Sep 17 00:00:00 2001
From: tursom <tursom@foxmail.com>
Date: Wed, 10 Jan 2024 23:50:57 +0800
Subject: [PATCH] add ts-core/ts-http

---
 build.gradle.kts                              |   6 +-
 gradle.properties                             |   2 +-
 settings.gradle.kts                           |   1 +
 .../kotlin/cn/tursom/core/util/concurrent.kt  |  42 +++
 ts-core/ts-http/README.md                     |  25 ++
 ts-core/ts-http/build.gradle.kts              |   6 +
 .../cn/tursom/http/client/OkHttpDelete.kt     | 112 ++++++++
 .../kotlin/cn/tursom/http/client/OkHttpGet.kt |  30 +++
 .../cn/tursom/http/client/OkHttpPatch.kt      | 239 +++++++++++++++++
 .../cn/tursom/http/client/OkHttpPost.kt       | 152 +++++++++++
 .../kotlin/cn/tursom/http/client/OkHttpPut.kt | 240 ++++++++++++++++++
 .../kotlin/cn/tursom/http/client/okhttp.kt    | 105 ++++++++
 .../test/kotlin/cn/tursom/proxy/Example.kt    |   2 +-
 .../kotlin/cn/tursom/gradle/PublishPlugin.kt  |   6 -
 .../kotlin/cn/tursom/gradle/Dependencies.kt   |  10 +
 15 files changed, 967 insertions(+), 11 deletions(-)
 create mode 100644 ts-core/src/main/kotlin/cn/tursom/core/util/concurrent.kt
 create mode 100644 ts-core/ts-http/README.md
 create mode 100644 ts-core/ts-http/build.gradle.kts
 create mode 100644 ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpDelete.kt
 create mode 100644 ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpGet.kt
 create mode 100644 ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPatch.kt
 create mode 100644 ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPost.kt
 create mode 100644 ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPut.kt
 create mode 100644 ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/okhttp.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index 72a51e3..47cfc1e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,6 +13,9 @@ plugins {
 }
 
 allprojects {
+  group = "cn.tursom"
+  version = "1.1-SNAPSHOT"
+
   apply(plugin = "org.jetbrains.kotlin.jvm")
   apply(plugin = "maven-publish")
   apply(plugin = "ts-gradle-env")
@@ -21,9 +24,6 @@ allprojects {
   apply(plugin = "ts-gradle-publish")
   apply(plugin = "ts-gradle-repos")
 
-  group = "cn.tursom"
-  version = "1.1-SNAPSHOT"
-
   tasks.withType<KotlinCompile>().configureEach {
     kotlinOptions.jvmTarget = "21"
     kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
diff --git a/gradle.properties b/gradle.properties
index c436ebe..e44792b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,4 +21,4 @@ android.enableJetifier=true
 kotlin.code.style=official
 
 project.groupId=cn.tursom
-project.version=1.0-SNAPSHOT
+project.version=1.1-SNAPSHOT
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2f9bdf3..47c530b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -19,6 +19,7 @@ include("ts-core:ts-yaml")
 include("ts-core:ts-json")
 include("ts-core:ts-xml")
 include("ts-core:ts-async-http")
+include("ts-core:ts-http")
 include("ts-core:ts-proxy")
 include("ts-core:ts-proxy-jdk")
 include("ts-core:ts-reflect")
diff --git a/ts-core/src/main/kotlin/cn/tursom/core/util/concurrent.kt b/ts-core/src/main/kotlin/cn/tursom/core/util/concurrent.kt
new file mode 100644
index 0000000..d4a8c16
--- /dev/null
+++ b/ts-core/src/main/kotlin/cn/tursom/core/util/concurrent.kt
@@ -0,0 +1,42 @@
+package cn.tursom.core.util
+
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+
+inline fun <V> withFuture(callback: (future: CompletableFuture<V>) -> Unit): V {
+  val future = CompletableFuture<V>()
+  callback(future)
+  return future.get()
+}
+
+inline fun <V> withFuture(
+  timeout: Long,
+  timeUnit: TimeUnit,
+  callback: (future: CompletableFuture<V>) -> Unit,
+): V {
+  val future = CompletableFuture<V>()
+  callback(future)
+  return future.get(timeout, timeUnit)
+}
+
+fun <R> withTimeout(
+  timeout: Long,
+  timeUnit: TimeUnit,
+  callback: () -> R,
+): R {
+  val future = CompletableFuture<R>()
+
+  val execThread = Thread.ofVirtual()
+    .start {
+      future.complete(callback())
+    }
+
+  try {
+    return future.get(timeout, timeUnit)
+  } catch (t: Throwable) {
+    execThread.interrupt()
+    future.cancel(true)
+
+    throw t
+  }
+}
diff --git a/ts-core/ts-http/README.md b/ts-core/ts-http/README.md
new file mode 100644
index 0000000..1df1faf
--- /dev/null
+++ b/ts-core/ts-http/README.md
@@ -0,0 +1,25 @@
+# ts-http 模块
+## 简介
+ts-http 模块负责提供同步的 http 请求接口。目前我们仅对 OkHttp 进行了少量封装,以提高易用性。
+
+封装的功能包括:
+1. DSL 支持。DSL 形式的请求构建结构更加清晰,易于维护。
+2. json 解析。这部分需要引入 GSON 依赖。
+3. 其他接口封装。如 Call.str()、Call.bytes() 等。
+
+## 功能描述
+### DSL 支持
+ts-http 模块提供轻量级的 DSL 封装。提供的方法有:
+- ```Call.Factory.newCall(builder: Request.Builder.() -> Unit): Call``` 用于构建新请求
+- ```WebSocket.Factory.newWebSocket(listener: WebSocketListener, builder: Request.Builder.() -> Unit): WebSocket``` 用于构建 WebSocket 请求
+- ```<B : Request.Builder> B.url(url: String? = null, builder: HttpUrl.Builder.() -> Unit): B``` 用于构建请求 url
+- ```form(builder: FormBody.Builder.() -> Unit): FormBody``` 用于构建 form 请求体
+- ```FormBody.Builder.build(builder: FormBody.Builder.() -> Unit): FormBody``` 同上
+
+### json 解析
+ts-http 模块使用 GSON 提供 json 解析功能,考虑到有些项目可能不希望引入 GSON 依赖,因此没有自动引入 GSON。用户如果希望使用此功能,需要手动引入 GSON 依赖。由于 ts-http 只使用了 GSON 最基础的功能,因此对 GSON 的版本没有过高要求,只要有```T fromJson(String json, Class<T> classOfT)```,```T fromJson(String json, Type typeOfT)```和```com.google.gson.reflect.TypeToken<T>```即可。
+
+ts-http 为 Call 和 ResponseBody 分别添加了```inline fun <reified T : Any> json(): T```和```inline fun <reified T : Any> jsonTyped(): T```这两个方法,前者直接使用```T::class.java```获取 Class 对象,后者则使用 TypeToken 获取 Type 对象,用户可以根据需求使用不同的方法。
+
+### 其他接口封装
+ts-http 还为 GET 和 POST 单独封装了额外的接口以便于使用。
diff --git a/ts-core/ts-http/build.gradle.kts b/ts-core/ts-http/build.gradle.kts
new file mode 100644
index 0000000..26ad97f
--- /dev/null
+++ b/ts-core/ts-http/build.gradle.kts
@@ -0,0 +1,6 @@
+dependencies {
+  api(project(":"))
+  api(project(":ts-core"))
+  api(group = "com.squareup.okhttp3", name = "okhttp", version = "4.12.0")
+  compileOnly(group = "io.netty", name = "netty-all", version = nettyVersion)
+}
diff --git a/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpDelete.kt b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpDelete.kt
new file mode 100644
index 0000000..411d56a
--- /dev/null
+++ b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpDelete.kt
@@ -0,0 +1,112 @@
+package cn.tursom.http.client
+
+import okhttp3.Call
+import okhttp3.Request
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import java.io.File
+
+@JvmOverloads
+@OkhttpMaker
+inline fun Call.Factory.delete(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): Response = newCall {
+  delete(body())
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.delete(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): Response = newCall {
+  delete(body)
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.delete(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): Response = delete(url, headers) {
+  form {
+    add(param)
+  }
+}
+
+@JvmOverloads
+fun Call.Factory.delete(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+) = delete(url, headers) {
+  body.toRequestBody()
+}
+
+@JvmOverloads
+fun Call.Factory.delete(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+) = delete(url, headers) {
+  body.toRequestBody()
+}
+
+@JvmOverloads
+fun Call.Factory.delete(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+) = delete(url, headers) {
+  body.asRequestBody()
+}
+
+@JvmOverloads
+fun Call.Factory.delete(
+  url: String,
+  param: Map<String, String?>? = null,
+  headers: Map<String, String>? = null,
+): Response = newCall {
+  url(url) {
+    addQueryParameters(param)
+  }
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.deleteStr(
+  url: String,
+  param: Map<String, String>? = null,
+  headers: Map<String, String>? = null,
+): String = delete(url, param, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.deleteByteArray(
+  url: String,
+  param: Map<String, String>? = null,
+  headers: Map<String, String>? = null,
+): ByteArray = delete(url, param, headers).body!!.bytes()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.deleteJson(
+  url: String,
+  param: Map<String, String>? = null,
+  headers: Map<String, String>? = null,
+): String = delete(url, param, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.deleteJsonTyped(
+  url: String,
+  param: Map<String, String>? = null,
+  headers: Map<String, String>? = null,
+): T = delete(url, param, headers).body!!.jsonTyped()
+
+
diff --git a/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpGet.kt b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpGet.kt
new file mode 100644
index 0000000..df42d73
--- /dev/null
+++ b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpGet.kt
@@ -0,0 +1,30 @@
+package cn.tursom.http.client
+
+import okhttp3.Call
+import okhttp3.Response
+
+@JvmOverloads
+fun Call.Factory.get(
+  url: String,
+  param: Map<String, String?>? = null,
+  headers: Map<String, String>? = null,
+): Response = newCall {
+  url(url) {
+    addQueryParameters(param)
+  }
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.getStr(
+  url: String,
+  param: Map<String, String>? = null,
+  headers: Map<String, String>? = null,
+): String = get(url, param, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.getByteArray(
+  url: String,
+  param: Map<String, String>? = null,
+  headers: Map<String, String>? = null,
+): ByteArray = get(url, param, headers).body!!.bytes()
diff --git a/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPatch.kt b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPatch.kt
new file mode 100644
index 0000000..2f4bdd8
--- /dev/null
+++ b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPatch.kt
@@ -0,0 +1,239 @@
+package cn.tursom.http.client
+
+import okhttp3.Call
+import okhttp3.Request
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import java.io.File
+
+@JvmOverloads
+@OkhttpMaker
+inline fun Call.Factory.patch(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): Response = newCall {
+  put(body())
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.patch(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): Response = newCall {
+  put(body)
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.patch(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): Response = patch(url, headers) {
+  form {
+    add(param)
+  }
+}
+
+@JvmOverloads
+fun Call.Factory.patch(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+) = patch(url, headers) {
+  body.toRequestBody()
+}
+
+@JvmOverloads
+fun Call.Factory.patch(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+) = patch(url, headers) {
+  body.toRequestBody()
+}
+
+@JvmOverloads
+fun Call.Factory.patch(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+) = patch(url, headers) {
+  body.asRequestBody()
+}
+
+@JvmOverloads
+fun Call.Factory.patchStr(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): String = patch(url, headers, body).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.patchStr(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): String = patch(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.patchStr(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): String = patch(url, param, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.patchStr(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): String = patch(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.patchStr(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): String = patch(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.patchStr(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): String = patch(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.patchBytes(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): ByteArray = patch(url, headers, body).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.patchBytes(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): ByteArray = patch(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.patchBytes(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): ByteArray = patch(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.patchBytes(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): ByteArray = patch(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.patchBytes(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): ByteArray = patch(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.patchBytes(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): ByteArray = patch(url, param, headers).body!!.bytes()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJson(
+  url: String,
+  json: String,
+  headers: Map<String, String>? = null,
+): T = patch(url, json, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJson(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): T = patch(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJson(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): T = patch(url, param, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJson(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): T = patch(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJson(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): T = patch(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJson(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): T = patch(url, headers, body).body!!.json()
+
+// patchJsonTyped
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJsonTyped(
+  url: String,
+  json: String,
+  headers: Map<String, String>? = null,
+): T = patch(url, json, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJsonTyped(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): T = patch(url, body, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJsonTyped(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): T = patch(url, param, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJsonTyped(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): T = patch(url, body, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJsonTyped(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): T = patch(url, body, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.patchJsonTyped(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): T = patch(url, headers, body).body!!.jsonTyped()
diff --git a/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPost.kt b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPost.kt
new file mode 100644
index 0000000..0755b2e
--- /dev/null
+++ b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPost.kt
@@ -0,0 +1,152 @@
+package cn.tursom.http.client
+
+import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
+
+@JvmOverloads
+@OkhttpMaker
+inline fun Call.Factory.post(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): Response = newCall {
+  post(body())
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.post(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): Response = newCall {
+  post(body)
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.post(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): Response = post(url, headers) {
+  form {
+    add(param)
+  }
+}
+
+@JvmOverloads
+fun Call.Factory.post(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+) = post(url, headers) {
+  body.toRequestBody("text/plain;charset=utf-8".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.post(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+) = post(url, headers) {
+  body.asRequestBody("application/octet-stream".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.post(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+) = post(url, headers) {
+  body.toRequestBody("application/octet-stream".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+@OkhttpMaker
+inline fun Call.Factory.postStr(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): String = post(url, headers, body).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.postStr(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): String = post(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.postStr(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): String = postStr(url, headers) {
+  FormBody.Builder().add(param).build()
+}
+
+@JvmOverloads
+fun Call.Factory.postStr(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): String = postStr(url, headers) {
+  body.toRequestBody("text/plain;charset=utf-8".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.postStr(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): String = postStr(url, headers) {
+  body.asRequestBody("application/octet-stream".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+@OkhttpMaker
+inline fun Call.Factory.postByteArray(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): ByteArray = post(url, headers, body).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.postByteArray(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): ByteArray = post(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.postByteArray(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): ByteArray = postByteArray(url, headers) {
+  FormBody.Builder().add(param).build()
+}
+
+@JvmOverloads
+fun Call.Factory.postByteArray(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): ByteArray = postByteArray(url, headers) {
+  body.toRequestBody("text/plain;charset=utf-8".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.postByteArray(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): ByteArray = postByteArray(url, headers) {
+  body.asRequestBody("application/octet-stream".toMediaTypeOrNull())
+}
diff --git a/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPut.kt b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPut.kt
new file mode 100644
index 0000000..30fefe5
--- /dev/null
+++ b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/OkHttpPut.kt
@@ -0,0 +1,240 @@
+package cn.tursom.http.client;
+
+import okhttp3.Call
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.Request
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import java.io.File
+import kotlin.jvm.JvmOverloads;
+
+@JvmOverloads
+@OkhttpMaker
+inline fun Call.Factory.put(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): Response = newCall {
+  put(body())
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.put(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): Response = newCall {
+  put(body)
+  url(url)
+  addHeaders(headers)
+}.execute()
+
+@JvmOverloads
+fun Call.Factory.put(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): Response = put(url, headers) {
+  form {
+    add(param)
+  }
+}
+
+@JvmOverloads
+fun Call.Factory.put(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+) = put(url, headers) {
+  body.toRequestBody("text/plain;charset=utf-8".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.put(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+) = put(url, headers) {
+  body.asRequestBody("application/octet-stream".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.put(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+) = put(url, headers) {
+  body.toRequestBody("application/octet-stream".toMediaTypeOrNull())
+}
+
+@JvmOverloads
+fun Call.Factory.putStr(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): String = put(url, headers, body).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.putStr(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): String = put(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.putStr(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): String = put(url, param, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.putStr(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): String = put(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.putStr(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): String = put(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.putStr(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): String = put(url, body, headers).body!!.string()
+
+@JvmOverloads
+fun Call.Factory.putBytes(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): ByteArray = put(url, headers, body).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.putBytes(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): ByteArray = put(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.putBytes(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): ByteArray = put(url, param, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.putBytes(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): ByteArray = put(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.putBytes(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): ByteArray = put(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+fun Call.Factory.putBytes(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): ByteArray = put(url, body, headers).body!!.bytes()
+
+@JvmOverloads
+inline fun <reified T : Any> Call.Factory.putJson(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): T = put(url, headers, body).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJson(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJson(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): T = put(url, param, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJson(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJson(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJson(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.json()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJsonTyped(
+  url: String,
+  headers: Map<String, String>? = null,
+  body: Request.Builder.() -> RequestBody,
+): T = put(url, headers, body).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJsonTyped(
+  url: String,
+  body: RequestBody,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJsonTyped(
+  url: String,
+  param: Map<String, String>,
+  headers: Map<String, String>? = null,
+): T = put(url, param, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJsonTyped(
+  url: String,
+  body: String,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJsonTyped(
+  url: String,
+  body: File,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.jsonTyped()
+
+@JvmOverloads
+inline fun <reified T : Any>  Call.Factory.putJsonTyped(
+  url: String,
+  body: ByteArray,
+  headers: Map<String, String>? = null,
+): T = put(url, body, headers).body!!.jsonTyped()
diff --git a/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/okhttp.kt b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/okhttp.kt
new file mode 100644
index 0000000..42bbd76
--- /dev/null
+++ b/ts-core/ts-http/src/main/kotlin/cn/tursom/http/client/okhttp.kt
@@ -0,0 +1,105 @@
+@file:Suppress("unused")
+
+package cn.tursom.http.client
+
+import cn.tursom.core.util.Utils.gson
+import cn.tursom.core.util.fromJson
+import cn.tursom.core.util.fromJsonTyped
+import okhttp3.*
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import java.net.InetSocketAddress
+import java.net.Proxy
+import java.net.SocketAddress
+
+@DslMarker
+@Retention(AnnotationRetention.BINARY)
+annotation class OkhttpMaker
+
+@OkhttpMaker
+object Okhttp : Call.Factory, WebSocket.Factory {
+  val direct: OkHttpClient = OkHttpClient().newBuilder()
+    .retryOnConnectionFailure(true)
+    .build()
+  val socket: OkHttpClient = proxy()
+
+  var default: OkHttpClient = direct
+
+  override fun newCall(request: Request): Call = default.newCall(request)
+  override fun newWebSocket(request: Request, listener: WebSocketListener): WebSocket =
+    default.newWebSocket(request, listener)
+
+  @JvmOverloads
+  fun proxy(
+    host: String = "127.0.0.1",
+    port: Int = 1080,
+    type: Proxy.Type = Proxy.Type.SOCKS,
+    builder: OkHttpClient.Builder = OkHttpClient().newBuilder(),
+  ): OkHttpClient = builder
+    .proxy(Proxy(type, InetSocketAddress(host, port) as SocketAddress))
+    .retryOnConnectionFailure(true)
+    .build()
+}
+
+@OkhttpMaker
+inline fun <B : Request.Builder> B.url(
+  url: String? = null,
+  builder: HttpUrl.Builder.() -> Unit,
+): B = apply {
+  val urlBuilder = url?.toHttpUrl()?.newBuilder() ?: HttpUrl.Builder()
+
+  urlBuilder.builder()
+  url(urlBuilder.build())
+}
+
+fun <B : Request.Builder> B.addHeaders(headers: Map<String, String>?): B = apply {
+  headers?.forEach { (k, v) ->
+    addHeader(k, v)
+  }
+}
+
+@OkhttpMaker
+inline fun form(builder: FormBody.Builder.() -> Unit): FormBody =
+  FormBody.Builder().build(builder)
+
+fun HttpUrl.Builder.addQueryParameters(params: Map<String, String?>?) {
+  params?.forEach { (k, v) ->
+    addQueryParameter(k, v)
+  }
+}
+
+@OkhttpMaker
+inline infix fun FormBody.Builder.build(builder: FormBody.Builder.() -> Unit): FormBody {
+  val form = FormBody.Builder()
+  form.builder()
+  return form.build()
+}
+
+fun FormBody.Builder.add(forms: Map<String, String>?) = apply {
+  forms?.forEach { (k, v) ->
+    add(k, v)
+  }
+}
+
+fun FormBody.Builder.addEncoded(forms: Map<String, String>?) = apply {
+  forms?.forEach { (k, v) ->
+    addEncoded(k, v)
+  }
+}
+
+fun Call.str() = execute().body!!.string()
+fun Call.bytes() = execute().body!!.bytes()
+inline fun <reified T : Any> Call.json(): T = execute().body!!.json<T>()
+inline fun <reified T : Any> Call.jsonTyped(): T = execute().body!!.jsonTyped<T>()
+
+@OkhttpMaker
+inline fun WebSocket.Factory.newWebSocket(
+  listener: WebSocketListener,
+  builder: Request.Builder.() -> Unit,
+): WebSocket = newWebSocket(Request.Builder().apply(builder).build(), listener)
+
+@OkhttpMaker
+inline fun Call.Factory.newCall(builder: Request.Builder.() -> Unit) =
+  newCall(Request.Builder().apply(builder).build())
+
+inline fun <reified T : Any> ResponseBody.json(): T = gson.fromJson<T>(string())
+inline fun <reified T : Any> ResponseBody.jsonTyped(): T = gson.fromJsonTyped<T>(string())
diff --git a/ts-core/ts-proxy/src/test/kotlin/cn/tursom/proxy/Example.kt b/ts-core/ts-proxy/src/test/kotlin/cn/tursom/proxy/Example.kt
index e26ca98..5939755 100644
--- a/ts-core/ts-proxy/src/test/kotlin/cn/tursom/proxy/Example.kt
+++ b/ts-core/ts-proxy/src/test/kotlin/cn/tursom/proxy/Example.kt
@@ -83,7 +83,7 @@ class Example {
 
   @Test
   fun benchmark() {
-    val (t, container) = Proxy.get<TestClass>()
+    val (t, container) = Proxy.get<TestClass>(useDirectAccessor = true)
     container.addProxy(GetA(t))
 
     println(t.javaClass)
diff --git a/ts-gradle/ts-gradle-publish/src/main/kotlin/cn/tursom/gradle/PublishPlugin.kt b/ts-gradle/ts-gradle-publish/src/main/kotlin/cn/tursom/gradle/PublishPlugin.kt
index 117227e..f5e96a8 100644
--- a/ts-gradle/ts-gradle-publish/src/main/kotlin/cn/tursom/gradle/PublishPlugin.kt
+++ b/ts-gradle/ts-gradle-publish/src/main/kotlin/cn/tursom/gradle/PublishPlugin.kt
@@ -51,12 +51,6 @@ private fun Project.createTursomPublishRepository(repositoryHandler: RepositoryH
         uri("https://jmp.mvn.tursom.cn:20080/repository/maven-releases/")
       }
 
-      repository.authentication{ ac ->
-        ac.forEach {
-
-        }
-      }
-
       repository.credentials(PasswordCredentials::class.java) { credentials ->
         val artifactoryUser: String = rootProject.ext["tursom.artifactoryUser"]!!.toString()
         val artifactoryPassword: String = rootProject.ext["tursom.artifactoryPassword"]!!.toString()
diff --git a/ts-gradle/ts-gradle-repos/src/main/kotlin/cn/tursom/gradle/Dependencies.kt b/ts-gradle/ts-gradle-repos/src/main/kotlin/cn/tursom/gradle/Dependencies.kt
index 9ce99c7..c53cdfa 100644
--- a/ts-gradle/ts-gradle-repos/src/main/kotlin/cn/tursom/gradle/Dependencies.kt
+++ b/ts-gradle/ts-gradle-repos/src/main/kotlin/cn/tursom/gradle/Dependencies.kt
@@ -123,6 +123,7 @@ val DependencyHandler.`ts-delegation` get() = tursomServer("ts-delegation")
 val DependencyHandler.`ts-observer` get() = tursomServer("ts-observer")
 val DependencyHandler.`ts-encrypt` get() = tursomServer("ts-encrypt")
 val DependencyHandler.`ts-hash` get() = tursomServer("ts-hash")
+val DependencyHandler.`ts-http` get() = tursomServer("ts-http")
 val DependencyHandler.`ts-json` get() = tursomServer("ts-json")
 val DependencyHandler.`ts-log` get() = tursomServer("ts-log")
 val DependencyHandler.`ts-mail` get() = tursomServer("ts-mail")
@@ -192,6 +193,10 @@ fun DependencyHandler.`ts-hash`(
   dependencyConfiguration: Action<ExternalModuleDependency>? = null
 ) = tursomServer("ts-hash", dependencyConfiguration = dependencyConfiguration)
 
+fun DependencyHandler.`ts-http`(
+  dependencyConfiguration: Action<ExternalModuleDependency>? = null
+) = tursomServer("ts-http", dependencyConfiguration = dependencyConfiguration)
+
 fun DependencyHandler.`ts-json`(
   dependencyConfiguration: Action<ExternalModuleDependency>? = null
 ) = tursomServer("ts-json", dependencyConfiguration = dependencyConfiguration)
@@ -284,6 +289,7 @@ val DependencyHandler.ts_delegation get() = tursomServer("ts-delegation")
 val DependencyHandler.ts_observer get() = tursomServer("ts-observer")
 val DependencyHandler.ts_encrypt get() = tursomServer("ts-encrypt")
 val DependencyHandler.ts_hash get() = tursomServer("ts-hash")
+val DependencyHandler.ts_http get() = tursomServer("ts-http")
 val DependencyHandler.ts_json get() = tursomServer("ts-json")
 val DependencyHandler.ts_log get() = tursomServer("ts-log")
 val DependencyHandler.ts_mail get() = tursomServer("ts-mail")
@@ -353,6 +359,10 @@ fun DependencyHandler.ts_hash(
   dependencyConfiguration: Action<ExternalModuleDependency>? = null
 ) = tursomServer("ts-hash", dependencyConfiguration = dependencyConfiguration)
 
+fun DependencyHandler.ts_http(
+  dependencyConfiguration: Action<ExternalModuleDependency>? = null
+) = tursomServer("ts-http", dependencyConfiguration = dependencyConfiguration)
+
 fun DependencyHandler.ts_json(
   dependencyConfiguration: Action<ExternalModuleDependency>? = null
 ) = tursomServer("ts-json", dependencyConfiguration = dependencyConfiguration)