Skip to content

Commit 2e3569b

Browse files
author
Robert Winkler
committed
Fixed a lot
1 parent 24e44e2 commit 2e3569b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1994
-395
lines changed

kotlin-wot-binding-http/src/main/kotlin/http/HttpProtocolClient.kt

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package ai.ancf.lmos.wot.binding.http
22

3-
import ai.ancf.lmos.wot.Servient
3+
import ai.ancf.lmos.wot.content.Content
44
import ai.ancf.lmos.wot.security.BasicSecurityScheme
55
import ai.ancf.lmos.wot.security.BearerSecurityScheme
66
import ai.ancf.lmos.wot.security.NoSecurityScheme
77
import ai.ancf.lmos.wot.security.SecurityScheme
88
import ai.ancf.lmos.wot.thing.form.Form
9-
import ai.anfc.lmos.wot.binding.Content
109
import ai.anfc.lmos.wot.binding.ProtocolClient
1110
import ai.anfc.lmos.wot.binding.ProtocolClientException
1211
import io.ktor.client.*
@@ -58,15 +57,6 @@ class HttpProtocolClient(
5857
}
5958
}.flowOn(Dispatchers.IO) // Run the flow on IO thread, as it involves network operations
6059

61-
override fun supports(scheme: String) = scheme == "http" || scheme == "https"
62-
63-
override suspend fun start(servient: Servient) {
64-
}
65-
66-
override suspend fun stop() {
67-
client.close()
68-
}
69-
7060
override fun setSecurity(metadata: List<SecurityScheme>, credentials: Map<String, String>): Boolean {
7161
if (metadata.isEmpty()) {
7262
log.warn("HttpClient without security")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ai.ancf.lmos.wot.binding.http
2+
3+
import ai.anfc.lmos.wot.binding.ProtocolClientFactory
4+
5+
/**
6+
* Creates new [HttpProtocolClient] instances.
7+
*/
8+
open class HttpProtocolClientFactory : ProtocolClientFactory {
9+
override fun toString(): String {
10+
return "HttpClient"
11+
}
12+
override val scheme: String
13+
get() = "http"
14+
override val client: HttpProtocolClient
15+
get() = HttpProtocolClient()
16+
17+
override suspend fun init() {
18+
TODO("Not yet implemented")
19+
}
20+
21+
override suspend fun destroy() {
22+
TODO("Not yet implemented")
23+
}
24+
}

kotlin-wot-binding-http/src/main/kotlin/http/HttpProtocolServer.kt

Lines changed: 82 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package ai.ancf.lmos.wot.binding.http
22

33
import ai.ancf.lmos.wot.Servient
4-
import ai.ancf.lmos.wot.thing.ContentManager
4+
import ai.ancf.lmos.wot.content.Content
5+
import ai.ancf.lmos.wot.content.ContentCodecException
6+
import ai.ancf.lmos.wot.content.ContentManager
57
import ai.ancf.lmos.wot.thing.ExposedThing
6-
import ai.ancf.lmos.wot.thing.action.ExposedThingAction
78
import ai.ancf.lmos.wot.thing.form.Form
89
import ai.ancf.lmos.wot.thing.form.Operation
910
import ai.ancf.lmos.wot.thing.schema.InteractionAffordance
10-
import ai.anfc.lmos.wot.binding.Content
1111
import ai.anfc.lmos.wot.binding.ProtocolServer
1212
import ai.anfc.lmos.wot.binding.ProtocolServerException
1313
import com.fasterxml.jackson.databind.DeserializationFeature
@@ -22,12 +22,13 @@ import io.ktor.server.request.*
2222
import io.ktor.server.routing.*
2323
import io.ktor.util.reflect.*
2424
import org.slf4j.LoggerFactory
25+
import java.util.concurrent.ExecutionException
2526
import kotlin.collections.set
2627

2728
/**
2829
* Allows exposing Things via HTTP.
2930
*/
30-
class HttpProtocolServer(
31+
open class HttpProtocolServer(
3132
private val wait: Boolean = false,
3233
private val bindHost: String = "0.0.0.0",
3334
private val bindPort: Int = 8080,
@@ -36,7 +37,7 @@ class HttpProtocolServer(
3637
val things: MutableMap<String, ExposedThing> = mutableMapOf()
3738
var started = false
3839
private var server: EmbeddedServer<*, *>? = null
39-
private val actualAddresses: List<String> = listOf()
40+
private var actualAddresses: List<String> = listOf("http://$bindHost:$bindPort")
4041

4142
companion object {
4243
private val log = LoggerFactory.getLogger(HttpProtocolServer::class.java)
@@ -57,16 +58,16 @@ class HttpProtocolServer(
5758
}
5859

5960
// Expose a thing
60-
override suspend fun expose(thing: ExposedThing) {
61+
override fun expose(thing: ExposedThing) {
6162
if (!started) throw ProtocolServerException("Server has not started yet")
6263

6364
log.info("Exposing thing '{}'", thing.id)
6465
things[thing.id] = thing
6566

6667
for (address in actualAddresses) {
67-
for (contentType in ContentManager.getOfferedMediaTypes()) {
68+
for (contentType in ContentManager.offeredMediaTypes) {
6869
// make reporting of all properties optional?
69-
val href = (address + "/" + thing.id).toString() + "/all/properties"
70+
val href = "$address/${thing.id}/all/properties"
7071
val form = Form(href = href, contentType = contentType, op = listOf( Operation.READ_ALL_PROPERTIES,
7172
Operation.READ_MULTIPLE_PROPERTIES))
7273

@@ -80,10 +81,8 @@ class HttpProtocolServer(
8081
}
8182
}
8283

83-
fun exposeProperties(thing: ExposedThing, address: String, contentType: String) {
84-
val properties = thing.exposedProperties
85-
86-
properties.forEach { (name, property) ->
84+
internal fun exposeProperties(thing: ExposedThing, address: String, contentType: String) {
85+
thing.properties.forEach { (name, property) ->
8786

8887
val href = getHrefWithVariablePattern(address, thing, "properties", name, property)
8988

@@ -96,7 +95,7 @@ class HttpProtocolServer(
9695

9796
// Create the main form and add it to the property
9897
val form = Form(href = href, contentType = contentType, op = operations)
99-
property.forms?.plusAssign(form)
98+
property.forms += form
10099
log.debug("Assign '{}' to Property '{}'", href, name)
101100

102101
// If the property is observable, add an additional form with an observable href
@@ -108,15 +107,14 @@ class HttpProtocolServer(
108107
op = listOf(Operation.OBSERVE_PROPERTY),
109108
subprotocol = "longpoll"
110109
)
111-
property.forms?.plusAssign(observableForm)
110+
property.forms += observableForm
112111
log.debug("Assign '{}' to observe Property '{}'", observableHref, name)
113112
}
114113
}
115114
}
116115

117-
fun exposeActions(thing: ExposedThing, address: String, contentType: String) {
118-
val actions: Map<String, ExposedThingAction<*, *>> = thing.exposedActions
119-
actions.forEach { (name, action) ->
116+
internal fun exposeActions(thing: ExposedThing, address: String, contentType: String) {
117+
thing.actions.forEach { (name, action) ->
120118
val href: String = getHrefWithVariablePattern(address, thing, "actions", name, action)
121119
// Initialize the form using named parameters
122120
val form = Form(
@@ -126,14 +124,13 @@ class HttpProtocolServer(
126124
)
127125

128126
// Add the form to the action
129-
action.forms?.plusAssign(form)
127+
action.forms += form
130128
log.debug("Assign '{}' to Action '{}'", href, name)
131129
}
132130
}
133131

134-
fun exposeEvents(thing: ExposedThing, address: String, contentType: String) {
135-
val events = thing.exposedEvents
136-
events.forEach { (name, event) ->
132+
internal fun exposeEvents(thing: ExposedThing, address: String, contentType: String) {
133+
thing.events.forEach { (name, event) ->
137134
val href = getHrefWithVariablePattern(address, thing, "events", name, event)
138135

139136
// Create the form using named parameters directly
@@ -145,7 +142,7 @@ class HttpProtocolServer(
145142
)
146143

147144
// Add the form to the event
148-
event.forms?.plusAssign(form)
145+
event.forms += form
149146
log.debug("Assign '{}' to Event '{}'", href, name)
150147
}
151148
}
@@ -159,7 +156,7 @@ class HttpProtocolServer(
159156
): String {
160157
var variables = ""
161158
val uriVariables = interaction.uriVariables?.keys
162-
if (uriVariables != null) {
159+
if (!uriVariables.isNullOrEmpty()) {
163160
variables = "{?" + java.lang.String.join(",", uriVariables) + "}"
164161
}
165162
return "$address/${thing.id}/$type/$interactionName$variables"
@@ -208,9 +205,23 @@ fun Application.setupRouting(servient: Servient) {
208205
val id = call.parameters["id"] ?: return@get call.response.status(HttpStatusCode.BadRequest)
209206
val propertyName = call.parameters["name"]
210207
val thing = servient.things[id] ?: return@get call.response.status(HttpStatusCode.NotFound)
211-
val property = thing.exposedProperties[propertyName]
208+
val property = thing.properties[propertyName]
212209
if (property != null) {
213-
// call.respond(property.read())
210+
if (!property.writeOnly) {
211+
try {
212+
val value = property.read()
213+
//contentType = getOrDefaultRequestContentType(call.request)
214+
//val content = ContentManager.valueToContent(value, contentType.toString())
215+
call.respond(value, typeInfo<Any>())
216+
}
217+
catch (e: ContentCodecException) {
218+
call.response.status(HttpStatusCode.InternalServerError)
219+
} catch (e: ExecutionException) {
220+
call.response.status(HttpStatusCode.InternalServerError)
221+
}
222+
} else {
223+
call.response.status(HttpStatusCode.BadRequest)
224+
}
214225
} else {
215226
call.response.status(HttpStatusCode.NotFound)
216227
}
@@ -219,15 +230,13 @@ fun Application.setupRouting(servient: Servient) {
219230
val id = call.parameters["id"] ?: return@put call.response.status(HttpStatusCode.BadRequest)
220231
val propertyName = call.parameters["name"]
221232
val thing = servient.things[id] ?: return@put call.response.status(HttpStatusCode.NotFound)
222-
val property = thing.exposedProperties[propertyName]
233+
val property = thing.properties[propertyName]
223234
if (property != null) {
224235
if (!property.readOnly) {
225-
val contentType = getOrDefaultRequestContentType(call.request)
226-
val content = Content(contentType.toString(), call.receiveText())
227-
val input = ContentManager.contentToValue(content, property)
228-
229-
//val newValue = property.write(input)
230-
//call.respond(newValue)
236+
//val contentType = getOrDefaultRequestContentType(call.request)
237+
//val content = Content(contentType.toString(), call.receiveChannel().toByteArray())
238+
//val newValue = property.write(call.receive())
239+
//call.respond(newValue, typeInfo<Any>())
231240
} else {
232241
call.response.status(HttpStatusCode.BadRequest)
233242
}
@@ -240,10 +249,26 @@ fun Application.setupRouting(servient: Servient) {
240249
val id = call.parameters["id"] ?: return@post call.response.status(HttpStatusCode.BadRequest)
241250
val actionName = call.parameters["name"]
242251
val thing = servient.things[id] ?: return@post call.response.status(HttpStatusCode.NotFound)
243-
val action = thing.exposedActions[actionName]
252+
val action = thing.actions[actionName]
244253
if (action != null) {
245-
val input = call.receive<Any>()
246-
call.respond("TODO Return action response", typeInfo<String>())
254+
val requestContentType = getOrDefaultRequestContentType(call.request).toString()
255+
val content = Content(requestContentType, call.receive())
256+
257+
if(action.input != null){
258+
val input = ContentManager.contentToValue(content, action.input!!)
259+
val newValue = action.invokeAction(null, null)
260+
call.respond(newValue, typeInfo<Any>())
261+
}else{
262+
val newValue = action.invokeAction(null, null)
263+
call.respond(newValue, typeInfo<Any>())
264+
}
265+
266+
267+
/*
268+
val options = mapOf<String, Map<String, Any>>(
269+
"uriVariables" to parseUrlParameters(request.queryMap().toMap(), action.uriVariables)
270+
)
271+
*/
247272
} else {
248273
call.response.status(HttpStatusCode.NotFound)
249274
}
@@ -272,4 +297,25 @@ private fun getOrDefaultRequestContentType(request: RoutingRequest): ContentType
272297
} else {
273298
contentType
274299
}
275-
}
300+
}
301+
302+
/*
303+
private fun parseUrlParameters(
304+
urlParams: Map<String, Array<String>>,
305+
uriVariables: MutableMap<String, DataSchema<Any>>?
306+
): Map<String, Any> {
307+
return urlParams.mapNotNull { (name, urlValue) ->
308+
val uriVariable = uriVariables?.get(name)
309+
val type = uriVariable["type"]
310+
311+
val value: Any = when (type) {
312+
"integer", "number" -> urlValue.firstOrNull()?.toIntOrNull()
313+
"string" -> urlValue.firstOrNull()
314+
else -> {
315+
null
316+
}
317+
}
318+
name to value
319+
}.toMap()
320+
}
321+
*/
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package ai.ancf.lmos.wot.binding.http
2+
3+
/**
4+
* Creates new [HttpProtocolClient] instances that allow consuming Things via HTTPS.
5+
*/
6+
class HttpsProtocolClientFactory() : HttpProtocolClientFactory() {
7+
8+
override fun toString(): String {
9+
return "HttpsClient"
10+
}
11+
12+
override val scheme: String
13+
get() = "https"
14+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ai.ancf.lmos.wot.binding.http.routes
2+
3+
import ai.ancf.lmos.wot.Servient
4+
import ai.ancf.lmos.wot.thing.ExposedThing
5+
import io.ktor.http.*
6+
import io.ktor.server.request.*
7+
import io.ktor.server.routing.*
8+
import io.ktor.util.*
9+
10+
internal abstract class AbstractInteractionRoute(private val servient: Servient, private val securityScheme: String, private val things: Map<String, ExposedThing>
11+
) : AbstractRoute() {
12+
13+
private fun checkCredentials(securityScheme: String?, id: String, request: RoutingRequest): Boolean {
14+
if (securityScheme == null) {
15+
// No security configured -> always authorized
16+
return true
17+
}
18+
19+
log.debug("HttpServer checking credentials for '{}'", id)
20+
val credentials = servient.getCredentials(id) as? Map<*, *> ?: return false
21+
22+
val header = request.header(HttpHeaders.Authorization)?.takeIf { it.isNotBlank() }
23+
return when (securityScheme) {
24+
"Basic" -> {
25+
header?.removePrefix("Basic ")?.decodeBase64Bytes()?.toString(Charsets.UTF_8)?.let { decodedHeader ->
26+
val (givenUsername, givenPassword) = decodedHeader.split(":", limit = 2)
27+
val requiredUsername = credentials["username"] as? String
28+
val requiredPassword = credentials["password"] as? String
29+
givenUsername == requiredUsername && givenPassword == requiredPassword
30+
} ?: run {
31+
log.warn("Unable to decode username and password from authorization header")
32+
false
33+
}
34+
}
35+
36+
"Bearer" -> {
37+
val requiredToken = credentials["token"] as? String
38+
header?.removePrefix("Bearer ") == requiredToken
39+
}
40+
41+
else -> {
42+
log.warn("Unknown security scheme provided. Decline access.")
43+
false
44+
}
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)