Skip to content

Commit 619e73f

Browse files
committed
WIP
1 parent 7989e45 commit 619e73f

File tree

15 files changed

+147
-80
lines changed

15 files changed

+147
-80
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ Add dependency to `build.gradle.kts`:
1414

1515
```kts
1616
dependencies {
17-
implementation("com.coditory.ktserver:ktserver-jdk:$version")
17+
implementation("com.coditory.kttp:kttp-server-jdk:$version")
1818
}
1919
```
2020

2121
## TBD
2222

23+
- Rename to kttp
2324
- Check nested routing
2425
- Check 404 and error handling
26+
- Handle head
27+
- Handle 405 response - Method not allowed
28+
- Handle 406 response - Not Acceptable
2529
- Upload file
2630
- Serve files
2731
- Websocket
2832
- klog api
33+
- kttp client

ktserver-api/src/main/kotlin/com/coditory/ktserver/HttpAction.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

ktserver-api/src/main/kotlin/com/coditory/ktserver/HttpErrorHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.coditory.ktserver
33
import com.coditory.ktserver.http.HttpResponse
44
import com.coditory.ktserver.http.HttpStatus
55

6-
interface HttpErrorHandler {
6+
fun interface HttpErrorHandler {
77
fun handle(exchange: HttpExchange, e: Throwable): HttpResponse
88

99
companion object {
Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
11
package com.coditory.ktserver
22

3-
import com.coditory.ktserver.http.ContentType
4-
import com.coditory.ktserver.http.HttpRequest
5-
import com.coditory.ktserver.http.HttpRequestMethod
3+
import com.coditory.ktserver.http.HttpRequestHead
64
import com.coditory.ktserver.http.HttpResponse
7-
import com.coditory.ktserver.http.PathPattern
85

9-
interface HttpHandler : HttpAction {
10-
val pathPattern: PathPattern
11-
fun matches(request: HttpRequest) = pathPattern.matches(request.uri.path)
6+
fun interface HttpHandler {
7+
suspend fun handle(exchange: HttpExchange): HttpResponse
128
}
139

14-
internal class SimpleHttpHandler(
15-
val method: HttpRequestMethod? = null,
16-
override val pathPattern: PathPattern,
17-
private val consumes: ContentType? = null,
18-
private val produces: ContentType? = null,
19-
private val action: HttpAction,
20-
) : HttpHandler {
21-
override fun matches(request: HttpRequest): Boolean {
22-
if (method != null && method != request.method) return false
23-
if (!pathPattern.matches(request.uri.path)) return false
24-
if (consumes == null && produces == null) return true
25-
// TODO: Negotiate content with Content-Type and Accept
26-
return true
27-
}
10+
interface HttpMatchingHandler : HttpHandler {
11+
fun matches(request: HttpRequestHead): Boolean
2812

29-
override suspend fun handle(exchange: HttpExchange): HttpResponse {
30-
return action.handle(exchange)
13+
companion object {
14+
fun matching(matcher: HttpRequestMatcher, handler: HttpHandler): HttpMatchingHandler = HttpCompositeMatchingHandler(matcher, handler)
3115
}
3216
}
17+
18+
internal data class HttpCompositeMatchingHandler(
19+
val matcher: HttpRequestMatcher,
20+
val handler: HttpHandler,
21+
) : HttpMatchingHandler {
22+
override fun matches(request: HttpRequestHead) = matcher.matches(request)
23+
override suspend fun handle(exchange: HttpExchange) = handler.handle(exchange)
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.coditory.ktserver
2+
3+
import com.coditory.ktserver.http.ContentType
4+
import com.coditory.ktserver.http.HttpRequestHead
5+
import com.coditory.ktserver.http.HttpRequestMethod
6+
import com.coditory.ktserver.http.PathPattern
7+
8+
fun interface HttpRequestMatcher {
9+
fun matches(request: HttpRequestHead): Boolean
10+
}
11+
12+
internal data class HttpSimpleRequestMatcher(
13+
val method: HttpRequestMethod? = null,
14+
val pathPattern: PathPattern? = null,
15+
val consumes: ContentType? = null,
16+
val produces: ContentType? = null,
17+
) : HttpRequestMatcher {
18+
constructor(
19+
method: HttpRequestMethod? = null,
20+
path: String,
21+
consumes: ContentType? = null,
22+
produces: ContentType? = null,
23+
) : this(
24+
method = method,
25+
pathPattern = PathPattern(path),
26+
consumes = consumes,
27+
produces = produces,
28+
)
29+
30+
override fun matches(request: HttpRequestHead): Boolean {
31+
if (method != null && method != request.method) return false
32+
if (pathPattern != null && !pathPattern.matches(request.uri.path)) return false
33+
if (consumes == null && produces == null) return true
34+
// TODO: Negotiate content with Content-Type and Accept
35+
return true
36+
}
37+
}

ktserver-api/src/main/kotlin/com/coditory/ktserver/HttpRoute.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ import com.coditory.ktserver.http.HttpRequestMethod
55
import com.coditory.ktserver.http.PathPattern
66

77
interface HttpRoute {
8-
fun context(path: String, config: HttpRoute.() -> Unit)
8+
fun routing(path: String, config: HttpRoute.() -> Unit)
99
fun filter(filter: HttpFilter)
10-
fun handler(handler: HttpHandler)
10+
fun handler(handler: HttpMatchingHandler)
1111

12-
fun get(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpAction) {
13-
handler(SimpleHttpHandler(HttpRequestMethod.GET, PathPattern(path), consumes, produces, action))
12+
fun get(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpHandler) {
13+
handler(SimpleHttpMatchingHandler(HttpRequestMethod.GET, PathPattern(path), consumes, produces, action))
1414
}
1515

16-
fun post(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpAction) {
17-
handler(SimpleHttpHandler(HttpRequestMethod.POST, PathPattern(path), consumes, produces, action))
16+
fun post(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpHandler) {
17+
handler(SimpleHttpMatchingHandler(HttpRequestMethod.POST, PathPattern(path), consumes, produces, action))
1818
}
1919

20-
fun put(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpAction) {
21-
handler(SimpleHttpHandler(HttpRequestMethod.PUT, PathPattern(path), consumes, produces, action))
20+
fun put(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpHandler) {
21+
handler(SimpleHttpMatchingHandler(HttpRequestMethod.PUT, PathPattern(path), consumes, produces, action))
2222
}
2323

24-
fun delete(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpAction) {
25-
handler(SimpleHttpHandler(HttpRequestMethod.DELETE, PathPattern(path), consumes, produces, action))
24+
fun delete(path: String, produces: ContentType? = null, consumes: ContentType? = null, action: HttpHandler) {
25+
handler(SimpleHttpMatchingHandler(HttpRequestMethod.DELETE, PathPattern(path), consumes, produces, action))
2626
}
2727
}

ktserver-api/src/main/kotlin/com/coditory/ktserver/http/PathPattern.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.coditory.ktserver.http
22

33
data class PathPattern(
4-
private val pattern: String,
4+
val pattern: String,
55
) : Comparable<PathPattern> {
66
private val regex: Regex by lazy {
77
val starChars = "a-zA-Z0-9-_"
88
val pattern = pattern
9+
.replace(Regex("\\*\\*+"), "**")
910
.replace("**", "[$starChars/]+")
1011
.replace("*", "[$starChars]+")
1112
Regex(pattern)
@@ -15,15 +16,6 @@ data class PathPattern(
1516
return regex.matches(path)
1617
}
1718

18-
fun prefix(path: String): PathPattern {
19-
val combined = (path + "/" + this.pattern)
20-
.replace(Regex("/+"), "/")
21-
.replace(Regex("/$"), "")
22-
.replace("/?", "?")
23-
.replace("/#", "#")
24-
return PathPattern(combined)
25-
}
26-
2719
override fun compareTo(other: PathPattern): Int {
2820
return this.pattern.compareTo(other.pattern)
2921
}

ktserver-core/src/main/kotlin/com/coditory/ktserver/HttpCompositeRouter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi
99

1010
@OptIn(DelicateCoroutinesApi::class)
1111
class HttpCompositeRouter(
12-
notFoundAction: HttpAction = NotFoundHttpAction(),
12+
notFoundAction: HttpHandler = NotFoundHttpHandler(),
1313
private val responseSender: HttpResponseSender = DefaultHttpResponseSender(ScoredHttpSerDeserializer.default()),
1414
private val errorHandler: HttpErrorHandler = HttpErrorHandler.default(),
1515
) {

ktserver-core/src/main/kotlin/com/coditory/ktserver/HttpHandlerChain.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.coditory.ktserver
33
import com.coditory.ktserver.http.HttpResponse
44

55
open class HttpHandlerChain(
6-
private val action: HttpAction,
6+
private val action: HttpHandler,
77
) : HttpChain {
88
override suspend fun doFilter(exchange: HttpExchange): HttpResponse {
99
return action.handle(exchange)

ktserver-core/src/main/kotlin/com/coditory/ktserver/HttpRouteNode.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.coditory.ktserver
22

33
import com.coditory.ktserver.http.HttpRequest
4+
import com.coditory.ktserver.http.PathPattern
45

56
class HttpRouteNode : HttpRoute {
67
private val children = mutableMapOf<String, HttpRouteNode>()
7-
private val handlers = mutableListOf<HttpHandler>()
8+
private val genericPathChunks = mutableSetOf<String>()
9+
private val handlers = mutableListOf<HttpMatchingHandler>()
810
private val filters = mutableListOf<HttpFilter>()
911

10-
fun getMatchingHandler(request: HttpRequest): HttpHandler? {
12+
fun getMatchingHandler(request: HttpRequest): HttpMatchingHandler? {
1113
return handlers.find { it.matches(request) }
1214
}
1315

@@ -26,8 +28,11 @@ class HttpRouteNode : HttpRoute {
2628
result.add(this)
2729
if (chunks.isEmpty()) return
2830
val key = chunks.first()
29-
val child = children[key] ?: return
30-
child.aggregate(chunks.drop(1), result)
31+
val child = children[key]
32+
child?.aggregate(chunks.drop(1), result)
33+
genericPathChunks
34+
.mapNotNull { children[it] }
35+
.forEach { it.aggregate(chunks.drop(1), result) }
3136
}
3237

3338
fun child(path: String): HttpRouteNode {
@@ -37,21 +42,25 @@ class HttpRouteNode : HttpRoute {
3742
}
3843

3944
private fun child(chunks: List<String>): HttpRouteNode {
45+
if (chunks.isEmpty()) return this
4046
val key = chunks.first()
4147
val child = children[key] ?: HttpRouteNode()
4248
children[key] = child
49+
if (PathPattern.isGenericPathChunk(key)) {
50+
genericPathChunks.add(key)
51+
}
4352
return child.child(chunks.drop(1))
4453
}
4554

46-
fun addHandler(handler: HttpHandler) {
55+
fun addHandler(handler: HttpMatchingHandler) {
4756
handlers.add(handler)
4857
}
4958

5059
fun addFilter(filter: HttpFilter) {
5160
filters.add(filter)
5261
}
5362

54-
override fun context(path: String, config: HttpRoute.() -> Unit) {
63+
override fun routing(path: String, config: HttpRoute.() -> Unit) {
5564
val node = child(path)
5665
with(node, config)
5766
}
@@ -60,7 +69,7 @@ class HttpRouteNode : HttpRoute {
6069
filters.add(filter)
6170
}
6271

63-
override fun handler(handler: HttpHandler) {
72+
override fun handler(handler: HttpMatchingHandler) {
6473
handlers.add(handler)
6574
}
6675
}

0 commit comments

Comments
 (0)