-
Notifications
You must be signed in to change notification settings - Fork 7
/
HttpCall.kt
182 lines (157 loc) 路 5.88 KB
/
HttpCall.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package dev.alpas.http
import dev.alpas.*
import dev.alpas.auth.AuthChannel
import dev.alpas.auth.AuthConfig
import dev.alpas.auth.Authenticatable
import dev.alpas.auth.UserProvider
import dev.alpas.exceptions.ExceptionHandler
import dev.alpas.exceptions.HttpException
import dev.alpas.exceptions.ValidationException
import dev.alpas.ozone.orAbort
import dev.alpas.routing.RouteResult
import dev.alpas.routing.UrlGenerator
import dev.alpas.validation.ErrorBag
import dev.alpas.validation.Rule
import dev.alpas.validation.ValidationGuard
import mu.KotlinLogging
import org.eclipse.jetty.http.HttpStatus
import uy.klutter.core.uri.buildUri
import java.time.ZoneOffset
import java.time.ZonedDateTime
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
@Suppress("unused")
class HttpCall internal constructor(
private val container: Container,
private val request: Requestable,
private val response: Responsable,
internal val route: RouteResult
) : Container by container,
Requestable by request,
Responsable by response,
RequestParamsBagContract by RequestParamsBag(request, route) {
internal constructor(container: Container, req: HttpServletRequest, res: HttpServletResponse, route: RouteResult)
: this(container, Request(req), Response(res), route)
val logger by lazy { KotlinLogging.logger {} }
var isDropped = false
private set
private val exceptionHandler by lazy { make { ExceptionHandler() } }
val authChannel: AuthChannel by lazy { config<AuthConfig>().channel(this, route.target().authChannel) }
internal val userProvider: UserProvider? by lazy { authChannel.userProvider }
val isAuthenticated by lazy { authChannel.isLoggedIn() }
val isFromGuest by lazy { !isAuthenticated }
val user: Authenticatable by lazy { authChannel.user.orAbort() }
val env by lazy { make<Environment>() }
val redirector by lazy { Redirector(request, response, urlGenerator) }
val urlGenerator: UrlGenerator by lazy { container.make<UrlGenerator>() }
init {
singleton(UrlGenerator(buildUri(request.rootUrl).toURI(), make(), make()))
}
fun isSigned(): Boolean {
return urlGenerator.checkSignature(fullUrl)
}
@Suppress("UNCHECKED_CAST")
fun <T : Authenticatable> caller(): T {
return user as T
}
@Suppress("UNCHECKED_CAST")
fun <T : Authenticatable> callerId(): Long {
return caller<T>().id
}
fun close() {
if (!jettyRequest.isHandled) {
response.finalize(this)
}
// By this time we have sent a response back to the client and now we need to clear the
// previous flash messages to avoid showing these messages again in the next request.
if (sessionIsValid()) {
session.clearPreviousFlashBag()
}
jettyRequest.isHandled = true
}
fun applyRules(attribute: String, failfast: Boolean = false, rules: ValidationGuard.() -> Unit): HttpCall {
ValidationGuard(failfast).also {
it.call = this
it.rules()
it.validate(attribute, errorBag)
if (it.shouldFailFast) {
checkValidationErrors { errorBag ->
it.handleError(errorBag)
}
}
}
return this
}
fun applyRules(rules: Map<String, Iterable<Rule>>, failfast: Boolean = false): HttpCall {
ValidationGuard(failfast).also {
it.call = this
it.validate(rules, errorBag)
checkValidationErrors { errorBag ->
it.handleError(errorBag)
}
}
return this
}
fun validate() {
checkValidationErrors()
}
fun <T : ValidationGuard> validateUsing(validator: KClass<out T>): T {
return validator.createInstance().also {
it.call = this
it.validate(errorBag)
checkValidationErrors { errorBag ->
it.handleError(errorBag)
}
}
}
private fun checkValidationErrors(onErrorHandler: (ErrorBag) -> Boolean = fun(_: ErrorBag) = false) {
if (!errorBag.isEmpty()) {
if (!onErrorHandler(errorBag)) {
throw ValidationException(errorBag = errorBag)
}
}
}
fun routeNamed(name: String, params: Map<String, Any>? = null, absolute: Boolean = true): String {
return urlGenerator.route(name, params, absolute)
}
fun timezone(): ZoneOffset {
return make<AppConfig>().timezone
}
fun nowInCurrentTimezone(): ZonedDateTime {
return ZonedDateTime.now(timezone())
}
fun redirect(
to: String,
status: Int = HttpStatus.MOVED_TEMPORARILY_302,
headers: Map<String, String> = emptyMap()
) {
redirect().to(to, status, headers)
}
fun redirect(): Redirectable {
return redirector
}
private fun handleException(e: Throwable) {
// Check if the top layer is an HTTP exception type since most of the times the exception is thrown from
// a controller, the controller will be wrapped in an InvocationTargetException object. Hence, we'd have to
// check the cause of this exception to see whether the cause is an actual HTTP exception or not.
if (HttpException::class.java.isAssignableFrom(e::class.java)) {
val exceptionHandler = exceptionHandler
exceptionHandler.handle(e as HttpException, this)
return
} else {
e.cause?.let {
return handleException(it)
}
}
exceptionHandler.handle(e, this)
return
}
fun drop(e: Exception) {
isDropped = true
handleException(e)
close()
}
internal fun sessionIsValid() = env.supportsSession && !servletResponse.isCommitted && session.isValid()
}