Http Server / Client communication #7
6 changed files with 204 additions and 98 deletions
|
@ -1,11 +1,8 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.task
|
package com.faraphel.tasks_valider.connectivity.task
|
||||||
|
|
||||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
import com.faraphel.tasks_valider.connectivity.task.session.TaskSessionManager
|
||||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
|
||||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||||
import com.google.gson.Gson
|
|
||||||
import fi.iki.elonen.NanoHTTPD
|
import fi.iki.elonen.NanoHTTPD
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,78 +14,11 @@ class TaskServer(
|
||||||
private val port: Int,
|
private val port: Int,
|
||||||
private val database: TaskDatabase
|
private val database: TaskDatabase
|
||||||
) : NanoHTTPD(port) {
|
) : NanoHTTPD(port) {
|
||||||
|
private val sessionManager = TaskSessionManager() ///< the session manager
|
||||||
// Session
|
|
||||||
|
|
||||||
// TODO(Faraphel): theses functions should be moved somewhere else for specific session management
|
|
||||||
|
|
||||||
private val sessions = mutableMapOf<String, TaskSession>() ///< sessions specific data
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new session
|
|
||||||
* @param session the data for the session (optional)
|
|
||||||
* @return a new session identifiant
|
|
||||||
*/
|
|
||||||
private fun newSessionData(session: TaskSession = TaskSession()): String {
|
|
||||||
val sessionId = UUID.randomUUID().toString()
|
|
||||||
this.sessions[sessionId] = session
|
|
||||||
return sessionId
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data from a http session
|
|
||||||
* @param httpSession the HTTP session
|
|
||||||
* @return the session data
|
|
||||||
*/
|
|
||||||
private fun getSessionData(httpSession: IHTTPSession): TaskSession? {
|
|
||||||
val sessionId = httpSession.cookies.read("sessionId") ?: return null
|
|
||||||
val sessionData = this.getSessionData(sessionId)
|
|
||||||
return sessionData
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data from a session identifiant
|
|
||||||
* @param sessionId the identifiant of the session
|
|
||||||
* @return the session data
|
|
||||||
*/
|
|
||||||
private fun getSessionData(sessionId: String): TaskSession? {
|
|
||||||
return this.sessions[sessionId]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data from a http session. If it does not exist, create it.
|
|
||||||
* @param httpSession the HTTP session
|
|
||||||
*/
|
|
||||||
private fun getOrCreateSessionData(httpSession: IHTTPSession): TaskSession {
|
|
||||||
// try to get the session directly
|
|
||||||
var session = this.getSessionData(httpSession)
|
|
||||||
|
|
||||||
// if the session does not exist, create it
|
|
||||||
if (session == null) {
|
|
||||||
val sessionId = this.newSessionData()
|
|
||||||
session = this.getSessionData(sessionId)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the session
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert new cookies for a session in a response
|
|
||||||
* @param response the response to inject cookies into
|
|
||||||
* @param cookies the cookie handler
|
|
||||||
*/
|
|
||||||
private fun responseSetSessionData(response: Response, cookies: CookieHandler): Response {
|
|
||||||
cookies.set(Cookie("sessionId", this.newSessionData()))
|
|
||||||
cookies.unloadQueue(response)
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP
|
|
||||||
|
|
||||||
override fun serve(httpSession: IHTTPSession): Response {
|
override fun serve(httpSession: IHTTPSession): Response {
|
||||||
// get the session data of the client
|
// get the session data of the client
|
||||||
val taskSession = this.getOrCreateSessionData(httpSession)
|
val taskSession = this.sessionManager.getOrCreateSessionData(httpSession)
|
||||||
|
|
||||||
// get the method used
|
// get the method used
|
||||||
val method: Method = httpSession.method
|
val method: Method = httpSession.method
|
||||||
|
@ -108,9 +38,8 @@ class TaskServer(
|
||||||
// get the response from the correct part of the application
|
// get the response from the correct part of the application
|
||||||
val response = when (requestType) {
|
val response = when (requestType) {
|
||||||
// session requests
|
// session requests
|
||||||
"sessions" -> {
|
"sessions" ->
|
||||||
TODO("Faraphel: handle admin changing permission")
|
this.sessionManager.handleApiRequest(taskSession, httpSession, method)
|
||||||
}
|
|
||||||
|
|
||||||
// entities requests
|
// entities requests
|
||||||
"entities" -> {
|
"entities" -> {
|
||||||
|
@ -121,26 +50,25 @@ class TaskServer(
|
||||||
"Invalid entity name"
|
"Invalid entity name"
|
||||||
)
|
)
|
||||||
|
|
||||||
this.database.handleApiRequest(taskSession, httpSession, method, entityName)
|
// TODO(Faraphel): the uri arguments should be handled in the handleApiRequest
|
||||||
|
return this.database.handleApiRequest(taskSession, httpSession, method, entityName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// invalid requests
|
// invalid requests
|
||||||
else -> {
|
else ->
|
||||||
newFixedLengthResponse(
|
newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"Invalid request type"
|
"Invalid request type"
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap additional information in the response
|
// wrap additional information in the response
|
||||||
return this.responseSetSessionData(response, httpSession.cookies)
|
return this.sessionManager.responseSetSessionData(response, httpSession.cookies)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the server with the default configuration
|
* Start the server with the default configuration
|
||||||
*/
|
*/
|
||||||
override fun start() {
|
override fun start() = super.start(SOCKET_READ_TIMEOUT, false)
|
||||||
super.start(SOCKET_READ_TIMEOUT, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
package com.faraphel.tasks_valider.connectivity.task.session
|
package com.faraphel.tasks_valider.connectivity.task.session
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A permission level that can be used for in the task system
|
|
||||||
*/
|
|
||||||
enum class TaskPermission {
|
enum class TaskPermission {
|
||||||
NONE,
|
READ,
|
||||||
STUDENT,
|
WRITE,
|
||||||
TEACHER,
|
ADMIN
|
||||||
ADMIN,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.task.session
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A role system that can be used for in the task system
|
||||||
|
*/
|
||||||
|
enum class TaskRole(val value: String) {
|
||||||
|
NONE("none") {
|
||||||
|
override var permissions: List<TaskPermission> = listOf()
|
||||||
|
},
|
||||||
|
STUDENT("student") {
|
||||||
|
override var permissions = listOf(
|
||||||
|
TaskPermission.READ
|
||||||
|
)
|
||||||
|
},
|
||||||
|
TEACHER("teacher") {
|
||||||
|
override var permissions: List<TaskPermission> = listOf(
|
||||||
|
TaskPermission.READ,
|
||||||
|
TaskPermission.WRITE
|
||||||
|
)
|
||||||
|
},
|
||||||
|
ADMIN("admin") {
|
||||||
|
override var permissions: List<TaskPermission> = listOf(
|
||||||
|
TaskPermission.READ,
|
||||||
|
TaskPermission.WRITE,
|
||||||
|
TaskPermission.ADMIN
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract var permissions: List<TaskPermission>
|
||||||
|
}
|
|
@ -3,8 +3,8 @@ package com.faraphel.tasks_valider.connectivity.task.session
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* store the data of a session in the task system
|
* store the data of a session in the task system
|
||||||
* @param permission the permission accorded to the session
|
* @param role the role accorded to the session
|
||||||
*/
|
*/
|
||||||
data class TaskSession(
|
data class TaskSession(
|
||||||
var permission: TaskPermission = TaskPermission.STUDENT
|
var role: TaskRole = TaskRole.STUDENT
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
package com.faraphel.tasks_valider.connectivity.task.session
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import fi.iki.elonen.NanoHTTPD
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The manager for the session system
|
||||||
|
*/
|
||||||
|
class TaskSessionManager {
|
||||||
|
private val jsonParser = Gson() ///< the json parser
|
||||||
|
private val jsonTypeToken = object : TypeToken<Map<String, String>>(){} ///< the json type
|
||||||
|
private val sessions = mutableMapOf<String, TaskSession>() ///< sessions specific data
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new session
|
||||||
|
* @param session the data for the session (optional)
|
||||||
|
* @param sessionId the session id to use (optional)
|
||||||
|
* @return a new session identifiant
|
||||||
|
*/
|
||||||
|
fun newSessionData(
|
||||||
|
session: TaskSession = TaskSession(),
|
||||||
|
sessionId: String = UUID.randomUUID().toString()
|
||||||
|
): String {
|
||||||
|
this.sessions[sessionId] = session
|
||||||
|
return sessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from a http session
|
||||||
|
* @param httpSession the HTTP session
|
||||||
|
* @return the session data
|
||||||
|
*/
|
||||||
|
fun getSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession? {
|
||||||
|
val sessionId = httpSession.cookies.read("sessionId") ?: return null
|
||||||
|
val sessionData = this.getSessionData(sessionId)
|
||||||
|
return sessionData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from a session identifiant
|
||||||
|
* @param sessionId the identifiant of the session
|
||||||
|
* @return the session data
|
||||||
|
*/
|
||||||
|
fun getSessionData(sessionId: String): TaskSession? {
|
||||||
|
return this.sessions[sessionId]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get data from a http session. If it does not exist, create it.
|
||||||
|
* @param httpSession the HTTP session
|
||||||
|
*/
|
||||||
|
fun getOrCreateSessionData(httpSession: NanoHTTPD.IHTTPSession): TaskSession {
|
||||||
|
// try to get the session directly
|
||||||
|
var session = this.getSessionData(httpSession)
|
||||||
|
|
||||||
|
// if the session does not exist, create it
|
||||||
|
if (session == null) {
|
||||||
|
val sessionId = this.newSessionData()
|
||||||
|
session = this.getSessionData(sessionId)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the session
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert new cookies for a session in a response
|
||||||
|
* @param response the response to inject cookies into
|
||||||
|
* @param cookies the cookie handler
|
||||||
|
*/
|
||||||
|
fun responseSetSessionData(
|
||||||
|
response: NanoHTTPD.Response,
|
||||||
|
cookies: NanoHTTPD.CookieHandler
|
||||||
|
): NanoHTTPD.Response {
|
||||||
|
cookies.set(NanoHTTPD.Cookie("sessionId", this.newSessionData()))
|
||||||
|
cookies.unloadQueue(response)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a HTTP Api request
|
||||||
|
* @param taskSession the data of the client session
|
||||||
|
* @param httpSession the data of the http session
|
||||||
|
* @param method the method used
|
||||||
|
*/
|
||||||
|
fun handleApiRequest(
|
||||||
|
taskSession: TaskSession,
|
||||||
|
httpSession: NanoHTTPD.IHTTPSession,
|
||||||
|
method: NanoHTTPD.Method
|
||||||
|
): NanoHTTPD.Response {
|
||||||
|
when (method) {
|
||||||
|
// get a client session data
|
||||||
|
NanoHTTPD.Method.GET -> {
|
||||||
|
TODO("Faraphel: implement get")
|
||||||
|
}
|
||||||
|
// change a client session data
|
||||||
|
NanoHTTPD.Method.POST -> {
|
||||||
|
// check the permission of the session
|
||||||
|
if (taskSession.role.permissions.contains(TaskPermission.ADMIN))
|
||||||
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
|
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||||
|
"text/plain",
|
||||||
|
"Forbidden"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parse the content of the request
|
||||||
|
val data: Map<String, String> = jsonParser.fromJson(
|
||||||
|
httpSession.inputStream.bufferedReader().readText(),
|
||||||
|
jsonTypeToken.type
|
||||||
|
)
|
||||||
|
|
||||||
|
// update the role
|
||||||
|
data["role"]?.let { role ->
|
||||||
|
try {
|
||||||
|
taskSession.role = TaskRole.valueOf(role)
|
||||||
|
} catch (exception: IllegalArgumentException) {
|
||||||
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
|
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||||
|
"text/plain",
|
||||||
|
"Invalid role"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// NOTE(Faraphel): does a modification on the object require updating the array ?
|
||||||
|
}
|
||||||
|
|
||||||
|
// success message
|
||||||
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
|
NanoHTTPD.Response.Status.OK,
|
||||||
|
"text/plain",
|
||||||
|
"Session updated"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// other action are limited
|
||||||
|
else -> {
|
||||||
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
|
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||||
|
"text/plain",
|
||||||
|
"Method not allowed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||||
|
import com.faraphel.tasks_valider.connectivity.task.session.TaskRole
|
||||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||||
import com.faraphel.tasks_valider.database.api.*
|
import com.faraphel.tasks_valider.database.api.*
|
||||||
import com.faraphel.tasks_valider.database.api.base.BaseApi
|
import com.faraphel.tasks_valider.database.api.base.BaseApi
|
||||||
|
@ -83,10 +84,11 @@ abstract class TaskDatabase : RoomDatabase() {
|
||||||
)
|
)
|
||||||
|
|
||||||
// dispatch the request to the correct entity API
|
// dispatch the request to the correct entity API
|
||||||
return when (method) {
|
when (method) {
|
||||||
// check if the data is in the database
|
// check if the data is in the database
|
||||||
NanoHTTPD.Method.HEAD -> {
|
NanoHTTPD.Method.HEAD -> {
|
||||||
if (taskSession.permission < TaskPermission.STUDENT)
|
// check the permission of the session
|
||||||
|
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||||
return NanoHTTPD.newFixedLengthResponse(
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
|
@ -97,7 +99,8 @@ abstract class TaskDatabase : RoomDatabase() {
|
||||||
}
|
}
|
||||||
// get the data from the database
|
// get the data from the database
|
||||||
NanoHTTPD.Method.GET -> {
|
NanoHTTPD.Method.GET -> {
|
||||||
if (taskSession.permission < TaskPermission.STUDENT)
|
// check the permission of the session
|
||||||
|
if (taskSession.role.permissions.contains(TaskPermission.READ))
|
||||||
return NanoHTTPD.newFixedLengthResponse(
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
|
@ -108,7 +111,8 @@ abstract class TaskDatabase : RoomDatabase() {
|
||||||
}
|
}
|
||||||
// insert the data into the database
|
// insert the data into the database
|
||||||
NanoHTTPD.Method.POST -> {
|
NanoHTTPD.Method.POST -> {
|
||||||
if (taskSession.permission < TaskPermission.TEACHER)
|
// check the permission of the session
|
||||||
|
if (taskSession.role.permissions.contains(TaskPermission.WRITE))
|
||||||
return NanoHTTPD.newFixedLengthResponse(
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
|
@ -119,7 +123,8 @@ abstract class TaskDatabase : RoomDatabase() {
|
||||||
}
|
}
|
||||||
// delete the data from the database
|
// delete the data from the database
|
||||||
NanoHTTPD.Method.DELETE -> {
|
NanoHTTPD.Method.DELETE -> {
|
||||||
if (taskSession.permission < TaskPermission.TEACHER)
|
// check the permission of the session
|
||||||
|
if (taskSession.role.permissions.contains(TaskPermission.WRITE))
|
||||||
return NanoHTTPD.newFixedLengthResponse(
|
return NanoHTTPD.newFixedLengthResponse(
|
||||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||||
"text/plain",
|
"text/plain",
|
||||||
|
|
Loading…
Reference in a new issue