Merge pull request #87 from UprootStaging/usePicoServer

Use pico server
This commit is contained in:
hrj 2021-04-18 20:16:59 +05:30 committed by GitHub
commit a942cd2a93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 329 additions and 130 deletions

View File

@ -20,10 +20,9 @@ public class FontFunCaptcha implements ChallengeProvider {
public Map<String, List<String>> supportedParameters() {
return Map.of(
"supportedLevels", List.of("medium"),
"supportedMedia", List.of("image/png"),
"supportedInputType", List.of("text")
);
"supportedLevels", List.of("medium"),
"supportedMedia", List.of("image/png"),
"supportedInputType", List.of("text"));
}
public void configure(String config) {

View File

@ -57,10 +57,9 @@ public class GifCaptcha implements ChallengeProvider {
public Map<String, List<String>> supportedParameters() {
return Map.of(
"supportedLevels", List.of("hard"),
"supportedMedia", List.of("image/gif"),
"supportedInputType", List.of("text")
);
"supportedLevels", List.of("hard"),
"supportedMedia", List.of("image/gif"),
"supportedInputType", List.of("text"));
}
public Challenge returnChallenge() {

View File

@ -29,10 +29,9 @@ public class ShadowTextCaptcha implements ChallengeProvider {
public Map<String, List<String>> supportedParameters() {
return Map.of(
"supportedLevels", List.of("easy"),
"supportedMedia", List.of("image/png"),
"supportedInputType", List.of("text")
);
"supportedLevels", List.of("easy"),
"supportedMedia", List.of("image/png"),
"supportedInputType", List.of("text"));
}
public boolean checkAnswer(String secret, String answer) {

View File

@ -0,0 +1,289 @@
// 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 static final Pattern ampersandPattern = Pattern.compile("&");
private static final Pattern equalPattern = Pattern.compile("=");
private static final 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);
}
}
}

View File

@ -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],

View File

@ -3,139 +3,53 @@ 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 = picoserve.Server.builder()
.port(8888)
.backlog(32)
.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()
}
}
)
}