Http Server / Client communication #7
4 changed files with 205 additions and 28 deletions
|
@ -1,9 +1,11 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task
|
||||
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.database.TaskDatabase
|
||||
import com.faraphel.tasks_valider.database.api.base.BaseApi
|
||||
import com.google.gson.Gson
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
|
@ -15,39 +17,124 @@ class TaskServer(
|
|||
private val port: Int,
|
||||
private val database: TaskDatabase
|
||||
) : NanoHTTPD(port) {
|
||||
private val jsonParser = Gson()
|
||||
|
||||
override fun serve(session: IHTTPSession): Response {
|
||||
val method: Method = session.method
|
||||
val uri: String = session.uri
|
||||
// Session
|
||||
|
||||
// remove the first slash
|
||||
val entityName: String = uri.substring(1)
|
||||
// get the matching entity API in the database
|
||||
val entityApi = database.getApiFromName(entityName)
|
||||
// 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 {
|
||||
// get the session data of the client
|
||||
val taskSession = this.getOrCreateSessionData(httpSession)
|
||||
|
||||
// get the method used
|
||||
val method: Method = httpSession.method
|
||||
|
||||
// parse the url
|
||||
val uri: String = httpSession.uri.substring(1) // remove the first slash
|
||||
val uriComponents = uri.split("/")
|
||||
|
||||
// get the type of the request from the uri
|
||||
val requestType = uriComponents.getOrNull(0)
|
||||
?: return newFixedLengthResponse(
|
||||
Response.Status.NOT_FOUND,
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Invalid request type"
|
||||
)
|
||||
|
||||
// get the response from the correct part of the application
|
||||
val response = when (requestType) {
|
||||
// session requests
|
||||
"sessions" -> {
|
||||
TODO("Faraphel: handle admin changing permission")
|
||||
}
|
||||
|
||||
// entities requests
|
||||
"entities" -> {
|
||||
val entityName = uriComponents.getOrNull(1)
|
||||
?: return newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Invalid entity name"
|
||||
)
|
||||
|
||||
// handle the request
|
||||
return when (method) {
|
||||
// check if the data is in the database
|
||||
Method.HEAD -> entityApi.head(session)
|
||||
// get the data from the dao
|
||||
Method.GET -> entityApi.get(session)
|
||||
// insert the data into the database
|
||||
Method.POST -> entityApi.post(session)
|
||||
// other methods are not allowed
|
||||
else ->
|
||||
newFixedLengthResponse(
|
||||
Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Method not allowed"
|
||||
)
|
||||
// TODO(Faraphel): implement a permission system
|
||||
this.database.handleApiRequest(taskSession, httpSession, method, entityName)
|
||||
}
|
||||
// invalid requests
|
||||
else -> {
|
||||
newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.BAD_REQUEST,
|
||||
"text/plain",
|
||||
"Invalid request type"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// wrap additional information in the response
|
||||
return this.responseSetSessionData(response, httpSession.cookies)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
|
||||
/**
|
||||
* A permission level that can be used for in the task system
|
||||
*/
|
||||
enum class TaskPermission {
|
||||
NONE,
|
||||
STUDENT,
|
||||
TEACHER,
|
||||
ADMIN,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.faraphel.tasks_valider.connectivity.task.session
|
||||
|
||||
|
||||
/**
|
||||
* store the data of a session in the task system
|
||||
* @param permission the permission accorded to the session
|
||||
*/
|
||||
data class TaskSession(
|
||||
var permission: TaskPermission = TaskPermission.STUDENT
|
||||
)
|
|
@ -3,6 +3,8 @@ package com.faraphel.tasks_valider.database
|
|||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskPermission
|
||||
import com.faraphel.tasks_valider.connectivity.task.session.TaskSession
|
||||
import com.faraphel.tasks_valider.database.api.*
|
||||
import com.faraphel.tasks_valider.database.api.base.BaseApi
|
||||
import com.faraphel.tasks_valider.database.converters.InstantConverter
|
||||
|
@ -18,6 +20,7 @@ import com.faraphel.tasks_valider.database.entities.StudentEntity
|
|||
import com.faraphel.tasks_valider.database.entities.TaskEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TaskGroupEntity
|
||||
import com.faraphel.tasks_valider.database.entities.TeacherEntity
|
||||
import fi.iki.elonen.NanoHTTPD
|
||||
|
||||
|
||||
/**
|
||||
|
@ -63,10 +66,75 @@ abstract class TaskDatabase : RoomDatabase() {
|
|||
)
|
||||
|
||||
/**
|
||||
* get the api for an entity from its name.
|
||||
* @param name the name of the entity
|
||||
* dispatch an API request
|
||||
*/
|
||||
fun getApiFromName(name: String): BaseApi? {
|
||||
return this.api[name]
|
||||
fun handleApiRequest(
|
||||
taskSession: TaskSession,
|
||||
httpSession: NanoHTTPD.IHTTPSession,
|
||||
method: NanoHTTPD.Method,
|
||||
entityName: String
|
||||
): NanoHTTPD.Response {
|
||||
// get the correspond Api object for this entity
|
||||
val entityApi = this.api[entityName]
|
||||
?: return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.NOT_FOUND,
|
||||
"text/plain",
|
||||
"Invalid entity name"
|
||||
)
|
||||
|
||||
// dispatch the request to the correct entity API
|
||||
return when (method) {
|
||||
// check if the data is in the database
|
||||
NanoHTTPD.Method.HEAD -> {
|
||||
if (taskSession.permission < TaskPermission.STUDENT)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.head(httpSession)
|
||||
}
|
||||
// get the data from the database
|
||||
NanoHTTPD.Method.GET -> {
|
||||
if (taskSession.permission < TaskPermission.STUDENT)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.get(httpSession)
|
||||
}
|
||||
// insert the data into the database
|
||||
NanoHTTPD.Method.POST -> {
|
||||
if (taskSession.permission < TaskPermission.TEACHER)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.post(httpSession)
|
||||
}
|
||||
// delete the data from the database
|
||||
NanoHTTPD.Method.DELETE -> {
|
||||
if (taskSession.permission < TaskPermission.TEACHER)
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/plain",
|
||||
"Forbidden"
|
||||
)
|
||||
|
||||
return entityApi.delete(httpSession)
|
||||
}
|
||||
// other methods are not allowed
|
||||
else ->
|
||||
return NanoHTTPD.newFixedLengthResponse(
|
||||
NanoHTTPD.Response.Status.METHOD_NOT_ALLOWED,
|
||||
"text/plain",
|
||||
"Method not allowed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue