From 35510de012602937f182b2d0ae0f2f83a6f20192 Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Mon, 1 Dec 2025 12:25:13 +0100 Subject: [PATCH] http app --- app.c | 230 ++++++++++------------------------------------------- app2.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++ os.h | 1 + os_linux.c | 51 ++++++------ 4 files changed, 277 insertions(+), 210 deletions(-) create mode 100644 app2.c diff --git a/app.c b/app.c index 8cbf0e3..2c76fb2 100644 --- a/app.c +++ b/app.c @@ -1,209 +1,65 @@ -#include "core.h" #define DJSTD_BASIC_ENTRY - #include "core.c" -#include "signal.h" - -Server *openServer = NULL; -SocketList *openSockets = NULL; - -void handleSigint(int dummy) { - if (openServer) { - println(""); - println("Closing server socket."); - serverClose(openServer); - println("Success."); - } - if (openSockets && openSockets->length) { - println(""); - println("Closing open sockets."); - for (EachEl(*openSockets, Socket, socket)) { - socketClose(socket); - } - println("Success."); - } - signal(SIGINT, SIG_DFL); - raise(SIGINT); -} -typedef struct ChatClient ChatClient; -struct ChatClient { - Socket *socket; - string nickname; -}; -DefineList(ChatClient, ChatClient); +int djstd_entry(Arena *arena, StringList args) { + Socket sock = socketConnect(arena, (SocketConnectInfo){ .address="::1", .port=8080, .blocking=true }); -void startServer(Arena *arena, int32 port) { - println("Starting server..."); - Server server = serverInit((ServerInitInfo){ - .concurrentClients=2, - .port=port, - .memory=Megabytes(64), - .maxEvents=64, - }); - openServer = &server; + string newLine = s("\r\n"); + string lines[] = { + s("GET / HTTP/1.1"), + s("Host: localhost"), + }; - serverListen(&server); - if (server.listening) { - println("Listening on port %d", port); + for (EachInArray(lines, i)) { + socketWriteStr(&sock, lines[i]); + socketWriteStr(&sock, newLine); } + socketWriteStr(&sock, newLine); - Arena *serverLoopArena = arenaAlloc(Megabytes(64)); - ChatClientList chatClients = PushListZero(arena, ChatClientList, 256); + CharList body = EmptyList(); + bool streamingBody = false; Forever { - ServerEvent *nextEvent; - - do { - nextEvent = serverGetNextEvent(&server); - switch (nextEvent->type) { - case ServerEventType_AcceptClient: { - Socket *client = serverAccept(&server); - if (client != NULL) { - println("New client connected from %d", client->address); - } - break; - }; - case ServerEventType_ClientMessage: { - StringResult clientMsg = socketReadStr(serverLoopArena, nextEvent->tClientMessage.client); - ChatClient *chatClient = NULL; - if (clientMsg.valid) { - if (strStartsWith(clientMsg.result, s("hello-"))) { - StringList nickSplit = strSplit(serverLoopArena, s("-"), clientMsg.result); - if (nickSplit.length == 2 && nickSplit.data[1].length > 0) { - string newNick = PushString(arena, nickSplit.data[1].length); - newNick.length = nickSplit.data[1].length; - memcpy(newNick.str, nickSplit.data[1].str, nickSplit.data[1].length); - ChatClient newChatClient = (ChatClient){ - .socket=nextEvent->tClientMessage.client, - .nickname=newNick, - }; - AppendList(&chatClients, newChatClient); - println("Client from %d calls themselves \"%S\"", newChatClient.socket->address, newChatClient.nickname); - } - } else { - for (EachEl(chatClients, ChatClient, maybeChatClient)) { - if (maybeChatClient->socket->handle == nextEvent->tClientMessage.client->handle) { - chatClient = maybeChatClient; - } - } - if (chatClient != NULL) { - if (strStartsWith(clientMsg.result, s("say-"))) { - StringList saySplit = strSplit(arena, s("-"), clientMsg.result); - if (saySplit.length == 2 && saySplit.data[1].length > 0) { - string broadcast = strPrintf(serverLoopArena, "%S says:\n%S", chatClient->nickname, saySplit.data[1]); - for (EachEl(server.clients, Socket, client)) { - socketWriteStr(client, broadcast); - } - } - } else { - // Invalid client message - } + StringResult response = socketReadStr(arena, &sock); + if (response.valid) { + if (streamingBody) { + if (body.capacity - body.length >= response.result.length) { + memcpy(body.data + body.length, response.result.str, response.result.length); + body.length += response.result.length; + } + } else { + StringList lines = strSplit(arena, s("\r\n"), response.result); + for (EachEl(lines, string, line)) { + if (body.capacity > 0 && strEql(*line, s(""))) { + streamingBody = true; + } else if (streamingBody && (body.capacity - body.length) >= line->length) { + // TODO(dledda): append list to list, string to string, whatever + // TODO(dledda): `joinStringList` + memcpy(body.data + body.length, line->str, line->length); + body.length += line->length; + } else { + // TODO(dledda): `strContains` + // TODO(dledda): `stringContains` -> `strContainsChar` + StringList split = strSplit(arena, s("Content-Length: "), *line); + if (split.length > 1) { + Int32Result lengthResult = parsePositiveInt(split.data[1]); + if (lengthResult.valid) { + body = PushList(arena, CharList, lengthResult.result); } } } - break; - }; - case ServerEventType_None: { - break; - }; - default: - break; - } - } while (nextEvent != NULL); - - arenaFreeFrom(serverLoopArena, 0); - } - - println("Shutting down chat."); - serverClose(&server); -} - -void clearStdInLn() { - print("\r"); - print(ANSI_INSTRUCTION(J)); -} - -void clearStdInLnAfterInput() { - print("\r"); - print(ANSI_INSTRUCTION(A)); - print(ANSI_INSTRUCTION(J)); -} - -void startClient(Arena *arena, string addr, int32 port, string nickname) { - fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); - - println("Connecting to server at [%S]:%d with nickname \"%S\"", addr, port, nickname); - Socket server = socketConnect(arena, (SocketConnectInfo){ .address=addr, .port=port }); - if (server.closed) { - println("Connection error. Closing."); - } else { - println("Connected successfully"); - string message = strPrintf(arena, "hello-%S", nickname); - CharList inputBuf = PushList(arena, CharList, 512); - socketWriteStr(&server, message); - - print("(you)> "); - Forever { - Scratch scratch = scratchStart(&arena, 1); - - int32 numRead = read(0, inputBuf.data + inputBuf.length, inputBuf.capacity - inputBuf.length); - if (numRead >= 0) { - inputBuf.length += numRead; - if (inputBuf.data[inputBuf.length - 1] == '\n') { - clearStdInLnAfterInput(); - socketWriteStr(&server, strPrintf(scratch.arena, "say-%S", (string){.str=inputBuf.data,.length=inputBuf.length})); - inputBuf.length = 0; } } - - StringResult serverMsg = socketReadStr(scratch.arena, &server); - if (serverMsg.valid && serverMsg.result.length > 0) { - clearStdInLn(); - println("%S", serverMsg.result); - print("(you)> %S", (string){.str=inputBuf.data, inputBuf.length}); - } - - scratchEnd(scratch); } - } - socketClose(&server); -} - -int djstd_entry(Arena *arena, StringList args) { - signal(SIGINT, &handleSigint); - - bool argumentErr = true; - bool isServer = strEql(args.data[0], s("server")); - bool isClient = strEql(args.data[0], s("client")); - if (isServer) { - Int32Result portParsed = parsePositiveInt(args.data[1]); - if (portParsed.valid) { - startServer(arena, portParsed.result); - argumentErr = false; - } - } else if (isClient) { - if (args.length == 3) { - StringList split = strSplit(arena, s("]:"), args.data[1]); - if (split.length == 2) { - Int32Result portParsed = parsePositiveInt(split.data[1]); - string addr = strSlice(split.data[0], 1, split.data[0].length); - string nickname = args.data[2]; - if (portParsed.valid && addr.length > 0 && nickname.length > 0) { - startClient(arena, addr, portParsed.result, nickname); - argumentErr = false; - } - } + if (streamingBody == true && body.length == body.capacity) { + break; } } - if (argumentErr) { - println("Usage:"); - println("server [PORT]"); - println("OR"); - println("client [REMOTE_ADDRESS:PORT] [NICKNAME]"); - } + // TODO(dledda): string from CharList/ByteList + // TODO(dledda): `printStr` + print("%S", (string){.str=body.data, .length=body.length}); return 0; } diff --git a/app2.c b/app2.c new file mode 100644 index 0000000..c1ab71c --- /dev/null +++ b/app2.c @@ -0,0 +1,205 @@ +#include "core.h" +#define DJSTD_BASIC_ENTRY + +#include "core.c" +#include "signal.h" + +Server *openServer = NULL; +SocketList *openSockets = NULL; + +void handleSigint(int dummy) { + if (openServer) { + println(""); + println("Closing server socket."); + serverClose(openServer); + println("Success."); + } + if (openSockets && openSockets->length) { + println(""); + println("Closing open sockets."); + for (EachEl(*openSockets, Socket, socket)) { + socketClose(socket); + } + println("Success."); + } + signal(SIGINT, SIG_DFL); + raise(SIGINT); +} + +typedef struct ChatClient ChatClient; +struct ChatClient { + Socket *socket; + string nickname; +}; +DefineList(ChatClient, ChatClient); + +void startServer(Arena *arena, int32 port) { + println("Starting server..."); + Server server = serverInit((ServerInitInfo){ + .concurrentClients=2, + .port=port, + .memory=Megabytes(64), + .maxEvents=64, + }); + openServer = &server; + + serverListen(&server); + if (server.listening) { + println("Listening on port %d", port); + } + + Arena *serverLoopArena = arenaAlloc(Megabytes(64)); + ChatClientList chatClients = PushListZero(arena, ChatClientList, 256); + + ServerEvent *nextEvent; + + Forever { + nextEvent = serverGetNextEvent(&server); + switch (nextEvent->type) { + case ServerEventType_AcceptClient: { + Socket *client = serverAccept(&server); + if (client != NULL) { + println("New client connected from %d", client->address); + } + break; + }; + case ServerEventType_ClientMessage: { + StringResult clientMsg = socketReadStr(serverLoopArena, nextEvent->tClientMessage.client); + ChatClient *chatClient = NULL; + if (clientMsg.valid) { + if (strStartsWith(clientMsg.result, s("hello-"))) { + StringList nickSplit = strSplit(serverLoopArena, s("-"), clientMsg.result); + if (nickSplit.length == 2 && nickSplit.data[1].length > 0) { + string newNick = PushString(arena, nickSplit.data[1].length); + newNick.length = nickSplit.data[1].length; + memcpy(newNick.str, nickSplit.data[1].str, nickSplit.data[1].length); + ChatClient newChatClient = (ChatClient){ + .socket=nextEvent->tClientMessage.client, + .nickname=newNick, + }; + AppendList(&chatClients, newChatClient); + println("Client from %d calls themselves \"%S\"", newChatClient.socket->address, newChatClient.nickname); + } + } else { + for (EachEl(chatClients, ChatClient, maybeChatClient)) { + if (maybeChatClient->socket->handle == nextEvent->tClientMessage.client->handle) { + chatClient = maybeChatClient; + } + } + if (chatClient != NULL) { + if (strStartsWith(clientMsg.result, s("say-"))) { + StringList saySplit = strSplit(arena, s("-"), clientMsg.result); + if (saySplit.length == 2 && saySplit.data[1].length > 0) { + string broadcast = strPrintf(serverLoopArena, "%S says:\n%S", chatClient->nickname, saySplit.data[1]); + for (EachEl(server.clients, Socket, client)) { + socketWriteStr(client, broadcast); + } + } + } else { + // Invalid client message + } + } + } + } + break; + }; + case ServerEventType_None: + default: + break; + } + + arenaFreeFrom(serverLoopArena, 0); + } + + println("Shutting down chat."); + serverClose(&server); +} + +void clearStdInLn() { + print("\r"); + print(ANSI_INSTRUCTION(J)); +} + +void clearStdInLnAfterInput() { + print("\r"); + print(ANSI_INSTRUCTION(A)); + print(ANSI_INSTRUCTION(J)); +} + +void startClient(Arena *arena, string addr, int32 port, string nickname) { + fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK); + + println("Connecting to server at [%S]:%d with nickname \"%S\"", addr, port, nickname); + Socket server = socketConnect(arena, (SocketConnectInfo){ .address=addr, .port=port }); + if (server.closed) { + println("Connection error. Closing."); + } else { + println("Connected successfully"); + string message = strPrintf(arena, "hello-%S", nickname); + CharList inputBuf = PushList(arena, CharList, 512); + socketWriteStr(&server, message); + + print("(you)> "); + Forever { + Scratch scratch = scratchStart(&arena, 1); + + int32 numRead = read(0, inputBuf.data + inputBuf.length, inputBuf.capacity - inputBuf.length); + if (numRead >= 0) { + inputBuf.length += numRead; + if (inputBuf.data[inputBuf.length - 1] == '\n') { + clearStdInLnAfterInput(); + socketWriteStr(&server, strPrintf(scratch.arena, "say-%S", (string){.str=inputBuf.data,.length=inputBuf.length})); + inputBuf.length = 0; + } + } + + StringResult serverMsg = socketReadStr(scratch.arena, &server); + if (serverMsg.valid && serverMsg.result.length > 0) { + clearStdInLn(); + println("%S", serverMsg.result); + print("(you)> %S", (string){.str=inputBuf.data, inputBuf.length}); + } + + scratchEnd(scratch); + } + } + socketClose(&server); +} + +int djstd_entry(Arena *arena, StringList args) { + signal(SIGINT, &handleSigint); + + bool argumentErr = true; + bool isServer = strEql(args.data[0], s("server")); + bool isClient = strEql(args.data[0], s("client")); + + if (isServer) { + Int32Result portParsed = parsePositiveInt(args.data[1]); + if (portParsed.valid) { + startServer(arena, portParsed.result); + argumentErr = false; + } + } else if (isClient) { + if (args.length == 3) { + StringList split = strSplit(arena, s("]:"), args.data[1]); + if (split.length == 2) { + Int32Result portParsed = parsePositiveInt(split.data[1]); + string addr = strSlice(split.data[0], 1, split.data[0].length); + string nickname = args.data[2]; + if (portParsed.valid && addr.length > 0 && nickname.length > 0) { + startClient(arena, addr, portParsed.result, nickname); + argumentErr = false; + } + } + } + } + + if (argumentErr) { + println("Usage:"); + println("server [PORT]"); + println("OR"); + println("client [REMOTE_ADDRESS:PORT] [NICKNAME]"); + } + + return 0; +} diff --git a/os.h b/os.h index e6063a5..dddee81 100644 --- a/os.h +++ b/os.h @@ -67,6 +67,7 @@ typedef struct SocketConnectInfo SocketConnectInfo; struct SocketConnectInfo { string address; uint16 port; + bool blocking; }; diff --git a/os_linux.c b/os_linux.c index 55bae27..d0a758e 100644 --- a/os_linux.c +++ b/os_linux.c @@ -5,15 +5,15 @@ #include "sys/mman.h" #include "sys/stat.h" -#include "unistd.h" // POSIX Standard -#include "stdio.h" +#include "string.h" // memcpy TODO(dledda): replace memcpy with custom impl? +#include "unistd.h" // POSIX Standard, read, write, close, open, etc. + #include "pthread.h" + #include "fcntl.h" #include "sys/epoll.h" - #include "sys/socket.h" #include "arpa/inet.h" -#include "string.h" // memcpy void *os_alloc(uint64 capacity) { @@ -34,17 +34,21 @@ void os_free(void *ptr, uint64 size) { string os_readEntireFile(Arena *arena, string filename) { Scratch temp = scratchStart(&arena, 1); - FILE *input = fopen(cstring(temp.arena, filename), "r"); + int input = open(cstring(temp.arena, filename), O_RDONLY); string readBuffer; if (input) { struct stat st; stat((char *)filename.str, &st); uint64 fsize = st.st_size; readBuffer = PushString(arena, fsize); - fread(readBuffer.str, sizeof(byte), readBuffer.length, input); - fclose(input); + int64 bytesRead = read(input, readBuffer.str, readBuffer.length); + close(input); + if (bytesRead == -1) { + arenaPopTo(arena, readBuffer.str); + return s(""); + } } else { - readBuffer = PushString(arena, 0); + readBuffer = s(""); } scratchEnd(temp); @@ -55,11 +59,13 @@ bool os_writeEntireFile(Arena *arena, string filename, const byte *contents, uin Scratch temp = scratchStart(&arena, 1); bool result = false; - FILE *output = fopen(cstring(temp.arena, filename), "w"); + int output = open(cstring(temp.arena, filename), O_WRONLY); if (output) { - fwrite(contents, sizeof(byte), contentsLength, output); - fclose(output); - result = true; + int64 bytesWritten = write(output, contents, contentsLength); + if (bytesWritten != -1) { + result = true; + } + close(output); } scratchEnd(temp); @@ -70,11 +76,13 @@ bool os_fileAppend(Arena *arena, string filename, const byte *contents, uint64 c Scratch temp = scratchStart(&arena, 1); bool result = false; - FILE *output = fopen(cstring(temp.arena, filename), "a"); + int output = open(cstring(temp.arena, filename), O_APPEND); if (output) { - fwrite(contents, sizeof(byte), contentsLength, output); - fclose(output); - result = true; + int bytesWritten = write(output, contents, contentsLength); + if (bytesWritten != -1) { + result = true; + } + close(output); } scratchEnd(temp); @@ -98,12 +106,10 @@ void os_println(StdStream target, const char *fmt, va_list argList) { write(0, (const void *)result.str, result.length); break; case StdStream_stderr: - fflush(stderr); write(2, (const void *)result.str, result.length); break; case StdStream_stdout: default: - fflush(stdout); write(1, (const void *)result.str, result.length); break; } @@ -121,12 +127,10 @@ void os_print(StdStream target, const char *fmt, va_list argList) { write(0, (const void *)result.str, result.length); break; case StdStream_stderr: - fflush(stderr); write(2, (const void *)result.str, result.length); break; case StdStream_stdout: default: - fflush(stdout); write(1, (const void *)result.str, result.length); break; } @@ -211,8 +215,6 @@ Socket *serverAccept(Server *s) { uint64 clientSockHandle = accept((int)(uint64)s->handle, (struct sockaddr *)clientAddr, &clientAddrLen); if (clientSockHandle == -1) { clientSockHandle = (uint64)NULL; - println("ERR server accept"); - perror("accept"); } else { fcntl((uint64)clientSockHandle, F_SETFL, fcntl((uint64)clientSockHandle, F_GETFL, 0) | O_NONBLOCK); } @@ -339,7 +341,9 @@ void socketClose(Socket *s) { Socket socketConnect(Arena *arena, SocketConnectInfo info) { int socketFd = socket(AF_INET6, SOCK_STREAM, 0 /* IPPROTO_TCP */); - fcntl(socketFd, F_SETFL, fcntl(socketFd, F_GETFL, 0) | O_NONBLOCK); + if (!info.blocking) { + fcntl(socketFd, F_SETFL, fcntl(socketFd, F_GETFL, 0) | O_NONBLOCK); + } struct sockaddr_in6 *remoteAddr = PushStructZero(arena, struct sockaddr_in6); remoteAddr->sin6_family = AF_INET6; @@ -352,6 +356,7 @@ Socket socketConnect(Arena *arena, SocketConnectInfo info) { .address=(Address *)remoteAddr, .closed=false, //.closed=connectErr == -1, + // TODO(dledda): investigate error behaviour }; return result;