diff --git a/build.sbt b/build.sbt index c9d5e50..01eeee4 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ lazy val root = (project in file(".")).settings( libraryDependencies += "org.json4s" % "json4s-jackson_2.13" % "3.6.11" ) -unmanagedResourceDirectories in Compile += { baseDirectory.value / "lib" } +Compile / unmanagedResourceDirectories += { baseDirectory.value / "lib" } scalacOptions ++= List( "-Yrangepos", "-Ywarn-unused", @@ -23,8 +23,8 @@ scalacOptions ++= List( ) javacOptions += "-g:none" compileOrder := CompileOrder.JavaThenScala -mainClass in assembly := Some("lc.LCFramework") -mainClass in (Compile, run) := Some("lc.LCFramework") -assemblyJarName in assembly := "LibreCaptcha.jar" +assembly / mainClass := Some("lc.LCFramework") +Compile / run / mainClass := Some("lc.LCFramework") +assembly / assemblyJarName := "LibreCaptcha.jar" -fork in run := true +run / fork := true diff --git a/project/build.properties b/project/build.properties index dbae93b..e67343a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.9 +sbt.version=1.5.0 diff --git a/src/main/scala/lc/core/captcha.scala b/src/main/scala/lc/core/captcha.scala index 34cda2f..4a04dc7 100644 --- a/src/main/scala/lc/core/captcha.scala +++ b/src/main/scala/lc/core/captcha.scala @@ -42,7 +42,7 @@ object Captcha { val token = if (rs.next()) { rs.getInt("token") } - println("Added new challenge: " + token.toString) + // println("Added new challenge: " + token.toString) token.asInstanceOf[Int] } diff --git a/src/main/scala/lc/database/statements.scala b/src/main/scala/lc/database/statements.scala index 1439b56..d23d6e2 100644 --- a/src/main/scala/lc/database/statements.scala +++ b/src/main/scala/lc/database/statements.scala @@ -4,7 +4,7 @@ import lc.database.DBConn import java.sql.Statement import java.sql.PreparedStatement -class Statements(dbConn: DBConn) { +class Statements(dbConn: DBConn, maxAttempts: Int) { private val stmt = dbConn.getStatement() @@ -71,13 +71,14 @@ class Statements(dbConn: DBConn) { ) val tokenPstmt: PreparedStatement = dbConn.con.prepareStatement( - "SELECT token " + - "FROM challenge " + - "WHERE attempted < 10 AND " + - "contentLevel = ? AND " + - "contentType = ? AND " + - "contentInput = ? " + - "ORDER BY RAND() LIMIT 1" + s""" + SELECT token + FROM challenge + WHERE attempted < $maxAttempts AND + contentLevel = ? AND + contentType = ? AND + contentInput = ? + ORDER BY RAND() LIMIT 1""" ) val deleteAnswerPstmt: PreparedStatement = dbConn.con.prepareStatement( @@ -85,9 +86,9 @@ class Statements(dbConn: DBConn) { ) val challengeGCPstmt: PreparedStatement = dbConn.con.prepareStatement( - "DELETE FROM challenge " + - "WHERE attempted >= 10 AND " + - "token NOT IN (SELECT token FROM mapId)" + s"""DELETE FROM challenge + WHERE attempted >= $maxAttempts AND + token NOT IN (SELECT token FROM mapId)""" ) val mapIdGCPstmt: PreparedStatement = dbConn.con.prepareStatement( @@ -109,6 +110,14 @@ class Statements(dbConn: DBConn) { } object Statements { + /* Note: h2 documentation recommends using a separate DB connection per thread + But in practice, as of version 1.4.200, multiple connections occassionally shows error on the console of the form + ``` + org.h2.jdbc.JdbcSQLNonTransientException: General error: "java.lang.NullPointerException"; SQL statement: + SELECT image FROM challenge c, mapId m WHERE c.token=m.token AND m.uuid = ? [50000-200] + ``` + */ private val dbConn: DBConn = new DBConn() - val tlStmts: ThreadLocal[Statements] = ThreadLocal.withInitial(() => new Statements(dbConn)) + private val maxAttempts = 10 + val tlStmts: ThreadLocal[Statements] = ThreadLocal.withInitial(() => new Statements(dbConn, maxAttempts)) } diff --git a/src/main/scala/lc/server/Server.scala b/src/main/scala/lc/server/Server.scala index 73dff4b..cd33cf9 100644 --- a/src/main/scala/lc/server/Server.scala +++ b/src/main/scala/lc/server/Server.scala @@ -14,20 +14,21 @@ class Server(port: Int) { implicit val formats: DefaultFormats.type = DefaultFormats val server: HttpServer = HttpServer.create(new InetSocketAddress(port), 32) + server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool()) private def getRequestJson(ex: HttpExchange): JValue = { val requestBody = ex.getRequestBody val bytes = requestBody.readAllBytes - val string = bytes.map(_.toChar).mkString + val string = new String(bytes) parse(string) } + private val eqPattern = java.util.regex.Pattern.compile("=") private def getPathParameter(ex: HttpExchange): Either[String, Error] = { try { - val uri = ex.getRequestURI.toString - val pathParam = uri.split("\\?")(1) - val param = pathParam.split("=") - if (param(0) == "id") { + val query = ex.getRequestURI.getQuery + val param = eqPattern.split(query) + if(param(0) == "id"){ Left(param(1)) } else { Right(Error(ErrorMessageEnum.INVALID_PARAM.toString + "=> id")) diff --git a/tests/locustfile.py b/tests/locustfile.py index df55b4c..941c45e 100644 --- a/tests/locustfile.py +++ b/tests/locustfile.py @@ -19,7 +19,7 @@ def _(environment, **kw): environment.process_exit_code = 0 class QuickStartUser(SequentialTaskSet): - wait_time = between(0.1,1) + wait_time = between(0.1,0.2) @task def captcha(self): @@ -52,6 +52,6 @@ class QuickStartUser(SequentialTaskSet): class User(FastHttpUser): - wait_time = between(0.1,1) + wait_time = between(0.1,0.2) tasks = [QuickStartUser] host = "http://localhost:8888" diff --git a/tests/run.sh b/tests/run.sh index 34ed4fd..0d10e93 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -7,7 +7,7 @@ java -jar target/scala-2.13/LibreCaptcha.jar & JAVA_PID=$! sleep 4 -locust --headless -u 1000 -r 100 --run-time 4m --stop-timeout 30 -f tests/locustfile.py +locust --headless -u 300 -r 100 --run-time 4m --stop-timeout 30 -f tests/locustfile.py status=$? kill $JAVA_PID