Cleartext HTTP traffic to localhost not permitted - Android Kotlin - android

I am making an Android app using Kotlin.
I made a .NET Web Api to which I connect to from Kotlin.
My problem is that when I send a request to my Web API, I get an exception "Cleartext HTTP traffic to localhost not permitted".
I have tried modifying my AndroidManifest.xml by adding "android:usesCleartextTraffic="true"" and adding a network_security_config.xml (and referencing it in the manifest android:networkSecurityConfig="#xml/network_security_config").
Nothing works.
My code for sending a request to my Web API:
const val URL = "http://localhost:24517/bar?"
const val USERNAME = "username=";
const val PASSWORD = "password=";
class BarRepo : IRepoBar{
override fun getBarByUser(userName: String, password: String): Bar {
val url = "$URL$USERNAME$userName&$PASSWORD$password"
val apiResponse = URL(url).readText()
val obj = Json.decodeFromString<Bar>(apiResponse)
return obj
}
}
The code breaks at "val apiResponse = URL(url).readText()" with the mentioned exception.

Turns out I had to put my local ip address to access it.

Related

Not sure how to resolve the issue "com.squareup.moshi.JsonDataException: Expected BEGIN_ARRAY but was STRING at path $"

I am working on an Android app written in Kotlin. I am using Moshi and Retrofit to push and pull some data from a server. In this error, I am pushing data up to the server and expecting a json string response with a "record" for every row if it was successful or not. I am syncing multiple tables this way and one is not working for some reason. Below is comparing Devices (working) with PalletsPicked (throwing this error).
// The Sync Up Call for Devices
val repResult = SyncUpApi.retrofitService.syncUp(
"devices", _deviceid, _deviceKey, devicesJson
)
// The Sync Up Call for Pallets Picked
val repResult = SyncUpApi.retrofitService.syncUp(
"pallets_picked", _deviceid, _deviceKey, palletsPickedJson
)
And here is the sync up Api
import com.squabtracker.spoc.entity.SyncUpResponseEntity
import retrofit2.http.*
interface SyncUpApiService {
#FormUrlEncoded
#POST("sync_up.php")
suspend fun syncUp(#Field("table") Table: String,
#Field("deviceid") DeviceID: Int,
#Field("key") DeviceKey: String,
#Field("data") Data: String): List<SyncUpResponseEntity>
}
object SyncUpApi {
val retrofitService : SyncUpApiService by lazy {
Network.retrofit.create(SyncUpApiService::class.java) }
}
Here is the structure of the SyncUpResponseEntity
data class SyncUpResponseEntity (
#ColumnInfo #Json(name= "primary_key") val PrimaryKey: Long,
#ColumnInfo #Json(name= "fk_repid") val fkRepID: Int,
#ColumnInfo #Json(name= "table_name") val TableName: String,
#ColumnInfo #Json(name= "success") val Success: Int
)
On the server-side the data is accepted clean and before looping through each item an response is created $response = array();
Then during each loop in Devices:
$response[] = array(
"primary_key" => $item['pk_deviceid'],
"fk_repid" => $item['fk_repid'],
"table_name" => "devices",
"success" => $success);
And in PalletsPicked
$response[] = array(
"primary_key" => $item['pk_pallets_pickedid'],
"fk_repid" => $item['fk_repid'],
"table_name" => "pallets_picked",
"success" => $success);
Finally before sending back to the device - happens for both Devices and Pallets Picked
echo json_encode($response);
The response from the server for Devices
[{"primary_key":5,"fk_repid":870,"table_name":"devices","success":1}]
And the response for Pallets Picked
[{"primary_key":1009700278,"fk_repid":749,"table_name":"pallets_picked","success":1}]
In Kotlin I have the call to the SyncUpApi wrapped in a try-catch and for PalletsPicked it is erroring out and I am not sure why it works for some like Devices, but not for this one. The inital error was:
com.squareup.moshi.JsonEncodingException: Use JsonReader.setLenient(true) to accept malformed JSON at path $
Then after setting this to lenient I got:
com.squareup.moshi.JsonDataException: Expected BEGIN_ARRAY but was STRING at path $
If there is any other code that needs to be shown to help out just let me know...
Any help is much appreciated!
Thanks!
I found this SO article on how to add logging
How can I debug my retrofit API call?
After adding this as per the accepted answer I was able to see in full detail my call and response. Turns out the PHP was spitting out an undefined index alert before returning the actual json data that I wanted. I fixed this and everything is working now!

Special characters are encoded in POST request in android. How to avoid this?

I am calling a post API with some parameters. one the parameters is
activatecode="aaaaaa$rr"
when the API call is made, it is sent as
activatecode=aaaaaa%24rr
The $ is encoded as %24. How to avoid this and send the special character as it is?
I am using Retrofit 2.9.0.
I have this service :
interface WordpressService {
#GET("wp-json/wp/v2/posts")
suspend fun getPosts(
#Query("page") page: Int,
#Query("per_page") limit: Int,
#Query("_fields", encoded = true) fields: String = "date,link,title,content,excerpt,author"
): List<Post>
}
Without putting encode = true, I end up with this request :
GET http://example.org/wp-json/wp/v2/posts?page=1&per_page=10&_fields=date%2Clink%2Ctitle%2Ccontent%2Cexcerpt%2Cauthor
With encode = true, I get :
GET http://motspirituel.org/wp-json/wp/v2/posts?page=1&per_page=10&_fields=date,link,title,content,excerpt,author
So in my case, adding encode = true in annotation solved my problem.

Ktor with Spring boot WebFlux web api

I have a simple Spring Boot WebFlux api and I am trying to use Ktor to call it.
Here is my controller
#RestController
#RequestMapping("api/v1")
class TestController {
#RequestMapping(method = [RequestMethod.GET], value = ["/values"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun sayHello(#RequestParam(value = "name") name:String): Flux<Example> {
return Flux.interval(Duration.ofSeconds(1))
.map { Example(number = generateNumber()) }
.share()
}
fun generateNumber(): Int{
return ThreadLocalRandom.current().nextInt(100)
}
}
It just returns an object with a number every second
Now on the client side is where I want to use Ktor to get the data but I am unsure what to do. Reading the docs it looks like Scoped streaming is what I need but I am not sure how to get it to work with my controller
Here is the client code I have so far.
suspend fun getData(): Flow<Example> {
return flow {
val client = HttpClient()
client.get<HttpStatement>("http://192.168.7.24:8080/api/v1/values?name=hello").execute{
val example = it.receive<Example>()
emit(example)
}
}
}
When I try to make the call on the Android client I get this error
NoTransformationFoundException: No transformation found: class
io.ktor.utils.io.ByteBufferChannel (Kotlin reflection is not
available)
So it looks like I cant just serialize the stream into my object so how do I serialize ByteReadChannel to an object?
This is my first time trying out spring and ktor
To be able to serialize the data into an object you need to do t manually by reading the data into a byte array
client.get<HttpStatement>("http://192.168.7.24:8080/api/v1/values?name=hello").execute{
val channel = it.receive<ByteReadChannel>()
val byteArray = ByteArray(it.content.availableForRead)
channel.readAvailable(byteArray,0,byteArray.size)
val example:Example = Json.parse<Example>(stringFromUtf8Bytes(byteArray))
}
You can install a plugin for JSON serialization/deserialization, which will allow you to use data classes as generic parameters
https://ktor.io/docs/json.html#jackson

Serializer for xml data while calling http post method (Ktor lib)

As I am newbie in Android, I am building an application in which I need to update layer data on Geo server. For that scenario, I m calling post method along with XML body request.
I need to send data in xml format in request body and need to get response in xml. For that, I tried using XmlSerializer instead of JsonFeature but I got error saying "XmlSerializer is not comapanion object, need to initialize here"
val httpClient = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json.nonstrict)
}
}
Thanks in Advance!!
There are no XML related features, but you can use existing Json with the XML serializer
The first thing you need to do is to find an appropriate serializer. I used https://github.com/pdvrieze/xmlutil.
implementation("net.devrieze:xmlutil-android:0.20.0.1")
You also can use other platform dependencies(I used the JVM and multiplatform).
Next you can configure the JsonFeature feature with a custom serializer:
val client = HttpClient {
Json {
serializer = XMLSerializer()
accept(ContentType.Application.Xml)
}
}
Using ContentType.Application.Xml
And finally, add the serializer:
#OptIn(ImplicitReflectionSerializer::class)
class XMLSerializer(private val format: XML = XML()) : JsonSerializer {
override fun read(type: TypeInfo, body: Input): Any {
val text = body.readText()
val deserializationStrategy = format.context.getContextual(type.type)
val mapper = deserializationStrategy
?: type.kotlinType?.let { serializer(it) }
?: type.type.serializer()
return format.parse(mapper, text) ?: error("Failed to parse response of type $type. The result is null.")
}
override fun write(data: Any, contentType: ContentType): OutgoingContent {
val serializer = data::class.serializer() as KSerializer<Any>
val text = format.stringify(serializer, data, null)
return TextContent(text, contentType)
}
}
Here is the full result sample with the server(adopted to run without Android): https://gist.github.com/e5l/3b4d5d704b4d7c6e2a65cf68de8e9ca4

Twitter API 401's on /account/verify_credentials

I am trying to make use of the twitter api, and am setting up a handler to deal with twitter api requests.
To do this I am using an HTTPUrlConnection and following the Twitter api docs
I've managed to get authenticated using the 3-legged OAuth process, but am stuck when actually trying to make requests with the oauth token.
Here is an example of what my auth headers look like:
Accept=*/*,
Connection=close,
User-Agent=OAuth gem v0.4.4,
Content-Type=application/x-www-form-urlencoded,
Authorization=
OAuth oauth_consumer_key=****&
oauth_nonce=bbmthpoiuq&
oauth_signature=*****%3D&
oauth_signature_method=HMAC-SHA1&
oauth_timestamp=1570586135&
oauth_token=*****&
oauth_version=1.0,
Host=api.twitter.com
for each header in the auth header I add it to my HTTP GET call like this:
urlConnection.setRequestProperty(header.key, header.value)
Note that Authorization points to one string
OAuth oauth_consumer_key=****&oauth_nonce=bbmthpoiuq&oauth_signature=*****%3Doauth_signature_method=HMAC-SHA1oauth_timestamp=1570586135&oauth_token=*****&oauth_version=1.0,Host=api.twitter.com
The following params are collected as follows:
oauth_consumer_key is my application API key
oauth_signature is computed by the hmacSign function below
oauth_token is the "oauth_token" received in the response from /oauth/access_token
The hmacSign function:
private fun hmacSign(requestType: RequestType, url: String, params: Map<String, String>): String {
val type = "HmacSHA1"
val key = "$API_SECRET&$tokenSecret"
val value = makeURLSafe("${requestType.string}&$url${getURLString(params.toList().sortedBy { it.first }.toMap())}")
val mac = javax.crypto.Mac.getInstance(type)
val secret = javax.crypto.spec.SecretKeySpec(key.toByteArray(), type)
mac.init(secret)
val digest = mac.doFinal(value.toByteArray())
return makeURLSafe(Base64.encodeToString(digest, NO_WRAP))
}
private fun makeURLSafe(url: String) : String {
return url.replace("/", "%2F")
.replace(",", "%2C")
.replace("=", "%3D")
.replace(":", "%3A")
.replace(" ", "%2520")
}
protected fun getURLString(params: Map<String, Any>) : String {
if (params.isEmpty()) return ""
return params.toList().fold("?") { total, current ->
var updated = total
updated += if (total == "?")
"${current.first}=${current.second}"
else
"&${current.first}=${current.second}"
updated
}
}
In the GET call I'm referring to, tokenSecret would be the oauth secret received from /oauth/access_token
After i make the call I get a 400: Bad Request
Is there anything obvious I'm doing wrong?
Update: By putting the params at the end of the url like ?key=value&key2=value2... instead of a 400 I get a 401. So I'm not sure which is worse, or which is the right way to do it.
Okay, finally got it working
So using the suggestion in the comments, I downloaded postman and copied all my info into postman - when i made the request there, I got a 200!
So then i looked back and tried to figure out what was different and there were a few things:
First the hmac sign function was missing an &
New function (added another & after the url):
private fun hmacSign(requestType: RequestType, url: String, params: Map<String, String>): String {
val type = "HmacSHA1"
val key = "$API_SECRET&$tokenSecret"
val value = makeURLSafe("${requestType.string}&$url&${getURLString(params.toList().sortedBy { it.first }.toMap())}")
val mac = javax.crypto.Mac.getInstance(type)
val secret = javax.crypto.spec.SecretKeySpec(key.toByteArray(), type)
mac.init(secret)
val digest = mac.doFinal(value.toByteArray())
return makeURLSafe(Base64.encodeToString(digest, NO_WRAP))
}
Next I noticed my auth header had its params seperated with & but they all should've been replaced with , - i also needed to surround each of my values in ""
So it looked like:
OAuth oauth_consumer_key="****",oauth_nonce="bbmthpoiuq",oauth_signature="*****%3D",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1570586135",oauth_token="*****",oauth_version="1.0",Host="api.twitter.com"
After these changes i started getting 200!
Hopefully this helps someone else (though im sure its unlikely considering how specific these issues were)

Categories

Resources