[server] Add server module

This commit is contained in:
aNNiMON 2023-11-09 19:54:26 +02:00 committed by Victor Melnik
parent 523c76dd38
commit e59e09aeb8
6 changed files with 248 additions and 4 deletions

View File

@ -6,6 +6,10 @@ ext {
socket: '1.0.2', // io.socket:socket.io-client socket: '1.0.2', // io.socket:socket.io-client
jline: '2.14.5', // jline:jline jline: '2.14.5', // jline:jline
javalin: '5.6.3', // io.javalin:javalin
slf4j: '2.0.9', // org.slf4j:slf4j-simple
jackson: '2.15.3', // com.fasterxml.jackson.core:jackson-databind
junit: '5.9.2', // org.junit:junit-bom junit: '5.9.2', // org.junit:junit-bom
jmh: '1.37', // org.openjdk.jmh:jmh-core jmh: '1.37', // org.openjdk.jmh:jmh-core
assertj: '3.24.2' // org.assertj:assertj-core assertj: '3.24.2' // org.assertj:assertj-core

View File

@ -0,0 +1,21 @@
plugins {
id 'java-library'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
group = 'com.annimon.module'
version = '2.0-SNAPSHOT'
dependencies {
compileOnlyApi project(":ownlang-core")
implementation "io.javalin:javalin:${versions.javalin}"
implementation "org.slf4j:slf4j-simple:${versions.slf4j}"
implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
testImplementation platform("org.junit:junit-bom:")
testImplementation 'org.junit.jupiter:junit-jupiter'
}
test {
useJUnitPlatform()
}

View File

@ -0,0 +1,91 @@
package com.annimon.ownlang.modules.server;
import com.annimon.ownlang.lib.*;
import io.javalin.http.Context;
import io.javalin.http.HttpStatus;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class ContextValue extends MapValue {
private final Context ctx;
public ContextValue(@NotNull Context ctx) {
super(10);
this.ctx = ctx;
init();
}
private void init() {
set("body", Converters.voidToString(ctx::body));
set("characterEncoding", Converters.voidToString(ctx::characterEncoding));
set("contentType", Converters.voidToString(ctx::contentType));
set("contextPath", Converters.voidToString(ctx::contextPath));
set("fullUrl", Converters.voidToString(ctx::fullUrl));
set("host", Converters.voidToString(ctx::host));
set("ip", Converters.voidToString(ctx::ip));
set("matchedPath", Converters.voidToString(ctx::matchedPath));
set("path", Converters.voidToString(ctx::path));
set("protocol", Converters.voidToString(ctx::protocol));
set("queryString", Converters.voidToString(ctx::queryString));
set("url", Converters.voidToString(ctx::url));
set("userAgent", Converters.voidToString(ctx::userAgent));
set("contentLength", Converters.voidToInt(ctx::contentLength));
set("port", Converters.voidToInt(ctx::port));
set("statusCode", Converters.voidToInt(ctx::statusCode));
set("json", objectToContext(ctx::json));
set("jsonStream", objectToContext(ctx::jsonStream));
set("render", this::render);
set("result", this::result);
set("redirect", this::redirect);
}
private Value render(Value[] args) {
Arguments.checkOrOr(1, 2, args.length);
String filePath = args[0].asString();
if (args.length == 1) {
ctx.render(filePath);
} else {
MapValue map = (MapValue) args[1];
Map<String, Object> data = new HashMap<>(map.size());
map.getMap().forEach((k, v) -> data.put(k.asString(), v.asJavaObject()));
ctx.render(filePath, data);
}
return this;
}
private Value redirect(Value[] args) {
Arguments.checkOrOr(1, 2, args.length);
HttpStatus status = args.length == 1 ? HttpStatus.FOUND : HttpStatus.forStatus(args[1].asInt());
ctx.redirect(args[0].asString(), status);
return this;
}
private Value result(Value[] args) {
Arguments.checkOrOr(0, 1, args.length);
if (args.length == 0) {
return new StringValue(ctx.result());
} else {
final var arg = args[0];
if (arg.type() == Types.ARRAY) {
ctx.result(ValueUtils.toByteArray((ArrayValue) arg));
} else {
ctx.result(arg.asString());
}
return this;
}
}
private Value objectToContext(Consumer<Object> consumer) {
return new FunctionValue(args -> {
Arguments.check(1, args.length);
consumer.accept(args[0].asJavaObject());
return this;
});
}
}

View File

@ -0,0 +1,86 @@
package com.annimon.ownlang.modules.server;
import com.annimon.ownlang.lib.*;
import io.javalin.Javalin;
import io.javalin.http.Handler;
import io.javalin.security.RouteRole;
import java.util.Arrays;
public class ServerValue extends MapValue {
private final Javalin server;
public ServerValue(Javalin server) {
super(10);
this.server = server;
init();
}
private void init() {
set("get", httpMethod(server::get));
set("post", httpMethod(server::post));
set("put", httpMethod(server::put));
set("patch", httpMethod(server::patch));
set("head", httpMethod(server::head));
set("delete", httpMethod(server::delete));
set("options", httpMethod(server::options));
set("error", this::error);
set("start", this::start);
}
private Value error(Value[] args) {
Arguments.checkOrOr(2, 3, args.length);
final int handlerIndex;
final String contentType;
if (args.length == 2) {
contentType = "*";
handlerIndex = 1;
} else {
contentType = args[1].asString();
handlerIndex = 2;
}
int status = args[0].asInt();
final Handler handler = toHandler(ValueUtils.consumeFunction(args[handlerIndex], handlerIndex));
server.error(status, contentType, handler);
return this;
}
private Value start(Value[] args) {
Arguments.checkRange(0, 2, args.length);
switch (args.length) {
case 0 -> server.start();
case 1 -> server.start(args[0].asInt());
case 2 -> server.start(args[0].asString(), args[1].asInt());
}
return this;
}
private FunctionValue httpMethod(HttpMethodHandler httpHandler) {
return new FunctionValue(args -> {
Arguments.checkAtLeast(2, args.length);
final String path = args[0].asString();
final Handler handler = toHandler(ValueUtils.consumeFunction(args[1], 1));
final Role[] roles;
if (args.length == 2) {
roles = new Role[0];
} else {
roles = Arrays.stream(args)
.skip(2)
.map(Role::new)
.toArray(Role[]::new);
}
httpHandler.handle(path, handler, roles);
return this;
});
}
private Handler toHandler(Function function) {
return ctx -> function.execute(new ContextValue(ctx));
}
private interface HttpMethodHandler {
void handle(String path, Handler handler, RouteRole[] roles);
}
private record Role(Value value) implements RouteRole { }
}

View File

@ -0,0 +1,40 @@
package com.annimon.ownlang.modules.server;
import com.annimon.ownlang.lib.*;
import com.annimon.ownlang.modules.Module;
import io.javalin.Javalin;
import java.util.Map;
import static java.util.Map.entry;
public final class server implements Module {
@Override
public Map<String, Value> constants() {
return Map.of();
}
@Override
public Map<String, Function> functions() {
return Map.ofEntries(
entry("newServer", this::newServer),
entry("serve", this::serve)
);
}
private Value newServer(Value[] args) {
Arguments.checkOrOr(0, 1, args.length);
if (args.length == 0) {
return new ServerValue(Javalin.create());
} else {
final Function configConsumer = ValueUtils.consumeFunction(args[0], 0);
return new ServerValue(Javalin.create(config -> configConsumer.execute()));
}
}
private Value serve(Value[] args) {
// get path, port
// javalin start()
return NumberValue.ZERO;
}
}

View File

@ -5,7 +5,9 @@ include 'ownlang-parser'
include 'ownlang-desktop' include 'ownlang-desktop'
include 'ownlang-utils' include 'ownlang-utils'
include 'modules:main' final def modules = ['main', 'canvasfx', 'server']
findProject(':modules:main')?.name = 'main'
include 'modules:canvasfx' for (final def module in modules) {
findProject(':modules:canvasfx')?.name = 'canvasfx' include "modules:$module"
findProject(":modules:$module")?.name = module
}