diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..80d7887
--- /dev/null
+++ b/.drone.yml
@@ -0,0 +1,31 @@
+kind: pipeline
+type: docker
+name: Deployment
+
+trigger:
+ branch:
+ - production
+ - dev
+
+steps:
+ - name: 'Unit Tests'
+ image: ubuntu:latest
+ commands:
+ - apt update && apt install openjdk-11-jdk -y
+ - ./gradlew :test
+
+ - name: deploy to server
+ image: ubuntu:latest
+ depends_on:
+ - 'Unit Tests'
+ environment:
+ SSH_PRIVATE_KEY:
+ from_secret: ???
+ SSH_PUBLIC_KEY:
+ from_secret: ???
+ USER:
+ from_secret: ???
+ IP:
+ from_secret: ???
+ commands:
+ - drone/deliver.sh $DRONE_BRANCH
diff --git a/.gitignore b/.gitignore
index dfa9b60..13e1c73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ build
.gradle
.idea
*/.gradle/
+/log
\ No newline at end of file
diff --git a/API/src/main/scala/org/tbasket/api/Endpoint.scala b/API/src/main/scala/org/tbasket/api/Endpoint.scala
index 09b81b4..be27204 100644
--- a/API/src/main/scala/org/tbasket/api/Endpoint.scala
+++ b/API/src/main/scala/org/tbasket/api/Endpoint.scala
@@ -1,36 +1,58 @@
package org.tbasket.api
+import org.apache.logging.log4j.LogManager
+import org.tbasket.api.Endpoint.LOG
import org.tbasket.api.compute.APIRequestHandler
import zio._
import zio.http.ServerConfig.LeakDetectionLevel
import zio.http._
+import zio.http.model.Method.{GET, POST}
+import zio.http.model.Status
import scala.collection.mutable
-class Endpoint(hostname: String, port: Int, rootPath: Option[String]) extends ZIOAppDefault {
+class Endpoint(hostname: String, port: Int) extends ZIOAppDefault {
- private val Root: Path = rootPath.map(Path.decode).getOrElse(Path.root)
- private val handlers = mutable.HashMap.empty[String, APIRequestHandler]
+ private val handlers = mutable.HashMap.empty[String, APIRequestHandler]
def bind(path: String)(handler: APIRequestHandler): Unit = {
handlers.put(path, handler)
}
- private val app = Http.collectZIO[Request] {
- case r@_ -> Root / path if handlers.contains(path) =>
- handlers(path).handle.provideEnvironment(ZEnvironment(r))
- case _ -> path =>
- ZIO.fail(new APIException(s"Unable to find a handler for page '$path'"))
+ //set generic required headers
+ private def transform(response: Response): Response = {
+ response.withAccessControlAllowOrigin("*")
}
+
+
+ private val app = Http.collect[Request] {
+ case r@GET -> _ / path if handlers.contains(path) =>
+ transform(handlers(path).get(r))
+ case r@POST -> _ / path if handlers.contains(path) =>
+ transform(handlers(path).post(r))
+ case r@method -> path =>
+ val ipInsights = r.remoteAddress
+ .map(ip => s": request received from $ip.")
+ .getOrElse("")
+ LOG.error(s"Was unable to find a handler for request '$path' with method $method ${ipInsights}")
+ Response(Status.NotFound)
+ }
+
val run = {
val config = ServerConfig.default
.port(port)
.leakDetection(LeakDetectionLevel.PARANOID)
val configLayer = ServerConfig.live(config)
- (Server.install(app).flatMap { port =>
- Console.printLine(s"Listening API entries on $hostname:$port$rootPath")
- } *> ZIO.never).provide(configLayer, Server.live)
+ Server.install(app).flatMap { port =>
+ LOG.info(s"Listening API entries on $hostname:$port")
+ ZIO.never
+ }.provide(configLayer, Server.live)
}
}
+
+object Endpoint {
+ final val LOG = LogManager.getLogger("API")
+
+}
diff --git a/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala b/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala
index b9334c9..a0807d4 100644
--- a/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala
+++ b/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala
@@ -1,11 +1,12 @@
package org.tbasket.api.compute
-import org.tbasket.api.APIException
-import zio.ZIO
+import zio.http.model.Status
import zio.http.{Request, Response}
trait APIRequestHandler {
- def handle: ZIO[Request, APIException, Response]
+ def get(request: Request): Response = Response(Status.MethodNotAllowed)
+
+ def post(request: Request): Response = Response(Status.MethodNotAllowed)
}
diff --git a/build.gradle b/build.gradle
index 8a40242..d9bbb9b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,9 @@ plugins {
id 'java'
id 'java-library'
id 'scala'
- id 'application'
+ id 'application' //for 'run' task
+ id 'com.adarshr.test-logger' version '3.2.0' //fancy prints during tests
+ id 'com.github.johnrengelman.shadow' version '7.1.2' //for optimised jar
}
final var scalaVersion = "2.13"
@@ -15,15 +17,27 @@ repositories {
mavenCentral()
}
+shadowJar {
+ archivesBaseName = "server"
+}
dependencies {
implementation project(':API')
implementation project(':DB')
- implementation "io.circe:circe-core_$scalaVersion:0.15.0-M1"
+ testImplementation "io.circe:circe-core_$scalaVersion:0.15.0-M1"
+ testImplementation "io.circe:circe-parser_$scalaVersion:0.15.0-M1"
+
+}
+
+testlogger {
+ theme 'mocha'
}
test {
useJUnitPlatform()
+ testlogger {
+ theme 'standard-parallel'
+ }
}
run {
@@ -33,7 +47,6 @@ run {
allprojects {
apply plugin: 'scala'
apply plugin: 'java-library'
- version '1.0-SNAPSHOT'
repositories {
mavenCentral()
@@ -43,6 +56,9 @@ allprojects {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+ implementation 'org.apache.logging.log4j:log4j-core:2.17.2'
+ implementation 'org.apache.logging.log4j:log4j-api:2.17.2'
+ implementation 'org.slf4j:slf4j-simple:2.0.4'
implementation "org.scala-lang:scala-library:$scalaVersion.10"
}
diff --git a/drone/deliver.sh b/drone/deliver.sh
new file mode 100644
index 0000000..77c9b82
--- /dev/null
+++ b/drone/deliver.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+apt update && apt install openjdk-11-jdk sftp -y
+
+PATH="$PATH:."
+
+gradlew :shadowJar
+
+mkdir -p ~/.ssh
+echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
+echo "$SSH_PUBLIC_KEY" > ~/.ssh/id_rsa.pub
+sftp "$USER@$IP:tbasket/" <<< $'put build/libs/server-all.jar drone/deploy.sh'
\ No newline at end of file
diff --git a/drone/deploy.sh b/drone/deploy.sh
new file mode 100644
index 0000000..f1e2b5f
--- /dev/null
+++ b/drone/deploy.sh
@@ -0,0 +1,19 @@
+
+
+SERVER_JAR_NAME="server-all.jar"
+
+OLD_PID=$(ps -aux | grep "-jar $SERVER_JAR_NAME" | tr -s " " | cut -d " " -f2)
+
+#if $OLD_PID is not empty but isn't a number, something went wrong
+if [ "$OLD_PID" ] && ! grep -E -q "^[0-9]+$"; then
+ echo "error, unable to retrieve old server pid: $OLD_PID" >&2
+ exit 2
+fi
+
+
+if [ "$OLD_PID" ]; then
+ #will cause the old server to gracefully shutdown
+ echo "shutting down old server version ..."
+ kill SIGQUIT "$OLD_PID"
+ wait "$OLD_PID"
+fi
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..5abd931
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/scala/org/tbasket/EndpointSetup.scala b/src/main/scala/org/tbasket/EndpointSetup.scala
index 1e22d7f..578cd5d 100644
--- a/src/main/scala/org/tbasket/EndpointSetup.scala
+++ b/src/main/scala/org/tbasket/EndpointSetup.scala
@@ -7,11 +7,11 @@ import java.util.Properties
object EndpointSetup {
- final val EndpointUrl = "endpoint.url"
+ final val EndpointUrl = "endpoint.url"
final val EndpointUrlDefault = s"localhost:48485"
def setupEndpoint(): Endpoint = {
- println("Initializing API endpoint...")
+ Main.LOG.debug("Initializing API endpoint...")
val endpoint = createEndpoint()
endpoint.bind("counter")(IncrementHandler)
endpoint
@@ -21,13 +21,12 @@ object EndpointSetup {
val properties = new Properties()
val in = getClass.getClassLoader.getResourceAsStream("server.properties")
properties.load(in)
- val (hostname, port, endpointPath) = properties
+ val (hostname, port) = properties
.getProperty(EndpointUrl, EndpointUrlDefault) match {
- case s"$ip:$port/$endpointPath" => (ip, port.toInt, Some(endpointPath))
- case s"$ip:$port" => (ip, port.toInt, None)
- case v => throw new InternalBasketServerException(s"$EndpointUrl property value is wrong: $v must be :/[endpointPath]")
+ case s"$ip:$port" => (ip, port.toInt)
+ case v => throw new InternalBasketServerException(s"$EndpointUrl property value is wrong: $v must be :/[endpointPath]")
}
- new Endpoint(hostname, port, endpointPath)
+ new Endpoint(hostname, port)
}
}
diff --git a/src/main/scala/org/tbasket/Main.scala b/src/main/scala/org/tbasket/Main.scala
index 52e14ae..6977068 100644
--- a/src/main/scala/org/tbasket/Main.scala
+++ b/src/main/scala/org/tbasket/Main.scala
@@ -1,14 +1,18 @@
package org.tbasket
+import org.apache.logging.log4j.LogManager
import zio._
+import java.lang
import scala.io.StdIn
import scala.util.control.NonFatal
object Main {
+ final val LOG = LogManager.getLogger("Core")
def main(args: Array[String]): Unit = {
+ LOG.info("Starting server")
val endpoint = EndpointSetup.setupEndpoint()
val runtime = Runtime.default
Unsafe.unsafe { implicit u =>
@@ -18,9 +22,12 @@ object Main {
throw e
}
}
+ LOG.info("Server successfully started")
println("enter to exit")
StdIn.readLine()
}
+ //add a shutdown hook to log when the server is about to get killed
+ lang.Runtime.getRuntime.addShutdownHook(new Thread(() => LOG.info("Server shutdowns")))
}
diff --git a/src/main/scala/org/tbasket/compute/IncrementHandler.scala b/src/main/scala/org/tbasket/compute/IncrementHandler.scala
index fe78c0d..e8aed70 100644
--- a/src/main/scala/org/tbasket/compute/IncrementHandler.scala
+++ b/src/main/scala/org/tbasket/compute/IncrementHandler.scala
@@ -1,23 +1,21 @@
package org.tbasket.compute
-import org.tbasket.api.APIException
import org.tbasket.api.compute.APIRequestHandler
-import zio.ZIO
-import zio.http.model.Method._
import zio.http.{Request, Response}
+
object IncrementHandler extends APIRequestHandler {
- private var i = 0
+ @volatile private var i = 0
+
+ def getCounter: Int = i
+ override def get(request: Request): Response = {
+ Response.json(s"{\"value\": $i}")
+ }
- override def handle: ZIO[Request, APIException, Response] = {r: Request =>
- r match {
- case Request(_, _, GET, _, _, _) =>
- ZIO.succeed(Response.json(s"value: $i"))
- case Request(_, _, POST, _, _, _) =>
- i += 1
- ZIO.succeed(Response.ok)
- Console
- }
+ override def post(request: Request): Response = {
+ i += 1
+ println(s"Counter is now $i")
+ Response.ok
}
}
diff --git a/src/test/scala/org/tbasket/test/IncrementRequestHandler.scala b/src/test/scala/org/tbasket/test/IncrementRequestHandler.scala
new file mode 100644
index 0000000..17f0aac
--- /dev/null
+++ b/src/test/scala/org/tbasket/test/IncrementRequestHandler.scala
@@ -0,0 +1,39 @@
+package org.tbasket.test
+
+import io.circe.parser._
+import org.junit.jupiter.api.{Assertions, Test}
+import org.tbasket.compute.IncrementHandler
+import zio.http.model.Status
+import zio.http.{Body, Path, Request, URL}
+
+class IncrementRequestHandler {
+
+ private val url = URL(Path.decode("counter"))
+
+ @Test
+ def testMakeIncrement(): Unit = {
+ val last = IncrementHandler.getCounter
+ val response = IncrementHandler.post(Request.post(Body.empty, url))
+ Assertions.assertEquals(response.status, Status.Ok)
+ Assertions.assertEquals(last + 1, IncrementHandler.getCounter)
+ }
+
+ @Test
+ def testGetIncrement(): Unit = {
+ val counter = IncrementHandler.getCounter
+ val response = IncrementHandler.get(Request.post(Body.empty, url))
+ for {
+ json <- response.body.asString
+ } yield parse(json) match {
+ case Left(failure) => Assertions.fail(s"returned json is invalid ($failure)")
+ case Right(json) =>
+ val valueJsons = json.findAllByKey("value")
+ Assertions.assertEquals(1, valueJsons.size)
+ val valueCounter = valueJsons.head.asNumber.get.toInt.get
+ Assertions.assertEquals(counter, valueCounter)
+ }
+ Assertions.assertEquals(response.status, Status.Ok)
+ Assertions.assertEquals(counter, IncrementHandler.getCounter)
+ }
+
+}