Improve the HTTP implementation

This commit adds support for:

* Version negotiation.
* HEAD requests.
* Friendly error messages.
* More robust reference counting.

Signed-off-by: Graham <gpe@openrs2.org>
pull/132/head
Graham 3 years ago
parent 92a01b6262
commit 620808cb97
  1. 13
      game/src/main/kotlin/org/openrs2/game/net/crossdomain/CrossDomainChannelHandler.kt
  2. 81
      game/src/main/kotlin/org/openrs2/game/net/http/Http.kt
  3. 23
      game/src/main/kotlin/org/openrs2/game/net/http/HttpChannelHandler.kt

@ -1,14 +1,13 @@
package org.openrs2.game.net.crossdomain
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.handler.codec.http.HttpHeaderNames
import io.netty.handler.codec.http.HttpMethod
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpResponseStatus
import org.openrs2.buffer.use
import org.openrs2.game.net.http.Http
@ChannelHandler.Sharable
@ -29,15 +28,13 @@ public object CrossDomainChannelHandler : SimpleChannelInboundHandler<HttpReques
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpRequest) {
if (msg.method() != HttpMethod.GET || msg.uri() != ENDPOINT) {
ctx.write(Http.createResponse(HttpResponseStatus.BAD_REQUEST)).addListener(ChannelFutureListener.CLOSE)
Http.writeResponse(ctx, msg, HttpResponseStatus.BAD_REQUEST)
return
}
val response = Http.createResponse(HttpResponseStatus.OK)
response.headers().add(HttpHeaderNames.CONTENT_TYPE, Http.TEXT_X_CROSS_DOMAIN_POLICY)
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, POLICY.size)
ctx.write(response, ctx.voidPromise())
ctx.write(Unpooled.wrappedBuffer(POLICY)).addListener(ChannelFutureListener.CLOSE)
Unpooled.wrappedBuffer(POLICY).use { buf ->
Http.writeResponse(ctx, msg, buf, Http.TEXT_X_CROSS_DOMAIN_POLICY)
}
}
override fun channelReadComplete(ctx: ChannelHandlerContext) {

@ -1,20 +1,91 @@
package org.openrs2.game.net.http
import io.netty.buffer.ByteBuf
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.FileRegion
import io.netty.handler.codec.http.DefaultHttpResponse
import io.netty.handler.codec.http.HttpHeaderNames
import io.netty.handler.codec.http.HttpHeaderValues
import io.netty.handler.codec.http.HttpResponse
import io.netty.handler.codec.http.HttpMethod
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.codec.http.HttpVersion
import io.netty.util.ReferenceCounted
import org.openrs2.buffer.copiedBuffer
import org.openrs2.buffer.use
public object Http {
public const val MAX_CONTENT_LENGTH: Int = 65536
public const val TEXT_X_CROSS_DOMAIN_POLICY: String = "text/x-cross-domain-policy"
private const val BANNER = "OpenRS2"
public fun createResponse(status: HttpResponseStatus): HttpResponse {
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, status)
private fun writeResponse(
ctx: ChannelHandlerContext,
request: HttpRequest,
status: HttpResponseStatus,
content: ReferenceCounted,
contentType: CharSequence,
contentLength: Long
) {
val version = if (request.protocolVersion() == HttpVersion.HTTP_1_0) {
HttpVersion.HTTP_1_0
} else {
HttpVersion.HTTP_1_1
}
val response = DefaultHttpResponse(version, status)
response.headers().add(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
response.headers().add(HttpHeaderNames.SERVER, "OpenRS2")
return response
response.headers().add(HttpHeaderNames.SERVER, BANNER)
response.headers().add(HttpHeaderNames.CONTENT_TYPE, contentType)
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, contentLength)
if (request.method() == HttpMethod.HEAD) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE)
} else {
ctx.write(response, ctx.voidPromise())
ctx.write(content.retain()).addListener(ChannelFutureListener.CLOSE)
}
}
public fun writeResponse(
ctx: ChannelHandlerContext,
request: HttpRequest,
content: ByteBuf,
contentType: CharSequence
) {
writeResponse(ctx, request, HttpResponseStatus.OK, content, contentType, content.readableBytes().toLong())
}
public fun writeResponse(
ctx: ChannelHandlerContext,
request: HttpRequest,
content: FileRegion,
contentType: CharSequence
) {
writeResponse(ctx, request, HttpResponseStatus.OK, content, contentType, content.count())
}
public fun writeResponse(ctx: ChannelHandlerContext, request: HttpRequest, status: HttpResponseStatus) {
copiedBuffer(
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>$status</title>
</head>
<body>
<center>
<h1>$status</h1>
</center>
<hr />
<center>$BANNER</center>
</body>
</html>
""".trimIndent().plus("\n"), Charsets.UTF_8
).use { buf ->
writeResponse(ctx, request, status, buf, HttpHeaderValues.TEXT_HTML, buf.readableBytes().toLong())
}
}
}

@ -1,13 +1,12 @@
package org.openrs2.game.net.http
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.handler.codec.http.HttpHeaderNames
import io.netty.handler.codec.http.HttpHeaderValues
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpResponseStatus
import org.openrs2.buffer.use
import org.openrs2.game.net.FileProvider
import javax.inject.Inject
import javax.inject.Singleton
@ -24,22 +23,18 @@ public class HttpChannelHandler @Inject constructor(
override fun channelRead0(ctx: ChannelHandlerContext, msg: HttpRequest) {
val uri = msg.uri()
if (!uri.startsWith("/")) {
ctx.write(Http.createResponse(HttpResponseStatus.BAD_REQUEST)).addListener(ChannelFutureListener.CLOSE)
Http.writeResponse(ctx, msg, HttpResponseStatus.BAD_REQUEST)
return
}
val file = fileProvider.get(uri.substring(1))
if (file == null) {
ctx.write(Http.createResponse(HttpResponseStatus.NOT_FOUND)).addListener(ChannelFutureListener.CLOSE)
return
}
fileProvider.get(uri.substring(1)).use { file ->
if (file == null) {
Http.writeResponse(ctx, msg, HttpResponseStatus.NOT_FOUND)
return
}
val response = Http.createResponse(HttpResponseStatus.OK)
response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM)
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, file.count())
ctx.write(response, ctx.voidPromise())
ctx.write(file).addListener(ChannelFutureListener.CLOSE)
Http.writeResponse(ctx, msg, file, HttpHeaderValues.APPLICATION_OCTET_STREAM)
}
}
override fun channelReadComplete(ctx: ChannelHandlerContext) {

Loading…
Cancel
Save