treesummaryrefslogcommitdiff
path: root/src/main.zig
blob: b8a0715e718a8ae08c8f742e289038863abf91e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
const std = @import("std");
const Io = std.Io;
const WebSocket = std.http.Server.WebSocket;

var m = std.atomic.Mutex.unlocked;

var clients: std.ArrayList(*WebSocket) = undefined;

fn broadcast(sm: WebSocket.SmallMessage) void {
    if (!m.tryLock()) return;
    for (clients.items) |c| {
        c.writeMessage(sm.data, sm.opcode) catch {};
    }
    m.unlock();
}

fn handle_websocket(alloc: std.mem.Allocator, websocket: *WebSocket) void {
    if (!m.tryLock()) return;
    clients.append(alloc, websocket) catch {};
    std.debug.print("{} clients\n", .{clients.items.len});
    m.unlock();

    const i = clients.items.len;
    defer {
        _ = clients.swapRemove(i);
        std.debug.print("{} clients\n", .{clients.items.len});
    }

    websocket.writeMessage("welcome", .text) catch return;

    while (true) {
        const sm = websocket.readSmallMessage() catch break;
        broadcast(sm);
    }
}

fn handle_request(alloc: std.mem.Allocator, io: Io, stream: Io.net.Stream) void {
    var recv_buffer: [999]u8 = undefined;
    var send_buffer: [100]u8 = undefined;

    defer stream.close(io);

    var connection_br = stream.reader(io, &recv_buffer);
    var connection_bw = stream.writer(io, &send_buffer);
    var server = std.http.Server.init(&connection_br.interface, &connection_bw.interface);

    while (true) {
        var req = server.receiveHead() catch break;

        switch (req.upgradeRequested()) {
            .websocket => |ws| {
                var websocket = req.respondWebSocket(.{ .key = ws.? }) catch break;

                handle_websocket(alloc, &websocket);
            },
            else => {
                req.respond(
                    \\ <!doctype html>
                    \\ <html><head>
                    \\ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>&#128187;</text></svg>">
                    \\ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                    \\ <meta charset="utf-8">
                    \\ <style>
                    \\   :root { color-scheme: light dark; }
                    \\   body {
                    \\     margin: 0; padding: 0;
                    \\     line-height: 1.6;
                    \\     font-size: 16px;
                    \\   }
                    \\ textarea { width: 100%; height: calc(100% - 40px); }
                    \\ input { width: calc(100% - 80px); height: 40px; }
                    \\ button { width: 80px; height: 40px; }
                    \\ </style></head><body>
                    \\ <script>
                    \\ const socket = new WebSocket(`wss://${window.location.host}`);
                    \\ socket.addEventListener("open", (event) => {
                    \\   tv.value += "Connected!\n";
                    \\ });
                    \\ socket.addEventListener("message", (event) => {
                    \\   tv.value += `[${new Date().toLocaleString()}] ${event.data}\n`;
                    \\ });
                    \\ function Send() {
                    \\   if (tn.value.length == 0) { window.alert("Please enter a name"); return; }
                    \\   if (tb.value.length == 0) { return; }
                    \\   socket.send(`${tn.value}: ${tb.value}`);
                    \\   tb.value = "";   
                    \\ }
                    \\ tb.onkeypress = (e) => if (e.key === "Enter") Send();
                    \\ </script>
                    \\ <textarea id="tv" readonly></textarea>
                    \\ <div><input type="text" id="tn" /><input type="text" id="tb" />
                    \\ <button onclick="Send()">Send</button></div>
                    \\ </body></html>
                , .{ .status = .ok }) catch break;
            },
        }
    }

    // std.debug.print("closing http thread\n", .{});
}

pub fn main(init: std.process.Init) !void {
    // Prints to stderr, unbuffered, ignoring potential errors.
    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});

    // This is appropriate for anything that lives as long as the process.
    const arena: std.mem.Allocator = init.arena.allocator();

    clients = try .initCapacity(arena, 10);

    // Accessing command line arguments:
    const args = try init.minimal.args.toSlice(arena);
    for (args) |arg| {
        std.log.info("arg: {s}", .{arg});
    }

    // In order to do I/O operations need an `Io` instance.
    const io = init.io;

    var port: u16 = 10010;
    if (init.environ_map.get("PORT")) |s| {
        if (std.fmt.parseInt(u16, s, 10)) |p| {
            port = p;
        } else |e| {
            std.debug.print("{}\n", .{e});
        }
    }

    const address = try std.Io.net.IpAddress.parseIp4("0.0.0.0", port);
    var net_server = try address.listen(io, .{ .reuse_address = true });

    while (true) {
        const stream = try net_server.accept(io);

        _ = io.async(handle_request, .{ arena, io, stream });

        // std.debug.print("created http thread\n", .{});
    }
}