Switch from RabbitMQ to server-sent events

(not fully working yet)
This commit is contained in:
2021-05-03 20:58:40 +02:00
parent 533749c7c8
commit daa7209742
38 changed files with 22158 additions and 672 deletions

View File

@@ -44,7 +44,8 @@ dependencies {
implementation 'com.squareup.moshi:moshi:1.11.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.11.0'
implementation 'com.rabbitmq:amqp-client:5.9.0'
implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:okhttp-sse:4.9.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

View File

@@ -0,0 +1,90 @@
package de.nicolasklier.livebeat
import android.util.Log
import okhttp3.Response
import okhttp3.internal.platform.Platform
import okhttp3.internal.platform.Platform.Companion.INFO
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
import okio.IOException
import org.jetbrains.annotations.Nullable
import java.util.concurrent.BlockingQueue
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.TimeUnit
class EventSourceRecorder : EventSourceListener() {
private val events: BlockingQueue<Any> = LinkedBlockingDeque()
override fun onOpen(eventSource: EventSource, response: Response) {
Log.i("SSE", "Connection opened!")
}
override fun onEvent(
eventSource: EventSource, @Nullable id: String?, @Nullable type: String?,
data: String
) {
Log.i("SSE", "onEvent: " + data)
}
override fun onClosed(eventSource: EventSource) {
Log.i("SSE", "Connection to SSE got closed!")
events.add(Closed())
}
fun onFailure(
eventSource: EventSource?,
@Nullable t: Throwable
) {
Platform.get().log("[ES] onFailure", INFO, t)
}
private fun nextEvent(): Any {
return try {
val event: Any = events.poll(10, TimeUnit.SECONDS)
?: throw AssertionError("Timed out waiting for event.")
event
} catch (e: InterruptedException) {
throw AssertionError(e)
}
}
internal class Open(val eventSource: EventSource?, response: Response) {
val response: Response
override fun toString(): String {
return "Open[$response]"
}
init {
this.response = response
}
}
internal class Failure(val t: Throwable, response: Response?) {
val response: Response?
val responseBody: String?
override fun toString(): String {
return if (response == null) {
"Failure[$t]"
} else "Failure[$response]"
}
init {
this.response = response
var responseBody: String? = null
if (response != null) {
try {
responseBody = response.body.toString()
} catch (ignored: IOException) {
}
}
this.responseBody = responseBody
}
}
internal class Closed {
override fun toString(): String {
return "Closed[]"
}
}
}

View File

@@ -0,0 +1,58 @@
package de.nicolasklier.livebeat
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.sse.EventSource
import okhttp3.sse.EventSources
import java.lang.Error
import java.net.ConnectException
class HttpRequests {
companion object {
fun get(url: String, sendToken: Boolean = true): Response {
val client = OkHttpClient()
var req = Request.Builder()
.url(url)
.get()
.build()
if (sendToken) {
req = req.newBuilder().addHeader("token", MainActivity.TOKEN).build();
}
return client.newCall(req).execute();
}
fun post(url: String, body: String, sendToken: Boolean = true): Response {
try {
val client = OkHttpClient()
var req = Request.Builder()
.url(url)
.post(
(body).toRequestBody()
)
.header("Content-Type", "application/json")
.build()
if (sendToken) {
req = req.newBuilder().addHeader("token", MainActivity.TOKEN).build()
}
return client.newCall(req).execute()
} catch (e: ConnectException) {
throw Error("Connection to $url couldn't be made: ${e.message}")
}
}
fun sse(url: String) {
val client = OkHttpClient()
val req = Request.Builder().url(url).build();
val handler = EventSourceRecorder()
val factory = EventSources.createFactory(client)
val sse = factory.newEventSource(req, handler)
sse.request()
}
}
}

View File

@@ -20,6 +20,7 @@ import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@@ -31,6 +32,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.net.ConnectException
import java.util.logging.Logger
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
@@ -49,13 +51,13 @@ class MainActivity : AppCompatActivity() {
if (TOKEN == "") return;
Thread(Runnable {
val androidId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
val client = OkHttpClient()
/*val client = OkHttpClient()
val req = Request.Builder()
.url("$API_URL/phone/$androidId")
.header("token", TOKEN)
.get()
.build()
val response = client.newCall(req).execute()
.build()*/
val response = HttpRequests.get("$API_URL/phone/$androidId")
if (response.code != 200) {
Snackbar.make(findViewById<FloatingActionButton>(R.id.fab), "Device isn't registered yet. Registering ...", Snackbar.LENGTH_SHORT)
@@ -75,7 +77,7 @@ class MainActivity : AppCompatActivity() {
val phoneToJson = moshi.adapter(Phone::class.java)
val json = phoneToJson.toJson(phone)
val createPhone = Request.Builder()
/*val createPhone = Request.Builder()
.url("$API_URL/phone")
.post(
(json).toRequestBody()
@@ -83,7 +85,8 @@ class MainActivity : AppCompatActivity() {
.header("Content-Type", "application/json")
.header("token", TOKEN)
.build()
client.newCall(createPhone).execute()
client.newCall(createPhone).execute()*/
HttpRequests.post("$API_URL/phone", json);
}
}).start()
}
@@ -95,7 +98,7 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
val process = Runtime.getRuntime().exec("su")
//val process = Runtime.getRuntime().exec("su")
// Check authorization
val backendChecks = Thread(Runnable {
@@ -104,15 +107,40 @@ class MainActivity : AppCompatActivity() {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonToLogin = moshi.adapter(Login::class.java)
val token = "{ \"username\": \"" + username + "\"," +
"\"password\": \"" + password + "\" }"
try {
HttpRequests.post("$API_URL/user/login", token)
} catch (e: Error) {
Snackbar.make(findViewById<FloatingActionButton>(R.id.fab), "Backend server is not available", Snackbar.LENGTH_SHORT)
.setBackgroundTint(Color.RED)
.setActionTextColor(Color.WHITE)
.show();
return@Runnable
}
val client = OkHttpClient()
val req = Request.Builder()
.url("$API_URL/user/login")
.post(
("{ \"username\": \"" + username + "\"," +
"\"password\": \"" + password + "\" }").toRequestBody()
(token).toRequestBody()
)
.header("Content-Type", "application/json")
.build()
// Check if server is available.
try {
val testReq = Request.Builder()
.url("$API_URL/user/login")
.post(
("{ \"username\": \"" + username + "\"," +
"\"password\": \"" + password + "\" }").toRequestBody()
)
.header("Content-Type", "application/json")
.build()
client.newCall(testReq).execute();
} catch (e: ConnectException) {
}
val loginResponse = client.newCall(req).execute()
val responseBody = loginResponse.body!!.string()
@@ -141,8 +169,10 @@ class MainActivity : AppCompatActivity() {
val userInfoResponseBody = userinfoResponse.body!!.string()
USER = jsonToUser.fromJson(userInfoResponseBody)
val intent = Intent(this, TrackerService::class.java)
// Only start service if authentication went good.
startService(Intent(this, TrackerService::class.java))
startService(intent)
Snackbar.make(findViewById<FloatingActionButton>(R.id.fab), "Login succeeded", Snackbar.LENGTH_SHORT)
.setBackgroundTint(Color.GREEN)
@@ -171,17 +201,8 @@ class MainActivity : AppCompatActivity() {
this.broadcastReceiver = object : BroadcastReceiver() {
@SuppressLint("CutPasteId")
override fun onReceive(context: Context, intent: Intent) {
val statusRabbit = intent.getBooleanExtra("statusRabbit", false)
val statusHttp = intent.getIntExtra("statusHttp", 404)
if (statusRabbit) {
findViewById<TextView>(R.id.rabbitStatus).text = "connected"
findViewById<TextView>(R.id.rabbitStatus).setTextColor(Color.GREEN)
} else {
findViewById<TextView>(R.id.rabbitStatus).text = "disconnected"
findViewById<TextView>(R.id.rabbitStatus).setTextColor(Color.RED)
}
/*if (statusHttp == 200) {
findViewById<TextView>(R.id.httpStatus).text = "ONLINE (no login)"
findViewById<TextView>(R.id.httpStatus).setTextColor(Color.CYAN)

View File

@@ -27,7 +27,7 @@ class User(
val type: String,
val lastLogin: String,
val twoFASecret: String?,
val brokerToken: String,
val eventToken: String,
val createdAt: String
)

View File

@@ -10,7 +10,6 @@ import android.content.pm.PackageManager
import android.graphics.Color
import android.location.LocationManager
import android.os.BatteryManager
import android.os.Bundle
import android.os.IBinder
import android.provider.Settings
import android.util.Log
@@ -18,14 +17,9 @@ import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import com.rabbitmq.client.Channel
import com.rabbitmq.client.Connection
import com.rabbitmq.client.ConnectionFactory
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException
import java.util.concurrent.TimeoutException
class TrackerService : Service() {
@@ -36,38 +30,9 @@ class TrackerService : Service() {
return null
}
private fun connectWithBroker() {
// This thread only connects to RabbitMQ
val connectionThread = Thread(Runnable {
val client = OkHttpClient()
val factory = ConnectionFactory()
factory.username = MainActivity.USER!!.name
factory.password = MainActivity.USER!!.brokerToken
factory.virtualHost = "/"
factory.host = "192.168.178.26"
factory.port = 5672
factory.isAutomaticRecoveryEnabled = true
try {
conn[0] = factory.newConnection()
channel[0] = conn[0]?.createChannel()
val intent = Intent("de.nicolasklier.livebeat")
val bundle = Bundle()
bundle.putBoolean("statusRabbit", true)
intent.putExtras(bundle)
this.sendBroadcast(intent)
channel[0]?.queueDeclare("tracker-" + factory.username, true, false, false, null)
//channel[0]?.basicPublish("", "Tracker", null, "Test message".toByteArray())
Log.i("RabbitMQ", "run: Published test message")
} catch (e: IOException) {
e.printStackTrace()
} catch (e: TimeoutException) {
e.printStackTrace()
}
})
connectionThread.start()
@SuppressLint("CheckResult")
private fun subscribeToEvents() {
HttpRequests.sse("http://192.168.178.26/user/events?token=${MainActivity.USER?.eventToken}")
}
@SuppressLint("HardwareIds")
@@ -105,7 +70,7 @@ class TrackerService : Service() {
}
}
connectWithBroker()
subscribeToEvents()
startForeground()
return super.onStartCommand(intent, flags, startId)
}

View File

@@ -68,31 +68,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/rabbitText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_weight="0"
android:text="RabbitMQ"
android:textColor="@color/white" />
<TextView
android:id="@+id/rabbitStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_weight="0"
android:fontFamily="sans-serif-black"
android:text="disconnected"
android:textAlignment="center"
android:textAllCaps="true"
android:textColor="@color/orange"
android:textSize="14sp" />
</LinearLayout>
android:orientation="horizontal"/>
<Space
android:layout_width="match_parent"