Skip to content

Commit

Permalink
Develop Netty HTTP client
Browse files Browse the repository at this point in the history
  • Loading branch information
jaguililla committed Jun 11, 2024
1 parent 261339d commit e8eb78d
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,14 @@ package com.hexagonkt.http.client.netty
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.handler.codec.http.FullHttpResponse
import io.netty.handler.codec.http.HttpUtil
import io.netty.handler.codec.http.LastHttpContent
import io.netty.util.CharsetUtil
import java.util.concurrent.CompletableFuture

class HttpClientResponseHandler : SimpleChannelInboundHandler<FullHttpResponse>() {

lateinit var response: FullHttpResponse
var response: CompletableFuture<FullHttpResponse> = CompletableFuture()

override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpResponse) {
System.err.println("STATUS: " + msg.status())
System.err.println("VERSION: " + msg.protocolVersion())
System.err.println()
response = msg

if (!msg.headers().isEmpty) {
for (name in msg.headers().names()) {
for (value in msg.headers().getAll(name)) {
System.err.println("HEADER: $name = $value")
}
}
System.err.println()
}

if (HttpUtil.isTransferEncodingChunked(msg)) {
System.err.println("CHUNKED CONTENT {")
} else {
System.err.println("CONTENT {")
}

System.err.print(msg.content().toString(CharsetUtil.UTF_8))
System.err.flush()

if (msg is LastHttpContent) {
System.err.println("} END OF CONTENT")
ctx.close()
}
response.complete(msg)
ctx.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ open class NettyClientAdapter(

val context = channel.pipeline().context(HttpClientResponseHandler::class.java)
val responseHandler = context.handler() as HttpClientResponseHandler
return createResponse(responseHandler.response)
return createResponse(responseHandler.response.join())
}

override fun ws(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,20 @@ package com.hexagonkt.http.client.netty

import com.hexagonkt.http.client.HttpClient
import com.hexagonkt.http.model.HttpRequest
import com.hexagonkt.http.server.HttpServerSettings
import com.hexagonkt.http.server.jetty.serve
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelInitializer
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.codec.http.*
import io.netty.util.CharsetUtil
import org.junit.jupiter.api.Test
import kotlin.IllegalStateException
import java.net.URI
import java.util.concurrent.CompletableFuture
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

Expand All @@ -16,4 +28,86 @@ internal class NettyClientAdapterTest {
val expectedMessage = "HTTP client *MUST BE STARTED* before sending requests"
assertEquals(expectedMessage, message)
}

fun sendHttpRequest(uri: URI): String? {

serve(HttpServerSettings(bindPort = 2001)) {
get {
ok("foo")
}
}

val group = NioEventLoopGroup()
val responseContent = CompletableFuture<String>()

try {
val bootstrap = Bootstrap()
bootstrap
.group(group)
.channel(NioSocketChannel::class.java)
.handler(object : ChannelInitializer<SocketChannel>() {
override fun initChannel(ch: SocketChannel) {
ch.pipeline().addLast(HttpClientCodec())
ch.pipeline().addLast(HttpObjectAggregator(8192))
ch.pipeline().addLast(
object : SimpleChannelInboundHandler<FullHttpResponse>() {
override fun channelRead0(
ctx: io.netty.channel.ChannelHandlerContext,
msg: FullHttpResponse
) {
responseContent.complete(
msg.content().toString(CharsetUtil.UTF_8)
)
ctx.close()
}

override fun exceptionCaught(
ctx: io.netty.channel.ChannelHandlerContext,
cause: Throwable
) {
responseContent.completeExceptionally(cause)
ctx.close()
}
}
)
}
})

// Make the connection attempt
val channel =
bootstrap.connect(uri.host, if (uri.port == -1) 80 else uri.port).sync().channel()

// Prepare the HTTP request
val request = DefaultFullHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, uri.rawPath, Unpooled.EMPTY_BUFFER
)

// Set HTTP headers
request.headers().set(HttpHeaderNames.HOST, uri.host)

// Send the HTTP request
channel.writeAndFlush(request).sync()

// Close the channel gracefully
channel.closeFuture().sync()

// Wait for the response
responseContent.join()
} finally {
// Shut down the event loop to terminate all threads
group.shutdownGracefully()
}

return responseContent.get()
}

@Test fun test() {
try {
val uri = URI("http://localhost:2001")
val response = sendHttpRequest(uri)
println("Response received: $response")
} catch (e: Exception) {
e.printStackTrace()
}
}
}

0 comments on commit e8eb78d

Please sign in to comment.