前言
JDK中自带的一个包com.sun.net.httpserver,使用他可以简单快速的创建一个http服务,支持http和https,但也只是简单,很多功能还需要自己写。
下面来做一个演示。
fun main() {
var httpServer = HttpServer.create()
httpServer.bind(InetSocketAddress(8080), 0);
httpServer.createContext("/", object : HttpHandler {
override fun handle(exchange: HttpExchange) {
println(exchange.requestURI.query)
var data = "Hello http server".toByteArray()
exchange.sendResponseHeaders(200, data.size.toLong());
exchange.responseBody.write(data)
exchange.responseBody.flush()
}
})
httpServer.setExecutor(null);
httpServer.start()
}
复制代码
这几行就创建了一个Http,并且实现了/路径的访问
HttpExchange是用来做输入输出的,不过像参数解析这些还得自己手写,可以通过下面语句获取url中整个的参数,注意的是getQuery方法是获取的被解码后的字符,如果要获取未被解码的字符需要使用getRawQuery。
var query = exchange.requestURI.query
复制代码
比如访问http://localhost:8080/ass/sdf?id=1&data=2
,那么拿到的值就是id=1&data=2
,下面做一下解析。
当然这个还不严谨,特殊编码没有处理。
fun String.toQueryMap(): Map<String, String> {
var mutableMapOf = mutableMapOf<String, String>()
var split = this.split("&")
split.forEach {
var item = it.split("=")
mutableMapOf[item[0]] = item[1]
}
return mutableMapOf
}
fun main() {
var httpServer = HttpServer.create()
httpServer.bind(InetSocketAddress(8080), 0);
httpServer.createContext("/", object : HttpHandler {
override fun handle(exchange: HttpExchange) {
var query = exchange.requestURI.query
var querymap = query.toQueryMap().toString()
var data = querymap.toByteArray()
exchange.sendResponseHeaders(200, data.size.toLong());
exchange.responseBody.write(data)
exchange.responseBody.flush()
}
})
httpServer.setExecutor(null);
httpServer.start()
}
复制代码
在往下实现一个类似SpringBoot的@Controller注解。
定义请求参数注解
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class RequestParam(val name: String = "")
复制代码
定义Get请求映射地址
@Target(AnnotationTarget.FUNCTION)
annotation class GetMapping(
val name: String = ""
)
复制代码
定义控制器
annotation class Controller(){
}
复制代码
控制器实现
@Controller
class IndexController {
@GetMapping("/echo")
fun index(@RequestParam("data") string: String): String {
return "$string"
}
@GetMapping("/sendMessage")
fun sendMessage(@RequestParam("msg") string: String): String {
return "sendMessage ${string}"
}
}
复制代码
核心实现
import com.sun.net.httpserver.HttpExchange
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer
import jdk.internal.org.objectweb.asm.ClassReader
import jdk.internal.org.objectweb.asm.tree.ClassNode
import java.io.File
import java.io.IOException
import java.lang.reflect.Method
import java.net.InetSocketAddress
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
var beans = mutableMapOf<String, Any>()
var urlMapping = mutableMapOf<String, UrlHandler>()
class Test {
}
class UrlHandler(var obj: Any, var method: Method) {
fun invoke(vararg args: Any): String {
return method.invoke(obj, *args) as String
}
}
fun String.toQueryMap(): MutableMap<String, String> {
var mutableMapOf = mutableMapOf<String, String>()
try {
var split = this.split("&")
split.forEach {
var item = it.split("=")
mutableMapOf[item[0]] = item[1]
}
} catch (e: Exception) {
e.printStackTrace()
}
return mutableMapOf
}
fun scanClassPath(): List<String> {
var directory = ClassLoader.getSystemResource(".").file
var classFileList = mutableListOf<String>()
Files.walkFileTree(Paths.get(directory), object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (file.toString().endsWith(".class")) {
classFileList.add(file.toString())
}
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
return FileVisitResult.CONTINUE
}
})
return classFileList
}
fun doScan(files: List<String>, controllers: MutableList<String>) {
files.forEach {
var classReader = ClassReader(File(it).inputStream())
var classNode = ClassNode()
classReader.accept(classNode, ClassReader.SKIP_CODE)
var controllerDesc = "L" + Controller::class.java.simpleName + ";"
var hasControllerAnnotation =
classNode.visibleAnnotations.stream().filter { it.desc == controllerDesc }.count() > 0
if (hasControllerAnnotation) {
controllers.add(classNode.name.replace("/", "."))
}
}
}
fun getBean(clazz: Class<*>): Any? {
if (beans.containsKey(clazz.name)) {
return beans[clazz.name]
}
var newInstance = clazz.newInstance()
beans[clazz.name] = newInstance
clazz.declaredMethods.forEach {
var mapping = it.getDeclaredAnnotation(GetMapping::class.java)
if (mapping != null) {
var url = mapping.name
urlMapping[url] = UrlHandler(newInstance, it)
}
}
return beans[clazz.name]
}
fun getHandler(url: String): UrlHandler? {
var indexOf = url.indexOf("?")
if (indexOf != -1) {
var requestPath = url.subSequence(0, indexOf)
return urlMapping.get(requestPath);
}
return urlMapping.get(url);
}
fun dispatcher(exchange: HttpExchange): String {
var query = exchange.requestURI.query
var querymap = mutableMapOf<String, String>()
query?.apply {
querymap = query.toQueryMap()
}
var handler = getHandler(exchange.requestURI.toString())
if (handler != null) {
var method = handler.method
if (method.parameterCount >= 1) {
var args = mutableListOf<String>()
var parameters = method.parameters
parameters.forEach {
var requestParam = it.getDeclaredAnnotation(RequestParam::class.java)
var param = querymap.getOrDefault(requestParam.name,"")
args.add(param)
}
try {
return handler.invoke(*args.toTypedArray())
} catch (e: Exception) {
e.printStackTrace()
}
}
return handler.invoke()
}
return "404";
}
fun main() {
var controllers = mutableListOf<String>()
var classFileList = scanClassPath()
doScan(classFileList, controllers)
controllers.forEach { getBean(Class.forName(it)) }
var httpServer = HttpServer.create()
httpServer.bind(InetSocketAddress(8080), 0);
httpServer.createContext("/", object : HttpHandler {
override fun handle(exchange: HttpExchange) {
var dispatcher = dispatcher(exchange)
exchange.sendResponseHeaders(200, dispatcher.length.toLong());
exchange.responseBody.write(dispatcher.toByteArray())
exchange.responseBody.flush()
}
})
httpServer.setExecutor(null);
httpServer.start()
}
复制代码