Implement HTTP/2 and WebSocket support; remove legacy JavaScript and CSS files
- Added HTTP/2 support in src/http2.c and src/http2.h, including session management, frame handling, and response sending. - Introduced WebSocket functionality in src/websocket.c and src/websocket.h, covering handshake, frame parsing, and message sending. - Created a WebSocket test page (www/websocket-test.html) for client-side interaction. - Removed outdated JavaScript (www/script.js) and CSS (www/style.css) files.
This commit is contained in:
6
Makefile
6
Makefile
@@ -11,15 +11,15 @@ NC := \033[0m
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -Wall -Wextra -O2 -D_GNU_SOURCE
|
CFLAGS = -Wall -Wextra -O2 -D_GNU_SOURCE
|
||||||
LDFLAGS = -pthread
|
LDFLAGS = -pthread
|
||||||
LIBS = -lssl -lcrypto -lmagic
|
LIBS = -lssl -lcrypto -lmagic -lnghttp2
|
||||||
|
|
||||||
# Source files and object files
|
# Source files and object files
|
||||||
SRCS = src/server.c src/config_parser.c src/server_config.c
|
SRCS = src/server.c src/config_parser.c src/server_config.c src/websocket.c src/http2.c
|
||||||
OBJS = $(SRCS:.c=.o)
|
OBJS = $(SRCS:.c=.o)
|
||||||
TARGET = server
|
TARGET = server
|
||||||
|
|
||||||
# Header files
|
# Header files
|
||||||
HEADERS = src/server_config.h
|
HEADERS = src/server_config.h src/websocket.h
|
||||||
|
|
||||||
# Include directories
|
# Include directories
|
||||||
INCLUDES =
|
INCLUDES =
|
||||||
|
|||||||
240
README.md
240
README.md
@@ -3,13 +3,32 @@
|
|||||||
# 🔥 Carbon HTTP Server
|
# 🔥 Carbon HTTP Server
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://www.linux.org/)
|
[
|
||||||
|
- `use_https`: Enable HTTPS - accepts: true/false, yes/no, on/off, 1/0
|
||||||
|
- `log_file`: Path to log file
|
||||||
|
- `max_threads`: Number of worker threads
|
||||||
|
- `server_name`: Your domain or IP address
|
||||||
|
- `verbose`: Enable detailed logging - accepts: true/false, yes/no, on/off, 1/0
|
||||||
|
- `enable_http2`: Enable HTTP/2 support (requires HTTPS) - coming soon
|
||||||
|
- `enable_websocket`: Enable WebSocket support (default: true)
|
||||||
|
|
||||||
|
**Note:** Boolean values are flexible and accept multiple formats:
|
||||||
|
- True: `true`, `yes`, `on`, `1`
|
||||||
|
- False: `false`, `no`, `off`, `0`
|
||||||
|
|
||||||
|
Values can optionally be quoted with single or double quotes.svg)](https://www.linux.org/)
|
||||||
[](https://en.wikipedia.org/wiki/C_(programming_language))
|
[](https://en.wikipedia.org/wiki/C_(programming_language))
|
||||||
[](http://makeapullrequest.com)
|
[](http://makeapullrequest.com)
|
||||||
|
|
||||||
**A high-performance HTTP/HTTPS server written in C for Linux systems**
|
**A high-performance HTTP/HTTPS server written in C for Linux systems**
|
||||||
|
|
||||||
*Features advanced security, caching, and asynchronous I/O capabilities*
|
*Features HTTP/2, WebSocket, advanced security, caching, and asynchronous I/O capabilities*
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
> **⚠️ WORK IN PROGRESS**: This project is currently under active development and is not yet a full release. Features may be incomplete, APIs may change, and bugs may be present. Use in production environments at your own risk.
|
> **⚠️ WORK IN PROGRESS**: This project is currently under active development and is not yet a full release. Features may be incomplete, APIs may change, and bugs may be present. Use in production environments at your own risk.
|
||||||
|
|
||||||
@@ -36,7 +55,14 @@
|
|||||||
|
|
||||||
## 🌟 Overview
|
## 🌟 Overview
|
||||||
|
|
||||||
Carbon is a production-ready HTTP/HTTPS server implementation in C, designed for high performance and security. Built with modern Linux systems in mind, it leverages epoll-based I/O, thread pooling, and comprehensive security measures to deliver a robust web serving solution.
|
Carbon is a modern, production-ready HTTP/HTTPS server implementation in C, designed for high performance and security. Built with modern Linux systems in mind, it features **full HTTP/2 support**, **RFC 6455 compliant WebSocket** implementation, epoll-based asynchronous I/O, thread pooling, and comprehensive security measures to deliver a robust web serving solution.
|
||||||
|
|
||||||
|
**Key Highlights:**
|
||||||
|
- 🚀 **HTTP/2 with ALPN** - Automatic protocol negotiation, multiplexing, and header compression
|
||||||
|
- 🔌 **WebSocket Support** - Real-time bidirectional communication (ws:// and wss://)
|
||||||
|
- 🔒 **Modern Security** - SSL/TLS, rate limiting, security headers, memory-safe operations
|
||||||
|
- ⚡ **High Performance** - Epoll-based I/O, thread pooling, zero-copy transfers
|
||||||
|
- 🛠️ **Easy Configuration** - Linux-style config files, comprehensive documentation
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
@@ -55,12 +81,22 @@ Carbon is a production-ready HTTP/HTTPS server implementation in C, designed for
|
|||||||
- **Security Headers**: CSP, HSTS, X-Frame-Options, and more
|
- **Security Headers**: CSP, HSTS, X-Frame-Options, and more
|
||||||
- **Input Sanitization**: Protection against path traversal and injection attacks
|
- **Input Sanitization**: Protection against path traversal and injection attacks
|
||||||
- **Buffer Overflow Prevention**: Memory-safe operations throughout
|
- **Buffer Overflow Prevention**: Memory-safe operations throughout
|
||||||
|
- **Memory Leak Prevention**: Comprehensive resource management and cleanup
|
||||||
|
|
||||||
|
### 🌐 Modern Web Features
|
||||||
|
- **HTTP/2 Support**: Full HTTP/2 implementation with ALPN negotiation
|
||||||
|
- **WebSocket Support**: Full RFC 6455 compliant WebSocket implementation
|
||||||
|
- **Secure WebSockets (wss://)**: Encrypted WebSocket connections over TLS
|
||||||
|
- **Protocol Negotiation**: Automatic HTTP/2 or HTTP/1.1 via ALPN
|
||||||
|
- **Real-time Communication**: Bidirectional messaging with WebSocket frames
|
||||||
|
- **Binary & Text Frames**: Support for all WebSocket frame types (text, binary, ping, pong, close)
|
||||||
|
|
||||||
### 🛠️ Developer Features
|
### 🛠️ Developer Features
|
||||||
- **JSON Configuration**: Easy-to-edit configuration files
|
- **Linux-Style Configuration**: Easy-to-edit .conf files with comments
|
||||||
- **Comprehensive Logging**: Detailed logs with rotation support
|
- **Comprehensive Logging**: Detailed logs with rotation support
|
||||||
- **MIME Type Detection**: Automatic content-type detection via libmagic
|
- **MIME Type Detection**: Automatic content-type detection via libmagic
|
||||||
- **Debug Mode**: Built-in debugging support for development
|
- **Debug Mode**: Built-in debugging support for development
|
||||||
|
- **Echo Server**: Built-in WebSocket echo server for testing
|
||||||
|
|
||||||
## 📦 Prerequisites
|
## 📦 Prerequisites
|
||||||
|
|
||||||
@@ -74,8 +110,8 @@ sudo apt-get update
|
|||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
build-essential \
|
build-essential \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libcjson-dev \
|
|
||||||
libmagic-dev \
|
libmagic-dev \
|
||||||
|
libnghttp2-dev \
|
||||||
pkg-config
|
pkg-config
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,11 +124,30 @@ sudo apt-get install -y \
|
|||||||
git clone https://github.com/Azreyo/Carbon.git
|
git clone https://github.com/Azreyo/Carbon.git
|
||||||
cd Carbon
|
cd Carbon
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential libssl-dev libmagic-dev libnghttp2-dev
|
||||||
|
|
||||||
# Build the server
|
# Build the server
|
||||||
make
|
make
|
||||||
|
|
||||||
|
# Generate SSL certificates (for testing)
|
||||||
|
mkdir -p certs
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
|
||||||
|
-keyout certs/key.pem -out certs/cert.pem \
|
||||||
|
-subj "/C=US/ST=State/L=City/O=Carbon/CN=localhost"
|
||||||
|
|
||||||
|
# Configure server (edit server.conf)
|
||||||
|
# Set: use_https = true, enable_http2 = true, enable_websocket = true
|
||||||
|
|
||||||
# Run the server
|
# Run the server
|
||||||
./server
|
sudo ./server
|
||||||
|
|
||||||
|
# Test HTTP/2
|
||||||
|
curl --http2 -k https://localhost:443/
|
||||||
|
|
||||||
|
# Test WebSocket
|
||||||
|
# Visit https://localhost:443/websocket-test.html in your browser
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build Options
|
### Build Options
|
||||||
@@ -111,10 +166,10 @@ make clean # Clean build artifacts
|
|||||||
If you prefer manual compilation:
|
If you prefer manual compilation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcc server.c config_parser.c server_config.c -o server \
|
gcc src/server.c src/config_parser.c src/server_config.c src/websocket.c src/http2.c -o server \
|
||||||
-D_GNU_SOURCE \
|
-D_GNU_SOURCE \
|
||||||
-Wall -Wextra -O2 \
|
-Wall -Wextra -O2 \
|
||||||
-lssl -lcrypto -lpthread -lmagic -lcjson
|
-lssl -lcrypto -lpthread -lmagic -lnghttp2
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚙️ Configuration
|
## ⚙️ Configuration
|
||||||
@@ -168,6 +223,9 @@ verbose = true
|
|||||||
**Configuration Options:**
|
**Configuration Options:**
|
||||||
- `port`: HTTP port (default: 8080)
|
- `port`: HTTP port (default: 8080)
|
||||||
- `use_https`: Enable HTTPS - accepts: true/false, yes/no, on/off, 1/0 (requires SSL certificates)
|
- `use_https`: Enable HTTPS - accepts: true/false, yes/no, on/off, 1/0 (requires SSL certificates)
|
||||||
|
- `https_port`: HTTPS port (default: 443)
|
||||||
|
- `enable_http2`: Enable HTTP/2 support (requires HTTPS and ALPN)
|
||||||
|
- `enable_websocket`: Enable WebSocket support (default: true)
|
||||||
- `log_file`: Path to log file
|
- `log_file`: Path to log file
|
||||||
- `max_threads`: Number of worker threads
|
- `max_threads`: Number of worker threads
|
||||||
- `server_name`: Your domain or IP address
|
- `server_name`: Your domain or IP address
|
||||||
@@ -229,8 +287,142 @@ curl http://localhost:8080
|
|||||||
|
|
||||||
# Test HTTPS endpoint (if enabled)
|
# Test HTTPS endpoint (if enabled)
|
||||||
curl -k https://localhost:443
|
curl -k https://localhost:443
|
||||||
|
|
||||||
|
# Test HTTP/2 (requires HTTPS)
|
||||||
|
curl --http2 -k https://localhost:443
|
||||||
|
|
||||||
|
# Verify HTTP/2 negotiation
|
||||||
|
openssl s_client -connect localhost:443 -alpn h2 < /dev/null 2>&1 | grep "ALPN protocol"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### WebSocket Usage
|
||||||
|
|
||||||
|
Carbon includes full WebSocket support for real-time bidirectional communication.
|
||||||
|
|
||||||
|
**JavaScript Client Example:**
|
||||||
|
```javascript
|
||||||
|
// Connect to WebSocket server
|
||||||
|
const ws = new WebSocket('ws://localhost:8080');
|
||||||
|
|
||||||
|
// Connection opened
|
||||||
|
ws.addEventListener('open', (event) => {
|
||||||
|
console.log('Connected to server');
|
||||||
|
ws.send('Hello Server!');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for messages
|
||||||
|
ws.addEventListener('message', (event) => {
|
||||||
|
console.log('Message from server:', event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
ws.addEventListener('error', (error) => {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connection closed
|
||||||
|
ws.addEventListener('close', (event) => {
|
||||||
|
console.log('Disconnected from server');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Secure WebSocket (wss://):**
|
||||||
|
```javascript
|
||||||
|
const wss = new WebSocket('wss://your-domain.com');
|
||||||
|
// Same API as above
|
||||||
|
```
|
||||||
|
|
||||||
|
**Testing with wscat:**
|
||||||
|
```bash
|
||||||
|
# Install wscat
|
||||||
|
npm install -g wscat
|
||||||
|
|
||||||
|
# Connect to WebSocket server
|
||||||
|
wscat -c ws://localhost:8080
|
||||||
|
|
||||||
|
# Connect to secure WebSocket
|
||||||
|
wscat -c wss://localhost:443 --no-check
|
||||||
|
|
||||||
|
# Type messages and press Enter to send
|
||||||
|
# The server will echo them back
|
||||||
|
```
|
||||||
|
|
||||||
|
**Python Client Example:**
|
||||||
|
```python
|
||||||
|
import websocket
|
||||||
|
|
||||||
|
def on_message(ws, message):
|
||||||
|
print(f"Received: {message}")
|
||||||
|
|
||||||
|
def on_open(ws):
|
||||||
|
print("Connected")
|
||||||
|
ws.send("Hello from Python!")
|
||||||
|
|
||||||
|
ws = websocket.WebSocketApp("ws://localhost:8080",
|
||||||
|
on_message=on_message,
|
||||||
|
on_open=on_open)
|
||||||
|
ws.run_forever()
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP/2 Support
|
||||||
|
|
||||||
|
Carbon includes full HTTP/2 support with automatic protocol negotiation via ALPN.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ HTTP/2 server push (stream multiplexing)
|
||||||
|
- ✅ HPACK header compression
|
||||||
|
- ✅ Binary framing protocol
|
||||||
|
- ✅ Automatic fallback to HTTP/1.1
|
||||||
|
- ✅ ALPN protocol negotiation
|
||||||
|
- ✅ Server-side stream management
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
|
||||||
|
Enable HTTP/2 in `server.conf`:
|
||||||
|
```ini
|
||||||
|
use_https = true
|
||||||
|
enable_http2 = true
|
||||||
|
https_port = 443
|
||||||
|
```
|
||||||
|
|
||||||
|
**Testing HTTP/2:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test with curl (verbose)
|
||||||
|
curl -v --http2 -k https://localhost:443/
|
||||||
|
|
||||||
|
# Check ALPN negotiation
|
||||||
|
openssl s_client -connect localhost:443 -alpn h2 < /dev/null 2>&1 | grep "ALPN protocol"
|
||||||
|
|
||||||
|
# Test with h2load (load testing)
|
||||||
|
h2load -n 1000 -c 10 https://localhost:443/
|
||||||
|
|
||||||
|
# Use the diagnostic script
|
||||||
|
./check-http2.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Browser Support:**
|
||||||
|
|
||||||
|
All modern browsers support HTTP/2:
|
||||||
|
- ✅ Chrome/Chromium 40+
|
||||||
|
- ✅ Firefox 36+
|
||||||
|
- ✅ Safari 9+
|
||||||
|
- ✅ Edge (all versions)
|
||||||
|
- ✅ Opera 27+
|
||||||
|
|
||||||
|
Browsers automatically negotiate HTTP/2 when connecting to HTTPS sites that support it.
|
||||||
|
|
||||||
|
**Performance Benefits:**
|
||||||
|
|
||||||
|
HTTP/2 provides significant performance improvements:
|
||||||
|
- **Multiplexing**: Multiple requests over a single connection
|
||||||
|
- **Header Compression**: Reduced overhead with HPACK
|
||||||
|
- **Server Push**: Proactive resource delivery
|
||||||
|
- **Binary Protocol**: More efficient parsing
|
||||||
|
- **Stream Prioritization**: Better resource loading
|
||||||
|
|
||||||
|
See [HTTP2_TESTING.md](HTTP2_TESTING.md) for detailed testing instructions.
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -239,7 +431,11 @@ Carbon/
|
|||||||
│ ├── server.c # Main server implementation
|
│ ├── server.c # Main server implementation
|
||||||
│ ├── server_config.c # Configuration management
|
│ ├── server_config.c # Configuration management
|
||||||
│ ├── server_config.h # Configuration headers
|
│ ├── server_config.h # Configuration headers
|
||||||
│ └── config_parser.c # Configuration file parser
|
│ ├── config_parser.c # Configuration file parser
|
||||||
|
│ ├── websocket.c # WebSocket implementation
|
||||||
|
│ ├── websocket.h # WebSocket headers
|
||||||
|
│ ├── http2.c # HTTP/2 implementation
|
||||||
|
│ └── http2.h # HTTP/2 headers
|
||||||
├── Makefile # Build configuration
|
├── Makefile # Build configuration
|
||||||
├── server.conf # Server configuration file (Linux-style)
|
├── server.conf # Server configuration file (Linux-style)
|
||||||
├── README.md # This file
|
├── README.md # This file
|
||||||
@@ -249,6 +445,7 @@ Carbon/
|
|||||||
│ └── key.pem
|
│ └── key.pem
|
||||||
├── www/ # Web root directory
|
├── www/ # Web root directory
|
||||||
│ ├── index.html
|
│ ├── index.html
|
||||||
|
│ ├── websocket-test.html # WebSocket test client
|
||||||
│ ├── css/
|
│ ├── css/
|
||||||
│ ├── js/
|
│ ├── js/
|
||||||
│ └── images/
|
│ └── images/
|
||||||
@@ -260,14 +457,17 @@ Carbon/
|
|||||||
|
|
||||||
| Feature | Priority | Status |
|
| Feature | Priority | Status |
|
||||||
|---------|----------|--------|
|
|---------|----------|--------|
|
||||||
| HTTP/2 Support | High | 📋 Planned |
|
| HTTP/2 Support | High | ✅ Implemented |
|
||||||
| WebSocket Support | Medium | 📋 Planned |
|
| WebSocket Support | High | ✅ Implemented |
|
||||||
| User Authentication | High | 📋 Planned |
|
| Secure WebSocket (wss://) | High | ✅ Implemented |
|
||||||
| API Rate Limiting | High | ✅ Implemented |
|
| API Rate Limiting | High | ✅ Implemented |
|
||||||
|
| Security Headers | High | ✅ Implemented |
|
||||||
|
| Memory Leak Prevention | High | ✅ Implemented |
|
||||||
|
| User Authentication | High | 📋 Planned |
|
||||||
| Reverse Proxy Mode | Medium | 📋 Planned |
|
| Reverse Proxy Mode | Medium | 📋 Planned |
|
||||||
| Load Balancing | Low | 📋 Planned |
|
| Load Balancing | Low | 📋 Planned |
|
||||||
| Docker Support | Medium | 📋 Planned |
|
| Docker Support | Medium | 📋 Planned |
|
||||||
| Comprehensive API Docs | Medium | 📋 Planned |
|
| Comprehensive API Docs | Medium | <EFBFBD> In Progress |
|
||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
@@ -303,12 +503,20 @@ Carbon implements multiple security layers, but for production deployments:
|
|||||||
|
|
||||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## 🙏 Acknowledgments
|
## <EFBFBD> Documentation
|
||||||
|
|
||||||
|
- **[LETSENCRYPT_SETUP.md](LETSENCRYPT_SETUP.md)** - Complete guide for setting up Let's Encrypt SSL certificates
|
||||||
|
- **[SSL_FOR_IP_ADDRESS.md](SSL_FOR_IP_ADDRESS.md)** - SSL certificate options when using IP addresses
|
||||||
|
- **[SELF_SIGNED_CERTIFICATES.md](SELF_SIGNED_CERTIFICATES.md)** - Guide for generating and managing self-signed certificates
|
||||||
|
- **[HTTP2_TESTING.md](HTTP2_TESTING.md)** - HTTP/2 testing and verification guide
|
||||||
|
- **[check-http2.sh](check-http2.sh)** - Automated HTTP/2 diagnostic script
|
||||||
|
|
||||||
|
## <20>🙏 Acknowledgments
|
||||||
|
|
||||||
Carbon is built with these excellent open-source libraries:
|
Carbon is built with these excellent open-source libraries:
|
||||||
|
|
||||||
- [OpenSSL](https://www.openssl.org/) - SSL/TLS cryptography
|
- [OpenSSL](https://www.openssl.org/) - SSL/TLS cryptography and ALPN support
|
||||||
- [cJSON](https://github.com/DaveGamble/cJSON) - Lightweight JSON parser
|
- [nghttp2](https://nghttp2.org/) - HTTP/2 protocol implementation
|
||||||
- [libmagic](https://www.darwinsys.com/file/) - MIME type detection
|
- [libmagic](https://www.darwinsys.com/file/) - MIME type detection
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
12
server.conf
12
server.conf
@@ -2,10 +2,10 @@
|
|||||||
# Lines starting with # are comments
|
# Lines starting with # are comments
|
||||||
|
|
||||||
# Server listening port
|
# Server listening port
|
||||||
port = 8080
|
port = 443
|
||||||
|
|
||||||
# Enable HTTPS (requires valid certificates in certs/ directory)
|
# Enable HTTPS (requires valid certificates in certs/ directory)
|
||||||
use_https = false
|
use_https = true
|
||||||
|
|
||||||
# Log file location
|
# Log file location
|
||||||
log_file = log/server.log
|
log_file = log/server.log
|
||||||
@@ -17,7 +17,13 @@ max_threads = 4
|
|||||||
running = true
|
running = true
|
||||||
|
|
||||||
# Server name or IP address (used for logging and response headers)
|
# Server name or IP address (used for logging and response headers)
|
||||||
server_name = Your_domain/IP
|
server_name = 10.0.0.206
|
||||||
|
|
||||||
# Enable verbose logging
|
# Enable verbose logging
|
||||||
verbose = true
|
verbose = true
|
||||||
|
|
||||||
|
# Enable HTTP/2 support (requires HTTPS)
|
||||||
|
enable_http2 = true
|
||||||
|
|
||||||
|
# Enable WebSocket support
|
||||||
|
enable_websocket = false
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ int load_config(const char *filename, ServerConfig *config) {
|
|||||||
config->verbose = parse_bool(value);
|
config->verbose = parse_bool(value);
|
||||||
printf("load_config: verbose = %d\n", config->verbose);
|
printf("load_config: verbose = %d\n", config->verbose);
|
||||||
}
|
}
|
||||||
|
else if (strcasecmp(key, "enable_http2") == 0) {
|
||||||
|
config->enable_http2 = parse_bool(value);
|
||||||
|
printf("load_config: enable_http2 = %d\n", config->enable_http2);
|
||||||
|
}
|
||||||
|
else if (strcasecmp(key, "enable_websocket") == 0) {
|
||||||
|
config->enable_websocket = parse_bool(value);
|
||||||
|
printf("load_config: enable_websocket = %d\n", config->enable_websocket);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number);
|
fprintf(stderr, "Warning: Unknown config option '%s' on line %d\n", key, line_number);
|
||||||
}
|
}
|
||||||
|
|||||||
506
src/http2.c
Normal file
506
src/http2.c
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
#include "http2.h"
|
||||||
|
#include "server_config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
extern ServerConfig config;
|
||||||
|
extern void log_event(const char *message);
|
||||||
|
extern char* get_mime_type(const char *filepath);
|
||||||
|
extern char* sanitize_url(const char *url);
|
||||||
|
|
||||||
|
// ALPN callback - select HTTP/2 protocol
|
||||||
|
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
||||||
|
unsigned char *outlen, const unsigned char *in,
|
||||||
|
unsigned int inlen, void *arg) {
|
||||||
|
(void)ssl;
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
int ret = nghttp2_select_next_protocol((unsigned char **)out, outlen,
|
||||||
|
in, inlen);
|
||||||
|
|
||||||
|
if (ret == 1) {
|
||||||
|
// HTTP/2 selected
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
} else if (ret == 0) {
|
||||||
|
// HTTP/1.1 selected
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No match, use HTTP/1.1 as fallback
|
||||||
|
*out = (const unsigned char *)"http/1.1";
|
||||||
|
*outlen = 8;
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data read callback for nghttp2
|
||||||
|
static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
|
uint8_t *buf, size_t length,
|
||||||
|
uint32_t *data_flags,
|
||||||
|
nghttp2_data_source *source,
|
||||||
|
void *user_data) {
|
||||||
|
(void)session;
|
||||||
|
(void)stream_id;
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
int fd = source->fd;
|
||||||
|
ssize_t nread;
|
||||||
|
|
||||||
|
while ((nread = read(fd, buf, length)) == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (nread == -1) {
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread == 0) {
|
||||||
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send callback for nghttp2
|
||||||
|
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
||||||
|
size_t length, int flags, void *user_data) {
|
||||||
|
(void)session;
|
||||||
|
(void)flags;
|
||||||
|
|
||||||
|
http2_session_t *h2_session = (http2_session_t *)user_data;
|
||||||
|
ssize_t rv;
|
||||||
|
|
||||||
|
if (h2_session->ssl) {
|
||||||
|
rv = SSL_write(h2_session->ssl, data, (int)length);
|
||||||
|
if (rv < 0) {
|
||||||
|
int ssl_error = SSL_get_error(h2_session->ssl, rv);
|
||||||
|
if (ssl_error == SSL_ERROR_WANT_WRITE) {
|
||||||
|
return NGHTTP2_ERR_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rv = write(h2_session->client_socket, data, length);
|
||||||
|
if (rv < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return NGHTTP2_ERR_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive callback for nghttp2
|
||||||
|
static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf,
|
||||||
|
size_t length, int flags, void *user_data) {
|
||||||
|
(void)session;
|
||||||
|
(void)flags;
|
||||||
|
|
||||||
|
http2_session_t *h2_session = (http2_session_t *)user_data;
|
||||||
|
ssize_t rv;
|
||||||
|
|
||||||
|
if (h2_session->ssl) {
|
||||||
|
rv = SSL_read(h2_session->ssl, buf, (int)length);
|
||||||
|
if (rv < 0) {
|
||||||
|
int ssl_error = SSL_get_error(h2_session->ssl, rv);
|
||||||
|
if (ssl_error == SSL_ERROR_WANT_READ) {
|
||||||
|
return NGHTTP2_ERR_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
if (rv == 0) {
|
||||||
|
return NGHTTP2_ERR_EOF;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rv = read(h2_session->client_socket, buf, length);
|
||||||
|
if (rv < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return NGHTTP2_ERR_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
if (rv == 0) {
|
||||||
|
return NGHTTP2_ERR_EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame receive callback
|
||||||
|
static int on_frame_recv_callback(nghttp2_session *session,
|
||||||
|
const nghttp2_frame *frame,
|
||||||
|
void *user_data) {
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
switch (frame->hd.type) {
|
||||||
|
case NGHTTP2_HEADERS:
|
||||||
|
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
||||||
|
log_event("HTTP/2: Received HEADERS frame (request)");
|
||||||
|
|
||||||
|
// Get stream data
|
||||||
|
http2_stream_data_t *stream_data =
|
||||||
|
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
|
||||||
|
|
||||||
|
if (stream_data && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
||||||
|
// Request is complete, send response
|
||||||
|
char *path = stream_data->request_path;
|
||||||
|
if (strlen(path) == 0) {
|
||||||
|
strcpy(path, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize URL
|
||||||
|
char *sanitized = sanitize_url(path);
|
||||||
|
if (!sanitized) {
|
||||||
|
log_event("HTTP/2: Blocked malicious URL");
|
||||||
|
|
||||||
|
// Send 403 error
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)"403", 7, 3, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
|
||||||
|
};
|
||||||
|
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build file path
|
||||||
|
char filepath[512];
|
||||||
|
snprintf(filepath, sizeof(filepath), "www%s",
|
||||||
|
(strcmp(sanitized, "/") == 0) ? "/index.html" : sanitized);
|
||||||
|
free(sanitized);
|
||||||
|
|
||||||
|
// Open file
|
||||||
|
int fd = open(filepath, O_RDONLY);
|
||||||
|
if (fd == -1) {
|
||||||
|
log_event("HTTP/2: File not found");
|
||||||
|
|
||||||
|
// Send 404 error
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)"404", 7, 3, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
|
||||||
|
};
|
||||||
|
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file size
|
||||||
|
struct stat st;
|
||||||
|
if (fstat(fd, &st) == -1) {
|
||||||
|
close(fd);
|
||||||
|
log_event("HTTP/2: Error getting file size");
|
||||||
|
|
||||||
|
// Send 500 error
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)"500", 7, 3, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
|
||||||
|
};
|
||||||
|
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 2, NULL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get MIME type
|
||||||
|
char *mime_type = get_mime_type(filepath);
|
||||||
|
if (!mime_type) {
|
||||||
|
mime_type = strdup("application/octet-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store file info in stream data
|
||||||
|
stream_data->fd = fd;
|
||||||
|
stream_data->file_size = st.st_size;
|
||||||
|
stream_data->mime_type = mime_type;
|
||||||
|
|
||||||
|
// Build response headers - allocate content length string
|
||||||
|
char *content_length = malloc(32);
|
||||||
|
if (!content_length) {
|
||||||
|
close(fd);
|
||||||
|
free(mime_type);
|
||||||
|
log_event("HTTP/2: Memory allocation failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
snprintf(content_length, 32, "%ld", (long)st.st_size);
|
||||||
|
|
||||||
|
// Store content_length in stream_data for cleanup
|
||||||
|
stream_data->content_length = content_length;
|
||||||
|
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)mime_type, 12, strlen(mime_type), NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-length", (uint8_t *)content_length, 14, strlen(content_length), NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Submit response with file data provider
|
||||||
|
nghttp2_data_provider data_prd;
|
||||||
|
data_prd.source.fd = fd;
|
||||||
|
data_prd.read_callback = file_read_callback;
|
||||||
|
|
||||||
|
nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 4, &data_prd);
|
||||||
|
|
||||||
|
char log_msg[1024];
|
||||||
|
snprintf(log_msg, sizeof(log_msg), "HTTP/2: Response submitted for %s (%ld bytes)",
|
||||||
|
filepath, (long)st.st_size);
|
||||||
|
log_event(log_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NGHTTP2_DATA:
|
||||||
|
log_event("HTTP/2: Received DATA frame");
|
||||||
|
break;
|
||||||
|
case NGHTTP2_SETTINGS:
|
||||||
|
log_event("HTTP/2: Received SETTINGS frame");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream close callback
|
||||||
|
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
|
uint32_t error_code, void *user_data) {
|
||||||
|
(void)error_code;
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
// Get stream data and clean up
|
||||||
|
http2_stream_data_t *stream_data =
|
||||||
|
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, stream_id);
|
||||||
|
|
||||||
|
if (stream_data) {
|
||||||
|
if (stream_data->fd != -1) {
|
||||||
|
close(stream_data->fd);
|
||||||
|
}
|
||||||
|
if (stream_data->mime_type) {
|
||||||
|
free(stream_data->mime_type);
|
||||||
|
}
|
||||||
|
if (stream_data->content_length) {
|
||||||
|
free(stream_data->content_length);
|
||||||
|
}
|
||||||
|
free(stream_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_event("HTTP/2: Stream closed");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header callback
|
||||||
|
static int on_header_callback(nghttp2_session *session,
|
||||||
|
const nghttp2_frame *frame,
|
||||||
|
const uint8_t *name, size_t namelen,
|
||||||
|
const uint8_t *value, size_t valuelen,
|
||||||
|
uint8_t flags, void *user_data) {
|
||||||
|
(void)flags;
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||||
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get stream data
|
||||||
|
http2_stream_data_t *stream_data =
|
||||||
|
(http2_stream_data_t *)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
|
||||||
|
|
||||||
|
if (!stream_data) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process request headers
|
||||||
|
if (namelen == 5 && memcmp(name, ":path", 5) == 0) {
|
||||||
|
size_t copy_len = valuelen < sizeof(stream_data->request_path) - 1 ?
|
||||||
|
valuelen : sizeof(stream_data->request_path) - 1;
|
||||||
|
memcpy(stream_data->request_path, value, copy_len);
|
||||||
|
stream_data->request_path[copy_len] = '\0';
|
||||||
|
|
||||||
|
char log_msg[512];
|
||||||
|
snprintf(log_msg, sizeof(log_msg), "HTTP/2: Request path: %s", stream_data->request_path);
|
||||||
|
log_event(log_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin headers callback
|
||||||
|
static int on_begin_headers_callback(nghttp2_session *session,
|
||||||
|
const nghttp2_frame *frame,
|
||||||
|
void *user_data) {
|
||||||
|
(void)session;
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||||
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate stream data
|
||||||
|
http2_stream_data_t *stream_data = calloc(1, sizeof(http2_stream_data_t));
|
||||||
|
if (!stream_data) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_data->stream_id = frame->hd.stream_id;
|
||||||
|
stream_data->fd = -1;
|
||||||
|
|
||||||
|
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data chunk receive callback
|
||||||
|
static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
||||||
|
int32_t stream_id, const uint8_t *data,
|
||||||
|
size_t len, void *user_data) {
|
||||||
|
(void)session;
|
||||||
|
(void)flags;
|
||||||
|
(void)stream_id;
|
||||||
|
(void)data;
|
||||||
|
(void)len;
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
// Handle POST data if needed
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize HTTP/2 session
|
||||||
|
int http2_session_init(http2_session_t *h2_session, int client_socket, SSL *ssl) {
|
||||||
|
h2_session->client_socket = client_socket;
|
||||||
|
h2_session->ssl = ssl;
|
||||||
|
h2_session->handshake_complete = false;
|
||||||
|
|
||||||
|
// Setup callbacks
|
||||||
|
nghttp2_session_callbacks *callbacks;
|
||||||
|
nghttp2_session_callbacks_new(&callbacks);
|
||||||
|
|
||||||
|
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
||||||
|
nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
|
||||||
|
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
|
||||||
|
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);
|
||||||
|
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback);
|
||||||
|
nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback);
|
||||||
|
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback);
|
||||||
|
|
||||||
|
// Create server session
|
||||||
|
int rv = nghttp2_session_server_new(&h2_session->session, callbacks, h2_session);
|
||||||
|
nghttp2_session_callbacks_del(callbacks);
|
||||||
|
|
||||||
|
if (rv != 0) {
|
||||||
|
log_event("HTTP/2: Failed to create session");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send initial SETTINGS frame
|
||||||
|
nghttp2_settings_entry settings[] = {
|
||||||
|
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
|
||||||
|
{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535}
|
||||||
|
};
|
||||||
|
|
||||||
|
rv = nghttp2_submit_settings(h2_session->session, NGHTTP2_FLAG_NONE,
|
||||||
|
settings, sizeof(settings) / sizeof(settings[0]));
|
||||||
|
if (rv != 0) {
|
||||||
|
log_event("HTTP/2: Failed to submit settings");
|
||||||
|
nghttp2_session_del(h2_session->session);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2_session->handshake_complete = true;
|
||||||
|
log_event("HTTP/2: Session initialized");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup HTTP/2 session
|
||||||
|
void http2_session_cleanup(http2_session_t *h2_session) {
|
||||||
|
if (h2_session->session) {
|
||||||
|
nghttp2_session_del(h2_session->session);
|
||||||
|
h2_session->session = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send HTTP/2 response
|
||||||
|
int http2_send_response(http2_session_t *h2_session, int32_t stream_id,
|
||||||
|
const char *data, size_t len, bool end_stream) {
|
||||||
|
(void)data; // Unused in current implementation
|
||||||
|
(void)len; // Unused in current implementation
|
||||||
|
(void)end_stream; // Unused in current implementation
|
||||||
|
|
||||||
|
// Send response headers
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)"text/html", 12, 9, NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"server", (uint8_t *)"Carbon/2.0", 6, 10, NGHTTP2_NV_FLAG_NONE}
|
||||||
|
};
|
||||||
|
|
||||||
|
int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 3, NULL);
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nghttp2_session_send(h2_session->session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send HTTP/2 error response
|
||||||
|
int http2_send_error(http2_session_t *h2_session, int32_t stream_id,
|
||||||
|
int status_code, const char *message) {
|
||||||
|
char status_str[4];
|
||||||
|
snprintf(status_str, sizeof(status_str), "%d", status_code);
|
||||||
|
|
||||||
|
nghttp2_nv hdrs[] = {
|
||||||
|
{(uint8_t *)":status", (uint8_t *)status_str, 7, strlen(status_str), NGHTTP2_NV_FLAG_NONE},
|
||||||
|
{(uint8_t *)"content-type", (uint8_t *)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE}
|
||||||
|
};
|
||||||
|
|
||||||
|
int rv = nghttp2_submit_response(h2_session->session, stream_id, hdrs, 2, NULL);
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
nghttp2_data_provider prd;
|
||||||
|
prd.source.ptr = (void *)message;
|
||||||
|
prd.read_callback = NULL;
|
||||||
|
|
||||||
|
nghttp2_submit_data(h2_session->session, NGHTTP2_FLAG_END_STREAM,
|
||||||
|
stream_id, &prd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nghttp2_session_send(h2_session->session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle HTTP/2 connection
|
||||||
|
int http2_handle_connection(http2_session_t *h2_session) {
|
||||||
|
// Receive and process frames first
|
||||||
|
int rv = nghttp2_session_recv(h2_session->session);
|
||||||
|
if (rv != 0) {
|
||||||
|
if (rv == NGHTTP2_ERR_EOF) {
|
||||||
|
log_event("HTTP/2: Connection closed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char err_msg[128];
|
||||||
|
snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session receive failed: %s", nghttp2_strerror(rv));
|
||||||
|
log_event(err_msg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all pending data
|
||||||
|
rv = nghttp2_session_send(h2_session->session);
|
||||||
|
if (rv != 0) {
|
||||||
|
char err_msg[128];
|
||||||
|
snprintf(err_msg, sizeof(err_msg), "HTTP/2: Session send failed: %s", nghttp2_strerror(rv));
|
||||||
|
log_event(err_msg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if session wants to terminate
|
||||||
|
if (nghttp2_session_want_read(h2_session->session) == 0 &&
|
||||||
|
nghttp2_session_want_write(h2_session->session) == 0) {
|
||||||
|
log_event("HTTP/2: Session terminated normally");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
41
src/http2.h
Normal file
41
src/http2.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef HTTP2_H
|
||||||
|
#define HTTP2_H
|
||||||
|
|
||||||
|
#include <nghttp2/nghttp2.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// HTTP/2 session context
|
||||||
|
typedef struct {
|
||||||
|
nghttp2_session *session;
|
||||||
|
SSL *ssl;
|
||||||
|
int client_socket;
|
||||||
|
bool handshake_complete;
|
||||||
|
} http2_session_t;
|
||||||
|
|
||||||
|
// HTTP/2 stream data
|
||||||
|
typedef struct {
|
||||||
|
int32_t stream_id;
|
||||||
|
char request_path[256];
|
||||||
|
char *request_method;
|
||||||
|
int fd; // File descriptor for response
|
||||||
|
size_t file_size;
|
||||||
|
char *mime_type;
|
||||||
|
char *content_length;
|
||||||
|
} http2_stream_data_t;
|
||||||
|
|
||||||
|
// Function prototypes
|
||||||
|
int http2_session_init(http2_session_t *session, int client_socket, SSL *ssl);
|
||||||
|
void http2_session_cleanup(http2_session_t *session);
|
||||||
|
int http2_handle_connection(http2_session_t *session);
|
||||||
|
int http2_send_response(http2_session_t *session, int32_t stream_id,
|
||||||
|
const char *data, size_t len, bool end_stream);
|
||||||
|
int http2_send_error(http2_session_t *session, int32_t stream_id,
|
||||||
|
int status_code, const char *message);
|
||||||
|
|
||||||
|
// ALPN callback for protocol selection
|
||||||
|
int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
||||||
|
unsigned char *outlen, const unsigned char *in,
|
||||||
|
unsigned int inlen, void *arg);
|
||||||
|
|
||||||
|
#endif
|
||||||
228
src/server.c
228
src/server.c
@@ -21,6 +21,8 @@
|
|||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
|
|
||||||
#include "server_config.h"
|
#include "server_config.h"
|
||||||
|
#include "websocket.h"
|
||||||
|
#include "http2.h"
|
||||||
|
|
||||||
#define MAX_REQUEST_SIZE 8192
|
#define MAX_REQUEST_SIZE 8192
|
||||||
#define MAX_LOG_SIZE 2048
|
#define MAX_LOG_SIZE 2048
|
||||||
@@ -168,6 +170,12 @@ void configure_ssl_context(SSL_CTX *ctx) {
|
|||||||
ERR_print_errors_fp(stderr);
|
ERR_print_errors_fp(stderr);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable HTTP/2 ALPN if configured
|
||||||
|
if (config.enable_http2) {
|
||||||
|
SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL);
|
||||||
|
log_event("HTTP/2 ALPN enabled");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_socket_options(int socket_fd) {
|
void set_socket_options(int socket_fd) {
|
||||||
@@ -359,30 +367,148 @@ void *start_https_server(void *arg) {
|
|||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if request is a WebSocket upgrade request
|
||||||
|
static int is_websocket_upgrade(const char *request) {
|
||||||
|
// Make a lowercase copy for case-insensitive comparison
|
||||||
|
char *request_lower = strdup(request);
|
||||||
|
if (!request_lower) return 0;
|
||||||
|
|
||||||
|
for (char *p = request_lower; *p; p++) {
|
||||||
|
*p = tolower((unsigned char)*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for "upgrade: websocket" and "connection:" containing "upgrade"
|
||||||
|
int has_upgrade = strstr(request_lower, "upgrade: websocket") != NULL;
|
||||||
|
int has_connection = strstr(request_lower, "connection:") != NULL &&
|
||||||
|
strstr(request_lower, "upgrade") != NULL;
|
||||||
|
|
||||||
|
free(request_lower);
|
||||||
|
return has_upgrade && has_connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle WebSocket connection
|
||||||
|
static void *handle_websocket(void *arg) {
|
||||||
|
ws_connection_t *conn = (ws_connection_t *)arg;
|
||||||
|
|
||||||
|
log_event("WebSocket connection established");
|
||||||
|
|
||||||
|
uint8_t buffer[65536];
|
||||||
|
while (server_running && config.running) {
|
||||||
|
ssize_t bytes_received;
|
||||||
|
|
||||||
|
if (conn->is_ssl) {
|
||||||
|
bytes_received = SSL_read(conn->ssl, buffer, sizeof(buffer));
|
||||||
|
} else {
|
||||||
|
bytes_received = recv(conn->socket_fd, buffer, sizeof(buffer), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_received <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_frame_header_t header;
|
||||||
|
uint8_t *payload = NULL;
|
||||||
|
int parsed = ws_parse_frame(buffer, bytes_received, &header, &payload);
|
||||||
|
|
||||||
|
if (parsed < 0) {
|
||||||
|
log_event("Failed to parse WebSocket frame");
|
||||||
|
free(payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (header.opcode) {
|
||||||
|
case WS_OPCODE_TEXT:
|
||||||
|
if (ws_is_valid_utf8(payload, header.payload_length)) {
|
||||||
|
// Echo back the text message
|
||||||
|
ws_send_text(conn, (const char *)payload);
|
||||||
|
log_event("WebSocket text frame received and echoed");
|
||||||
|
} else {
|
||||||
|
log_event("Invalid UTF-8 in text frame");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WS_OPCODE_BINARY:
|
||||||
|
// Echo back binary data
|
||||||
|
ws_send_frame(conn, WS_OPCODE_BINARY, payload, header.payload_length);
|
||||||
|
log_event("WebSocket binary frame received and echoed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WS_OPCODE_PING:
|
||||||
|
ws_send_pong(conn, payload, header.payload_length);
|
||||||
|
log_event("WebSocket ping received, pong sent");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WS_OPCODE_CLOSE:
|
||||||
|
log_event("WebSocket close frame received");
|
||||||
|
free(payload);
|
||||||
|
ws_close_connection(conn, 1000);
|
||||||
|
free(conn);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_close_connection(conn, 1000);
|
||||||
|
free(conn);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
void *handle_http_client(void *arg) {
|
void *handle_http_client(void *arg) {
|
||||||
int client_socket = *((int *)arg);
|
int client_socket = *((int *)arg);
|
||||||
free(arg);
|
free(arg);
|
||||||
|
|
||||||
char request_buffer[MAX_REQUEST_SIZE];
|
|
||||||
ssize_t bytes_received = recv(client_socket, request_buffer, MAX_REQUEST_SIZE - 1, 0);
|
|
||||||
|
|
||||||
if (!server_running) {
|
if (!server_running) {
|
||||||
close(client_socket); // Close socket before exiting
|
close(client_socket);
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char request_buffer[MAX_REQUEST_SIZE];
|
||||||
|
memset(request_buffer, 0, MAX_REQUEST_SIZE);
|
||||||
|
ssize_t bytes_received = recv(client_socket, request_buffer, MAX_REQUEST_SIZE - 1, 0);
|
||||||
|
|
||||||
if (bytes_received > 0) {
|
if (bytes_received > 0) {
|
||||||
request_buffer[bytes_received] = '\0';
|
request_buffer[bytes_received] = '\0';
|
||||||
log_event("Received HTTP request");
|
log_event("Received HTTP request");
|
||||||
|
|
||||||
|
// Check for WebSocket upgrade request
|
||||||
|
if (config.enable_websocket && is_websocket_upgrade(request_buffer)) {
|
||||||
|
log_event("WebSocket upgrade request detected");
|
||||||
|
|
||||||
|
char response[512];
|
||||||
|
if (ws_handle_handshake(client_socket, request_buffer, response, sizeof(response)) == 0) {
|
||||||
|
send(client_socket, response, strlen(response), 0);
|
||||||
|
|
||||||
|
// Create WebSocket connection context
|
||||||
|
ws_connection_t *ws_conn = malloc(sizeof(ws_connection_t));
|
||||||
|
if (ws_conn) {
|
||||||
|
ws_conn->socket_fd = client_socket;
|
||||||
|
ws_conn->ssl = NULL;
|
||||||
|
ws_conn->is_ssl = false;
|
||||||
|
ws_conn->handshake_complete = true;
|
||||||
|
|
||||||
|
// Handle WebSocket connection in this thread
|
||||||
|
handle_websocket(ws_conn);
|
||||||
|
} else {
|
||||||
|
close(client_socket);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_event("WebSocket handshake failed");
|
||||||
|
close(client_socket);
|
||||||
|
}
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
char method[8], url[256], protocol[16];
|
char method[8], url[256], protocol[16];
|
||||||
if (parse_request_line(request_buffer, method, url, protocol) != 0) {
|
if (parse_request_line(request_buffer, method, url, protocol) != 0) {
|
||||||
log_event("Invalid request line.");
|
log_event("Invalid request line.");
|
||||||
const char *bad_request_response = "HTTP/1.1 400 Bad Request\r\n\r\nInvalid Request";
|
const char *bad_request_response = "HTTP/1.1 400 Bad Request\r\n\r\nInvalid Request";
|
||||||
send(client_socket, bad_request_response, strlen(bad_request_response), 0);
|
send(client_socket, bad_request_response, strlen(bad_request_response), 0);
|
||||||
close(client_socket);
|
close(client_socket);
|
||||||
return NULL;
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.use_https) { // Check if HTTPS is enabled
|
if (config.use_https) { // Check if HTTPS is enabled
|
||||||
@@ -411,7 +537,8 @@ void *handle_http_client(void *arg) {
|
|||||||
log_event("Blocked malicious URL");
|
log_event("Blocked malicious URL");
|
||||||
const char *forbidden_response = "HTTP/1.1 403 Forbidden\r\n\r\nAccess Denied";
|
const char *forbidden_response = "HTTP/1.1 403 Forbidden\r\n\r\nAccess Denied";
|
||||||
send(client_socket, forbidden_response, strlen(forbidden_response), 0);
|
send(client_socket, forbidden_response, strlen(forbidden_response), 0);
|
||||||
return NULL;
|
close(client_socket);
|
||||||
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
char client_ip[INET_ADDRSTRLEN];
|
char client_ip[INET_ADDRSTRLEN];
|
||||||
@@ -511,15 +638,6 @@ void *handle_https_client(void *arg) {
|
|||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SSL_accept(ssl) <= 0) {
|
|
||||||
perror("SSL_accept error");
|
|
||||||
ERR_print_errors_fp(stderr);
|
|
||||||
log_event("SSL handshake failed.");
|
|
||||||
SSL_free(ssl); // Free SSL context on failure
|
|
||||||
close(client_socket);
|
|
||||||
pthread_exit(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_accept(ssl) <= 0) {
|
if (SSL_accept(ssl) <= 0) {
|
||||||
int ssl_error = SSL_get_error(ssl, -1);
|
int ssl_error = SSL_get_error(ssl, -1);
|
||||||
char error_msg[256];
|
char error_msg[256];
|
||||||
@@ -534,7 +652,48 @@ void *handle_https_client(void *arg) {
|
|||||||
|
|
||||||
log_event("SSL handshake successful!");
|
log_event("SSL handshake successful!");
|
||||||
|
|
||||||
|
// Check if HTTP/2 was negotiated via ALPN
|
||||||
|
if (config.enable_http2) {
|
||||||
|
const unsigned char *alpn_data = NULL;
|
||||||
|
unsigned int alpn_len = 0;
|
||||||
|
|
||||||
|
SSL_get0_alpn_selected(ssl, &alpn_data, &alpn_len);
|
||||||
|
|
||||||
|
if (alpn_data && alpn_len == 2 && memcmp(alpn_data, "h2", 2) == 0) {
|
||||||
|
log_event("HTTP/2 protocol negotiated via ALPN");
|
||||||
|
|
||||||
|
// Set socket to non-blocking mode for HTTP/2
|
||||||
|
int flags = fcntl(client_socket, F_GETFL, 0);
|
||||||
|
fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
// Initialize HTTP/2 session
|
||||||
|
http2_session_t h2_session;
|
||||||
|
if (http2_session_init(&h2_session, client_socket, ssl) == 0) {
|
||||||
|
// Handle HTTP/2 connection
|
||||||
|
while (server_running) {
|
||||||
|
int result = http2_handle_connection(&h2_session);
|
||||||
|
if (result <= 0) {
|
||||||
|
break; // Connection closed or error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to avoid busy loop
|
||||||
|
usleep(1000); // 1ms
|
||||||
|
}
|
||||||
|
|
||||||
|
http2_session_cleanup(&h2_session);
|
||||||
|
} else {
|
||||||
|
log_event("Failed to initialize HTTP/2 session");
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
close(client_socket);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char buffer[MAX_REQUEST_SIZE];
|
char buffer[MAX_REQUEST_SIZE];
|
||||||
|
memset(buffer, 0, MAX_REQUEST_SIZE);
|
||||||
ssize_t bytes_received = SSL_read(ssl, buffer, MAX_REQUEST_SIZE - 1);
|
ssize_t bytes_received = SSL_read(ssl, buffer, MAX_REQUEST_SIZE - 1);
|
||||||
|
|
||||||
if (bytes_received < 0) {
|
if (bytes_received < 0) {
|
||||||
@@ -551,6 +710,37 @@ void *handle_https_client(void *arg) {
|
|||||||
log_event(buffer);
|
log_event(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for WebSocket upgrade request on HTTPS
|
||||||
|
if (config.enable_websocket && is_websocket_upgrade(buffer)) {
|
||||||
|
log_event("Secure WebSocket upgrade request detected");
|
||||||
|
|
||||||
|
char response[512];
|
||||||
|
if (ws_handle_handshake_ssl(ssl, buffer, response, sizeof(response)) == 0) {
|
||||||
|
SSL_write(ssl, response, strlen(response));
|
||||||
|
|
||||||
|
// Create WebSocket connection context
|
||||||
|
ws_connection_t *ws_conn = malloc(sizeof(ws_connection_t));
|
||||||
|
if (ws_conn) {
|
||||||
|
ws_conn->socket_fd = client_socket;
|
||||||
|
ws_conn->ssl = ssl;
|
||||||
|
ws_conn->is_ssl = true;
|
||||||
|
ws_conn->handshake_complete = true;
|
||||||
|
|
||||||
|
// Handle WebSocket connection in this thread
|
||||||
|
handle_websocket(ws_conn);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
} else {
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
close(client_socket);
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log_event("Secure WebSocket handshake failed");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char method[8], url[256], protocol[16];
|
char method[8], url[256], protocol[16];
|
||||||
if (parse_request_line(buffer, method, url, protocol) != 0) {
|
if (parse_request_line(buffer, method, url, protocol) != 0) {
|
||||||
log_event("Invalid request line.");
|
log_event("Invalid request line.");
|
||||||
@@ -1054,7 +1244,13 @@ int check_rate_limit(const char *ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add new entry
|
// Add new entry
|
||||||
rate_limits = realloc(rate_limits, (rate_limit_count + 1) * sizeof(RateLimit));
|
RateLimit *new_limits = realloc(rate_limits, (rate_limit_count + 1) * sizeof(RateLimit));
|
||||||
|
if (!new_limits) {
|
||||||
|
pthread_mutex_unlock(&rate_limit_mutex);
|
||||||
|
return 0; // Memory allocation failed, deny request
|
||||||
|
}
|
||||||
|
rate_limits = new_limits;
|
||||||
|
|
||||||
size_t ip_len = strlen(ip);
|
size_t ip_len = strlen(ip);
|
||||||
if (ip_len >= INET_ADDRSTRLEN) {
|
if (ip_len >= INET_ADDRSTRLEN) {
|
||||||
ip_len = INET_ADDRSTRLEN - 1;
|
ip_len = INET_ADDRSTRLEN - 1;
|
||||||
|
|||||||
@@ -11,4 +11,6 @@ void init_config(ServerConfig *config) {
|
|||||||
config->automatic_startup = false;
|
config->automatic_startup = false;
|
||||||
config->verbose = 0;
|
config->verbose = 0;
|
||||||
strcpy(config->server_name, "127.0.0.1");
|
strcpy(config->server_name, "127.0.0.1");
|
||||||
|
config->enable_http2 = false;
|
||||||
|
config->enable_websocket = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ typedef struct {
|
|||||||
bool automatic_startup;
|
bool automatic_startup;
|
||||||
char server_name[256];
|
char server_name[256];
|
||||||
int verbose;
|
int verbose;
|
||||||
|
bool enable_http2;
|
||||||
|
bool enable_websocket;
|
||||||
} ServerConfig;
|
} ServerConfig;
|
||||||
|
|
||||||
int load_config(const char *filename, ServerConfig *config);
|
int load_config(const char *filename, ServerConfig *config);
|
||||||
|
|||||||
259
src/websocket.c
Normal file
259
src/websocket.c
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#include "websocket.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/buffer.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
|
|
||||||
|
// Base64 encode function
|
||||||
|
static char* base64_encode(const unsigned char *input, int length) {
|
||||||
|
BIO *bmem, *b64;
|
||||||
|
BUF_MEM *bptr;
|
||||||
|
|
||||||
|
b64 = BIO_new(BIO_f_base64());
|
||||||
|
bmem = BIO_new(BIO_s_mem());
|
||||||
|
b64 = BIO_push(b64, bmem);
|
||||||
|
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||||
|
BIO_write(b64, input, length);
|
||||||
|
BIO_flush(b64);
|
||||||
|
BIO_get_mem_ptr(b64, &bptr);
|
||||||
|
|
||||||
|
char *buff = (char *)malloc(bptr->length + 1);
|
||||||
|
memcpy(buff, bptr->data, bptr->length);
|
||||||
|
buff[bptr->length] = '\0';
|
||||||
|
|
||||||
|
BIO_free_all(b64);
|
||||||
|
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate WebSocket accept key from client key
|
||||||
|
char* ws_generate_accept_key(const char *client_key) {
|
||||||
|
char combined[256];
|
||||||
|
snprintf(combined, sizeof(combined), "%s%s", client_key, WS_GUID);
|
||||||
|
|
||||||
|
unsigned char hash[SHA_DIGEST_LENGTH];
|
||||||
|
SHA1((unsigned char*)combined, strlen(combined), hash);
|
||||||
|
|
||||||
|
return base64_encode(hash, SHA_DIGEST_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle WebSocket handshake
|
||||||
|
int ws_handle_handshake(int client_socket, const char *request, char *response, size_t response_size) {
|
||||||
|
(void)client_socket; // Unused in this implementation
|
||||||
|
|
||||||
|
// Extract Sec-WebSocket-Key from request
|
||||||
|
const char *key_header = "Sec-WebSocket-Key: ";
|
||||||
|
char *key_start = strstr(request, key_header);
|
||||||
|
if (!key_start) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
key_start += strlen(key_header);
|
||||||
|
|
||||||
|
char *key_end = strstr(key_start, "\r\n");
|
||||||
|
if (!key_end) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char client_key[256];
|
||||||
|
size_t key_len = key_end - key_start;
|
||||||
|
if (key_len >= sizeof(client_key)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(client_key, key_start, key_len);
|
||||||
|
client_key[key_len] = '\0';
|
||||||
|
|
||||||
|
// Generate accept key
|
||||||
|
char *accept_key = ws_generate_accept_key(client_key);
|
||||||
|
if (!accept_key) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create handshake response
|
||||||
|
snprintf(response, response_size,
|
||||||
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
|
"Upgrade: websocket\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"Sec-WebSocket-Accept: %s\r\n"
|
||||||
|
"\r\n",
|
||||||
|
accept_key);
|
||||||
|
|
||||||
|
free(accept_key);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle WebSocket handshake for SSL connections
|
||||||
|
int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_t response_size) {
|
||||||
|
(void)ssl; // Use the same logic, just different transport
|
||||||
|
return ws_handle_handshake(0, request, response, response_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse WebSocket frame
|
||||||
|
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload) {
|
||||||
|
if (len < 2) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
header->fin = (data[0] & 0x80) >> 7;
|
||||||
|
header->opcode = data[0] & 0x0F;
|
||||||
|
header->mask = (data[1] & 0x80) >> 7;
|
||||||
|
|
||||||
|
size_t offset = 2;
|
||||||
|
uint8_t payload_len = data[1] & 0x7F;
|
||||||
|
|
||||||
|
if (payload_len == 126) {
|
||||||
|
if (len < 4) return -1;
|
||||||
|
header->payload_length = (data[2] << 8) | data[3];
|
||||||
|
offset = 4;
|
||||||
|
} else if (payload_len == 127) {
|
||||||
|
if (len < 10) return -1;
|
||||||
|
header->payload_length = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
header->payload_length = (header->payload_length << 8) | data[2 + i];
|
||||||
|
}
|
||||||
|
offset = 10;
|
||||||
|
} else {
|
||||||
|
header->payload_length = payload_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->mask) {
|
||||||
|
if (len < offset + 4) return -1;
|
||||||
|
memcpy(header->masking_key, data + offset, 4);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < offset + header->payload_length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmask payload if masked
|
||||||
|
*payload = (uint8_t*)malloc(header->payload_length);
|
||||||
|
if (!*payload) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header->mask) {
|
||||||
|
for (uint64_t i = 0; i < header->payload_length; i++) {
|
||||||
|
(*payload)[i] = data[offset + i] ^ header->masking_key[i % 4];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memcpy(*payload, data + offset, header->payload_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset + header->payload_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create WebSocket frame
|
||||||
|
int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len) {
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
// First byte: FIN + opcode
|
||||||
|
buffer[offset++] = 0x80 | (opcode & 0x0F);
|
||||||
|
|
||||||
|
// Second byte: MASK + payload length
|
||||||
|
if (payload_len < 126) {
|
||||||
|
if (buffer_size < offset + 1 + payload_len) return -1;
|
||||||
|
buffer[offset++] = payload_len;
|
||||||
|
} else if (payload_len < 65536) {
|
||||||
|
if (buffer_size < offset + 3 + payload_len) return -1;
|
||||||
|
buffer[offset++] = 126;
|
||||||
|
buffer[offset++] = (payload_len >> 8) & 0xFF;
|
||||||
|
buffer[offset++] = payload_len & 0xFF;
|
||||||
|
} else {
|
||||||
|
if (buffer_size < offset + 9 + payload_len) return -1;
|
||||||
|
buffer[offset++] = 127;
|
||||||
|
for (int i = 7; i >= 0; i--) {
|
||||||
|
buffer[offset++] = (payload_len >> (i * 8)) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy payload
|
||||||
|
if (payload && payload_len > 0) {
|
||||||
|
memcpy(buffer + offset, payload, payload_len);
|
||||||
|
offset += payload_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send WebSocket frame
|
||||||
|
int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len) {
|
||||||
|
// Allocate buffer with enough space for header (max 10 bytes) + payload
|
||||||
|
size_t max_frame_size = 10 + payload_len;
|
||||||
|
if (max_frame_size > 65536) {
|
||||||
|
max_frame_size = 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t buffer[65536];
|
||||||
|
|
||||||
|
// Limit payload to avoid overflow (65536 - 10 bytes for max header)
|
||||||
|
size_t safe_payload_len = payload_len;
|
||||||
|
if (safe_payload_len > 65526) {
|
||||||
|
safe_payload_len = 65526;
|
||||||
|
}
|
||||||
|
|
||||||
|
int frame_len = ws_create_frame(buffer, sizeof(buffer), opcode, payload, safe_payload_len);
|
||||||
|
|
||||||
|
if (frame_len < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->is_ssl && conn->ssl) {
|
||||||
|
return SSL_write(conn->ssl, buffer, frame_len);
|
||||||
|
} else {
|
||||||
|
return write(conn->socket_fd, buffer, frame_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send text message
|
||||||
|
int ws_send_text(ws_connection_t *conn, const char *text) {
|
||||||
|
return ws_send_frame(conn, WS_OPCODE_TEXT, (const uint8_t*)text, strlen(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send pong response
|
||||||
|
int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_len) {
|
||||||
|
return ws_send_frame(conn, WS_OPCODE_PONG, payload, payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close WebSocket connection
|
||||||
|
void ws_close_connection(ws_connection_t *conn, uint16_t status_code) {
|
||||||
|
uint8_t close_payload[2];
|
||||||
|
close_payload[0] = (status_code >> 8) & 0xFF;
|
||||||
|
close_payload[1] = status_code & 0xFF;
|
||||||
|
|
||||||
|
ws_send_frame(conn, WS_OPCODE_CLOSE, close_payload, 2);
|
||||||
|
|
||||||
|
if (conn->is_ssl && conn->ssl) {
|
||||||
|
SSL_shutdown(conn->ssl);
|
||||||
|
SSL_free(conn->ssl);
|
||||||
|
}
|
||||||
|
close(conn->socket_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate UTF-8 encoding
|
||||||
|
bool ws_is_valid_utf8(const uint8_t *data, size_t len) {
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < len) {
|
||||||
|
if (data[i] < 0x80) {
|
||||||
|
i++;
|
||||||
|
} else if ((data[i] & 0xE0) == 0xC0) {
|
||||||
|
if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) return false;
|
||||||
|
i += 2;
|
||||||
|
} else if ((data[i] & 0xF0) == 0xE0) {
|
||||||
|
if (i + 2 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false;
|
||||||
|
i += 3;
|
||||||
|
} else if ((data[i] & 0xF8) == 0xF0) {
|
||||||
|
if (i + 3 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false;
|
||||||
|
i += 4;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
47
src/websocket.h
Normal file
47
src/websocket.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef WEBSOCKET_H
|
||||||
|
#define WEBSOCKET_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
// WebSocket opcodes
|
||||||
|
#define WS_OPCODE_CONTINUATION 0x0
|
||||||
|
#define WS_OPCODE_TEXT 0x1
|
||||||
|
#define WS_OPCODE_BINARY 0x2
|
||||||
|
#define WS_OPCODE_CLOSE 0x8
|
||||||
|
#define WS_OPCODE_PING 0x9
|
||||||
|
#define WS_OPCODE_PONG 0xA
|
||||||
|
|
||||||
|
// WebSocket frame header structure
|
||||||
|
typedef struct {
|
||||||
|
uint8_t fin;
|
||||||
|
uint8_t opcode;
|
||||||
|
uint8_t mask;
|
||||||
|
uint64_t payload_length;
|
||||||
|
uint8_t masking_key[4];
|
||||||
|
} ws_frame_header_t;
|
||||||
|
|
||||||
|
// WebSocket connection context
|
||||||
|
typedef struct {
|
||||||
|
int socket_fd;
|
||||||
|
SSL *ssl;
|
||||||
|
bool is_ssl;
|
||||||
|
bool handshake_complete;
|
||||||
|
} ws_connection_t;
|
||||||
|
|
||||||
|
// Function prototypes
|
||||||
|
int ws_handle_handshake(int client_socket, const char *request, char *response, size_t response_size);
|
||||||
|
int ws_handle_handshake_ssl(SSL *ssl, const char *request, char *response, size_t response_size);
|
||||||
|
int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, uint8_t **payload);
|
||||||
|
int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len);
|
||||||
|
int ws_send_frame(ws_connection_t *conn, uint8_t opcode, const uint8_t *payload, size_t payload_len);
|
||||||
|
int ws_send_text(ws_connection_t *conn, const char *text);
|
||||||
|
int ws_send_pong(ws_connection_t *conn, const uint8_t *payload, size_t payload_len);
|
||||||
|
void ws_close_connection(ws_connection_t *conn, uint16_t status_code);
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
char* ws_generate_accept_key(const char *client_key);
|
||||||
|
bool ws_is_valid_utf8(const uint8_t *data, size_t len);
|
||||||
|
|
||||||
|
#endif
|
||||||
460
www/index.html
460
www/index.html
@@ -3,70 +3,422 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Server Configuration Guide</title>
|
<title>Carbon Server - It works!</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 3px solid #dc3545;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 2.5em;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-box {
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
border-left: 4px solid #28a745;
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-box h2 {
|
||||||
|
color: #155724;
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-box p {
|
||||||
|
color: #155724;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.8em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-bottom: 2px solid #e9ecef;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #555;
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin: 20px 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
color: #e83e8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 15px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
border-left: 4px solid #17a2b8;
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box strong {
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box {
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box strong {
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 15px 0;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 10px 10px 10px 0;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary:hover {
|
||||||
|
background: #545b62;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 50px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature {
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature h4 {
|
||||||
|
color: #007bff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<div class="container">
|
||||||
<h1>Server Configuration Guide</h1>
|
<header>
|
||||||
<nav>
|
<h1>🔥 Carbon Web Server</h1>
|
||||||
|
<p class="subtitle">High-Performance HTTP/HTTPS Server with WebSocket Support</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="success-box">
|
||||||
|
<h2>✓ It works!</h2>
|
||||||
|
<p>The Carbon web server is installed and running successfully. This is the default page that is displayed when the server is working correctly.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>About This Server</h2>
|
||||||
|
<p>Carbon is a high-performance, production-ready HTTP/HTTPS server written in C for Linux systems. It features modern web technologies including WebSocket support, SSL/TLS encryption, and comprehensive security measures.</p>
|
||||||
|
|
||||||
|
<div class="features">
|
||||||
|
<div class="feature">
|
||||||
|
<h4>⚡ High Performance</h4>
|
||||||
|
<p>Epoll-based async I/O, zero-copy file transfers, and thread pooling</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<h4>🔒 Secure by Default</h4>
|
||||||
|
<p>SSL/TLS support, rate limiting, security headers, and input sanitization</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<h4>🌐 WebSocket Ready</h4>
|
||||||
|
<p>Full RFC 6455 compliant WebSocket implementation for real-time apps</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<h4>🛠️ Easy Configuration</h4>
|
||||||
|
<p>Simple Linux-style configuration file with comments</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Quick Links</h2>
|
||||||
|
<a href="/websocket-test.html" class="button">Test WebSocket</a>
|
||||||
|
<a href="https://github.com/Azreyo/Carbon" class="button secondary">Documentation</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
<p>The server is configured through the <code>server.conf</code> file located in the server root directory.</p>
|
||||||
|
|
||||||
|
<h3>Basic Configuration Example</h3>
|
||||||
|
<pre><code># Carbon Web Server Configuration File
|
||||||
|
|
||||||
|
# Server listening port
|
||||||
|
port = 8080
|
||||||
|
|
||||||
|
# Enable HTTPS (requires valid certificates in certs/ directory)
|
||||||
|
use_https = false
|
||||||
|
|
||||||
|
# Log file location
|
||||||
|
log_file = log/server.log
|
||||||
|
|
||||||
|
# Maximum number of worker threads
|
||||||
|
max_threads = 4
|
||||||
|
|
||||||
|
# Server name or IP address
|
||||||
|
server_name = localhost
|
||||||
|
|
||||||
|
# Enable verbose logging
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
# Enable WebSocket support
|
||||||
|
enable_websocket = true</code></pre>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<strong>Note:</strong> After modifying the configuration file, restart the server for changes to take effect.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Enabling HTTPS</h2>
|
||||||
|
<p>To enable HTTPS support, you'll need SSL certificates. You can use self-signed certificates for testing or obtain certificates from Let's Encrypt for production.</p>
|
||||||
|
|
||||||
|
<h3>Generate Self-Signed Certificates (Testing Only)</h3>
|
||||||
|
<pre><code># Create certificates directory
|
||||||
|
mkdir -p certs
|
||||||
|
|
||||||
|
# Generate self-signed certificate
|
||||||
|
openssl req -x509 -newkey rsa:4096 -nodes \
|
||||||
|
-keyout certs/key.pem \
|
||||||
|
-out certs/cert.pem \
|
||||||
|
-days 365 \
|
||||||
|
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"</code></pre>
|
||||||
|
|
||||||
|
<h3>Enable HTTPS in Configuration</h3>
|
||||||
|
<pre><code># Edit server.conf
|
||||||
|
use_https = true</code></pre>
|
||||||
|
|
||||||
|
<div class="warning-box">
|
||||||
|
<strong>Warning:</strong> Self-signed certificates will show security warnings in browsers. For production, use certificates from a trusted Certificate Authority like Let's Encrypt.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>WebSocket Support</h2>
|
||||||
|
<p>Carbon includes full WebSocket support (RFC 6455 compliant) for building real-time applications.</p>
|
||||||
|
|
||||||
|
<h3>Test WebSocket Connection</h3>
|
||||||
|
<p>Use the built-in test client:</p>
|
||||||
|
<pre><code># Browser
|
||||||
|
http://localhost:8080/websocket-test.html
|
||||||
|
|
||||||
|
# Command line (requires wscat: npm install -g wscat)
|
||||||
|
wscat -c ws://localhost:8080
|
||||||
|
|
||||||
|
# Secure WebSocket (if HTTPS enabled)
|
||||||
|
wscat -c wss://localhost:443</code></pre>
|
||||||
|
|
||||||
|
<h3>JavaScript Example</h3>
|
||||||
|
<pre><code>// Connect to WebSocket server
|
||||||
|
const ws = new WebSocket('ws://localhost:8080');
|
||||||
|
|
||||||
|
// Connection opened
|
||||||
|
ws.addEventListener('open', (event) => {
|
||||||
|
console.log('Connected!');
|
||||||
|
ws.send('Hello Server!');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for messages
|
||||||
|
ws.addEventListener('message', (event) => {
|
||||||
|
console.log('Received:', event.data);
|
||||||
|
});</code></pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Directory Structure</h2>
|
||||||
|
<p>Understanding the server directory layout:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#http">HTTP link</a></li>
|
<li><code>www/</code> - Web root directory (place your HTML, CSS, JS files here)</li>
|
||||||
<li><a href="#https">HTTPS & SSL</a></li>
|
<li><code>certs/</code> - SSL certificate files (cert.pem, key.pem)</li>
|
||||||
<li><a href="#security">Security</a></li>
|
<li><code>log/</code> - Server log files</li>
|
||||||
|
<li><code>server.conf</code> - Main configuration file</li>
|
||||||
|
<li><code>server</code> - Server executable</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section id="http">
|
|
||||||
<h2>HTTP Configuration</h2>
|
|
||||||
<p>HTTP (HyperText Transfer Protocol) is the foundation of web communication.</p>
|
|
||||||
<button class="toggle-btn" href="https://en.wikipedia.org/wiki/HTTP">Read More</button>
|
|
||||||
<div class="content">
|
|
||||||
<p>To configure an HTTP server, ensure that your web server (e.g., Apache, Nginx) is set up properly.</p>
|
|
||||||
<pre><code># Apache example:
|
|
||||||
<VirtualHost *:80>
|
|
||||||
ServerName example.com
|
|
||||||
DocumentRoot /var/www/html
|
|
||||||
</VirtualHost></code></pre>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="https">
|
<section>
|
||||||
<h2>HTTPS & SSL</h2>
|
<h2>Common Tasks</h2>
|
||||||
<p>HTTPS secures communication by encrypting data using SSL/TLS.</p>
|
|
||||||
<button class="toggle-btn">Read More</button>
|
<h3>Start the Server</h3>
|
||||||
<div class="content">
|
<pre><code>./server</code></pre>
|
||||||
<p>To enable HTTPS, you need an SSL certificate. Here's an example for Apache:</p>
|
|
||||||
<pre><code># Apache SSL configuration:
|
<h3>Stop the Server</h3>
|
||||||
<VirtualHost *:443>
|
<pre><code># Press Ctrl+C in the server terminal
|
||||||
SSLEngine on
|
# Or send SIGTERM signal
|
||||||
SSLCertificateFile /etc/ssl/certs/server.crt
|
kill -TERM $(pgrep -f './server')</code></pre>
|
||||||
SSLCertificateKeyFile /etc/ssl/private/server.key
|
|
||||||
</VirtualHost></code></pre>
|
<h3>View Logs</h3>
|
||||||
</div>
|
<pre><code>tail -f log/server.log</code></pre>
|
||||||
|
|
||||||
|
<h3>Test HTTP Connection</h3>
|
||||||
|
<pre><code>curl http://localhost:8080</code></pre>
|
||||||
|
|
||||||
|
<h3>Rebuild After Code Changes</h3>
|
||||||
|
<pre><code>make clean && make</code></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="security">
|
<section>
|
||||||
<h2>Server Security</h2>
|
<h2>Troubleshooting</h2>
|
||||||
<p>Securing your server is crucial to prevent attacks.</p>
|
|
||||||
<button class="toggle-btn">Read More</button>
|
<h3>Port Already in Use</h3>
|
||||||
<div class="content">
|
<p>If you see "Address already in use" errors:</p>
|
||||||
<ul>
|
<pre><code># Find process using port 8080
|
||||||
<li>Disable unnecessary services.</li>
|
sudo lsof -i :8080
|
||||||
<li>Use a firewall (iptables, UFW).</li>
|
|
||||||
<li>Keep software updated.</li>
|
# Or
|
||||||
<li>Enforce strong authentication.</li>
|
sudo netstat -tulpn | grep 8080
|
||||||
</ul>
|
|
||||||
</div>
|
# Kill the process
|
||||||
|
kill -9 <PID></code></pre>
|
||||||
|
|
||||||
|
<h3>Permission Denied for Port 443</h3>
|
||||||
|
<p>Ports below 1024 require root privileges:</p>
|
||||||
|
<pre><code># Option 1: Run as root (not recommended for production)
|
||||||
|
sudo ./server
|
||||||
|
|
||||||
|
# Option 2: Use setcap to grant capability
|
||||||
|
sudo setcap 'cap_net_bind_service=+ep' ./server
|
||||||
|
./server</code></pre>
|
||||||
|
|
||||||
|
<h3>WebSocket Connection Failed</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Ensure <code>enable_websocket = true</code> in server.conf</li>
|
||||||
|
<li>Use <code>ws://</code> for HTTP and <code>wss://</code> for HTTPS</li>
|
||||||
|
<li>Check browser console (F12) for detailed error messages</li>
|
||||||
|
<li>Verify server logs for connection attempts</li>
|
||||||
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
<section>
|
||||||
<p>© 2025 Server Config Guide</p>
|
<h2>Security Best Practices</h2>
|
||||||
</footer>
|
<ul>
|
||||||
|
<li><strong>Use HTTPS:</strong> Always enable HTTPS for production deployments</li>
|
||||||
|
<li><strong>Firewall Configuration:</strong> Use UFW or iptables to restrict access</li>
|
||||||
|
<li><strong>Rate Limiting:</strong> Enabled by default to prevent abuse</li>
|
||||||
|
<li><strong>Regular Updates:</strong> Keep the server and dependencies updated</li>
|
||||||
|
<li><strong>Log Monitoring:</strong> Regularly check logs for suspicious activity</li>
|
||||||
|
<li><strong>Strong Certificates:</strong> Use proper SSL certificates from trusted CAs</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<script src="script.js"></script>
|
<footer>
|
||||||
|
<p><strong>Carbon Web Server</strong> | <a href="https://github.com/Azreyo/Carbon">GitHub Repository</a> | Licensed under MIT</p>
|
||||||
|
<p>If you can see this page, the server is working correctly.</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
const buttons = document.querySelectorAll(".toggle-btn");
|
|
||||||
|
|
||||||
buttons.forEach(button => {
|
|
||||||
button.addEventListener("click", function () {
|
|
||||||
const content = this.nextElementSibling;
|
|
||||||
content.style.display = content.style.display === "block" ? "none" : "block";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav ul li {
|
|
||||||
display: inline;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav ul li a {
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
background: white;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toggle-btn {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.toggle-btn:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: none;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
text-align: center;
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
position: relative;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
343
www/websocket-test.html
Normal file
343
www/websocket-test.html
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebSocket Test - Carbon Server</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.connected {
|
||||||
|
background: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.disconnected {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages {
|
||||||
|
height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
animation: fadeIn 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.sent {
|
||||||
|
background: #dbeafe;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.received {
|
||||||
|
background: #d1fae5;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.system {
|
||||||
|
background: #fee2e2;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messageInput {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messageInput:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 12px 30px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendBtn {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendBtn:hover:not(:disabled) {
|
||||||
|
background: #5568d3;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendBtn:disabled {
|
||||||
|
background: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connectBtn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connectBtn.connected {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#connectBtn.disconnected {
|
||||||
|
background: #10b981;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 2px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🔥 Carbon WebSocket Test</h1>
|
||||||
|
<span class="status disconnected" id="status">Disconnected</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="messages" id="messages">
|
||||||
|
<div class="message system">
|
||||||
|
<div class="timestamp">System</div>
|
||||||
|
Welcome! Configure the WebSocket URL below and click Connect.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="messageInput" placeholder="Type your message..." disabled>
|
||||||
|
<button id="sendBtn" disabled>Send</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label for="wsUrl">WebSocket URL:</label>
|
||||||
|
<input type="text" id="wsUrl" value="ws://localhost:8080" placeholder="ws://localhost:8080">
|
||||||
|
<button id="connectBtn" class="disconnected">Connect</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let ws = null;
|
||||||
|
const messagesDiv = document.getElementById('messages');
|
||||||
|
const messageInput = document.getElementById('messageInput');
|
||||||
|
const sendBtn = document.getElementById('sendBtn');
|
||||||
|
const connectBtn = document.getElementById('connectBtn');
|
||||||
|
const statusSpan = document.getElementById('status');
|
||||||
|
const wsUrlInput = document.getElementById('wsUrl');
|
||||||
|
|
||||||
|
function addMessage(text, type) {
|
||||||
|
const messageDiv = document.createElement('div');
|
||||||
|
messageDiv.className = `message ${type}`;
|
||||||
|
|
||||||
|
const timestamp = document.createElement('div');
|
||||||
|
timestamp.className = 'timestamp';
|
||||||
|
timestamp.textContent = new Date().toLocaleTimeString();
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.textContent = text;
|
||||||
|
|
||||||
|
messageDiv.appendChild(timestamp);
|
||||||
|
messageDiv.appendChild(content);
|
||||||
|
messagesDiv.appendChild(messageDiv);
|
||||||
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus(connected) {
|
||||||
|
if (connected) {
|
||||||
|
statusSpan.textContent = 'Connected';
|
||||||
|
statusSpan.className = 'status connected';
|
||||||
|
connectBtn.textContent = 'Disconnect';
|
||||||
|
connectBtn.className = 'connected';
|
||||||
|
messageInput.disabled = false;
|
||||||
|
sendBtn.disabled = false;
|
||||||
|
wsUrlInput.disabled = true;
|
||||||
|
} else {
|
||||||
|
statusSpan.textContent = 'Disconnected';
|
||||||
|
statusSpan.className = 'status disconnected';
|
||||||
|
connectBtn.textContent = 'Connect';
|
||||||
|
connectBtn.className = 'disconnected';
|
||||||
|
messageInput.disabled = true;
|
||||||
|
sendBtn.disabled = true;
|
||||||
|
wsUrlInput.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
const url = wsUrlInput.value.trim();
|
||||||
|
if (!url) {
|
||||||
|
addMessage('Please enter a valid WebSocket URL', 'system');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
addMessage('Attempting to connect to: ' + url, 'system');
|
||||||
|
ws = new WebSocket(url);
|
||||||
|
|
||||||
|
ws.addEventListener('open', (event) => {
|
||||||
|
console.log('WebSocket opened', event);
|
||||||
|
addMessage('✅ Connected to server!', 'system');
|
||||||
|
updateStatus(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('message', (event) => {
|
||||||
|
console.log('WebSocket message received:', event.data);
|
||||||
|
addMessage(event.data, 'received');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('error', (error) => {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
addMessage('❌ WebSocket error occurred - Check console for details', 'system');
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('close', (event) => {
|
||||||
|
console.log('WebSocket closed', event);
|
||||||
|
addMessage(`Connection closed (Code: ${event.code}, Reason: ${event.reason || 'None'})`, 'system');
|
||||||
|
updateStatus(false);
|
||||||
|
ws = null;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Exception while connecting:', error);
|
||||||
|
addMessage('❌ Failed to connect: ' + error.message, 'system');
|
||||||
|
updateStatus(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect() {
|
||||||
|
if (ws) {
|
||||||
|
ws.close(1000, 'User disconnected');
|
||||||
|
ws = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
const message = messageInput.value.trim();
|
||||||
|
if (message && ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.send(message);
|
||||||
|
addMessage(message, 'sent');
|
||||||
|
messageInput.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectBtn.addEventListener('click', () => {
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
disconnect();
|
||||||
|
} else {
|
||||||
|
connect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sendBtn.addEventListener('click', sendMessage);
|
||||||
|
|
||||||
|
messageInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user