Skip to content

Commit e59e09a

Browse files
committed
[server] Add server module
1 parent 523c76d commit e59e09a

File tree

6 files changed

+248
-4
lines changed

6 files changed

+248
-4
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ ext {
66
socket: '1.0.2', // io.socket:socket.io-client
77
jline: '2.14.5', // jline:jline
88

9+
javalin: '5.6.3', // io.javalin:javalin
10+
slf4j: '2.0.9', // org.slf4j:slf4j-simple
11+
jackson: '2.15.3', // com.fasterxml.jackson.core:jackson-databind
12+
913
junit: '5.9.2', // org.junit:junit-bom
1014
jmh: '1.37', // org.openjdk.jmh:jmh-core
1115
assertj: '3.24.2' // org.assertj:assertj-core

modules/server/build.gradle

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
plugins {
2+
id 'java-library'
3+
id 'com.github.johnrengelman.shadow' version '8.1.1'
4+
}
5+
6+
group = 'com.annimon.module'
7+
version = '2.0-SNAPSHOT'
8+
9+
dependencies {
10+
compileOnlyApi project(":ownlang-core")
11+
implementation "io.javalin:javalin:${versions.javalin}"
12+
implementation "org.slf4j:slf4j-simple:${versions.slf4j}"
13+
implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}"
14+
15+
testImplementation platform("org.junit:junit-bom:")
16+
testImplementation 'org.junit.jupiter:junit-jupiter'
17+
}
18+
19+
test {
20+
useJUnitPlatform()
21+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.annimon.ownlang.modules.server;
2+
3+
import com.annimon.ownlang.lib.*;
4+
import io.javalin.http.Context;
5+
import io.javalin.http.HttpStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import java.util.function.Consumer;
10+
11+
public class ContextValue extends MapValue {
12+
13+
private final Context ctx;
14+
15+
public ContextValue(@NotNull Context ctx) {
16+
super(10);
17+
this.ctx = ctx;
18+
init();
19+
}
20+
21+
private void init() {
22+
set("body", Converters.voidToString(ctx::body));
23+
set("characterEncoding", Converters.voidToString(ctx::characterEncoding));
24+
set("contentType", Converters.voidToString(ctx::contentType));
25+
set("contextPath", Converters.voidToString(ctx::contextPath));
26+
set("fullUrl", Converters.voidToString(ctx::fullUrl));
27+
set("host", Converters.voidToString(ctx::host));
28+
set("ip", Converters.voidToString(ctx::ip));
29+
set("matchedPath", Converters.voidToString(ctx::matchedPath));
30+
set("path", Converters.voidToString(ctx::path));
31+
set("protocol", Converters.voidToString(ctx::protocol));
32+
set("queryString", Converters.voidToString(ctx::queryString));
33+
set("url", Converters.voidToString(ctx::url));
34+
set("userAgent", Converters.voidToString(ctx::userAgent));
35+
36+
set("contentLength", Converters.voidToInt(ctx::contentLength));
37+
set("port", Converters.voidToInt(ctx::port));
38+
set("statusCode", Converters.voidToInt(ctx::statusCode));
39+
40+
set("json", objectToContext(ctx::json));
41+
set("jsonStream", objectToContext(ctx::jsonStream));
42+
43+
set("render", this::render);
44+
set("result", this::result);
45+
set("redirect", this::redirect);
46+
}
47+
48+
private Value render(Value[] args) {
49+
Arguments.checkOrOr(1, 2, args.length);
50+
String filePath = args[0].asString();
51+
if (args.length == 1) {
52+
ctx.render(filePath);
53+
} else {
54+
MapValue map = (MapValue) args[1];
55+
Map<String, Object> data = new HashMap<>(map.size());
56+
map.getMap().forEach((k, v) -> data.put(k.asString(), v.asJavaObject()));
57+
ctx.render(filePath, data);
58+
}
59+
return this;
60+
}
61+
62+
private Value redirect(Value[] args) {
63+
Arguments.checkOrOr(1, 2, args.length);
64+
HttpStatus status = args.length == 1 ? HttpStatus.FOUND : HttpStatus.forStatus(args[1].asInt());
65+
ctx.redirect(args[0].asString(), status);
66+
return this;
67+
}
68+
69+
private Value result(Value[] args) {
70+
Arguments.checkOrOr(0, 1, args.length);
71+
if (args.length == 0) {
72+
return new StringValue(ctx.result());
73+
} else {
74+
final var arg = args[0];
75+
if (arg.type() == Types.ARRAY) {
76+
ctx.result(ValueUtils.toByteArray((ArrayValue) arg));
77+
} else {
78+
ctx.result(arg.asString());
79+
}
80+
return this;
81+
}
82+
}
83+
84+
private Value objectToContext(Consumer<Object> consumer) {
85+
return new FunctionValue(args -> {
86+
Arguments.check(1, args.length);
87+
consumer.accept(args[0].asJavaObject());
88+
return this;
89+
});
90+
}
91+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.annimon.ownlang.modules.server;
2+
3+
import com.annimon.ownlang.lib.*;
4+
import io.javalin.Javalin;
5+
import io.javalin.http.Handler;
6+
import io.javalin.security.RouteRole;
7+
import java.util.Arrays;
8+
9+
public class ServerValue extends MapValue {
10+
11+
private final Javalin server;
12+
13+
public ServerValue(Javalin server) {
14+
super(10);
15+
this.server = server;
16+
init();
17+
}
18+
19+
private void init() {
20+
set("get", httpMethod(server::get));
21+
set("post", httpMethod(server::post));
22+
set("put", httpMethod(server::put));
23+
set("patch", httpMethod(server::patch));
24+
set("head", httpMethod(server::head));
25+
set("delete", httpMethod(server::delete));
26+
set("options", httpMethod(server::options));
27+
set("error", this::error);
28+
set("start", this::start);
29+
}
30+
31+
private Value error(Value[] args) {
32+
Arguments.checkOrOr(2, 3, args.length);
33+
final int handlerIndex;
34+
final String contentType;
35+
if (args.length == 2) {
36+
contentType = "*";
37+
handlerIndex = 1;
38+
} else {
39+
contentType = args[1].asString();
40+
handlerIndex = 2;
41+
}
42+
int status = args[0].asInt();
43+
final Handler handler = toHandler(ValueUtils.consumeFunction(args[handlerIndex], handlerIndex));
44+
server.error(status, contentType, handler);
45+
return this;
46+
}
47+
48+
private Value start(Value[] args) {
49+
Arguments.checkRange(0, 2, args.length);
50+
switch (args.length) {
51+
case 0 -> server.start();
52+
case 1 -> server.start(args[0].asInt());
53+
case 2 -> server.start(args[0].asString(), args[1].asInt());
54+
}
55+
return this;
56+
}
57+
58+
private FunctionValue httpMethod(HttpMethodHandler httpHandler) {
59+
return new FunctionValue(args -> {
60+
Arguments.checkAtLeast(2, args.length);
61+
final String path = args[0].asString();
62+
final Handler handler = toHandler(ValueUtils.consumeFunction(args[1], 1));
63+
final Role[] roles;
64+
if (args.length == 2) {
65+
roles = new Role[0];
66+
} else {
67+
roles = Arrays.stream(args)
68+
.skip(2)
69+
.map(Role::new)
70+
.toArray(Role[]::new);
71+
}
72+
httpHandler.handle(path, handler, roles);
73+
return this;
74+
});
75+
}
76+
77+
private Handler toHandler(Function function) {
78+
return ctx -> function.execute(new ContextValue(ctx));
79+
}
80+
81+
private interface HttpMethodHandler {
82+
void handle(String path, Handler handler, RouteRole[] roles);
83+
}
84+
85+
private record Role(Value value) implements RouteRole { }
86+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.annimon.ownlang.modules.server;
2+
3+
import com.annimon.ownlang.lib.*;
4+
import com.annimon.ownlang.modules.Module;
5+
import io.javalin.Javalin;
6+
import java.util.Map;
7+
import static java.util.Map.entry;
8+
9+
public final class server implements Module {
10+
11+
@Override
12+
public Map<String, Value> constants() {
13+
return Map.of();
14+
}
15+
16+
@Override
17+
public Map<String, Function> functions() {
18+
return Map.ofEntries(
19+
entry("newServer", this::newServer),
20+
entry("serve", this::serve)
21+
);
22+
}
23+
24+
private Value newServer(Value[] args) {
25+
Arguments.checkOrOr(0, 1, args.length);
26+
if (args.length == 0) {
27+
return new ServerValue(Javalin.create());
28+
} else {
29+
final Function configConsumer = ValueUtils.consumeFunction(args[0], 0);
30+
return new ServerValue(Javalin.create(config -> configConsumer.execute()));
31+
}
32+
}
33+
34+
private Value serve(Value[] args) {
35+
// get path, port
36+
// javalin start()
37+
return NumberValue.ZERO;
38+
39+
}
40+
}

settings.gradle

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ include 'ownlang-parser'
55
include 'ownlang-desktop'
66
include 'ownlang-utils'
77

8-
include 'modules:main'
9-
findProject(':modules:main')?.name = 'main'
10-
include 'modules:canvasfx'
11-
findProject(':modules:canvasfx')?.name = 'canvasfx'
8+
final def modules = ['main', 'canvasfx', 'server']
9+
10+
for (final def module in modules) {
11+
include "modules:$module"
12+
findProject(":modules:$module")?.name = module
13+
}

0 commit comments

Comments
 (0)