Https request POST issue in Kotlin - kotlin

I'm new to Kotlin and Android developing.
I'm trying to use the HttpsURL to POST a value of '0' or any other value
to the 'data' variable https://www.mytestsite.com/on-test.php where is a 'data'
variable set inside.
My Kotlin code is reaching the '../on-php' url but the POST method is likely not executed
or no data was transferred for some unknown reason.
I also made another 'post.php' from where I'm posting to the same on-test.php like my Android app,
and that way it is working.
I have no any idea what I'm doing wrong.
Any help I would appreciate.
Here is my code I'm trying to make working:
btn_test.setOnClickListener {
object : AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg params: Void?): Void? {
val url = "https://www.mytestsite.com/on-test.php"
val data = JSONObject()
data.put("data", "0")
val connection = URL(url).openConnection() as HttpsURLConnection
connection.requestMethod = "POST"
connection.doOutput = true
connection.setRequestProperty("Content-Type", "application/json")
connection.connect()
val wr = DataOutputStream(connection.outputStream)
wr.writeBytes(data.toString())
wr.flush()
wr.close()
error_data = connection.responseMessage
return null
}
}.execute()
text_status.setText(error_data)
}

Related

MultiPart File Upload with Quarkus / Kotlin

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.

Kotlin - chain of scope functions

I'm new to Kotlin language and I would like to know if it's a good practice have a chain of scope functions. As example, I'm writing a function that calls some API (an utilitary function), parse the string response to specific object, do a small verification and return an object.
Is a good practice have a chain of scope functions like this code above?
fun execRequest(endpoint: String, method: String = "GET", body: String? = ""): String =
defaultHttpRequestBuilder()
.uri(URI.create(endpoint))
.method(method, HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
.run { httpClient.send(this, HttpResponse.BodyHandlers.ofString()) }
.let { it.body() }
fun processLoginRequest(challenge: String) =
execRequest(buildEndpoint("login", challenge))
.let {
mapper.readValue<LoginResponse>(it)
}
.let {
val authSituation = Auth(it.skip, it.challenge)
if (it.skip) {
val acceptResponse = acceptLoginRequest(challenge, it.subject)
authSituation.redirectTo = acceptResponse.redirectTo
}
authSituation
}
This code looks awful in my opinion. Is there another way to write it in a "Kotlin way"?
I think this question is likely to be closed due to answers being a matter of opinion, but since you asked about my comment, here is how you could break up the first function.
fun execRequest(endpoint: String, method: String = "GET", body: String? = ""): String {
val request = defaultHttpRequestBuilder()
.uri(URI.create(endpoint))
.method(method, HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
return response.body()
}
I might break up the second function like this
fun processLoginRequest(challenge: String): Auth {
val httpResponse = execRequest(buildEndpoint("login", challenge))
val loginResponse: LoginResponse = mapper.readValue(httpResponse)
return Auth(loginResponse.skip, loginResponse.challenge)
.apply {
if (loginResponse.skip)
redirectTo = acceptLoginRequest(challenge, it.subject).redirectTo
}
}

Can you get the response data from Volley outside of the stringRequest variable in Kotlin?

I want to get the Volley stringRequest response from my website outside of the variable.
val queue = Volley.newRequestQueue(this)
val url = ""
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
var obj = JSONObject(response) <-- cant access this variable outside of stringRequest
},
Response.ErrorListener { textView3.text = "That didn't work!" })
stringRequest.body.toString() <-- cant covert null to string
stringRequest.headers.toString() <-- output is {}
//here i want to do something like
if (response == "True") {
//do something
}
On the website that I'm accessing there is nothing more than {"check":"True"}
This implementation is asynchronous in the way its built in, What you actually can do to look it more like synchronous is if you use coroutines in your project, You can use suspendCoroutine, see
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/suspend-coroutine.html
Example:
suspend fun getData(url: String) = suspendCoroutine<String?> { cont ->
val queue = Volley.newRequestQueue(this)
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
cont.resume(response)
},
Response.ErrorListener { cont.resume(null) })
queue.add(stringRequest)
}
Now you can access the response string out of Response.Listener()
Edit: Additionally you can do cont.resumeWithException(e) instead if you dont want to return nullable expression and check nullability every time you use this function.

kotlin: retrofit2 getting 404 url not found error

Getting Response{protocol=http/1.1, code=404, message=Not Found, url=https://test.test.com/service/one}
The url is correct as postman works fine.
I have tried looking into this error but most things come back with URL was in correct. and the error itself is vague.
code that starts it. the builder is a json string that is valid. I have tested it in postman.
CoroutineScope(Dispatchers.Default).launch {
val call = submitService.submitCarton(builder.toString())
Log.d("submit", "begining")
withContext(Dispatchers.Main) {
if (call.isSuccessful) {
Log.d("submit",call.body() as String)
} else {
Log.d("submit", "else....")
}
}
}
service factory:
fun makeSubmitService() : SubmitService{
val url = "https://test.test.com/service/"
return Retrofit.Builder().baseUrl(url)
.client(okHttpClient).addConverterFactory(GsonConverterFactory.create())
.build().create(SubmitService::class.java)
}
interface:
interface SubmitService {
#POST("one")
suspend fun submitCarton(#Body json: String): Response<myModel>
}
Expected results are a json response however I am not getting that far.
edit: I created a okhttpclient and did a request manual and I get a message 200 ok.
code for my test
val JSON = MediaType.parse("application/json; charset=utf-8")
val client = OkHttpClient()
val body = "some json"
val requestBody = RequestBody.create(JSON, body)
val request = Request.Builder()
.url("https://test.test.com/service/one")
.post(requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(request: Request, e: IOException) {
Log.e("test", e.toString())
}
#Throws(IOException::class)
override fun onResponse(response: Response) {
Log.d("test", response.toString())
}
})
Solved it myself.
Issue was dumb, retrofit2 was giving 404 even though the web service was returning a error message.
added
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
private val interceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
private val okHttpClient = OkHttpClient().newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build()
found out retrofit was sending a very unformatted string
"{ \"all my json filled with \" }"
instead of
{ json }
fixed it by adding
.addConverterFactory(ScalarsConverterFactory.create())
to my service factory
for anyone wondering why I am basically creating the json as a string instead of using a JSON object is because the service I talk to really really wants it to be in a very specific order which JSON just don't care about it however it wants it to look like JSON as well...

Handle empty body response from service

Im trying to call a service via POST that sends an e-mail to the user, the body always return empty, I have to deal with the response code.
e.g. 204 = success.
Im trying to deal this way, but im not succeeding
Service:
#POST("xxxxxx")
fun resendBankSlip(#Path("userId") userId: Int): Deferred<Response>
ViewModel:
scope.launch {
try {
_loading.value = true
val response = repository.sendEmail(userId)
if (!response.isSuccessful) {
_error.value = R.string.generic_error_message
}
} catch (e: Throwable) {
_error.value = R.string.generic_error_message
} finally {
_loading.value = false
}
}
The error happens on val response = repository.sendEmail(userId)
Exception:
java.lang.IllegalArgumentException: 'okhttp3.Response' is not a valid response body type. Did you mean ResponseBody?
for method EmailService.sendEmail
Any idea?
You probably confused okhttp3.Response with retrofit.Response. Try to use retrofit2.Response wrapper in API response like that:
#POST("xxxxxx")
fun resendBankSlip(#Path("userId") userId: Int): Deferred<retrofit2.Response<Unit>>
After that you can easily get response code via response.code().
Also note that I passed Unit as Response's type argument because you don't need the body. In other cases you should pass an actual type of response body.