Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to HttpServer #76

Merged
merged 11 commits into from
Apr 11, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.bloop
.metals
.vscode
.bsp

# for python test env
/testEnv/
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ lazy val root = (project in file(".")).settings(
name := "LibreCaptcha",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-core" % "4.0.12",
libraryDependencies += "com.sksamuel.scrimage" % "scrimage-filters" % "4.0.12",
libraryDependencies += "org.json4s" % "json4s-jackson_2.13" % "3.6.11"
libraryDependencies += "org.json4s" % "json4s-jackson_2.13" % "3.6.11",
libraryDependencies += "com.sun.net.httpserver" % "http" % "20070405"
rr83019 marked this conversation as resolved.
Show resolved Hide resolved
)

unmanagedResourceDirectories in Compile += { baseDirectory.value / "lib" }
Expand Down
3,042 changes: 0 additions & 3,042 deletions src/main/java/lc/server/HTTPServer.java

This file was deleted.

6 changes: 2 additions & 4 deletions src/main/scala/lc/Main.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package lc

import lc.core.{Captcha, CaptchaProviders}
import lc.core.CaptchaProviders
import lc.server.Server
import lc.background.BackgroundTask
import lc.core.Config

object LCFramework {
def main(args: scala.Array[String]): Unit = {
val captcha = new Captcha()
val server = new Server(port = Config.port, captcha = captcha)
val backgroundTask = new BackgroundTask(
captcha = captcha,
throttle = Config.throttle,
timeLimit = Config.captchaExpiryTimeLimit
)
backgroundTask.beginThread(delay = Config.threadDelay)
val server = new Server(port = Config.port)
server.start()
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/main/scala/lc/background/taskThread.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import java.util.concurrent.{ScheduledThreadPoolExecutor, TimeUnit}
import lc.core.Captcha
import lc.core.{Parameters, Size}

class BackgroundTask(captcha: Captcha, throttle: Int, timeLimit: Int) {
class BackgroundTask(throttle: Int, timeLimit: Int) {

private val task = new Runnable {
def run(): Unit = {
try {

val mapIdGCPstmt = Statements.tlStmts.get.mapIdGCPstmt
mapIdGCPstmt.setInt(1, timeLimit)
mapIdGCPstmt.executeUpdate()
Expand All @@ -23,10 +22,10 @@ class BackgroundTask(captcha: Captcha, throttle: Int, timeLimit: Int) {
if (imageNum.next())
throttleIn = (throttleIn - imageNum.getInt("total"))
while (0 < throttleIn) {
captcha.generateChallenge(Parameters("medium", "image/png", "text", Option(Size(0, 0))))
Captcha.generateChallenge(Parameters("medium", "image/png", "text", Option(Size(0, 0))))
throttleIn -= 1
}
} catch { case e: Exception => println(e) }
} catch { case exception: Exception => println(exception.getStackTrace()) }
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/scala/lc/core/captcha.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package lc.core

import java.sql.{Blob, ResultSet}
import java.sql.ResultSet
import java.util.UUID
import java.io.ByteArrayInputStream
import lc.database.Statements
import lc.core.CaptchaProviders
import lc.captchas.interfaces.ChallengeProvider
import java.sql.Blob

class Captcha {

object Captcha {

def getCaptcha(id: Id): Array[Byte] = {
var image: Array[Byte] = null
Expand All @@ -32,7 +33,6 @@ class Captcha {

def generateChallenge(param: Parameters): Int = {
val provider = CaptchaProviders.getProvider(param)
if (!provider.isInstanceOf[ChallengeProvider]) return -1
val providerId = provider.getId()
val challenge = provider.returnChallenge()
val blob = new ByteArrayInputStream(challenge.content)
Expand Down Expand Up @@ -68,7 +68,7 @@ class Captcha {
return false
}

def getChallenge(param: Parameters): ChallengeResult = {
def getChallenge(param: Parameters): ChallengeResult = {
try {
val validParam = validateParam(param)
if (validParam) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/lc/core/captchaFields.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ object ErrorMessageEnum extends Enumeration {
type ErrorMessage = Value

val SMW: Value = Value("Oops, something went worng!")
val INVALID_PARAM: Value = Value("Invalid Pramaters")
val INVALID_PARAM: Value = Value("Parameters invalid or missing")
val NO_CAPTCHA: Value = Value("No captcha for the provided parameters")
val BAD_METHOD: Value = Value("Bad request method")
}
1 change: 1 addition & 0 deletions src/main/scala/lc/core/captchaProviders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ object CaptchaProviders {

def getProvider(param: Parameters): ChallengeProvider = {
val providerConfig = filterProviderByParam(param).toList
if (providerConfig.length == 0) throw new NoSuchElementException(ErrorMessageEnum.NO_CAPTCHA.toString)
val randomIndex = getNextRandomInt(providerConfig.length)
val providerIndex = providerConfig(randomIndex)._1
val selectedProvider = providers(providerIndex)
Expand Down
7 changes: 6 additions & 1 deletion src/main/scala/lc/core/config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ object Config {
configFile.close
configFileContent
} catch {
case _: FileNotFoundException =>
case _: FileNotFoundException => {
val configFileContent = getDefaultConfig()
val configFile = new PrintWriter(new File(configFilePath))
configFile.write(configFileContent)
configFile.close
configFileContent
}
case exception: Exception => {
println(exception.getStackTrace)
throw new Exception(exception.getMessage)
}
}

private val configJson = parse(configString)
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/lc/core/models.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ sealed trait ChallengeResult
case class Size(height: Int, width: Int)
case class Parameters(level: String, media: String, input_type: String, size: Option[Size])
case class Id(id: String) extends ChallengeResult
case class Error(message: String) extends ChallengeResult
case class Image(image: Array[Byte]) extends ChallengeResult
case class Answer(answer: String, id: String)
case class Result(result: String)
case class Error(message: String) extends ChallengeResult
case class Response(statusCode: Int, message: Array[Byte])
case class CaptchaConfig(
name: String,
allowedLevels: List[String],
Expand Down
144 changes: 102 additions & 42 deletions src/main/scala/lc/server/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,119 @@ import org.json4s.DefaultFormats
import org.json4s.jackson.JsonMethods.parse
import org.json4s.jackson.Serialization.write
import lc.core.Captcha
import lc.core.{Parameters, Id, Answer}
import lc.server.HTTPServer
import lc.core.ErrorMessageEnum
import lc.core.{Parameters, Id, Answer, Response}
import org.json4s.JsonAST.JValue
import com.sun.net.httpserver.{HttpServer, HttpExchange}
import java.net.InetSocketAddress

class Server(port: Int, captcha: Captcha) {
val server = new HTTPServer(port)
val host: HTTPServer.VirtualHost = server.getVirtualHost(null)
class Server(port: Int) {

implicit val formats: DefaultFormats.type = DefaultFormats
val server: HttpServer = HttpServer.create(new InetSocketAddress(port), 32)

host.addContext(
private def getRequestJson(ex: HttpExchange): JValue = {
val requestBody = ex.getRequestBody
val bytes = requestBody.readAllBytes
val string = bytes.map(_.toChar).mkString
parse(string)
}

private def getPathParameter(ex: HttpExchange): String = {
try {
val uri = ex.getRequestURI.toString
val param = uri.split("\\?")(1)
param.split("=")(1)
} catch {
case exception: ArrayIndexOutOfBoundsException => {
println(exception.getStackTrace)
throw new Exception(ErrorMessageEnum.INVALID_PARAM.toString)
}
}
}

private def sendResponse(statusCode: Int, response: Array[Byte], ex: HttpExchange): Unit = {
ex.sendResponseHeaders(statusCode, response.length)
val os = ex.getResponseBody
os.write(response)
os.close
}

private def getException(exception: Exception): Response = {
println(exception.printStackTrace)
val message = ("message" -> exception.getMessage)
val messageByte = write(message).getBytes
Response(500, messageByte)
}

private def getBadRequestError(): Response = {
val message = ("message" -> ErrorMessageEnum.BAD_METHOD.toString)
Response(405, write(message).getBytes)
}

private def makeApiWorker(path: String, f: (String, HttpExchange) => Response): Unit = {
server.createContext(
path,
ex => {
val requestMethod = ex.getRequestMethod
val response =
try {
f(requestMethod, ex)
} catch {
case exception: Exception => {
getException(exception)
}
}
sendResponse(statusCode = response.statusCode, response = response.message, ex = ex)
}
)
}

def start(): Unit = {
println("Starting server on port:" + port)
server.start()
}

makeApiWorker(
"/v1/captcha",
(req, resp) => {
val body = req.getJson()
val json = parse(body)
val param = json.extract[Parameters]
val id = captcha.getChallenge(param)
resp.getHeaders().add("Content-Type", "application/json")
resp.send(200, write(id))
0
},
"POST"
(method: String, ex: HttpExchange) => {
if (method == "POST") {
val json = getRequestJson(ex)
val param = json.extract[Parameters]
val id = Captcha.getChallenge(param)
Response(200, write(id).getBytes)
} else {
getBadRequestError()
}
}
)

host.addContext(
makeApiWorker(
"/v1/media",
(req, resp) => {
val params = req.getParams()
val id = Id(params.get("id"))
val image = captcha.getCaptcha(id)
resp.getHeaders().add("Content-Type", "image/png")
resp.send(200, image)
0
},
"GET"
(method: String, ex: HttpExchange) => {
if (method == "GET") {
val param = getPathParameter(ex)
val id = Id(param)
val image = Captcha.getCaptcha(id)
Response(200, image)
} else {
getBadRequestError()
}
}
)

host.addContext(
makeApiWorker(
"/v1/answer",
(req, resp) => {
val body = req.getJson()
val json = parse(body)
val answer = json.extract[Answer]
val result = captcha.checkAnswer(answer)
resp.getHeaders().add("Content-Type", "application/json")
resp.send(200, write(result))
0
},
"POST"
(method: String, ex: HttpExchange) => {
if (method == "POST") {
val json = getRequestJson(ex)
val answer = json.extract[Answer]
val result = Captcha.checkAnswer(answer)
Response(200, write(result).getBytes)
} else {
getBadRequestError()
}
}
)

def start(): Unit = {
println("Starting server on port:" + port)
server.start()
}

}