AsyncRoutedHttpHandler 添加对协程的支持

This commit is contained in:
tursom 2019-12-14 17:01:33 +08:00
parent 9f1b852976
commit 024dd8c7cf
4 changed files with 309 additions and 83 deletions

View File

@ -3,4 +3,4 @@ package cn.tursom.log
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory 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)

View File

@ -5,4 +5,5 @@ include 'AsyncSocket'
include 'log' include 'log'
include 'json' include 'json'
include 'utils:yaml' include 'utils:yaml'
include 'web:web-coroutine'

View File

@ -27,10 +27,10 @@ import java.lang.reflect.Method
@Suppress("MemberVisibilityCanBePrivate", "unused") @Suppress("MemberVisibilityCanBePrivate", "unused")
open class RoutedHttpHandler( open class RoutedHttpHandler(
target: Any? = null, target: Any? = null,
val routerMaker: () -> Router<(HttpContent) -> Unit> = { SimpleRouter() } val routerMaker: () -> Router<Pair<Any?, (HttpContent) -> Unit>> = { SimpleRouter() }
) : HttpHandler<HttpContent, ExceptionContent> { ) : HttpHandler<HttpContent, ExceptionContent> {
protected val router: Router<(HttpContent) -> Unit> = routerMaker() protected val router: Router<Pair<Any?, (HttpContent) -> Unit>> = routerMaker()
protected val routerMap: HashMap<String, Router<(HttpContent) -> Unit>> = HashMap() protected val routerMap: HashMap<String, Router<Pair<Any?, (HttpContent) -> Unit>>> = HashMap()
init { init {
@Suppress("LeakingThis") @Suppress("LeakingThis")
@ -40,7 +40,7 @@ open class RoutedHttpHandler(
override fun handle(content: HttpContent) = if (content is MutableHttpContent) { override fun handle(content: HttpContent) = if (content is MutableHttpContent) {
handle(content, getHandler(content, content.method, content.uri)) handle(content, getHandler(content, content.method, content.uri))
} else { } 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)?) { open fun handle(content: HttpContent, handler: ((HttpContent) -> Unit)?) {
@ -55,11 +55,11 @@ open class RoutedHttpHandler(
content.finish(404) content.finish(404)
} }
fun addRouter(handler: Any) { open fun addRouter(handler: Any) {
@Suppress("LeakingThis") @Suppress("LeakingThis")
val clazz = handler.javaClass val clazz = handler.javaClass
clazz.methods.forEach { method -> clazz.methods.forEach { method ->
log?.debug("try mapping {}", method) log?.debug("try mapping {}({})", method, method.parameterTypes)
method.parameterTypes.let { method.parameterTypes.let {
if (!(it.size == 1 && HttpContent::class.java.isAssignableFrom(it[0])) && it.isNotEmpty()) { if (!(it.size == 1 && HttpContent::class.java.isAssignableFrom(it[0])) && it.isNotEmpty()) {
return@forEach return@forEach
@ -69,103 +69,143 @@ open class RoutedHttpHandler(
} }
} }
fun addRouter(route: String, handler: (HttpContent) -> Unit) { fun addRouter(route: String, handler: (HttpContent) -> Unit) = addRouter(route, null, handler)
router[safeRoute(route)] = 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) { fun addRouter(method: String, route: String, handler: (HttpContent) -> Unit) = addRouter(method, route, null, handler)
getRouter(method)[safeRoute(route)] = 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)? { 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) { if (router.first != null) {
router.second.forEach { (k, v) -> router.second.forEach { (k, v) ->
content.addParam(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>>> { fun getHandler(method: String, route: String): Pair<Pair<Any?, (HttpContent) -> Unit>?, List<Pair<String, String>>> {
val router = getRouter(method)[route] val safeRoute = safeRoute(route)
return if (router.first != null) router else this.router[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) { fun deleteRouter(route: String, method: String) {
getRouter(method).delRoute(safeRoute(route)) getRouter(method).delRoute(safeRoute(route))
} }
protected fun insertMapping(obj: Any, method: Method) { fun deleteRouter(handler: Any) {
method.annotations.forEach { annotation -> @Suppress("LeakingThis")
val routes: Array<out String> val clazz = handler.javaClass
val router: Router<(HttpContent) -> Unit> clazz.methods.forEach { method ->
when (annotation) { log?.debug("try mapping {}", method)
is Mapping -> { method.parameterTypes.let {
routes = annotation.route if (!(it.size == 1 && HttpContent::class.java.isAssignableFrom(it[0])) && it.isNotEmpty()) {
router = getRouter(annotation.method) 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 -> routes.forEach { route ->
log?.info("method route {} mapped to {}", route, method) log?.info("delete route {} mapped to {}", route, method)
router[safeRoute(route)] = if (method.parameterTypes.isEmpty()) when { val handler = router[safeRoute(route)].first
method.getAnnotation(Html::class.java) != null -> { content -> if (handler?.first == obj) {
method(obj)?.let { result -> finishHtml(result, content) } router.delRoute(safeRoute(route))
}
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)
} }
} }
} }
} }
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 method.isEmpty() -> router
else -> { else -> {
val upperCaseMethod = method.toUpperCase() val upperCaseMethod = method.toUpperCase()
@ -191,9 +231,12 @@ open class RoutedHttpHandler(
null 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) log?.debug("{}: autoReturn: {}", content.clientIp, result)
when (result) { when (result) {
is String -> content.finishText(result.toByteArray()) 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) log?.debug("{}: finishHtml: {}", content.clientIp, result)
when (result) { when (result) {
is ByteBuffer -> content.finishHtml(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) log?.debug("{}: finishText: {}", content.clientIp, result)
when (result) { when (result) {
is ByteBuffer -> content.finishText(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) log?.debug("{}: finishJson: {}", content.clientIp, result)
when (result) { when (result) {
is ByteBuffer -> content.finishJson(result) is ByteBuffer -> content.finishJson(result)

View File

@ -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
}
}
}