Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ project/
- **CMake** (version 3.10 or higher)
- **GCC** or another compatible C compiler
- **OpenSSL** development libraries
- **Linux** or **WSL** (Windows Subsystem for Linux) recommended for running this server
- **Linux**, **macOS**, or **WSL** (Windows Subsystem for Linux) supported for running this server

## Setup Instructions

Expand All @@ -57,6 +57,9 @@ project/
# On Debian/Ubuntu
sudo apt update
sudo apt install build-essential cmake libssl-dev

# On macOS using Homebrew
brew install cmake openssl
```

### 2. Clone the Repository
Expand Down Expand Up @@ -103,6 +106,16 @@ If port 8080 is already in use, you can specify a different port using the `--po

This will start the server on port 8081 instead of the default port.

### macOS-Specific Notes

When running on macOS, the following modifications have been made to ensure compatibility:

1. The file watcher implementation uses polling instead of Linux's inotify API
2. Socket options are configured differently to be compatible with macOS networking APIs
3. Cross-platform endian handling for WebSocket implementation

These changes ensure the server runs properly on macOS while maintaining Linux compatibility.

## Hot Reload Feature

The server includes a hot reload feature that automatically refreshes connected browsers when HTML files are modified:
Expand Down
11 changes: 8 additions & 3 deletions include/file_watcher.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
#ifndef FILE_WATCHER_H
#define FILE_WATCHER_H

#include <sys/inotify.h>
#include <pthread.h>
#include <stdbool.h>
#include <time.h>

#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
#ifdef __linux__
#include <sys/inotify.h>
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
#else
// macOS and other non-Linux platforms
#define BUF_LEN 4096
#endif

typedef struct {
char* path;
Expand Down
2 changes: 2 additions & 0 deletions include/socket_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef __linux__
#include <asm-generic/socket.h>
#endif

#define PORT 8080
#define BUFFER_SIZE 1024
Expand Down
58 changes: 0 additions & 58 deletions setup.sh

This file was deleted.

46 changes: 29 additions & 17 deletions src/file_watcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,32 +209,35 @@ static void rescan_directory_if_needed(const char* directory, html_files_t* file

void* watch_files(void* args) {
watcher_args_t* watcher_args = (watcher_args_t*)args;
int fd, wd;
char buffer[BUF_LEN];

time_t last_notification_time = 0;
const int DEBOUNCE_TIME_MS = 300;

#ifdef __linux__
int fd, wd;
fd = inotify_init();
if (fd < 0) {
fprintf(stderr, "%s%s[ERROR] %sinotify_init failed: %s%s\n",
fprintf(stderr, "%s%s[ERROR] %sinotify_init failed: %s%s\n",
BOLD, COLOR_RED, COLOR_RESET, strerror(errno), COLOR_RESET);
free(watcher_args->directory);
free(watcher_args);
return NULL;
}

wd = inotify_add_watch(fd, watcher_args->directory,
IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB);
wd = inotify_add_watch(fd, watcher_args->directory,
IN_MODIFY | IN_CREATE | IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB);
if (wd < 0) {
fprintf(stderr, "%s%s[ERROR] %sinotify_add_watch failed: %s%s\n",
fprintf(stderr, "%s%s[ERROR] %sinotify_add_watch failed: %s%s\n",
BOLD, COLOR_RED, COLOR_RESET, strerror(errno), COLOR_RESET);
close(fd);
free(watcher_args->directory);
free(watcher_args);
return NULL;
}
#endif

printf("%s%s[FILE WATCHER] %sActive - watching directory: %s%s%s\n",
printf("%s%s[FILE WATCHER] %sActive - watching directory: %s%s%s\n",
BOLD, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, watcher_args->directory, COLOR_RESET);

while (1) {
Expand All @@ -243,9 +246,10 @@ void* watch_files(void* args) {
change_detected = true;
}

#ifdef __linux__
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
FD_SET(fd, &read_fds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100000;
Expand All @@ -262,11 +266,11 @@ void* watch_files(void* args) {
if (event->len > 0) {
char* dot = strrchr(event->name, '.');
if (dot && (strcmp(dot, ".html") == 0)) {
printf("%s%s[FILE WATCHER] %sEvent detected: %s%s%s (mask: 0x%08x)\n",
printf("%s%s[FILE WATCHER] %sEvent detected: %s%s%s (mask: 0x%08x)\n",
BOLD, COLOR_BLUE, COLOR_RESET, COLOR_CYAN, event->name, COLOR_RESET, event->mask);
change_detected = true;
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s",
snprintf(full_path, sizeof(full_path), "%s/%s",
watcher_args->directory, event->name);
add_html_file(html_files, full_path);
}
Expand All @@ -276,9 +280,13 @@ void* watch_files(void* args) {
}
}
} else if (ret < 0 && errno != EINTR) {
fprintf(stderr, "%s%s[FILE WATCHER] %sSelect error: %s%s\n",
fprintf(stderr, "%s%s[FILE WATCHER] %sSelect error: %s%s\n",
BOLD, COLOR_RED, COLOR_RESET, strerror(errno), COLOR_RESET);
}
#else
// On macOS and other platforms, we rely on polling with check_all_files_for_changes
// which was already called above
#endif

rescan_directory_if_needed(watcher_args->directory, html_files);
if (change_detected) {
Expand All @@ -291,29 +299,33 @@ void* watch_files(void* args) {
pthread_mutex_unlock(watcher_args->mutex);

if (!already_signaled) {
usleep(100000);
usleep(100000);
pthread_mutex_lock(watcher_args->mutex);
*(watcher_args->file_changed) = true;
pthread_mutex_unlock(watcher_args->mutex);
printf("%s%s[FILE WATCHER] %sSignaled main thread about file changes%s\n",
pthread_mutex_unlock(watcher_args->mutex);
printf("%s%s[FILE WATCHER] %sSignaled main thread about file changes%s\n",
BOLD, COLOR_BLUE, COLOR_YELLOW, COLOR_RESET);
last_notification_time = current_time;
} else {
printf("%s%s[FILE WATCHER] %sSkipping notification - one already pending%s\n",
printf("%s%s[FILE WATCHER] %sSkipping notification - one already pending%s\n",
BOLD, COLOR_BLUE, COLOR_CYAN, COLOR_RESET);
}
} else {
printf("%s%s[FILE WATCHER] %sDebouncing - %d ms since last notification%s\n",
printf("%s%s[FILE WATCHER] %sDebouncing - %d ms since last notification%s\n",
BOLD, COLOR_BLUE, COLOR_CYAN, (int)ms_since_last, COLOR_RESET);
}
}
usleep(50000);
usleep(50000);
}

#ifdef __linux__
inotify_rm_watch(fd, wd);
close(fd);
#endif

free(watcher_args->directory);
free(watcher_args);
free_html_files(html_files);

return NULL;
}
}
2 changes: 1 addition & 1 deletion src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ void* ws_monitor_thread(void* args) {
pthread_mutex_unlock(monitor_args->mutex);

if (should_notify) {
usleep(RELOAD_DELAY_MS * 1000);ts
usleep(RELOAD_DELAY_MS * 1000);
printf("%s%s[HOT RELOAD] %sNotifying %s%d%s client(s) to reload%s\n",
BOLD, COLOR_MAGENTA, COLOR_RESET,
COLOR_YELLOW, monitor_args->clients->count, COLOR_RESET, COLOR_RESET);
Expand Down
15 changes: 12 additions & 3 deletions src/socket_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@ int initialize_server(struct sockaddr_in* address) {
return -1;
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) != 0) {
perror("setsockopt failed");
close(server_fd);
// Set SO_REUSEADDR on all platforms
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
perror("setsockopt SO_REUSEADDR failed");
close(server_fd);
return -1;
}

#ifdef __linux__
// SO_REUSEPORT is only set on Linux
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) != 0) {
perror("setsockopt SO_REUSEPORT failed");
// Continue anyway, this is not critical
}
#endif

address->sin_family = AF_INET;
address->sin_addr.s_addr = INADDR_ANY;
Expand Down
17 changes: 13 additions & 4 deletions src/websocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@
#include <sys/select.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <endian.h>
#include "request_handler.h"
#ifdef __linux__
#include <endian.h>
#elif defined(__APPLE__)
#include <machine/endian.h>
#include <libkern/OSByteOrder.h>
// Define Linux-compatible macros
#define __BYTE_ORDER BYTE_ORDER
#define __LITTLE_ENDIAN LITTLE_ENDIAN
#define __BIG_ENDIAN BIG_ENDIAN
#endif
#include "request_handler.h"

// Function declarations
static char* base64_encode(const unsigned char* input, int length);
Expand Down Expand Up @@ -640,8 +649,8 @@ int process_ws_frame(int client_socket, ws_clients_t* clients) {
break;

case WS_BINARY:
printf("%s%s[WebSocket] %sReceived binary message (%s%zu%s bytes)\n",
BOLD, COLOR_BLUE, COLOR_RESET, COLOR_YELLOW, payload_len, COLOR_RESET);
printf("%s%s[WebSocket] %sReceived binary message (%s%llu%s bytes)\n",
BOLD, COLOR_BLUE, COLOR_RESET, COLOR_YELLOW, (unsigned long long)payload_len, COLOR_RESET);
break;

case WS_CLOSE:
Expand Down