mirror of
https://github.com/librecaptcha/lc-core.git
synced 2025-01-27 13:03:12 -05:00
use picoServe library
Signed-off-by: hrj <harshad.rj@gmail.com>
This commit is contained in:
parent
43331f8dd7
commit
55288d3346
251
src/main/java/org/limium/picoserve/Server.java
Normal file
251
src/main/java/org/limium/picoserve/Server.java
Normal file
@ -0,0 +1,251 @@
|
||||
// Distributed under Apache 2 license
|
||||
// Copyright 2021 github.com/hrj
|
||||
|
||||
package org.limium.picoserve;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
public final class Server {
|
||||
private final HttpServer server;
|
||||
|
||||
public static interface Response {
|
||||
public int getCode();
|
||||
public byte[] getBytes();
|
||||
public Map<String, List<String>> getResponseHeaders();
|
||||
}
|
||||
|
||||
public static class ByteResponse implements Response {
|
||||
private final int code;
|
||||
private final byte[] bytes;
|
||||
private final Map<String, List<String>> responseHeaders;
|
||||
|
||||
public ByteResponse(final int code, final byte[] bytes) {
|
||||
this.code = code;
|
||||
this.bytes = bytes;
|
||||
this.responseHeaders = null;
|
||||
}
|
||||
|
||||
public ByteResponse(final int code, final byte[] bytes, final Map<String, List<String>> responseHeaders) {
|
||||
this.code = code;
|
||||
this.bytes = bytes;
|
||||
this.responseHeaders = responseHeaders;
|
||||
}
|
||||
|
||||
public int getCode() { return this.code; }
|
||||
public byte[] getBytes() { return this.bytes; }
|
||||
public Map<String, List<String>> getResponseHeaders() {
|
||||
return this.responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StringResponse extends ByteResponse {
|
||||
public StringResponse(final int code, final String msg) {
|
||||
super(code, msg.getBytes());
|
||||
}
|
||||
|
||||
public StringResponse(final int code, final String msg, final Map<String, List<String>> responseHeaders) {
|
||||
super(code, msg.getBytes(), responseHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
public final class Request {
|
||||
final HttpExchange exchange;
|
||||
Request(final HttpExchange exchange) {
|
||||
this.exchange = exchange;
|
||||
}
|
||||
public String getMethod() {
|
||||
return exchange.getRequestMethod();
|
||||
}
|
||||
public Map<String, List<String>> getQueryParams() {
|
||||
final var query = exchange.getRequestURI().getQuery();
|
||||
final var params = parseParams(query);
|
||||
return params;
|
||||
}
|
||||
public byte[] getBody() {
|
||||
try(final var bodyIS = exchange.getRequestBody()) {
|
||||
final var bytes = bodyIS.readAllBytes();
|
||||
bodyIS.close();
|
||||
return bytes;
|
||||
} catch (IOException ioe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public String getBodyString() {
|
||||
return new String(getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface Processor {
|
||||
public Response process(final Request request);
|
||||
}
|
||||
|
||||
public static class Handler {
|
||||
public final String path;
|
||||
public final Processor processor;
|
||||
public final String[] methods;
|
||||
public Handler(final String path, final Processor processor) {
|
||||
this.path = path;
|
||||
this.processor = processor;
|
||||
this.methods = new String[] {};
|
||||
}
|
||||
public Handler(final String path, final String methods, final Processor processor) {
|
||||
this.path = path;
|
||||
this.processor = processor;
|
||||
this.methods = methods.split(",");
|
||||
}
|
||||
}
|
||||
|
||||
public Server(final InetSocketAddress addr, final int backlog, final List<Handler> handlers, final Executor executor) throws IOException {
|
||||
this.server = HttpServer.create(addr, backlog);
|
||||
this.server.setExecutor(executor);
|
||||
for (final var handler: handlers) {
|
||||
// System.out.println("Registering handler for " + handler.path);
|
||||
this.server.createContext(handler.path, new HttpHandler() {
|
||||
public void handle(final HttpExchange exchange) {
|
||||
final var method = exchange.getRequestMethod();
|
||||
final Response errorResponse = checkMethods(handler.methods, method);
|
||||
try(final var os = exchange.getResponseBody()) {
|
||||
Response response;
|
||||
if (errorResponse != null) {
|
||||
response = errorResponse;
|
||||
} else {
|
||||
try {
|
||||
response = handler.processor.process(new Request(exchange));
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
response = new StringResponse(500, "Error: " + e);
|
||||
}
|
||||
}
|
||||
final var headersToSend = response.getResponseHeaders();
|
||||
if (headersToSend != null) {
|
||||
final var responseHeaders = exchange.getResponseHeaders();
|
||||
responseHeaders.putAll(headersToSend);
|
||||
}
|
||||
final var bytes = response.getBytes();
|
||||
final var code = response.getCode();
|
||||
exchange.sendResponseHeaders(code, bytes.length);
|
||||
os.write(bytes);
|
||||
os.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error: " + ioe);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static Response checkMethods(final String[] methods, final String method) {
|
||||
if (methods.length > 0) {
|
||||
var found = false;
|
||||
for (var m: methods) {
|
||||
if (m.equals(method)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return new StringResponse(404, "Method Not Accepted");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.server.start();
|
||||
}
|
||||
|
||||
public void stop(int delay) {
|
||||
this.server.stop(delay);
|
||||
}
|
||||
|
||||
public static ServerBuilder builder() {
|
||||
return new ServerBuilder();
|
||||
}
|
||||
|
||||
// Adapted from https://stackoverflow.com/a/37368660
|
||||
private final static Pattern ampersandPattern = Pattern.compile("&");
|
||||
private final static Pattern equalPattern = Pattern.compile("=");
|
||||
private final static Map<String, List<String>> emptyMap = Map.of();
|
||||
private static Map<String, List<String>> parseParams(final String query) {
|
||||
if (query == null) {
|
||||
return emptyMap;
|
||||
}
|
||||
final var params = ampersandPattern
|
||||
.splitAsStream(query)
|
||||
.map(s -> Arrays.copyOf(equalPattern.split(s, 2), 2))
|
||||
.collect(Collectors.groupingBy(s -> decode(s[0]), Collectors.mapping(s -> decode(s[1]), Collectors.toList())));
|
||||
return params;
|
||||
}
|
||||
|
||||
private static String decode(final String encoded) {
|
||||
return Optional.ofNullable(encoded)
|
||||
.map(e -> URLDecoder.decode(e, StandardCharsets.UTF_8))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public static class ServerBuilder {
|
||||
private InetSocketAddress mAddress = new InetSocketAddress(9000);
|
||||
private int backlog = 5;
|
||||
private List<Handler> handlers = new LinkedList<Handler>();
|
||||
private Executor executor = null;
|
||||
|
||||
public ServerBuilder port(final int port) {
|
||||
mAddress = new InetSocketAddress(port);
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder backlog(final int backlog) {
|
||||
this.backlog = backlog;
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder address(final InetSocketAddress addr) {
|
||||
mAddress = addr;
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder handle(final Handler handler) {
|
||||
handlers.add(handler);
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder GET(final String path, final Processor processor) {
|
||||
handlers.add(new Handler(path, "GET", request -> processor.process(request)));
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder POST(final String path, final Processor processor) {
|
||||
handlers.add(new Handler(path, "POST", request -> processor.process(request)));
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder PUT(final String path, final Processor processor) {
|
||||
handlers.add(new Handler(path, "PUT", request -> processor.process(request)));
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder DELETE(final String path, final Processor processor) {
|
||||
handlers.add(new Handler(path, "DELETE", request -> processor.process(request)));
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder HEAD(final String path, final Processor processor) {
|
||||
handlers.add(new Handler(path, "HEAD", request -> processor.process(request)));
|
||||
return this;
|
||||
}
|
||||
public ServerBuilder executor(final Executor executor) {
|
||||
this.executor = executor;
|
||||
return this;
|
||||
}
|
||||
public Server build() throws IOException {
|
||||
return new Server(mAddress, backlog, handlers, executor);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ case class Image(image: Array[Byte]) extends ByteConvert { def toBytes(): Array[
|
||||
case class Answer(answer: String, id: String)
|
||||
case class Success(result: String) extends ByteConvert { def toBytes(): Array[Byte] = { write(this).getBytes } }
|
||||
case class Error(message: String) extends ByteConvert { def toBytes(): Array[Byte] = { write(this).getBytes } }
|
||||
case class Response(statusCode: Int, message: Array[Byte])
|
||||
case class CaptchaConfig(
|
||||
name: String,
|
||||
allowedLevels: List[String],
|
||||
|
@ -3,139 +3,52 @@ package lc.server
|
||||
import org.json4s.jackson.JsonMethods.parse
|
||||
import lc.core.Captcha
|
||||
import lc.core.ErrorMessageEnum
|
||||
import lc.core.{Parameters, Id, Answer, Response, Error, ByteConvert}
|
||||
import org.json4s.JsonAST.JValue
|
||||
import com.sun.net.httpserver.{HttpServer, HttpExchange}
|
||||
import java.net.InetSocketAddress
|
||||
import lc.core.{Parameters, Id, Answer, Error, ByteConvert}
|
||||
import lc.core.Config.formats
|
||||
import org.limium.picoserve
|
||||
import org.limium.picoserve.Server.ByteResponse
|
||||
|
||||
class Server(port: Int) {
|
||||
|
||||
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 = new String(bytes)
|
||||
parse(string)
|
||||
}
|
||||
|
||||
private val eqPattern = java.util.regex.Pattern.compile("=")
|
||||
private def getPathParameter(ex: HttpExchange): Either[String, String] = {
|
||||
try {
|
||||
val query = ex.getRequestURI.getQuery
|
||||
val param = eqPattern.split(query)
|
||||
if (param(0) == "id") {
|
||||
Right(param(1))
|
||||
val server = picoserve.Server.builder()
|
||||
.port(8888)
|
||||
.POST("/v1/captcha", (request) => {
|
||||
val json = parse(request.getBodyString())
|
||||
val param = json.extract[Parameters]
|
||||
val id = Captcha.getChallenge(param)
|
||||
getResponse(id)
|
||||
})
|
||||
.GET("/v1/media", (request) => {
|
||||
val params = request.getQueryParams()
|
||||
val result = if (params.containsKey("id")) {
|
||||
val paramId = params.get("id").get(0)
|
||||
val id = Id(paramId)
|
||||
Captcha.getCaptcha(id)
|
||||
} else {
|
||||
Left(ErrorMessageEnum.INVALID_PARAM.toString + "=> id")
|
||||
Left(Error(ErrorMessageEnum.INVALID_PARAM.toString + "=> id"))
|
||||
}
|
||||
} catch {
|
||||
case exception: ArrayIndexOutOfBoundsException => {
|
||||
println(exception)
|
||||
Left(ErrorMessageEnum.INVALID_PARAM.toString + "=> id")
|
||||
}
|
||||
}
|
||||
}
|
||||
getResponse(result)
|
||||
})
|
||||
.POST("/v1/answer", (request) => {
|
||||
val json = parse(request.getBodyString())
|
||||
val answer = json.extract[Answer]
|
||||
val result = Captcha.checkAnswer(answer)
|
||||
getResponse(result)
|
||||
})
|
||||
.build()
|
||||
|
||||
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)
|
||||
val message = Error(exception.getMessage)
|
||||
Response(500, message.toBytes())
|
||||
}
|
||||
|
||||
private def getBadRequestError(): Response = {
|
||||
val message = Error(ErrorMessageEnum.BAD_METHOD.toString)
|
||||
Response(405, message.toBytes())
|
||||
}
|
||||
|
||||
private def getResponse(response: Either[Error, ByteConvert]): Response = {
|
||||
private def getResponse(response: Either[Error, ByteConvert]): ByteResponse = {
|
||||
response match {
|
||||
case Right(value) => {
|
||||
Response(200, value.toBytes())
|
||||
new ByteResponse(200, value.toBytes())
|
||||
}
|
||||
case Left(value) => {
|
||||
Response(500, value.toBytes())
|
||||
new ByteResponse(500, value.toBytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
(method: String, ex: HttpExchange) => {
|
||||
if (method == "POST") {
|
||||
val json = getRequestJson(ex)
|
||||
val param = json.extract[Parameters]
|
||||
val id = Captcha.getChallenge(param)
|
||||
getResponse(id)
|
||||
} else {
|
||||
getBadRequestError()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
makeApiWorker(
|
||||
"/v1/media",
|
||||
(method: String, ex: HttpExchange) => {
|
||||
if (method == "GET") {
|
||||
val param = getPathParameter(ex)
|
||||
val result = param match {
|
||||
case Right(value) => {
|
||||
val id = Id(value)
|
||||
Captcha.getCaptcha(id)
|
||||
}
|
||||
case Left(value) => Left(Error(value))
|
||||
}
|
||||
getResponse(result)
|
||||
} else {
|
||||
getBadRequestError()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
makeApiWorker(
|
||||
"/v1/answer",
|
||||
(method: String, ex: HttpExchange) => {
|
||||
if (method == "POST") {
|
||||
val json = getRequestJson(ex)
|
||||
val answer = json.extract[Answer]
|
||||
val result = Captcha.checkAnswer(answer)
|
||||
getResponse(result)
|
||||
} else {
|
||||
getBadRequestError()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user