netty server example
This commit is contained in:
@@ -0,0 +1,141 @@
|
|||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
import org.jboss.netty.bootstrap.ServerBootstrap
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory
|
||||||
|
import org.jboss.netty.channel.*
|
||||||
|
import org.jboss.netty.handler.codec.http.*
|
||||||
|
import org.jboss.netty.handler.stream.ChunkedWriteHandler
|
||||||
|
import org.jboss.netty.handler.codec.frame.TooLongFrameException
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers
|
||||||
|
import org.jboss.netty.util.CharsetUtil
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpResponseStatus.*
|
||||||
|
|
||||||
|
import netty.*
|
||||||
|
import jlstring.*
|
||||||
|
|
||||||
|
namespace jlstring {
|
||||||
|
fun String.replace(c: Char, by: Char) : String = (this as java.lang.String).replace(c, by) as String
|
||||||
|
|
||||||
|
fun String.contains(s: String) : Boolean = (this as java.lang.String).contains(s as java.lang.CharSequence)
|
||||||
|
|
||||||
|
fun java.lang.String.plus(s: Any?) : String = (this as String) + s.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace netty {
|
||||||
|
fun ChannelPipeline.with(op: fun ChannelPipeline.() ) : ChannelPipeline {
|
||||||
|
this.op()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HttpResponse.set(header: String, value: Any?) : Unit {
|
||||||
|
setHeader(header, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HttpResponse.setContent(content: Any?) : Unit {
|
||||||
|
setContent(ChannelBuffers.copiedBuffer(content.toString() as java.lang.String, CharsetUtil.UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ChannelHandlerContext.sendError(status: HttpResponseStatus?) {
|
||||||
|
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, status)
|
||||||
|
response["Content-Type"] = "text/plain; charset=UTF-8"
|
||||||
|
response.setContent("Failure: " + status.toString() + "\r\n")
|
||||||
|
|
||||||
|
// Close the connection as soon as the error message is sent.
|
||||||
|
this.getChannel()?.write(response)?.addListener(ChannelFutureListener.CLOSE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.decodeURI(encoding : String) : String? {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(this, encoding)
|
||||||
|
}
|
||||||
|
catch (e : UnsupportedEncodingException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.sanitizeUri() : String? {
|
||||||
|
val path = decodeURI("UTF-8") ?: decodeURI("ISO-8859-1")
|
||||||
|
if (path == null)
|
||||||
|
throw Error()
|
||||||
|
|
||||||
|
val localizedPath : String = path.replace('/', File.separatorChar)
|
||||||
|
return if (localizedPath.contains(File.separator + ".") ||
|
||||||
|
localizedPath.contains("." + File.separator) ||
|
||||||
|
localizedPath.startsWith(".") || endsWith("."))
|
||||||
|
null
|
||||||
|
else
|
||||||
|
localizedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
class StandardPipelineFactory(val config: fun ChannelPipeline.():Unit) : ChannelPipelineFactory {
|
||||||
|
override fun getPipeline() : ChannelPipeline {
|
||||||
|
val pipeline = DefaultChannelPipeline().with {
|
||||||
|
addLast("decoder", HttpRequestDecoder())
|
||||||
|
addLast("aggregator", HttpChunkAggregator(65536))
|
||||||
|
addLast("encoder", HttpResponseEncoder())
|
||||||
|
addLast("chunkedWriter", ChunkedWriteHandler())
|
||||||
|
}
|
||||||
|
pipeline.config () // can not move it inside with{} because of codegen bug
|
||||||
|
return pipeline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ServerBootstrap.configPipeline(config: fun ChannelPipeline.(): Unit) =
|
||||||
|
setPipelineFactory(
|
||||||
|
StandardPipelineFactory(config)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args : Array<String>) {
|
||||||
|
val bootstrap = ServerBootstrap(
|
||||||
|
NioServerSocketChannelFactory(
|
||||||
|
Executors.newCachedThreadPool(),
|
||||||
|
Executors.newCachedThreadPool()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
bootstrap.configPipeline {
|
||||||
|
addLast("handler", HttpStaticFileServerHandler(args[0]))
|
||||||
|
}
|
||||||
|
bootstrap.bind(InetSocketAddress(8080))
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpStaticFileServerHandler(val rootDir: String) : SimpleChannelUpstreamHandler() {
|
||||||
|
|
||||||
|
fun messageReceived(ctx : ChannelHandlerContext, e : MessageEvent) {
|
||||||
|
val request = e.getMessage() as HttpRequest
|
||||||
|
if (request.getMethod() != HttpMethod.GET) {
|
||||||
|
ctx.sendError(METHOD_NOT_ALLOWED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = request.getUri()?.sanitizeUri()
|
||||||
|
if (path == null) {
|
||||||
|
ctx.sendError(FORBIDDEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(rootDir + File.separator + path)
|
||||||
|
if (file.isHidden() || !file.exists()) {
|
||||||
|
ctx.sendError(NOT_FOUND)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!file.isFile()) {
|
||||||
|
ctx.sendError(FORBIDDEN)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var raf = RandomAccessFile(file, "r")
|
||||||
|
|
||||||
|
val fileLength = raf.length()
|
||||||
|
|
||||||
|
val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, OK)
|
||||||
|
response["Content-Length"] = fileLength
|
||||||
|
|
||||||
|
val ch = e.getChannel()
|
||||||
|
ch?.write(response)
|
||||||
|
ch?.write(DefaultFileRegion(raf.getChannel(), 0, fileLength))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user