simple page dispatcher
continuous-integration/drone/push Build is failing Details

dev
Override-6 2 years ago
parent c9f45d1bf4
commit b22a1230c7

3
.gitignore vendored

@ -5,10 +5,11 @@ target
*/.gradle/
.bsp
keys
www
log
server.properties
log
*.sqlite
*.$*

@ -4,4 +4,6 @@ emitter.cert=<x509 certificate path here>
endpoint.port=<enter port here>
database.prefix=database
database.prefix=<database config prefix>
pages.location=<location to html content>

@ -4,6 +4,7 @@ import org.apache.logging.log4j.LogManager
import org.tbasket.auth.Authenticator
import org.tbasket.config.{FileServerConfig, ServerConfig}
import org.tbasket.data.Database
import org.tbasket.dispatch.PageDispatcher
import org.tbasket.endpoint.Endpoint
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import zio.*
@ -23,16 +24,18 @@ object Main extends ZIOAppDefault:
final val LOG = LogManager.getLogger("Core")
override def run = ZIO.serviceWithZIO[ZIOAppArgs] { args =>
for
config <- retrieveConfig(args)
res <- setupAuth(config) <&> setupDatabase(config) <&> setupEndpoint(config)
yield
val (auth, db, ep) = res
(auth, db, ep)
}.flatMap((auth, db, ep) =>
ep.run.provide(db.datasourceLayer, db.contextLayer, auth)
)
retrieveConfig(args)
.flatMap(config => setupAuth(config) <&> setupDatabase(config) <&> setupEndpoint(config) <&> setupPageDispatcher(config))
.flatMap {
case (auth, db, ep, disp) =>
ep.run.provide(db.datasourceLayer, db.contextLayer, auth, disp)
}
}
private def setupPageDispatcher(config: ServerConfig) = ZIO.attempt {
val dispatcher = new PageDispatcher(config.pagesLocation)
ZLayer.succeed(dispatcher)
}
private def setupEndpoint(config: ServerConfig) = ZIO.attempt {
new Endpoint(config.endpointPort)

@ -62,6 +62,9 @@ final class FileServerConfig private(userProperties: Properties, schema: Propert
override val databaseConfigName: String = getPropertySafe("database.prefix")
override def pagesLocation: Path = Path.of(getPropertySafe("pages.location"))
private def schemaString = {
schema.stringPropertyNames()
.toArray(new Array[String](_))

@ -4,6 +4,7 @@ import pdi.jwt.JwtAlgorithm
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import zio.http.URL
import java.nio.file.Path
import java.security.cert.Certificate
trait ServerConfig {
@ -17,4 +18,6 @@ trait ServerConfig {
def databaseConfigName: String
def pagesLocation: Path
}

@ -0,0 +1,53 @@
package org.tbasket.dispatch
import zio.*
import zio.http.*
import zio.http.model.Status
import zio.http.model.Status.*
import zio.stream.ZStream
import java.nio.file.{Files, Path}
import scala.collection.mutable
class PageDispatcher(pagesLocation: Path) {
private val resources = resolveResources
def send(r: Request) =
ZIO.attempt(pagesLocation.toString + r.url.path.toString)
.filterOrFail(!_.startsWith("/"))(Forbidden)
.map(resources.get)
.someOrFail(NotFound)
.map(content => Response(status = Ok, body = content))
.catchSome {
case status: Status =>
ZIO.attempt(Response.status(status))
}
private def resolveResources: Map[String, Body] = {
val map = mutable.HashMap.empty[String, Body]
def resolveAll(loc: Path): Unit = {
Files.list(loc)
.forEach {
case d if Files.isDirectory(d) => resolveAll(d)
case f =>
val body = Body.fromStream(ZStream.fromPath(f))
val fileName = f.toString
map.put(fileName, body)
val extension = fileName.drop(fileName.indexOf('.'))
val dirPath = f.getParent.toString
if (extension == ".html")
map.put(dirPath, body) //also bind the dir path with the index body
else map.put(dirPath + extension, body)
}
}
resolveAll(pagesLocation)
map.toMap
}
}

@ -4,9 +4,11 @@ import io.getquill.context.qzio.{ZioContext, ZioJdbcContext}
import io.getquill.context.sql.idiom.SqlIdiom
import io.getquill.idiom.Idiom
import io.getquill.{Literal, NamingStrategy, SqliteDialect}
import io.netty.handler.codec.http.HttpMethod
import org.apache.logging.log4j.{LogManager, Logger}
import org.tbasket.auth.Authenticator
import org.tbasket.data.DatabaseContext
import org.tbasket.dispatch.PageDispatcher
import org.tbasket.endpoint.Endpoint.{Log, app}
import org.tbasket.error.*
import org.tbasket.handler.HandlerUtils.errorBody
@ -35,7 +37,7 @@ class Endpoint(port: Int):
Server.install(app).flatMap { port =>
Log.info(s"Listening API entries on $port")
ZIO.never
}.provideSome[DatabaseContext & Authenticator & DataSource](
}.provideSome[DatabaseContext & Authenticator & DataSource & PageDispatcher](
Scope.default,
serverConfigLayer,
ConnectionPool.fixed(4),
@ -54,6 +56,9 @@ object Endpoint:
case r@POST -> _ / "register" =>
RegisterPageHandler.post(r)
case r@GET -> _ =>
ZIO.serviceWithZIO[PageDispatcher](_.send(r))
case r@method -> path =>
val ipInsights = r.remoteAddress
.map(ip => s": request received from $ip.")

@ -8,6 +8,7 @@ import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
import java.nio.file.{Files, Path}
import java.security.cert.{Certificate, CertificateFactory}
import scala.reflect.io
object TestServerConfig extends ServerConfig {
new ProcessBuilder("bash", "./tests/resources/generate_keys.sh")
@ -27,4 +28,6 @@ object TestServerConfig extends ServerConfig {
override def endpointPort: Int = 5454
override def databaseConfigName: String = "test-database"
override def pagesLocation: io.Path = Path.of("www")
}

@ -1,11 +1,11 @@
package org.tbasket.test.pages
import zio.test.ZIOSpecDefault
import org.tbasket.endpoint.Endpoint.handle
import org.tbasket.handler.HandlerUtils.parseAttribute
import org.tbasket.test.TestUtils
import org.tbasket.test.TestUtils.getJsonBody
import org.tbasket.test.pages.RegisterPageHandlerTests.test
import zio.*
import zio.http.*
import zio.http.model.{HeaderNames, Headers, Status}
import zio.json.ast.JsonCursor
@ -16,7 +16,22 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
private def requestsSpec = suite("bad request tests")(
ZIO.attempt(Map(
"empty packet" -> Body.empty,
"with no mail attribute" -> Body.fromString("""{"password":"1234"}"""),
"with no password attribute" -> Body.fromString("""{"email":"valid.mail@x.y"}"""),
"with invalid json" -> Body.fromString("""this is a corrupted json""")
)).map(_.map { case (name, body) =>
test(name) {
for
response <- handle(Request.post(body, url))
json <- getJsonBody(response)
errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString)
yield
assert(response)(hasField("status", _.status, equalTo(Status.BadRequest)))
&& assertTrue(errorType.startsWith("invalid"))
}
})
)
private def registerSpec = suite("register tests")(