Skip to content

Commit 89d3e93

Browse files
committed
Replaced Regular HttpServer for Shelf
1 parent fb2252c commit 89d3e93

File tree

14 files changed

+331
-198
lines changed

14 files changed

+331
-198
lines changed

lib/scripting/extras/session.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import 'package:reyveld/event.dart';
66
import 'package:reyveld/scripting/lua.dart';
77
import 'package:reyveld/scripting/sinterface.dart';
88
import 'package:talker/talker.dart';
9+
import 'package:web_socket_channel/web_socket_channel.dart';
910

1011
/// This is the socket interface.
1112
/// This can be used from Lua to send data through the web socket,
1213
/// so the external client can know what the script is doing at a given time.
13-
class SessionInterface extends SInterface<WebSocket> {
14+
class SessionInterface extends SInterface<WebSocketChannel> {
1415
@override
1516
String get className => "Session";
1617

@@ -49,7 +50,7 @@ This can be used to send data through the web socket, log messages, etc.""";
4950
} else if (lua.socket!.closeCode != null) {
5051
return;
5152
}
52-
lua.socket!.add(
53+
lua.socket!.sink.add(
5354
SocketEvent.data(data, pid: lua.getPID(state) ?? "").toString());
5455
}),
5556
LEntry(

lib/scripting/lua.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:reyveld/skit/skit.dart' show SKitType;
1111
import 'package:reyveld/uuid.dart';
1212
import 'package:lua_dardo_async/lua.dart';
1313
import 'interfaces.dart' as portal;
14+
import 'package:web_socket_channel/web_socket_channel.dart';
1415

1516
typedef LuaArgs = ({List positional, Map named});
1617
typedef LuaResult = ({
@@ -21,7 +22,7 @@ typedef LuaResult = ({
2122

2223
/// The main class for running lua scripts.
2324
class Lua {
24-
final WebSocket? socket;
25+
final WebSocketChannel? socket;
2526

2627
final Set<Stopwatch> _stopwatches = {};
2728

lib/security/certificate/certificate.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@ class SCertificate extends SRoot {
1919

2020
SCertificate(super._node);
2121

22+
/// Returns the polices of the [SCertificate].
2223
List<SPolicy> get policies =>
2324
getChildren<SPolicy>().whereType<SPolicy>().toList();
2425

26+
/// Returns true if the [SCertificate] has the [SPolicyAll] policy.
2527
bool get completeAccess => policies.any((policy) => policy is SPolicyAll);
2628

29+
/// Returns the application name.
2730
String get appname => get("appname") ?? "Default";
2831

32+
/// Is the certificate authorized?
33+
/// The user may authorize or deauthorize a certificate at any time.
2934
bool get authorized => (get("authorized") ?? "1") == "1";
3035
set authorized(bool value) => set("authorized", value ? "1" : "0");
3136

lib/server.dart

Lines changed: 128 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import 'package:reyveld/scripting/lua.dart';
99
import 'package:reyveld/security/authveld.dart';
1010
import 'package:chalkdart/chalkstrings.dart';
1111
import 'package:cli_spin/cli_spin.dart';
12+
import 'package:reyveld/uuid.dart';
1213
import 'package:rxdart/rxdart.dart';
14+
import 'package:shelf_web_socket/shelf_web_socket.dart';
1315
import 'package:version/version.dart';
1416
import 'package:reyveld/extensions.dart';
1517
import 'package:reyveld/extras.dart';
1618
import 'package:http/http.dart' as http;
19+
import 'package:shelf_router/shelf_router.dart';
20+
import 'package:shelf/shelf.dart';
21+
import 'package:shelf/shelf_io.dart' as io;
1722

1823
typedef ReyveldSession = (Lua, WebSocket);
1924

@@ -29,9 +34,6 @@ Future<void> runServer() async {
2934
exit(0);
3035
}
3136

32-
/// The reroute version is the version that this Reyveld executable is rerouting to.
33-
Version rerouteVersion = await getMostRecentVersion();
34-
3537
isRunningSpinner.success("Ready to Start!".skyBlue);
3638

3739
File lockFile =
@@ -41,185 +43,100 @@ Future<void> runServer() async {
4143
CliSpin(spinner: CliSpinners.bounce).start("Starting Server...".skyBlue);
4244

4345
await Reyveld.deleteTempFiles();
44-
45-
final server = await HttpServer.bind(InternetAddress.anyIPv4, 7274);
46-
serverSpinner.success("Server Started!".skyBlue);
47-
48-
Map<String, ReyveldSession> sessions = {};
4946
Pool luaPool = Pool(int.parse(await Reyveld.getPerformanceOption("LUAPOOL")),
5047
timeout: parseDurationFromString(
5148
await Reyveld.getPerformanceOption("LUAPOOL_TIMEOUT")));
5249

53-
await for (HttpRequest request in server) {
54-
try {
55-
Version requestedVersion;
50+
HttpServer? server;
5651

57-
try {
58-
requestedVersion = Version.parse(request.uri.pathSegments.first);
59-
} catch (e) {
60-
requestedVersion = rerouteVersion;
61-
}
62-
63-
/// Check if the requested version is the same as this program's version.
64-
if (requestedVersion != Reyveld.version) {
65-
/// If not, check if the requested version is running.
66-
if (await isRunning(requestedVersion)) {
67-
/// The requested version is running.
68-
request.response.statusCode = HttpStatus.movedPermanently;
69-
await request.response.close();
70-
continue;
71-
} else {
72-
/// The requested version is not running.
73-
request.response.statusCode = HttpStatus.notFound;
74-
await request.response.close();
75-
Reyveld.talker.error(
76-
"Version not found. ${request.uri.pathSegments.firstOrNull} not found.");
77-
continue;
78-
}
79-
}
52+
final app = Router();
8053

81-
// Has the client requested a specific version?
82-
bool definedVersion = true;
54+
app.get("/authveld", (Request request) async {
55+
if (!request.url.queryParameters.containsKey("ticket")) {
56+
return Response.badRequest(body: "No ticket provided.");
57+
}
58+
return Response.ok(
59+
AuthVeld.authorizePage(request.url.queryParameters["ticket"]!)
60+
.codeUnits,
61+
headers: {"Content-Type": "text/html"});
62+
});
63+
64+
app.get("/heartbeat", (Request request) async {
65+
Reyveld.talker
66+
.verbose("Heartbeat checked at ${DateTime.now().toIso8601String()}.");
67+
return Response.ok("OK".codeUnits);
68+
});
69+
70+
app.get('/lua', webSocketHandler((webSocket, _) {
71+
final id = generateUUID();
72+
final lua = Lua(socket: webSocket);
73+
Reyveld.printToConsole("(SID:$id) Client connected.".skyBlue);
74+
Reyveld.talker.verbose("(SID:$id) Client connected.");
75+
// Listen for incoming messages
76+
webSocket.stream.listen((data) async {
8377
try {
84-
Version.parse(request.uri.pathSegments.first);
85-
} catch (_) {
86-
definedVersion = false;
87-
}
88-
final requestUrl =
89-
request.uri.pathSegments.sublist(definedVersion ? 1 : 0).join('/');
90-
91-
Reyveld.talker.verbose("Request: $requestUrl; ${request.method}");
92-
93-
/// If the requested version is the same as this program's version, continue as normal.
94-
if (request.method == "GET") {
95-
switch (requestUrl) {
96-
case "authveld":
97-
request.response.headers.contentType = ContentType.html;
98-
if (!request.uri.queryParameters.containsKey("ticket")) {
99-
request.response.statusCode = HttpStatus.badRequest;
100-
await request.response.close();
101-
continue;
102-
}
103-
request.response.add(
104-
AuthVeld.authorizePage(request.uri.queryParameters["ticket"]!)
105-
.codeUnits);
106-
await request.response.close();
107-
case "heartbeat":
108-
request.response.statusCode = HttpStatus.ok;
109-
request.response.add("OK".codeUnits);
110-
Reyveld.talker.verbose(
111-
"Heartbeat checked at ${DateTime.now().toIso8601String()}.");
112-
await request.response.close();
113-
case "lua":
114-
115-
/// We save the client's session ID here, as once the Garbage Collector collects the request, the ID will be gone as well.
116-
final id = request.session.id;
117-
if (WebSocketTransformer.isUpgradeRequest(request)) {
118-
final socket = await WebSocketTransformer.upgrade(request);
119-
sessions[id] = (Lua(socket: socket), socket);
120-
Reyveld.printToConsole('(SID:$id) Client connected.'.skyBlue);
121-
Reyveld.talker.info("(SID:$id) Client connected.");
122-
socket.listen((data) async {
123-
try {
124-
/// Run the request and get the result.
125-
Reyveld.talker.verbose("(SID:$id) Received request.");
126-
Reyveld.talker.verbose("(SID:$id) Request:\n$data");
127-
final result = await luaPool.withResource(
128-
() async => await sessions[id]!.$1.run(data));
129-
socket.add(SocketEvent.completed(result.result,
130-
pid: result.processId ?? "")
131-
.toString());
132-
Reyveld.talker.verbose(
133-
"(SID:$id, PID:${result.processId ?? ""}) Completed request.");
134-
} catch (e, st) {
135-
Reyveld.printToConsole(
136-
"There was a crash on a request, please check the log folder (${Reyveld.appDataPath}/logs) for more information."
137-
.red);
138-
socket.add(SocketEvent.error(e).toString());
139-
Reyveld.talker.error("(SID:$id)", e, st);
140-
}
141-
}, onDone: () {
142-
Reyveld.printToConsole('(SID:$id) Client disconnected'.skyBlue);
143-
Reyveld.talker.info("(SID:$id) Client disconnected.");
144-
socket.close();
145-
sessions.remove(id);
146-
return;
147-
}, cancelOnError: false);
148-
} else {
149-
request.response
150-
..statusCode = HttpStatus.forbidden
151-
..close();
152-
}
153-
case "permissions/details":
154-
request.response.headers.contentType = ContentType.html;
155-
request.response.add(
156-
AuthVeld.getDetailsPage(request.uri.queryParameters["ticket"]!)
157-
.codeUnits);
158-
await request.response.close();
159-
default:
160-
request.response.statusCode = HttpStatus.notFound;
161-
await request.response.close();
162-
}
163-
} else if (request.method == "POST") {
164-
switch (requestUrl) {
165-
case "authorize":
166-
final origin =
167-
request.headers["origin"] ?? request.headers['referer'];
168-
if (origin == null || origin.isEmpty) {
169-
request.response.statusCode = HttpStatus.forbidden;
170-
await request.response.close();
171-
continue;
172-
}
173-
await AuthVeld.authorize(request.uri.queryParameters["ticket"]!);
174-
request.response.headers.contentType = ContentType.json;
175-
request.response.add(jsonEncode({"allowed": true}).codeUnits);
176-
await request.response.close();
177-
case "deauthorize":
178-
final origin =
179-
request.headers["origin"] ?? request.headers['referer'];
180-
if (origin == null || origin.isEmpty) {
181-
request.response.statusCode = HttpStatus.forbidden;
182-
await request.response.close();
183-
continue;
184-
}
185-
await AuthVeld.deauthorize(request.uri.queryParameters["ticket"]!);
186-
request.response.headers.contentType = ContentType.json;
187-
request.response.add(jsonEncode({"allowed": false}).codeUnits);
188-
await request.response.close();
189-
default:
190-
request.response.statusCode = HttpStatus.notFound;
191-
await request.response.close();
192-
}
193-
} else if (request.method == "OPTIONS") {
194-
switch (requestUrl) {
195-
case "close":
196-
for (final session in sessions.entries) {
197-
await session.value.$1.awaitForCompletion().then((_) async {
198-
await session.value.$2
199-
.close(WebSocketStatus.goingAway, "Server closed.");
200-
});
201-
}
202-
request.response.statusCode = HttpStatus.ok;
203-
request.response.headers.contentType = ContentType.json;
204-
request.response.add(jsonEncode({}).codeUnits);
205-
await request.response.close();
206-
await server.close();
207-
await Reyveld.deleteTempFiles();
208-
exit(0);
209-
default:
210-
request.response.statusCode = HttpStatus.notFound;
211-
await request.response.close();
212-
}
78+
Reyveld.talker.verbose("(SID:$id) Received data:\n$data");
79+
final result =
80+
await luaPool.withResource(() async => await lua.run(data));
81+
webSocket.sink.add(
82+
SocketEvent.completed(result.result, pid: result.processId ?? "")
83+
.toString());
84+
} catch (e, st) {
85+
Reyveld.printToConsole(
86+
"There was a crash on a websocket, please check the log folder (${Reyveld.appDataPath}/logs) for more information."
87+
.red);
88+
webSocket.sink.add(SocketEvent.error(e).toString());
89+
Reyveld.talker.error("(SID:$id)", e, st);
21390
}
214-
} catch (e, st) {
91+
}, onError: (e) {
21592
Reyveld.printToConsole(
21693
"There was a crash on a websocket, please check the log folder (${Reyveld.appDataPath}/logs) for more information."
21794
.red);
218-
Reyveld.talker.critical("Crash Handler", e, st);
95+
Reyveld.talker.error("(SID:$id)", e);
96+
}, onDone: () {
97+
Reyveld.printToConsole("(SID:$id) Client disconnected.".orange);
98+
Reyveld.talker.verbose("(SID:$id) Client disconnected.");
99+
});
100+
}));
101+
102+
app.get("/permissions/details", (Request request) async {
103+
if (!request.url.queryParameters.containsKey("ticket")) {
104+
return Response.badRequest(body: "No ticket provided.");
219105
}
220-
}
221-
await lockFile.delete();
222-
exit(0);
106+
return Response.ok(
107+
AuthVeld.getDetailsPage(request.url.queryParameters["ticket"]!)
108+
.codeUnits,
109+
headers: {"Content-Type": "text/html"});
110+
});
111+
112+
app.post("/authorize", (Request request) async {
113+
final origin = request.headers["origin"] ?? request.headers['referer'];
114+
if (origin == null || origin.isEmpty) {
115+
return Response.forbidden("No origin provided in request.");
116+
}
117+
await AuthVeld.authorize(origin);
118+
119+
return Response.ok(jsonEncode({"allowed": true}).codeUnits);
120+
});
121+
122+
app.post("/deauthorize", (Request request) async {
123+
final origin = request.headers["origin"] ?? request.headers['referer'];
124+
if (origin == null || origin.isEmpty) {
125+
return Response.forbidden("No origin provided in request.");
126+
}
127+
await AuthVeld.deauthorize(origin);
128+
129+
return Response.ok(jsonEncode({"allowed": true}).codeUnits);
130+
});
131+
132+
final versionRedirect = createMiddleware(requestHandler: rerouteVersion);
133+
134+
final handler =
135+
const Pipeline().addMiddleware(versionRedirect).addHandler(app.call);
136+
137+
server = await io.serve(handler, "127.0.0.1", 7274);
138+
139+
serverSpinner.success("Server Started!".skyBlue);
223140
}
224141

225142
/// This function checks if the version of this Reyveld executable is already running.
@@ -262,3 +179,44 @@ Future<Version> getMostRecentVersion() async {
262179
}
263180
return currentVersion;
264181
}
182+
183+
/// This function reroutes a request to the most recent version, or the requested version if it exists.
184+
///
185+
/// For example, if there are two versions of Reyveld running, lets say 1.0.0 and 1.0.1, and the request doesn't specify a version;
186+
/// then by default it will reroute to the most recent version, which is 1.0.1.
187+
///
188+
/// If the request does specify a version, then it attempt to reroute to that version, however it must be running for it to be rerouted.
189+
Future<Response?> rerouteVersion(Request request) async {
190+
Version requestedVersion;
191+
Version defaultVersion = await getMostRecentVersion();
192+
193+
// Has the client requested a specific version?
194+
bool definedVersion = true;
195+
try {
196+
requestedVersion = Version.parse(request.url.pathSegments.first);
197+
} catch (_) {
198+
definedVersion = false;
199+
requestedVersion = defaultVersion;
200+
}
201+
202+
/// Check if the requested version is the same as this program's version.
203+
if (requestedVersion != Reyveld.version) {
204+
/// If not, check if the requested version is running.
205+
if (await isRunning(requestedVersion)) {
206+
/// The requested version is running.
207+
return Response.movedPermanently(request.url);
208+
} else {
209+
/// The requested version is not running.
210+
Reyveld.talker.error(
211+
"Version not found. ${request.url.pathSegments.firstOrNull} not found.");
212+
return Response.notFound(request.url);
213+
}
214+
}
215+
216+
final requestUrl =
217+
request.url.pathSegments.sublist(definedVersion ? 1 : 0).join('/');
218+
219+
request.change(path: requestUrl);
220+
221+
return null;
222+
}

0 commit comments

Comments
 (0)