Skip to content

Commit f575b43

Browse files
committed
[server] More server functions
1 parent e59e09a commit f575b43

File tree

7 files changed

+267
-23
lines changed

7 files changed

+267
-23
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.annimon.ownlang.modules.server;
2+
3+
import com.annimon.ownlang.lib.ArrayValue;
4+
import com.annimon.ownlang.lib.NumberValue;
5+
import com.annimon.ownlang.lib.Types;
6+
import com.annimon.ownlang.lib.Value;
7+
import io.javalin.config.JavalinConfig;
8+
import io.javalin.http.staticfiles.Location;
9+
import java.util.Map;
10+
import java.util.function.Consumer;
11+
12+
/*
13+
* Sample config:
14+
* {
15+
* "webjars": true,
16+
* "classpathDirs": ["dir1", "dir2"],
17+
* "externalDirs": ["dir1", "dir2"],
18+
*
19+
* "asyncTimeout": 6_000,
20+
* "defaultContentType": "text/plain",
21+
* "etags": true,
22+
* "maxRequestSize": 1_000_000,
23+
*
24+
* "caseInsensitiveRoutes": true,
25+
* "ignoreTrailingSlashes": true,
26+
* "multipleSlashesAsSingle": true,
27+
* "contextPath": "/",
28+
*
29+
* "basicAuth": ["user", "password"],
30+
* "dev": true,
31+
* "showBanner": false,
32+
* "sslRedirects": true
33+
* }
34+
*/
35+
36+
class Config {
37+
private final Map<String, Value> map;
38+
39+
public Config(Map<String, Value> map) {
40+
this.map = map;
41+
}
42+
43+
public void setup(JavalinConfig config) {
44+
// staticFiles
45+
ifTrue("webjars", config.staticFiles::enableWebjars);
46+
ifArray("classpathDirs", directories -> {
47+
for (Value directory : directories) {
48+
config.staticFiles.add(directory.asString(), Location.CLASSPATH);
49+
}
50+
});
51+
ifArray("externalDirs", directories -> {
52+
for (Value directory : directories) {
53+
config.staticFiles.add(directory.asString(), Location.EXTERNAL);
54+
}
55+
});
56+
57+
// http
58+
ifNumber("asyncTimeout", value -> config.http.asyncTimeout = value.asLong());
59+
ifString("defaultContentType", value -> config.http.defaultContentType = value);
60+
ifBoolean("etags", flag -> config.http.generateEtags = flag);
61+
ifNumber("maxRequestSize", value -> config.http.maxRequestSize = value.asLong());
62+
63+
// routing
64+
ifBoolean("caseInsensitiveRoutes", flag -> config.routing.caseInsensitiveRoutes = flag);
65+
ifBoolean("ignoreTrailingSlashes", flag -> config.routing.ignoreTrailingSlashes = flag);
66+
ifBoolean("multipleSlashesAsSingle", flag -> config.routing.treatMultipleSlashesAsSingleSlash = flag);
67+
ifString("contextPath", path -> config.routing.contextPath = path);
68+
69+
// other
70+
ifArray("basicAuth", arr -> config.plugins.enableBasicAuth(arr.get(0).asString(), arr.get(1).asString()));
71+
ifBoolean("showBanner", flag -> config.showJavalinBanner = flag);
72+
ifTrue("dev", config.plugins::enableDevLogging);
73+
ifTrue("sslRedirects", config.plugins::enableSslRedirects);
74+
}
75+
76+
private void ifTrue(String key, Runnable action) {
77+
if (map.containsKey(key) && map.get(key).asInt() != 0) {
78+
action.run();
79+
}
80+
}
81+
82+
private void ifBoolean(String key, Consumer<Boolean> consumer) {
83+
if (!map.containsKey(key)) return;
84+
consumer.accept(map.get(key).asInt() != 0);
85+
}
86+
87+
private void ifNumber(String key, Consumer<NumberValue> consumer) {
88+
if (!map.containsKey(key)) return;
89+
final Value value = map.get(key);
90+
if (value.type() == Types.NUMBER) {
91+
consumer.accept((NumberValue) value);
92+
}
93+
}
94+
95+
private void ifString(String key, Consumer<String> consumer) {
96+
if (!map.containsKey(key)) return;
97+
consumer.accept(map.get(key).asString());
98+
}
99+
100+
private void ifArray(String key, Consumer<ArrayValue> consumer) {
101+
if (!map.containsKey(key)) return;
102+
final Value value = map.get(key);
103+
if (value.type() == Types.ARRAY) {
104+
consumer.accept((ArrayValue) value);
105+
}
106+
}
107+
}

modules/server/src/main/java/com/annimon/ownlang/modules/server/ContextValue.java

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,108 @@
44
import io.javalin.http.Context;
55
import io.javalin.http.HttpStatus;
66
import org.jetbrains.annotations.NotNull;
7-
import java.util.HashMap;
87
import java.util.Map;
98
import java.util.function.Consumer;
109

11-
public class ContextValue extends MapValue {
10+
class ContextValue extends MapValue {
1211

1312
private final Context ctx;
1413

1514
public ContextValue(@NotNull Context ctx) {
16-
super(10);
15+
super(32);
1716
this.ctx = ctx;
1817
init();
1918
}
2019

2120
private void init() {
21+
set("attribute", this::attribute);
22+
set("basicAuthCredentials", this::basicAuthCredentials);
2223
set("body", Converters.voidToString(ctx::body));
24+
set("bodyAsBytes", this::bodyAsBytes);
2325
set("characterEncoding", Converters.voidToString(ctx::characterEncoding));
24-
set("contentType", Converters.voidToString(ctx::contentType));
26+
set("cookie", this::cookie);
27+
set("contentLength", Converters.voidToInt(ctx::contentLength));
28+
set("contentType", this::contentType);
2529
set("contextPath", Converters.voidToString(ctx::contextPath));
30+
set("endpointHandlerPath", Converters.voidToString(ctx::endpointHandlerPath));
31+
set("formParam", Converters.stringToString(ctx::formParam));
2632
set("fullUrl", Converters.voidToString(ctx::fullUrl));
33+
set("handlerType", Converters.voidToString(() -> ctx.handlerType().name()));
34+
set("header", this::header);
2735
set("host", Converters.voidToString(ctx::host));
36+
set("html", stringToContext(ctx::html));
2837
set("ip", Converters.voidToString(ctx::ip));
38+
set("json", objectToContext(ctx::json));
39+
set("jsonStream", objectToContext(ctx::jsonStream));
2940
set("matchedPath", Converters.voidToString(ctx::matchedPath));
3041
set("path", Converters.voidToString(ctx::path));
42+
set("port", Converters.voidToInt(ctx::port));
3143
set("protocol", Converters.voidToString(ctx::protocol));
3244
set("queryString", Converters.voidToString(ctx::queryString));
45+
set("redirect", this::redirect);
46+
set("removeCookie", this::removeCookie);
47+
set("render", this::render);
48+
set("result", this::result);
49+
set("statusCode", Converters.voidToInt(ctx::statusCode));
50+
set("scheme", Converters.voidToString(ctx::scheme));
3351
set("url", Converters.voidToString(ctx::url));
3452
set("userAgent", Converters.voidToString(ctx::userAgent));
53+
}
3554

36-
set("contentLength", Converters.voidToInt(ctx::contentLength));
37-
set("port", Converters.voidToInt(ctx::port));
38-
set("statusCode", Converters.voidToInt(ctx::statusCode));
55+
private Value attribute(Value[] args) {
56+
Arguments.checkOrOr(1, 2, args.length);
57+
String key = args[0].asString();
58+
if (args.length == 1) {
59+
return ctx.attribute(key);
60+
} else {
61+
ctx.attribute(key, args[1]);
62+
}
63+
return this;
64+
}
3965

40-
set("json", objectToContext(ctx::json));
41-
set("jsonStream", objectToContext(ctx::jsonStream));
66+
private Value basicAuthCredentials(Value[] args) {
67+
Arguments.check(0, args.length);
68+
final var cred = ctx.basicAuthCredentials();
69+
return ArrayValue.of(new String[] {
70+
cred.getUsername(),
71+
cred.getPassword()
72+
});
73+
}
4274

43-
set("render", this::render);
44-
set("result", this::result);
45-
set("redirect", this::redirect);
75+
private Value bodyAsBytes(Value[] args) {
76+
Arguments.check(0, args.length);
77+
return ArrayValue.of(ctx.bodyAsBytes());
78+
}
79+
80+
private Value cookie(Value[] args) {
81+
Arguments.checkRange(1, 3, args.length);
82+
if (args.length == 1) {
83+
return new StringValue(ctx.cookie(args[0].asString()));
84+
}
85+
int maxAge = args.length == 3 ? args[2].asInt() : -1;
86+
ctx.cookie(args[0].asString(), args[1].asString(), maxAge);
87+
return this;
88+
}
89+
90+
private Value contentType(Value[] args) {
91+
Arguments.checkOrOr(0, 1, args.length);
92+
if (args.length == 0) {
93+
return new StringValue(ctx.contentType());
94+
} else {
95+
ctx.contentType(args[0].asString());
96+
return this;
97+
}
98+
}
99+
100+
private Value header(Value[] args) {
101+
Arguments.checkOrOr(1, 2, args.length);
102+
String name = args[0].asString();
103+
if (args.length == 1) {
104+
return new StringValue(ctx.header(name));
105+
} else {
106+
ctx.header(name, args[1].asString());
107+
return this;
108+
}
46109
}
47110

48111
private Value render(Value[] args) {
@@ -51,9 +114,8 @@ private Value render(Value[] args) {
51114
if (args.length == 1) {
52115
ctx.render(filePath);
53116
} 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()));
117+
MapValue map = ValueUtils.consumeMap(args[1], 1);
118+
Map<String, Object> data = map.convertMap(Value::asString, Value::asJavaObject);
57119
ctx.render(filePath, data);
58120
}
59121
return this;
@@ -66,6 +128,14 @@ private Value redirect(Value[] args) {
66128
return this;
67129
}
68130

131+
private Value removeCookie(Value[] args) {
132+
Arguments.checkOrOr(1, 2, args.length);
133+
String name = args[0].asString();
134+
String path = args.length == 2 ? args[1].asString() : "/";
135+
ctx.removeCookie(name, path);
136+
return this;
137+
}
138+
69139
private Value result(Value[] args) {
70140
Arguments.checkOrOr(0, 1, args.length);
71141
if (args.length == 0) {
@@ -81,6 +151,14 @@ private Value result(Value[] args) {
81151
}
82152
}
83153

154+
private Value stringToContext(Consumer<String> consumer) {
155+
return new FunctionValue(args -> {
156+
Arguments.check(1, args.length);
157+
consumer.accept(args[0].asString());
158+
return this;
159+
});
160+
}
161+
84162
private Value objectToContext(Consumer<Object> consumer) {
85163
return new FunctionValue(args -> {
86164
Arguments.check(1, args.length);

modules/server/src/main/java/com/annimon/ownlang/modules/server/ServerValue.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
package com.annimon.ownlang.modules.server;
22

3+
import com.annimon.ownlang.exceptions.OwnLangRuntimeException;
34
import com.annimon.ownlang.lib.*;
45
import io.javalin.Javalin;
56
import io.javalin.http.Handler;
67
import io.javalin.security.RouteRole;
78
import java.util.Arrays;
89

9-
public class ServerValue extends MapValue {
10+
class ServerValue extends MapValue {
1011

1112
private final Javalin server;
1213

1314
public ServerValue(Javalin server) {
14-
super(10);
15+
super(12);
1516
this.server = server;
1617
init();
1718
}
@@ -25,7 +26,9 @@ private void init() {
2526
set("delete", httpMethod(server::delete));
2627
set("options", httpMethod(server::options));
2728
set("error", this::error);
29+
set("exception", this::exception);
2830
set("start", this::start);
31+
set("stop", this::stop);
2932
}
3033

3134
private Value error(Value[] args) {
@@ -45,6 +48,23 @@ private Value error(Value[] args) {
4548
return this;
4649
}
4750

51+
@SuppressWarnings("unchecked")
52+
private Value exception(Value[] args) {
53+
Arguments.check(2, args.length);
54+
try {
55+
String className = args[0].asString();
56+
final Class<?> clazz = Class.forName(className);
57+
final Function handler = ValueUtils.consumeFunction(args[1], 1);
58+
server.exception((Class<? extends Exception>) clazz, (exc, ctx) -> {
59+
Value exceptionType = new StringValue(exc.getClass().getName());
60+
handler.execute(exceptionType, new ContextValue(ctx));
61+
});
62+
} catch (ClassNotFoundException e) {
63+
throw new OwnLangRuntimeException(e);
64+
}
65+
return this;
66+
}
67+
4868
private Value start(Value[] args) {
4969
Arguments.checkRange(0, 2, args.length);
5070
switch (args.length) {
@@ -55,6 +75,12 @@ private Value start(Value[] args) {
5575
return this;
5676
}
5777

78+
private Value stop(Value[] args) {
79+
Arguments.check(0, args.length);
80+
server.stop();
81+
return this;
82+
}
83+
5884
private FunctionValue httpMethod(HttpMethodHandler httpHandler) {
5985
return new FunctionValue(args -> {
6086
Arguments.checkAtLeast(2, args.length);

modules/server/src/main/java/com/annimon/ownlang/modules/server/server.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.annimon.ownlang.lib.*;
44
import com.annimon.ownlang.modules.Module;
55
import io.javalin.Javalin;
6+
import io.javalin.http.staticfiles.Location;
67
import java.util.Map;
78
import static java.util.Map.entry;
89

@@ -26,15 +27,19 @@ private Value newServer(Value[] args) {
2627
if (args.length == 0) {
2728
return new ServerValue(Javalin.create());
2829
} else {
29-
final Function configConsumer = ValueUtils.consumeFunction(args[0], 0);
30-
return new ServerValue(Javalin.create(config -> configConsumer.execute()));
30+
final Map<String, Value> map = ValueUtils.consumeMap(args[0], 0).getMapStringKeys();
31+
final Config config = new Config(map);
32+
return new ServerValue(Javalin.create(config::setup));
3133
}
3234
}
3335

3436
private Value serve(Value[] args) {
35-
// get path, port
36-
// javalin start()
37-
return NumberValue.ZERO;
38-
37+
Arguments.checkRange(0, 2, args.length);
38+
int port = args.length >= 1 ? args[0].asInt() : 8080;
39+
String dir = args.length >= 2 ? args[1].asString() : ".";
40+
return new ServerValue(Javalin.create(config -> {
41+
config.staticFiles.add(dir, Location.EXTERNAL);
42+
config.showJavalinBanner = false;
43+
}).start(port));
3944
}
4045
}

ownlang-core/src/main/java/com/annimon/ownlang/lib/Converters.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.annimon.ownlang.lib;
22

33
import java.util.function.Predicate;
4+
import java.util.function.UnaryOperator;
45
import static com.annimon.ownlang.lib.ValueUtils.getFloatNumber;
56

67
/**
@@ -273,4 +274,11 @@ public static FunctionValue stringToBoolean(Predicate<String> f) {
273274
return NumberValue.fromBoolean(f.test(args[0].asString()));
274275
});
275276
}
277+
278+
public static FunctionValue stringToString(UnaryOperator<String> f) {
279+
return new FunctionValue(args -> {
280+
Arguments.check(1, args.length);
281+
return new StringValue(f.apply(args[0].asString()));
282+
});
283+
}
276284
}

0 commit comments

Comments
 (0)