Does the Ktor framework provide a way of accessing a route's path string within a request?
For example, if I set up a route such as:
routing {
get("/user/{user_id}") {
// possible to get the string "/user/{user_id}" here?
val path = someFunction()
assert(path == "/user/{user_id}")
}
}
To clarify, I'm looking for a way to access the unprocessed path string, i.e. "/user/{user_id}" in this case (accessing the path via call.request.path() gives me the path after the {user_id} has been filled in, e.g. "/user/123").
I can of course assign the path to a variable and pass it to both get and use it within the function body, but wondering if there's a way of getting at the route's path without doing that.
I don't think that is possible. What you could do instead is write such a class/object
object UserRoutes {
const val userDetails = "/users/{user_id}"
...
}
And reference that field from your routing module:
import package.UserRoutes
get(UserRoutes.userDetails) {...}
By doing so you would need to just reference that string from the given singleton. Also no need for the object wrapper but I think it looks neat that you can group the paths by somewhat their module name
I solved it like this
// Application.kt
private object Paths {
const val LOGIN = "/login"
...
}
fun Application.module(testing: Boolean = false) {
...
routing {
loginGet(Paths.LOGIN)
}
}
And to structure my extension functions, I put them in other files like this
// Auth.kt
fun Route.loginGet(path: String) = get(path) {
println("The path is: $path")
}
fun Route.fullPath(): String {
val parentPath = parent?.fullPath()?.let { if (it.endsWith("/")) it else "$it/" } ?: "/"
return when (selector) {
is TrailingSlashRouteSelector,
is AuthenticationRouteSelector -> parentPath
else -> parentPath + selector.toString()
}
}
I found a solution for this problem
val uri = "foos/foo"
get("$uri/{foo_id}") {
val path = call.request.path()
val firstPart = path.length
val secondPart = path.slice((firstPart+1) until path.length)
call.respondText("$secondPart")
}
try this code it's simple and robust
So all solutions so far miss the most obvious (and in my humble opinion - correct) way to extract the path variables:
routing {
get("/user/{user_id}") {
val userId = call.parameters["user_id"]
}
}
call.parameters["user_id"] will return a value of type String?
You can have multiple path variables and pull them out by this method.
Related
just started learning Kotlin. I'm trying to use okhttp to send a simple get request to a URL that contains only text.
I want the output of the request stored in a liveData variable, but when I run it, it crashes. Here's the class:
// gradle dependency added to build.gradle:
// implementation("com.squareup.okhttp3:okhttp:4.5.0")
//
// added this permission to AndroidManifest.xml just above the "application" section
// <uses-permission android:name="android.permission.INTERNET" />
//
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
class GetExample {
private val client = OkHttpClient()
private val _theResult = MutableLiveData<String?>()
val theResult: LiveData<String?> = _theResult
#Throws(IOException::class)
fun getText(url: String) {
val request = Request.Builder().url(url).build()
try {
client.newCall(request).execute()
.use { response -> _theResult.value = response.body?.string() }
} catch (e: IOException) {
_theResult.value = e.message
}
}
}
And to call this I'm using
val url = "https://raw.github.com/square/okhttp/master/README.md"
GetExample().getText(url)
and accessing the result with
var thisString: String? = GetExample().theResult.value
Help greatly appreciated
Lets break down a little what your code does, shall we?
val url = "https://raw.github.com/square/okhttp/master/README.md"
GetExample().getText(url)
var thisString: String? = GetExample().theResult.value
You first assign the url variable to be a github link. Then, you construct a new GetExample object and call getText on it, with the url parameter.
But now, you are assigning thisString to a new instance of GetExample, which means it doesn't contain the data from the object you called getText on.
To fix this problem, one might write something like this:
val url = "https://raw.github.com/square/okhttp/master/README.md"
val getter = GetExample()
getter.getText(url)
var thisString: String? = getter.theResult.value
What george said is true as well, but I haven't tested that so you need to take a look if that is a problem as well.
You are trying to execute this on the UI thread. That will not work.
Just try to run it on another thread, like the IO Thread,
and use postValue in liveData. Otherwise, you need to set the value on the UI thread.
E.g.,
try {
runBlocking(IO) {
client.newCall(request).execute()
.use { response -> _theResult.postValue(response.body?.string()) }
}
} catch (e: IOException) {
_theResult.value = e.message
}
I am trying to build a multi-part file upload REST route in Quarkus (using Kotlin), but having issues with the route mapping.
From the Client side I am posting a form that contains a text value and a file value.
const formData = new FormData();
formData.append("text", text);
formData.append("file", files[0]);
fetch('http://localhost:8080/data', {
method: 'POST',
body: formData
})
From the serverside, I am trying to retrieve the values as follows.
class FormData(#FormParam("text") #PartType(MediaType.TEXT_PLAIN) var text:String,
#FormParam("file") #PartType(MediaType.APPLICATION_OCTET_STREAM) var file:InputStream)
#Path("/data")
class FormUploadResource {
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
fun upload(#MultipartForm form:FormData) {
println(form.text)
println(form.file)
}
}
However, when I execute the endpoint, I get a org.jboss.resteasy.spi.ReaderException: java.lang.NoSuchMethodException: error.
I have tried to make sure that the text and file parameters are correctly being received, and have inspected the payload coming in with the following code
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
fun upload(input:MultipartFormDataInput) {
var map = input.getFormDataMap()
map.entries.forEach {
println("""${it.key} - ${it.value}""")
if (it.value is List<InputPart>) {
it.value.forEach { ip ->
println(""" --- ${ip.getMediaType()} """ )
}
}
}
}
And it correctly says
text - [org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl$PartImpl#660c4317]
--- text/plain;charset=UTF-8
file - [org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl$PartImpl#3aee346]
--- application/octet-stream;charset=UTF-8
I assume there is something going wrong with the FormData class that RestEasy isn't automagically mapping to it. I have tried changing the type for "file" to be ByteArray and File, and they both fail also.
I have struggled to find Kotlin specific answers, so it is possible this is a Kotlin oddity also.
I just went through this issue, showing no logs at all. I managed to make it work, it seems that a empty constructor is required :
class FormData() {
#FormParam("text") #PartType(MediaType.TEXT_PLAIN)
var text: String? = null
#FormParam("file") #PartType(MediaType.APPLICATION_OCTET_STREAM)
var file: InputStream? = null
}
However I must still be missing something because
class FormData(
#FormParam("text") #PartType(MediaType.TEXT_PLAIN) var text: String?,
#FormParam("file") #PartType(MediaType.APPLICATION_OCTET_STREAM) var file: InputStream?
) {
constructor() : this(null, null)
}
enters in the method but doesn't init values
Edit: after testing secondary constructors and primary constructors like this working one,
class FormData
{
#FormParam("file")
#PartType(MediaType.APPLICATION_OCTET_STREAM)
var file: InputStream? = null
constructor() {
this.file = null
}
}
It seems that an EMPTY PRIMARY CONSTRUCTOR is required :)
Hope it helped !
I won't mark this answer as correct, as I am not happy with the solution, but it at least works.
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
fun upload(input:MultipartFormDataInput) {
val map = input.getFormDataMap()
val text = map.get("text")?.first()?.getBodyAsString() ?: ""
val file = map.get("file")?.first()?.getBodyAsString() ?: ""
println(form.text)
println(form.file)
}
As you can see, I am getting the data directly from the MultipartFormDataInput, rather than auto-constructing the object. Hopefully someone is able to shed some light on why this work around is needed, or whether a better solution is available.
I am refactoring and adding to the API communication of an app. I'd like to get to this usage for my "json data objects". Instantiate with either the properties directly or from a json string.
userFromParams = User("user#example.com", "otherproperty")
userFromString = User.fromJson(someJsonString)!!
// userIWantFromString = User(someJsonString)
Getting userFromParams to serialize to JSON was not a problem. Just adding a toJson() function takes care of that.
data class User(email: String, other_property: String) {
fun toJson(): String {
return Moshi.Builder().build()
.adapter(User::class.java)
.toJson(this)
}
companion object {
fun fromJson(json: String): User? {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
return moshi.adapter(User::class.java).fromJson(json)
}
}
}
It is "fromJson" that I would like to get rid of ...because... I want to and I can't figure out how. The above class works (give or take wether to allow an optional object to be returned or not and so on) but it just bugs me that I get stuck trying to get to this nice clean overloaded initialization.
It does not strictly have to be a data class either, but it does seem appropriate here.
You can't really do that in any performant way. Any constructor invocation will instantiate a new object, but since Moshi handles object creation internally, you'll have two instances...
If you really REALLY want it though, you can try something like:
class User {
val email: String
val other_property: String
constructor(email: String, other_property: String) {
this.email = email
this.other_property = other_property
}
constructor(json: String) {
val delegate = Moshi.Builder().build().adapter(User::class.java).fromJson(json)
this.email = delegate.email
this.other_property = delegate.other_property
}
fun toJson(): String {
return Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
.adapter(User::class.java)
.toJson(this)
}
}
I played about with Kotlin's unsupported JavaScript backend in 1.0.x and am now trying to migrate my toy project to 1.1.x. It's the barest bones of a single-page web app interfacing with PouchDB. To add data to PouchDB you need JavaScript objects with specific properties _id and _rev. They also need to not have any other properties beginning with _ because they're reserved by PouchDB.
Now, if I create a class like this, I can send instances to PouchDB.
class PouchDoc(
var _id: String
) {
var _rev: String? = null
}
However, if I do anything to make the properties virtual -- have them override an interface, or make the class open and create a subclass which overrides them -- the _id field name becomes mangled to something like _id_mmz446$_0 and so PouchDB rejects the object. If I apply #JsName("_id") to the property, that only affects the generated getter and setter -- it still leaves the backing field with a mangled name.
Also, for any virtual properties whose names don't begin with _, PouchDB will accept the object but it only stores the backing fields with their mangled names, not the nicely-named properties.
For now I can work around things by making them not virtual, I think. But I was thinking of sharing interfaces between PouchDoc and non-PouchDoc classes in Kotlin, and it seems I can't do that.
Any idea how I could make this work, or does it need a Kotlin language change?
I think your problem should be covered by https://youtrack.jetbrains.com/issue/KT-8127
Also, I've created some other related issues:
https://youtrack.jetbrains.com/issue/KT-17682
https://youtrack.jetbrains.com/issue/KT-17683
And right now You can use one of next solutions, IMO third is most lightweight.
interface PouchDoc1 {
var id: String
var _id: String
get() = id
set(v) { id = v}
var rev: String?
var _rev: String?
get() = rev
set(v) { rev = v}
}
class Impl1 : PouchDoc1 {
override var id = "id0"
override var rev: String? = "rev0"
}
interface PouchDoc2 {
var id: String
get() = this.asDynamic()["_id"]
set(v) { this.asDynamic()["_id"] = v}
var rev: String?
get() = this.asDynamic()["_rev"]
set(v) { this.asDynamic()["_rev"] = v}
}
class Impl2 : PouchDoc2 {
init {
id = "id1"
rev = "rev1"
}
}
external interface PouchDoc3 { // marker interface
}
var PouchDoc3.id: String
get() = this.asDynamic()["_id"]
set(v) { this.asDynamic()["_id"] = v}
var PouchDoc3.rev: String?
get() = this.asDynamic()["_rev"]
set(v) { this.asDynamic()["_rev"] = v}
class Impl3 : PouchDoc3 {
init {
id = "id1"
rev = "rev1"
}
}
fun keys(a: Any) = js("Object").getOwnPropertyNames(a)
fun printKeys(a: Any) {
println(a::class.simpleName)
println(" instance keys: " + keys(a).toString())
println("__proto__ keys: " + keys(a.asDynamic().__proto__).toString())
println()
}
fun main(args: Array<String>) {
printKeys(Impl1())
printKeys(Impl2())
printKeys(Impl3())
}
I got a good answer from one of the JetBrains guys, Alexey Andreev, over on the JetBrains forum at https://discuss.kotlinlang.org/t/controlling-the-jsname-of-fields-for-pouchdb-interop/2531/. Before I describe that, I'll mention a further failed attempt at refining #bashor's answer.
Property delegates
I thought that #bashor's answer was crying out to use property delegates but I couldn't get that to work without infinite recursion.
class JSMapDelegate<T>(
val jsobject: dynamic
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return jsobject[property.name]
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
jsobject[property.name] = value
}
}
external interface PouchDoc4 {
var _id: String
var _rev: String
}
class Impl4() : PouchDoc4 {
override var _id: String by JSMapDelegate<String>(this)
override var _rev: String by JSMapDelegate<String>(this)
constructor(_id: String) : this() {
this._id = _id
}
}
The call within the delegate to jsobject[property.name] = value calls the set function for the property, which calls the delegate again ...
(Also, it turns out you can't put a delegate on a property in an interface, even though you can define a getter/setter pair which work just like a delegate, as #bashor's PouchDoc2 example shows.)
Using an external class
Alexey's answer on the Kotlin forums basically says, "You're mixing the business (with behaviour) and persistence (data only) layers: the right answer would be to explicitly serialise to/from JS but we don't provide that yet; as a workaround, use an external class." The point, I think, is that external classes don't turn into JavaScript which defines property getters/setters, because Kotlin doesn't let you define behaviour for external classes. Given that steer, I got the following to work, which does what I want.
external interface PouchDoc5 {
var _id: String
var _rev: String
}
external class Impl5 : PouchDoc5 {
override var _id: String
override var _rev: String
}
fun <T> create(): T = js("{ return {}; }")
fun Impl5(_id: String): Impl5 {
return create<Impl5>().apply {
this._id = _id
}
}
The output of keys for this is
null
instance keys: _id
__proto__ keys: toSource,toString,toLocaleString,valueOf,watch,unwatch,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,__defineGetter__,__defineSetter__,__lookupGetter__,__lookupSetter__,__proto__,constructor
Creating external classes
Three notes about creating instances of external classes. First, Alexey said to write
fun <T> create(): T = js("{}")
but for me (with Kotlin 1.1) that turns into
function jsobject() {
}
whose return value is undefined. I think this might be a bug, because the official doc recommends the shorter form, too.
Second, you can't do this
fun Impl5(_id: String): Impl5 {
return (js("{}") as Impl5).apply {
this._id = _id
}
}
because that explicitly inserts a type-check for Impl5, which throws ReferenceError: Impl5 is not defined (in Firefox, at least). The generic function approach skips the type-check. I'm guessing that's not a bug, since Alexey recommended it, but it seems odd, so I'll ask him.
Lastly, you can mark create as inline, though you'll need to suppress a warning :-)
I want to write a Spek test in Kotlin.
How to read an HTML file from the src/test/resources folder?
class MySpec : Spek(
{
describe("blah blah") {
given("blah blah") {
var fileContent: String = ""
beforeEachTest {
// How to read the file.html in src/test/resources/html/
fileContent = ...
}
it("should blah blah") {
...
}
}
}
}
)
val fileContent = MySpec::class.java.getResource("/html/file.html").readText()
No idea why this is so hard, but the simplest way I've found (without having to refer to a particular class) is:
fun getResourceAsText(path: String): String? =
object {}.javaClass.getResource(path)?.readText()
It returns null if no resource with this name is found (as documented).
And then passing in an absolute URL, e.g.
val html = getResourceAsText("/www/index.html")!!
another slightly different solution:
#Test
fun basicTest() {
"/html/file.html".asResource {
// test on `it` here...
println(it)
}
}
fun String.asResource(work: (String) -> Unit) {
val content = this.javaClass::class.java.getResource(this).readText()
work(content)
}
A slightly different solution:
class MySpec : Spek({
describe("blah blah") {
given("blah blah") {
var fileContent = ""
beforeEachTest {
html = this.javaClass.getResource("/html/file.html").readText()
}
it("should blah blah") {
...
}
}
}
})
Kotlin + Spring way:
#Autowired
private lateinit var resourceLoader: ResourceLoader
fun load() {
val html = resourceLoader.getResource("classpath:html/file.html").file
.readText(charset = Charsets.UTF_8)
}
Using Google Guava library Resources class:
import com.google.common.io.Resources;
val fileContent: String = Resources.getResource("/html/file.html").readText()
private fun loadResource(file: String) = {}::class.java.getResource(file).readText()
val fileContent = javaClass.getResource("/html/file.html").readText()
This is the way that I prefer to do it:
fun getResourceText(path: String): String {
return File(ClassLoader.getSystemResource(path).file).readText()
}
this top-level kotlin function will do the job in any case
fun loadResource(path: String): URL {
return Thread.currentThread().contextClassLoader.getResource(path)
}
or if you want a more robust function
fun loadResource(path: String): URL {
val resource = Thread.currentThread().contextClassLoader.getResource(path)
requireNotNull(resource) { "Resource $path not found" }
return resource
}
FYI: In all the above cases. getResource() is unsafe way of using nullable.
Haven't tried locally but I prefer this way:
fun readFile(resourcePath: String) = String::class.java.getResource(resourcePath)?.readText() ?: "<handle default. or handle custom exception>"
Or even as custom datatype function
private fun String.asResource() = this::class.java.getResource(resourcePath)?.readText() ?: "<handle default. or handle custom exception>"
and then you can call directly on path like:
// For suppose
val path = "/src/test/resources"
val content = path.asResource()
I prefer reading resources in this way:
object {}.javaClass.getResourceAsStream("/html/file.html")?.use { it.reader(Charsets.UTF_8).readText() }
Explenation:
getResourceAsStream instead getResource. The resource on classpath can be basically anywhere. e.g. packed inside another .jar file.
In these situations accessing resource via URL class returned from getResource method will fail. But accessing via method getResourceAsStream works in every situation.
object {} - This is not nice syntax, but it is not dependent on name of your class MyClass and works even in static (compenion object) block.
use to close stream - in most cases it is not necessary, but there can be some special classloaders, which may need it.
reader(Charsets.UTF_8) - UTF_8 is default encoding, but I prefer to be explicit. If you will encode your resource files in other encoding e.g. ISO-8859-2 you will not overlook it.
Another variation that handles null resource in place:
val content = object {}.javaClass
.getResource("/html/file.html")
?.let(URL::readText)
?: error("Cannot open/find the file")
// ?: "default text" // Instead of error()
You might find the File class useful:
import java.io.File
fun main(args: Array<String>) {
val content = File("src/main/resources/input.txt").readText()
print(content)
}