mirror of
https://github.com/tursom/TursomServer.git
synced 2025-02-03 16:20:16 +08:00
AsyncRoutedHttpHandler 添加对协程的支持
This commit is contained in:
parent
9f1b852976
commit
024dd8c7cf
@ -3,4 +3,4 @@ package cn.tursom.log
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
inline fun <reified T> LoggerFactory.getLogger(): Logger = LoggerFactory.getLogger(T::class.java)
|
||||
inline fun <reified T> T.slf4jLogger(): Logger = LoggerFactory.getLogger(T::class.java)
|
||||
|
@ -5,4 +5,5 @@ include 'AsyncSocket'
|
||||
include 'log'
|
||||
include 'json'
|
||||
include 'utils:yaml'
|
||||
include 'web:web-coroutine'
|
||||
|
||||
|
@ -27,10 +27,10 @@ import java.lang.reflect.Method
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
open class RoutedHttpHandler(
|
||||
target: Any? = null,
|
||||
val routerMaker: () -> Router<(HttpContent) -> Unit> = { SimpleRouter() }
|
||||
val routerMaker: () -> Router<Pair<Any?, (HttpContent) -> Unit>> = { SimpleRouter() }
|
||||
) : HttpHandler<HttpContent, ExceptionContent> {
|
||||
protected val router: Router<(HttpContent) -> Unit> = routerMaker()
|
||||
protected val routerMap: HashMap<String, Router<(HttpContent) -> Unit>> = HashMap()
|
||||
protected val router: Router<Pair<Any?, (HttpContent) -> Unit>> = routerMaker()
|
||||
protected val routerMap: HashMap<String, Router<Pair<Any?, (HttpContent) -> Unit>>> = HashMap()
|
||||
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
@ -40,7 +40,7 @@ open class RoutedHttpHandler(
|
||||
override fun handle(content: HttpContent) = if (content is MutableHttpContent) {
|
||||
handle(content, getHandler(content, content.method, content.uri))
|
||||
} else {
|
||||
handle(content, getHandler(content.method, content.uri).first)
|
||||
handle(content, getHandler(content.method, content.uri).first?.second)
|
||||
}
|
||||
|
||||
open fun handle(content: HttpContent, handler: ((HttpContent) -> Unit)?) {
|
||||
@ -55,11 +55,11 @@ open class RoutedHttpHandler(
|
||||
content.finish(404)
|
||||
}
|
||||
|
||||
fun addRouter(handler: Any) {
|
||||
open fun addRouter(handler: Any) {
|
||||
@Suppress("LeakingThis")
|
||||
val clazz = handler.javaClass
|
||||
clazz.methods.forEach { method ->
|
||||
log?.debug("try mapping {}", method)
|
||||
log?.debug("try mapping {}({})", method, method.parameterTypes)
|
||||
method.parameterTypes.let {
|
||||
if (!(it.size == 1 && HttpContent::class.java.isAssignableFrom(it[0])) && it.isNotEmpty()) {
|
||||
return@forEach
|
||||
@ -69,103 +69,143 @@ open class RoutedHttpHandler(
|
||||
}
|
||||
}
|
||||
|
||||
fun addRouter(route: String, handler: (HttpContent) -> Unit) {
|
||||
router[safeRoute(route)] = handler
|
||||
fun addRouter(route: String, handler: (HttpContent) -> Unit) = addRouter(route, null, handler)
|
||||
fun addRouter(route: String, obj: Any?, handler: (HttpContent) -> Unit) {
|
||||
router[safeRoute(route)] = obj to handler
|
||||
}
|
||||
|
||||
fun addRouter(method: String, route: String, handler: (HttpContent) -> Unit) {
|
||||
getRouter(method)[safeRoute(route)] = handler
|
||||
fun addRouter(method: String, route: String, handler: (HttpContent) -> Unit) = addRouter(method, route, null, handler)
|
||||
fun addRouter(method: String, route: String, obj: Any?, handler: (HttpContent) -> Unit) {
|
||||
getRouter(method)[safeRoute(route)] = obj to handler
|
||||
}
|
||||
|
||||
fun getHandler(content: MutableHttpContent, method: String, route: String): ((HttpContent) -> Unit)? {
|
||||
val router = getHandler(method, route)
|
||||
val safeRoute = safeRoute(route)
|
||||
val router = getHandler(method, safeRoute)
|
||||
if (router.first != null) {
|
||||
router.second.forEach { (k, v) ->
|
||||
content.addParam(k, v)
|
||||
}
|
||||
}
|
||||
return router.first ?: this.router[route].first
|
||||
return router.first?.second ?: this.router[safeRoute].first?.second
|
||||
}
|
||||
|
||||
fun getHandler(method: String, route: String): Pair<((HttpContent) -> Unit)?, List<Pair<String, String>>> {
|
||||
val router = getRouter(method)[route]
|
||||
return if (router.first != null) router else this.router[route]
|
||||
fun getHandler(method: String, route: String): Pair<Pair<Any?, (HttpContent) -> Unit>?, List<Pair<String, String>>> {
|
||||
val safeRoute = safeRoute(route)
|
||||
val router = getRouter(method)[safeRoute]
|
||||
return if (router.first != null) router else this.router[safeRoute]
|
||||
}
|
||||
|
||||
protected fun insertMapping(obj: Any, method: Method) {
|
||||
method.annotations.forEach { annotation ->
|
||||
log?.info("method route {} annotation {}", method, annotation)
|
||||
val (routes, router) = getRoutes(annotation) ?: return@forEach
|
||||
log?.info("method route {} mapped to {}", method, routes)
|
||||
routes.forEach { route ->
|
||||
router[safeRoute(route)] = if (method.parameterTypes.isEmpty()) {
|
||||
obj to when {
|
||||
method.getAnnotation(Html::class.java) != null -> { content ->
|
||||
method(obj)?.let { result -> finishHtml(result, content) }
|
||||
}
|
||||
method.getAnnotation(Text::class.java) != null -> { content ->
|
||||
method(obj)?.let { result -> finishText(result, content) }
|
||||
}
|
||||
method.getAnnotation(Json::class.java) != null -> { content ->
|
||||
method(obj)?.let { result -> finishJson(result, content) }
|
||||
}
|
||||
else -> { content ->
|
||||
method(obj)?.let { result -> autoReturn(result, content) }
|
||||
}
|
||||
}
|
||||
} else obj to when (method.returnType) {
|
||||
Void::class.java -> { content -> method(obj, content) }
|
||||
Void.TYPE -> { content -> method(obj, content) }
|
||||
Unit::class.java -> { content -> method(obj, content) }
|
||||
else -> when {
|
||||
method.getAnnotation(Html::class.java) != null -> { content ->
|
||||
method(obj, content)?.let { result -> finishHtml(result, content) }
|
||||
}
|
||||
method.getAnnotation(Text::class.java) != null -> { content ->
|
||||
method(obj, content)?.let { result -> finishText(result, content) }
|
||||
}
|
||||
method.getAnnotation(Json::class.java) != null -> { content ->
|
||||
method(obj, content)?.let { result -> finishJson(result, content) }
|
||||
}
|
||||
else -> { content ->
|
||||
method(obj, content)?.let { result -> autoReturn(result, content) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteRouter(route: String, method: String) {
|
||||
getRouter(method).delRoute(safeRoute(route))
|
||||
}
|
||||
|
||||
protected fun insertMapping(obj: Any, method: Method) {
|
||||
method.annotations.forEach { annotation ->
|
||||
val routes: Array<out String>
|
||||
val router: Router<(HttpContent) -> Unit>
|
||||
when (annotation) {
|
||||
is Mapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter(annotation.method)
|
||||
fun deleteRouter(handler: Any) {
|
||||
@Suppress("LeakingThis")
|
||||
val clazz = handler.javaClass
|
||||
clazz.methods.forEach { method ->
|
||||
log?.debug("try mapping {}", method)
|
||||
method.parameterTypes.let {
|
||||
if (!(it.size == 1 && HttpContent::class.java.isAssignableFrom(it[0])) && it.isNotEmpty()) {
|
||||
return@forEach
|
||||
}
|
||||
is GetMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("GET")
|
||||
}
|
||||
is PostMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("POST")
|
||||
}
|
||||
is PutMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("PUT")
|
||||
}
|
||||
is DeleteMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("DELETE")
|
||||
}
|
||||
is PatchMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("PATCH")
|
||||
}
|
||||
is TraceMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("TRACE")
|
||||
}
|
||||
is HeadMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("HEAD")
|
||||
}
|
||||
is OptionsMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("OPTIONS")
|
||||
}
|
||||
is ConnectMapping -> {
|
||||
routes = annotation.route
|
||||
router = getRouter("CONNECT")
|
||||
}
|
||||
else -> return@forEach
|
||||
}
|
||||
deleteMapping(handler, method)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun deleteMapping(obj: Any, method: Method) {
|
||||
method.annotations.forEach { annotation ->
|
||||
val (routes, router) = getRoutes(annotation) ?: return@forEach
|
||||
routes.forEach { route ->
|
||||
log?.info("method route {} mapped to {}", route, method)
|
||||
router[safeRoute(route)] = if (method.parameterTypes.isEmpty()) when {
|
||||
method.getAnnotation(Html::class.java) != null -> { content ->
|
||||
method(obj)?.let { result -> finishHtml(result, content) }
|
||||
}
|
||||
method.getAnnotation(Text::class.java) != null -> { content ->
|
||||
method(obj)?.let { result -> finishText(result, content) }
|
||||
}
|
||||
method.getAnnotation(Json::class.java) != null -> { content ->
|
||||
method(obj)?.let { result -> finishJson(result, content) }
|
||||
}
|
||||
else -> { content ->
|
||||
method(obj)?.let { result -> autoReturn(result, content) }
|
||||
}
|
||||
} else { content ->
|
||||
method(obj, content)
|
||||
log?.info("delete route {} mapped to {}", route, method)
|
||||
val handler = router[safeRoute(route)].first
|
||||
if (handler?.first == obj) {
|
||||
router.delRoute(safeRoute(route))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getRouter(method: String): Router<(HttpContent) -> Unit> = when {
|
||||
protected fun getRoutes(annotation: Annotation) = when (annotation) {
|
||||
is Mapping -> {
|
||||
annotation.route to getRouter(annotation.method)
|
||||
}
|
||||
is GetMapping -> {
|
||||
annotation.route to getRouter("GET")
|
||||
}
|
||||
is PostMapping -> {
|
||||
annotation.route to getRouter("POST")
|
||||
}
|
||||
is PutMapping -> {
|
||||
annotation.route to getRouter("PUT")
|
||||
}
|
||||
is DeleteMapping -> {
|
||||
annotation.route to getRouter("DELETE")
|
||||
}
|
||||
is PatchMapping -> {
|
||||
annotation.route to getRouter("PATCH")
|
||||
}
|
||||
is TraceMapping -> {
|
||||
annotation.route to getRouter("TRACE")
|
||||
}
|
||||
is HeadMapping -> {
|
||||
annotation.route to getRouter("HEAD")
|
||||
}
|
||||
is OptionsMapping -> {
|
||||
annotation.route to getRouter("OPTIONS")
|
||||
}
|
||||
is ConnectMapping -> {
|
||||
annotation.route to getRouter("CONNECT")
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
protected fun getRouter(method: String): Router<Pair<Any?, (HttpContent) -> Unit>> = when {
|
||||
method.isEmpty() -> router
|
||||
else -> {
|
||||
val upperCaseMethod = method.toUpperCase()
|
||||
@ -191,9 +231,12 @@ open class RoutedHttpHandler(
|
||||
null
|
||||
}
|
||||
|
||||
private fun safeRoute(route: String) = if (route.first() == '/') route else "/$route"
|
||||
fun safeRoute(route: String) = (
|
||||
if (route.startsWith('/')) route else "/$route").let {
|
||||
if (it.endsWith('/')) it.dropLast(1) else it
|
||||
}
|
||||
|
||||
private fun autoReturn(result: Any, content: HttpContent) {
|
||||
fun autoReturn(result: Any, content: HttpContent) {
|
||||
log?.debug("{}: autoReturn: {}", content.clientIp, result)
|
||||
when (result) {
|
||||
is String -> content.finishText(result.toByteArray())
|
||||
@ -210,7 +253,7 @@ open class RoutedHttpHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishHtml(result: Any, content: HttpContent) {
|
||||
fun finishHtml(result: Any, content: HttpContent) {
|
||||
log?.debug("{}: finishHtml: {}", content.clientIp, result)
|
||||
when (result) {
|
||||
is ByteBuffer -> content.finishHtml(result)
|
||||
@ -220,7 +263,7 @@ open class RoutedHttpHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishText(result: Any, content: HttpContent) {
|
||||
fun finishText(result: Any, content: HttpContent) {
|
||||
log?.debug("{}: finishText: {}", content.clientIp, result)
|
||||
when (result) {
|
||||
is ByteBuffer -> content.finishText(result)
|
||||
@ -230,7 +273,7 @@ open class RoutedHttpHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishJson(result: Any, content: HttpContent) {
|
||||
fun finishJson(result: Any, content: HttpContent) {
|
||||
log?.debug("{}: finishJson: {}", content.clientIp, result)
|
||||
when (result) {
|
||||
is ByteBuffer -> content.finishJson(result)
|
||||
|
@ -0,0 +1,182 @@
|
||||
package cn.tursom.web.router
|
||||
|
||||
import cn.tursom.web.HttpContent
|
||||
import cn.tursom.web.MutableHttpContent
|
||||
import cn.tursom.web.mapping.*
|
||||
import cn.tursom.web.result.Html
|
||||
import cn.tursom.web.result.Json
|
||||
import cn.tursom.web.result.Text
|
||||
import cn.tursom.web.router.impl.SimpleRouter
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
@Suppress("ProtectedInFinal", "unused", "MemberVisibilityCanBePrivate")
|
||||
open class AsyncRoutedHttpHandler(
|
||||
target: Any? = null,
|
||||
routerMaker: () -> Router<Pair<Any?, (HttpContent) -> Unit>> = { SimpleRouter() },
|
||||
val asyncRouterMaker: () -> Router<Pair<Any?, suspend (HttpContent) -> Unit>> = { SimpleRouter() }
|
||||
) : RoutedHttpHandler(target, routerMaker) {
|
||||
protected val asyncRouter: Router<Pair<Any?, suspend (HttpContent) -> Unit>> = asyncRouterMaker()
|
||||
protected val asyncRouterMap: HashMap<String, Router<Pair<Any?, suspend (HttpContent) -> Unit>>> = HashMap()
|
||||
|
||||
override fun handle(content: HttpContent) {
|
||||
if (content is MutableHttpContent) {
|
||||
val handler = getAsyncHandler(content, content.method, content.uri)
|
||||
if (handler != null) GlobalScope.launch {
|
||||
handle(content, handler)
|
||||
} else {
|
||||
handle(content, getHandler(content, content.method, content.uri))
|
||||
}
|
||||
} else {
|
||||
val handler = getAsyncHandler(content.method, content.uri).first?.second
|
||||
if (handler != null) GlobalScope.launch {
|
||||
handle(content, handler)
|
||||
} else {
|
||||
handle(content, getHandler(content.method, content.uri).first?.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun handle(content: HttpContent, handler: (suspend (HttpContent) -> Unit)?) {
|
||||
if (handler != null) {
|
||||
handler(content)
|
||||
} else {
|
||||
notFound(content)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAsyncHandler(method: String, route: String): Pair<Pair<Any?, suspend (HttpContent) -> Unit>?, List<Pair<String, String>>> {
|
||||
val safeRoute = safeRoute(route)
|
||||
val router = getAsyncRouter(method)[safeRoute]
|
||||
return if (router.first != null) router else this.asyncRouter[safeRoute]
|
||||
}
|
||||
|
||||
fun getAsyncHandler(content: MutableHttpContent, method: String, route: String): (suspend (HttpContent) -> Unit)? {
|
||||
val safeRoute = safeRoute(route)
|
||||
val router = getAsyncHandler(method, safeRoute)
|
||||
if (router.first != null) {
|
||||
router.second.forEach { (k, v) ->
|
||||
content.addParam(k, v)
|
||||
}
|
||||
}
|
||||
return router.first?.second ?: this.asyncRouter[safeRoute].first?.second
|
||||
}
|
||||
|
||||
override fun addRouter(handler: Any) {
|
||||
super.addRouter(handler)
|
||||
handler::class.members.forEach { member ->
|
||||
if (member.isSuspend) {
|
||||
member.parameters.let {
|
||||
if (it.size != 1 && !(it.size == 2 && HttpContent::class.java.isAssignableFrom(it[1].type.jvmErasure.java))) {
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
insertMapping(handler, member)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
protected fun insertMapping(obj: Any, method: KCallable<*>) {
|
||||
method.annotations.forEach { annotation ->
|
||||
log?.info("method route {} annotation {}", method, annotation)
|
||||
val (routes, router) = getAsyncRoutes(annotation) ?: return@forEach
|
||||
log?.info("method route {} mapped to {}", method, routes)
|
||||
routes.forEach { route ->
|
||||
router[safeRoute(route)] = if (method.parameters.size == 1) {
|
||||
obj to when {
|
||||
method.findAnnotation<Html>() != null -> { content ->
|
||||
(method as suspend Any.() -> Any?)(obj)?.let { result -> finishHtml(result, content) }
|
||||
}
|
||||
method.findAnnotation<Text>() != null -> { content ->
|
||||
(method as suspend Any.() -> Any?)(obj)?.let { result -> finishText(result, content) }
|
||||
}
|
||||
method.findAnnotation<Json>() != null -> { content ->
|
||||
(method as suspend Any.() -> Any?)(obj)?.let { result -> finishJson(result, content) }
|
||||
}
|
||||
else -> { content ->
|
||||
(method as suspend Any.() -> Any?)(obj)?.let { result -> autoReturn(result, content) }
|
||||
}
|
||||
}
|
||||
} else obj to when (method.returnType) {
|
||||
Void::class.java -> method as suspend (HttpContent) -> Unit
|
||||
Void.TYPE -> method as suspend (HttpContent) -> Unit
|
||||
Unit::class.java -> method as suspend (HttpContent) -> Unit
|
||||
else -> when {
|
||||
method.findAnnotation<Html>() != null -> { content ->
|
||||
(method as suspend Any.(HttpContent) -> Any?)(obj, content)?.let { result -> finishHtml(result, content) }
|
||||
}
|
||||
method.findAnnotation<Text>() != null -> { content ->
|
||||
(method as suspend Any.(HttpContent) -> Any?)(obj, content)?.let { result -> finishText(result, content) }
|
||||
}
|
||||
method.findAnnotation<Json>() != null -> { content ->
|
||||
(method as suspend Any.(HttpContent) -> Any?)(obj, content)?.let { result -> finishJson(result, content) }
|
||||
}
|
||||
else -> { content ->
|
||||
(method as suspend Any.(HttpContent) -> Any?)(obj, content)?.let { result -> autoReturn(result, content) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getAsyncRoutes(annotation: Annotation) = when (annotation) {
|
||||
is Mapping -> {
|
||||
annotation.route to getAsyncRouter(annotation.method)
|
||||
}
|
||||
is GetMapping -> {
|
||||
annotation.route to getAsyncRouter("GET")
|
||||
}
|
||||
is PostMapping -> {
|
||||
annotation.route to getAsyncRouter("POST")
|
||||
}
|
||||
is PutMapping -> {
|
||||
annotation.route to getAsyncRouter("PUT")
|
||||
}
|
||||
is DeleteMapping -> {
|
||||
annotation.route to getAsyncRouter("DELETE")
|
||||
}
|
||||
is PatchMapping -> {
|
||||
annotation.route to getAsyncRouter("PATCH")
|
||||
}
|
||||
is TraceMapping -> {
|
||||
annotation.route to getAsyncRouter("TRACE")
|
||||
}
|
||||
is HeadMapping -> {
|
||||
annotation.route to getAsyncRouter("HEAD")
|
||||
}
|
||||
is OptionsMapping -> {
|
||||
annotation.route to getAsyncRouter("OPTIONS")
|
||||
}
|
||||
is ConnectMapping -> {
|
||||
annotation.route to getAsyncRouter("CONNECT")
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
protected fun getAsyncRouter(method: String): Router<Pair<Any?, suspend (HttpContent) -> Unit>> = when {
|
||||
method.isEmpty() -> asyncRouter
|
||||
else -> {
|
||||
val upperCaseMethod = method.toUpperCase()
|
||||
var router = asyncRouterMap[upperCaseMethod]
|
||||
if (router == null) {
|
||||
router = asyncRouterMaker()
|
||||
asyncRouterMap[upperCaseMethod] = router
|
||||
}
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val log = try {
|
||||
LoggerFactory.getLogger(RoutedHttpHandler::class.java)
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user