diff --git a/ts-core/ts-ws-client/src/main/kotlin/cn/tursom/core/ws/WebSocketClient.kt b/ts-core/ts-ws-client/src/main/kotlin/cn/tursom/core/ws/WebSocketClient.kt
index 98dea5b..f735842 100644
--- a/ts-core/ts-ws-client/src/main/kotlin/cn/tursom/core/ws/WebSocketClient.kt
+++ b/ts-core/ts-ws-client/src/main/kotlin/cn/tursom/core/ws/WebSocketClient.kt
@@ -5,10 +5,7 @@ import cn.tursom.core.buffer.impl.NettyByteBuffer
 import io.netty.bootstrap.Bootstrap
 import io.netty.buffer.ByteBuf
 import io.netty.buffer.Unpooled
-import io.netty.channel.Channel
-import io.netty.channel.ChannelFuture
-import io.netty.channel.ChannelInitializer
-import io.netty.channel.EventLoopGroup
+import io.netty.channel.*
 import io.netty.channel.nio.NioEventLoopGroup
 import io.netty.channel.socket.SocketChannel
 import io.netty.channel.socket.nio.NioSocketChannel
@@ -37,7 +34,8 @@ class WebSocketClient(
   var initChannel: ((ch: SocketChannel) -> Unit)? = null
 ) {
   private val uri: URI = URI.create(url)
-  internal var ch: Channel? = null
+  var ch: Channel? = null
+    internal set
 
   fun open() {
     close()
@@ -111,10 +109,8 @@ class WebSocketClient(
       ch?.writeAndFlush(CloseWebSocketFrame())
     } else {
       ch?.writeAndFlush(CloseWebSocketFrame(WebSocketCloseStatus.NORMAL_CLOSURE, reasonText))
-    }
-    ch?.close()
+    }?.addListener(ChannelFutureListener.CLOSE)
     return ch?.closeFuture()
-    //ch?.closeFuture()?.sync()
   }
 
   fun write(text: String): ChannelFuture {
@@ -194,6 +190,6 @@ class WebSocketClient(
   }
 
   companion object {
-    private val group: EventLoopGroup = NioEventLoopGroup()
+    val group: EventLoopGroup = NioEventLoopGroup()
   }
 }
\ No newline at end of file
diff --git a/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt b/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt
index 46a7aa6..0db1360 100644
--- a/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt
+++ b/ts-web/ts-web-netty/src/main/kotlin/cn/tursom/web/netty/NettyHttpServer.kt
@@ -5,6 +5,7 @@ import cn.tursom.web.HttpHandler
 import cn.tursom.web.HttpServer
 import cn.tursom.web.WebSocketHandler
 import io.netty.bootstrap.ServerBootstrap
+import io.netty.buffer.ByteBufAllocator
 import io.netty.channel.ChannelFuture
 import io.netty.channel.ChannelInitializer
 import io.netty.channel.ChannelOption
@@ -16,6 +17,7 @@ import io.netty.handler.codec.http.HttpObject
 import io.netty.handler.codec.http.HttpObjectAggregator
 import io.netty.handler.codec.http.HttpServerCodec
 import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler
+import io.netty.handler.ssl.SslHandler
 import io.netty.handler.stream.ChunkedWriteHandler
 import io.netty.handler.timeout.ReadTimeoutHandler
 import io.netty.handler.timeout.WriteTimeoutHandler
@@ -30,9 +32,12 @@ class NettyHttpServer(
   var webSocketPath: Iterable<Pair<String, WebSocketHandler<NettyWebSocketContent>>> = listOf(),
   var readTimeout: Int? = 60,
   var writeTimeout: Int? = null,
-  decodeType: NettyHttpDecodeType = if (webSocketPath.iterator().hasNext()) NettyHttpDecodeType.FULL_HTTP else NettyHttpDecodeType.MULTI_PART,
+  decodeType: NettyHttpDecodeType = if (webSocketPath.iterator()
+      .hasNext()
+  ) NettyHttpDecodeType.FULL_HTTP else NettyHttpDecodeType.MULTI_PART,
   backlog: Int = 1024,
   val wrapWebSocketFrame: Boolean = false,
+  val sslHandlerBuilder: ((allocator: ByteBufAllocator) -> SslHandler)? = null,
 ) : HttpServer {
   constructor(
     port: Int,
@@ -41,16 +46,27 @@ class NettyHttpServer(
     webSocketPath: Iterable<Pair<String, WebSocketHandler<NettyWebSocketContent>>> = listOf(),
     readTimeout: Int? = 60,
     writeTimeout: Int? = null,
-    decodeType: NettyHttpDecodeType = if (webSocketPath.iterator().hasNext()) NettyHttpDecodeType.FULL_HTTP else NettyHttpDecodeType.MULTI_PART,
+    decodeType: NettyHttpDecodeType = if (webSocketPath.iterator()
+        .hasNext()
+    ) NettyHttpDecodeType.FULL_HTTP else NettyHttpDecodeType.MULTI_PART,
     backlog: Int = 1024,
     wrapWebSocketFrame: Boolean = false,
+    sslHandlerBuilder: ((allocator: ByteBufAllocator) -> SslHandler)? = null,
     handler: (content: NettyHttpContent) -> Unit,
   ) : this(
     port,
     object : HttpHandler<NettyHttpContent, NettyExceptionContent> {
       override fun handle(content: NettyHttpContent) = handler(content)
     },
-    bodySize, autoRun, webSocketPath, readTimeout, writeTimeout, decodeType, backlog, wrapWebSocketFrame
+    bodySize,
+    autoRun,
+    webSocketPath,
+    readTimeout,
+    writeTimeout,
+    decodeType,
+    backlog,
+    wrapWebSocketFrame,
+    sslHandlerBuilder
   )
 
   var decodeType: NettyHttpDecodeType = decodeType
@@ -73,6 +89,12 @@ class NettyHttpServer(
     .childHandler(object : ChannelInitializer<SocketChannel>() {
       override fun initChannel(ch: SocketChannel) {
         val pipeline = ch.pipeline()
+
+        val sslHandler = sslHandlerBuilder?.invoke(ch.alloc())
+        if (sslHandler != null) {
+          pipeline.addLast(sslHandler)
+        }
+
         readTimeout?.let {
           pipeline.addLast(ReadTimeoutHandler(it))
         }
@@ -97,22 +119,32 @@ class NettyHttpServer(
     .option(ChannelOption.SO_BACKLOG, backlog) // determining the number of connections queued
     .option(ChannelOption.SO_REUSEADDR, true)
     .childOption(ChannelOption.SO_KEEPALIVE, java.lang.Boolean.TRUE)
-  private val future: ChannelFuture = b.bind(port)
+  private var future: ChannelFuture? = null
 
   init {
     if (autoRun) run()
   }
 
   override fun run() {
+    if (future != null) return
+    future = b.bind(port)
     log?.warn("NettyHttpServer started on port {}", port)
-    log?.info("try http://localhost:{}/", port)
-    future.sync()
+    log?.info(
+      "try http{}://localhost:{}/",
+      if (sslHandlerBuilder == null) "" else "s",
+      when {
+        sslHandlerBuilder == null && port == 80 -> ""
+        sslHandlerBuilder != null && port == 443 -> ""
+        else -> port
+      }
+    )
+    future!!.sync()
   }
 
   override fun close() {
     log?.warn("NettyHttpServer({}) closed", port)
-    future.cancel(false)
-    future.channel().close()
+    future?.cancel(false)
+    future?.channel()?.close()
   }
 
   private fun updateHandler(): SimpleChannelInboundHandler<out HttpObject> {