diff --git a/ts-core/ts-async-http/build.gradle.kts b/ts-core/ts-async-http/build.gradle.kts
index 522cd35..b325add 100644
--- a/ts-core/ts-async-http/build.gradle.kts
+++ b/ts-core/ts-async-http/build.gradle.kts
@@ -10,9 +10,9 @@ dependencies {
   api(project(":ts-core:ts-buffer"))
   implementation(project(":ts-core:ts-xml"))
   api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
-  api(group = "com.squareup.retrofit2", name = "converter-gson", version = "2.9.0")
-  // https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit
-  api(group = "com.squareup.retrofit2", name = "retrofit", version = "2.9.0")
+  compileOnly("com.squareup.okhttp3:okhttp:4.9.1")
+  //api(group = "com.squareup.retrofit2", name = "converter-gson", version = "2.9.0")
+  //api(group = "com.squareup.retrofit2", name = "retrofit", version = "2.9.0")
 
   // https://mvnrepository.com/artifact/org.jsoup/jsoup
   api(group = "org.jsoup", name = "jsoup", version = "1.14.2")
diff --git a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/BlockingCallAdapterFactory.kt b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/BlockingCallAdapterFactory.kt
deleted file mode 100644
index c2ad949..0000000
--- a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/BlockingCallAdapterFactory.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package cn.tursom.http
-
-import retrofit2.Call
-import retrofit2.CallAdapter
-import retrofit2.Retrofit
-import java.lang.reflect.Type
-import java.util.concurrent.CompletableFuture
-
-object BlockingCallAdapterFactory : CallAdapter.Factory() {
-  override fun get(
-    returnType: Type,
-    annotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): CallAdapter<out Any?, out Any?>? {
-    if (getRawType(returnType) == Call::class.java) return null
-    if (getRawType(returnType) == CompletableFuture::class.java) return null
-    if (annotations.any { it is retrofit2.SkipCallbackExecutor }) return null
-    return object : CallAdapter<Any?, Any?> {
-      override fun responseType(): Type = returnType
-      override fun adapt(call: Call<Any?>): Any? = call.execute().body()
-    }
-  }
-}
\ No newline at end of file
diff --git a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/HtmlConverterFactory.kt b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/HtmlConverterFactory.kt
deleted file mode 100644
index 4711920..0000000
--- a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/HtmlConverterFactory.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package cn.tursom.http
-
-import cn.tursom.core.isInheritanceFrom
-import okhttp3.MediaType
-import okhttp3.RequestBody
-import okhttp3.ResponseBody
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Node
-import retrofit2.Converter
-import retrofit2.Retrofit
-import java.lang.reflect.Type
-
-object HtmlConverterFactory : Converter.Factory() {
-  override fun responseBodyConverter(
-    type: Type,
-    annotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): Converter<ResponseBody, out Node>? {
-    return if (type is Class<*> && Document::class.java.isInheritanceFrom(type)) {
-      DocumentResponseBodyConverter(retrofit.baseUrl().uri().toString())
-    } else {
-      null
-    }
-  }
-
-  override fun requestBodyConverter(
-    type: Type,
-    parameterAnnotations: Array<Annotation>,
-    methodAnnotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): Converter<in Node, RequestBody>? {
-    return if (type is Class<*> && type::class.java.isInheritanceFrom(Node::class.java)) {
-      NodeRequestBodyConverter
-    } else {
-      null
-    }
-  }
-
-  class DocumentResponseBodyConverter(
-    private val baseUri: String
-  ) : Converter<ResponseBody, Document> {
-    override fun convert(value: ResponseBody): Document {
-      return Jsoup.parse(value.string(), baseUri)
-    }
-  }
-
-  object NodeRequestBodyConverter : Converter<Node, RequestBody> {
-    override fun convert(value: Node): RequestBody {
-      return RequestBody.create(MediaType.parse("text/html; charset=utf-8"), value.outerHtml())
-    }
-  }
-}
\ No newline at end of file
diff --git a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/HttpRequest.kt b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/HttpRequest.kt
deleted file mode 100644
index 55b157c..0000000
--- a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/HttpRequest.kt
+++ /dev/null
@@ -1,175 +0,0 @@
-package cn.tursom.http
-
-import cn.tursom.core.buffer.ByteBuffer
-import cn.tursom.core.buffer.impl.HeapByteBuffer
-import java.io.InputStream
-import java.net.HttpURLConnection
-import java.net.URL
-import java.net.URLConnection
-import java.net.URLEncoder
-import java.nio.charset.Charset
-import java.util.zip.GZIPInputStream
-
-@Suppress("unused", "MemberVisibilityCanBePrivate")
-object HttpRequest {
-  val defaultHeader = mapOf(
-    "accept" to "*/*",
-    "connection" to "Keep-Alive",
-    "user-agent" to "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)",
-    "Accept-Encoding" to "gzip, deflate, sdch, br"
-  )
-
-  fun URLConnection.getCharset(): Charset {
-    val contentType = getHeaderField("content-type")
-    return if (contentType == null) {
-      Charsets.UTF_8
-    } else {
-      val startIndex = contentType.indexOf("charset=", ignoreCase = true) + 8
-      if (startIndex < 8) {
-        Charsets.UTF_8
-      } else {
-        var endIndex = contentType.indexOf(";", startIndex = startIndex, ignoreCase = true)
-        if (endIndex < 0) endIndex = contentType.length
-        if (startIndex == endIndex) {
-          Charsets.UTF_8
-        } else {
-          Charset.forName(contentType.substring(startIndex, endIndex))
-        }
-      }
-    }
-  }
-
-  fun URLConnection.getRealInputStream(): InputStream {
-    return if (getHeaderField("content-encoding")?.contains("gzip", true) == true) {
-      GZIPInputStream(inputStream)
-    } else {
-      inputStream
-    }
-  }
-
-  fun send(
-    method: String = "GET",
-    url: String,
-    headers: Map<String, String> = defaultHeader,
-    data: ByteBuffer? = null
-  ): HttpURLConnection {
-    val realUrl = URL(url)
-    val conn = realUrl.openConnection() as HttpURLConnection
-    headers.forEach { (key, value) ->
-      conn.setRequestProperty(key, value)
-    }
-    if (data != null) conn.doOutput = true
-    conn.doInput = true
-    conn.requestMethod = method
-
-    data?.let {
-      conn.outputStream.use { out ->
-        data.writeTo(out)
-        out.flush()
-      }
-    }
-
-    return conn
-  }
-
-  fun send(
-    method: String = "GET",
-    url: String,
-    headers: Map<String, String> = defaultHeader,
-    data: ByteArray?
-  ) = send(method, url, headers, data?.let { HeapByteBuffer(data) })
-
-  fun getContextStream(
-    method: String = "GET",
-    url: String,
-    headers: Map<String, String> = defaultHeader,
-    data: ByteBuffer? = null
-  ): InputStream = send(method, url, headers, data).inputStream
-
-  fun getContext(
-    method: String = "GET",
-    url: String,
-    headers: Map<String, String> = defaultHeader,
-    data: ByteBuffer? = null
-  ) = send(method, url, headers, data).getRealInputStream().readBytes()
-
-  fun getContextStr(
-    method: String = "GET",
-    url: String,
-    headers: Map<String, String> = defaultHeader,
-    data: ByteBuffer? = null
-  ): String {
-    val conn = send(method, url, headers, data)
-    return conn.getRealInputStream().readBytes().toString(conn.getCharset())
-  }
-
-  fun doGet(
-    url: String,
-    param: String? = null,
-    headers: Map<String, String> = defaultHeader
-  ): String = getContextStr(
-    "GET", if (param != null) {
-      "$url?$param"
-    } else {
-      url
-    }, headers
-  )
-
-  infix operator fun get(url: String): String = doGet(url, null)
-
-  fun doGet(
-    url: String,
-    param: Map<String, String>,
-    headers: Map<String, String> = defaultHeader
-  ): String {
-    val paramSB = StringBuilder()
-    return doGet(url, run {
-      param.forEach {
-        paramSB.append("${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}&")
-      }
-      if (paramSB.isNotEmpty()) paramSB.deleteCharAt(paramSB.lastIndex)
-      paramSB.toString()
-    }, headers)
-  }
-
-  fun doPost(
-    url: String,
-    data: ByteArray,
-    headers: Map<String, String> = defaultHeader
-  ): String = getContextStr("POST", url, headers, HeapByteBuffer(data))
-
-  fun doPost(
-    url: String,
-    param: Map<String, String>,
-    headers: Map<String, String> = defaultHeader
-  ): String {
-    val sb = StringBuilder()
-    param.forEach { (key, value) ->
-      sb.append("${URLEncoder.encode(key, "utf-8")}=${URLEncoder.encode(value, "utf-8")}&")
-    }
-    if (sb.isNotEmpty()) sb.deleteCharAt(sb.lastIndex)
-    return doPost(url, sb.toString().toByteArray(), headers)
-  }
-
-  fun doHead(
-    url: String,
-    param: String,
-    headers: Map<String, String> = defaultHeader
-  ): Map<String, List<String>> = send("HEAD", "$url?$param", headers).headerFields
-
-  fun doHead(
-    url: String,
-    param: Map<String, String>,
-    headers: Map<String, String> = defaultHeader
-  ): Map<String, List<String>> {
-    val paramSB = StringBuilder()
-    return doHead(url, run {
-      param.forEach {
-        paramSB.append("${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}&")
-      }
-      if (paramSB.isNotEmpty()) paramSB.deleteCharAt(paramSB.lastIndex)
-      paramSB.toString()
-    }, headers)
-  }
-
-}
\ No newline at end of file
diff --git a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/StringConverterFactory.kt b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/StringConverterFactory.kt
deleted file mode 100644
index 2baa5a4..0000000
--- a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/StringConverterFactory.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package cn.tursom.http
-
-import okhttp3.MediaType
-import okhttp3.RequestBody
-import okhttp3.ResponseBody
-import retrofit2.Converter
-import retrofit2.Retrofit
-import java.lang.reflect.Type
-
-object StringConverterFactory : Converter.Factory() {
-  override fun responseBodyConverter(
-    type: Type,
-    annotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): Converter<ResponseBody, *>? {
-    return if (type == String::class.java) {
-      StringResponseBodyConverter
-    } else {
-      null
-    }
-  }
-
-  override fun requestBodyConverter(
-    type: Type,
-    parameterAnnotations: Array<Annotation>,
-    methodAnnotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): Converter<*, RequestBody>? {
-    return if (type == String::class.java) {
-      StringRequestBodyConverter
-    } else {
-      null
-    }
-  }
-
-  object StringResponseBodyConverter : Converter<ResponseBody, String> {
-    override fun convert(value: ResponseBody): String? {
-      return value.string()
-    }
-  }
-
-  object StringRequestBodyConverter : Converter<String, RequestBody> {
-    override fun convert(value: String): RequestBody {
-      return RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), value)
-    }
-  }
-}
\ No newline at end of file
diff --git a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/XmlConverterFactory.kt b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/XmlConverterFactory.kt
deleted file mode 100644
index 6de7522..0000000
--- a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/XmlConverterFactory.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package cn.tursom.http
-
-import cn.tursom.core.isInheritanceFrom
-import cn.tursom.core.xml.Xml
-import okhttp3.MediaType
-import okhttp3.RequestBody
-import okhttp3.ResponseBody
-import org.dom4j.Document
-import org.dom4j.Node
-import retrofit2.Converter
-import retrofit2.Retrofit
-import java.lang.reflect.Type
-
-object XmlConverterFactory : Converter.Factory() {
-  override fun responseBodyConverter(
-    type: Type,
-    annotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): Converter<ResponseBody, out Node>? {
-    return if (type is Class<*> && Document::class.java.isInheritanceFrom(type)) {
-      DocumentResponseBodyConverter
-    } else {
-      null
-    }
-  }
-
-  override fun requestBodyConverter(
-    type: Type,
-    parameterAnnotations: Array<Annotation>,
-    methodAnnotations: Array<Annotation>,
-    retrofit: Retrofit
-  ): Converter<in Node, RequestBody>? {
-    return if (type is Class<*> && type.isInheritanceFrom(Node::class.java)) {
-      NodeRequestBodyConverter
-    } else {
-      null
-    }
-  }
-
-  object DocumentResponseBodyConverter : Converter<ResponseBody, Document> {
-    override fun convert(value: ResponseBody): Document {
-      return Xml.saxReader.read(value.string().reader())
-    }
-  }
-
-  object NodeRequestBodyConverter : Converter<Node, RequestBody> {
-    override fun convert(value: Node): RequestBody {
-      return RequestBody.create(MediaType.parse("text/xml; charset=utf-8"), value.asXML())
-    }
-  }
-}
\ No newline at end of file
diff --git a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/utils/AsyncHttpRequest.kt b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/client/AsyncHttpRequest.kt
similarity index 89%
rename from ts-core/ts-async-http/src/main/kotlin/cn/tursom/utils/AsyncHttpRequest.kt
rename to ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/client/AsyncHttpRequest.kt
index 2f5f4b5..e6a615f 100644
--- a/ts-core/ts-async-http/src/main/kotlin/cn/tursom/utils/AsyncHttpRequest.kt
+++ b/ts-core/ts-async-http/src/main/kotlin/cn/tursom/http/client/AsyncHttpRequest.kt
@@ -1,6 +1,7 @@
-package cn.tursom.utils
+package cn.tursom.http.client
 
 import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import java.io.File
 import java.io.IOException
 import java.net.InetSocketAddress
@@ -11,7 +12,6 @@ import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
 import kotlin.coroutines.suspendCoroutine
 
-
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 object AsyncHttpRequest {
   val defaultClient: OkHttpClient = OkHttpClient().newBuilder()
@@ -106,7 +106,7 @@ object AsyncHttpRequest {
     client: OkHttpClient = defaultClient
   ) = post(
     url,
-    RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), body),
+    RequestBody.create("text/plain;charset=utf-8".toMediaTypeOrNull(), body),
     headers,
     client
   )
@@ -118,7 +118,7 @@ object AsyncHttpRequest {
     client: OkHttpClient = defaultClient
   ) = post(
     url,
-    RequestBody.create(MediaType.parse("application/octet-stream"), body),
+    RequestBody.create("application/octet-stream".toMediaTypeOrNull(), body),
     headers,
     client
   )
@@ -130,7 +130,7 @@ object AsyncHttpRequest {
     client: OkHttpClient = defaultClient
   ) = post(
     url,
-    RequestBody.create(MediaType.parse("application/octet-stream"), body),
+    RequestBody.create("application/octet-stream".toMediaTypeOrNull(), body),
     headers,
     client
   )
@@ -151,7 +151,7 @@ object AsyncHttpRequest {
     client: OkHttpClient
   ): String {
     val response = get(url, param, headers, client)
-    return response.body()!!.string()
+    return response.body!!.string()
   }
 
   @Suppress("BlockingMethodInNonBlockingContext")
@@ -160,7 +160,7 @@ object AsyncHttpRequest {
     body: RequestBody,
     headers: Map<String, String>? = null,
     client: OkHttpClient
-  ): String = post(url, body, headers, client).body()!!.string()
+  ): String = post(url, body, headers, client).body!!.string()
 
   suspend fun postStr(
     url: String,
@@ -196,7 +196,7 @@ object AsyncHttpRequest {
     client: OkHttpClient
   ): String = postStr(
     url,
-    RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), body),
+    RequestBody.create("text/plain;charset=utf-8".toMediaTypeOrNull(), body),
     headers,
     client
   )
@@ -215,7 +215,7 @@ object AsyncHttpRequest {
     client: OkHttpClient
   ): String = postStr(
     url,
-    RequestBody.create(MediaType.parse("application/octet-stream"), body),
+    RequestBody.create("application/octet-stream".toMediaTypeOrNull(), body),
     headers,
     client
   )
@@ -237,7 +237,7 @@ object AsyncHttpRequest {
     param: Map<String, String>? = null,
     headers: Map<String, String>? = null,
     client: OkHttpClient
-  ): ByteArray = get(url, param, headers, client).body()!!.bytes()
+  ): ByteArray = get(url, param, headers, client).body!!.bytes()
 
 
   @Suppress("BlockingMethodInNonBlockingContext")
@@ -246,7 +246,7 @@ object AsyncHttpRequest {
     body: RequestBody,
     headers: Map<String, String>? = null,
     client: OkHttpClient
-  ): ByteArray = post(url, body, headers, client).body()!!.bytes()
+  ): ByteArray = post(url, body, headers, client).body!!.bytes()
 
 
   suspend fun postByteArray(
@@ -291,7 +291,7 @@ object AsyncHttpRequest {
     client: OkHttpClient
   ): ByteArray = postByteArray(
     url,
-    RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), body),
+    RequestBody.create("text/plain;charset=utf-8".toMediaTypeOrNull(), body),
     headers,
     client
   )
@@ -314,8 +314,8 @@ object AsyncHttpRequest {
     client: OkHttpClient
   ): ByteArray = postByteArray(
     url,
-    RequestBody.create(MediaType.parse("application/octet-stream"), body),
+    RequestBody.create("application/octet-stream".toMediaTypeOrNull(), body),
     headers,
     client
   )
-}
+}
\ No newline at end of file
diff --git a/ts-core/ts-ws-client/build.gradle.kts b/ts-core/ts-ws-client/build.gradle.kts
index 52bb39f..525eced 100644
--- a/ts-core/ts-ws-client/build.gradle.kts
+++ b/ts-core/ts-ws-client/build.gradle.kts
@@ -9,7 +9,7 @@ dependencies {
   api(project(":ts-core:ts-buffer"))
   api(project(":ts-core:ts-log"))
   compileOnly(project(":ts-socket"))
-  api(group = "io.netty", name = "netty-all", version = "4.1.67.Final")
+  api(group = "io.netty", name = "netty-all", version = nettyVersion)
 }
 
 
diff --git a/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt b/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt
index 56ee1d3..a467a60 100644
--- a/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt
+++ b/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpContent.kt
@@ -150,8 +150,10 @@ open class NettyHttpContent(
     if (log.traceEnabled) {
       log?.trace("write {}", message)
     }
-    getResponseBodyBuf().addComponent(Unpooled.wrappedBuffer(message.toByteArray()))
-    //responseBody.write(message.toByteArray())
+    val compositeByteBuf = getResponseBodyBuf()
+    val bytes = message.toByteArray()
+    compositeByteBuf.addComponent(Unpooled.wrappedBuffer(bytes))
+    compositeByteBuf.writerIndex(compositeByteBuf.writerIndex() + bytes.size)
   }
 
   override fun write(byte: Byte) {
diff --git a/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/TimeoutHandler.kt b/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/TimeoutHandler.kt
new file mode 100644
index 0000000..8d207b1
--- /dev/null
+++ b/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/TimeoutHandler.kt
@@ -0,0 +1,17 @@
+package cn.tursom.web.netty
+
+import io.netty.channel.ChannelHandler
+import io.netty.channel.ChannelHandlerContext
+import io.netty.handler.timeout.TimeoutException
+
+@ChannelHandler.Sharable
+object TimeoutHandler : ChannelHandler {
+  override fun handlerAdded(ctx: ChannelHandlerContext?) {}
+  override fun handlerRemoved(ctx: ChannelHandlerContext?) {}
+  override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) {
+    when (cause) {
+      is TimeoutException -> ctx.close()
+      else -> ctx.fireExceptionCaught(cause)
+    }
+  }
+}
\ No newline at end of file