docs, gurt:// <a> url

This commit is contained in:
Face
2025-08-15 13:52:01 +03:00
parent c117e602fe
commit 5dae5a4868
25 changed files with 1640 additions and 390 deletions

13
docs/docs/dns-system.md Normal file
View File

@@ -0,0 +1,13 @@
---
sidebar_position: 6
---
# DNS
The Gurted ecosystem features a custom DNS system that enables domain resolution for the gurt:// protocol. Unlike traditional DNS, Gurted DNS is designed specifically for the decentralized web ecosystem, providing:
- domain registration
- approval workflows (to ensure a free-of-charge, spam-free experience)
- archivable domain records
TODO: complete

View File

@@ -0,0 +1,8 @@
---
sidebar_position: 7
---
# Flumi (browser)
**Flumi** is the official browser for the Gurted ecosystem, built using the Godot game engine. It provides a complete web browsing experience for `gurt://` URLs with custom HTML/CSS rendering, Lua scripting support, and integration with the Gurted DNS system.

352
docs/docs/gurt-client.md Normal file
View File

@@ -0,0 +1,352 @@
---
sidebar_position: 3
---
# GURT Client Library
The GURT client library (for Rust) provides a high-level, HTTP-like interface for making requests to GURT servers. It handles TLS encryption, protocol handshakes, and connection management automatically.
## Bindings
- **Godot** by Gurted - [🔗 link](https://gurted.com/download)
- No bidings for other languages are currently available.
## Installation
Install via Cargo:
```bash
cargo add gurt
```
## Quick Start
```rust
use gurt::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let client = GurtClient::new();
// Make a GET request
let response = client.get("gurt://example.com/").await?;
println!("Status: {}", response.status_code);
println!("Body: {}", response.text()?);
Ok(())
}
```
## Creating a Client
### Default Client
```rust
let client = GurtClient::new();
```
### Custom Configuration
```rust
use tokio::time::Duration;
let config = GurtClientConfig {
connect_timeout: Duration::from_secs(10),
request_timeout: Duration::from_secs(30),
handshake_timeout: Duration::from_secs(5),
user_agent: "MyApp/1.0.0".to_string(),
max_redirects: 5,
};
let client = GurtClient::with_config(config);
```
## Making Requests
### GET Requests
```rust
let response = client.get("gurt://api.example.com/users").await?;
if response.is_success() {
println!("Success: {}", response.text()?);
} else {
println!("Error: {} {}", response.status_code, response.status_message);
}
```
### POST Requests
#### Text Data
```rust
let response = client.post("gurt://api.example.com/submit", "Hello, GURT!").await?;
```
#### JSON Data
```rust
use serde_json::json;
let data = json!({
"name": "John Doe",
"email": "john@example.com"
});
let response = client.post_json("gurt://api.example.com/users", &data).await?;
```
### PUT Requests
```rust
// Text data
let response = client.put("gurt://api.example.com/resource/123", "Updated content").await?;
// JSON data
let update_data = json!({"status": "completed"});
let response = client.put_json("gurt://api.example.com/tasks/456", &update_data).await?;
```
### DELETE Requests
```rust
let response = client.delete("gurt://api.example.com/users/123").await?;
```
### HEAD Requests
```rust
let response = client.head("gurt://api.example.com/large-file").await?;
// Check headers without downloading body
let content_length = response.headers.get("content-length");
```
### OPTIONS Requests
```rust
let response = client.options("gurt://api.example.com/endpoint").await?;
// Check allowed methods
let allowed_methods = response.headers.get("allow");
```
### PATCH Requests
```rust
let patch_data = json!({"name": "Updated Name"});
let response = client.patch_json("gurt://api.example.com/users/123", &patch_data).await?;
```
## Response Handling
### Response Structure
```rust
pub struct GurtResponse {
pub version: String,
pub status_code: u16,
pub status_message: String,
pub headers: HashMap<String, String>,
pub body: Vec<u8>,
}
```
### Accessing Response Data
```rust
let response = client.get("gurt://api.example.com/data").await?;
// Status information
println!("Status Code: {}", response.status_code);
println!("Status Message: {}", response.status_message);
// Headers
for (name, value) in &response.headers {
println!("{}: {}", name, value);
}
// Body as string
let text = response.text()?;
// Body as bytes
let bytes = &response.body;
// Parse JSON response
let json_data: serde_json::Value = serde_json::from_slice(&response.body)?;
```
### Status Code Checking
```rust
if response.is_success() {
// 2xx status codes
println!("Request successful");
} else if response.is_client_error() {
// 4xx status codes
println!("Client error: {}", response.status_message);
} else if response.is_server_error() {
// 5xx status codes
println!("Server error: {}", response.status_message);
}
```
## Protocol Implementation
The GURT client automatically handles the complete GURT protocol:
1. **TCP Connection**: Establishes initial connection to the server
2. **Handshake**: Sends `HANDSHAKE` request and waits for `101 Switching Protocols`
3. **TLS Upgrade**: Upgrades the connection to TLS 1.3 with GURT ALPN
4. **Request/Response**: Sends the actual HTTP-style request over encrypted connection
All of this happens transparently when you call methods like `get()`, `post()`, etc.
## URL Parsing
The client automatically parses `gurt://` URLs:
```rust
// These are all valid GURT URLs:
client.get("gurt://example.com/").await?; // Port 4878 (default)
client.get("gurt://example.com:8080/api").await?; // Custom port
client.get("gurt://192.168.1.100/test").await?; // IP address
client.get("gurt://localhost:4878/dev").await?; // Localhost
```
### URL Components
The client extracts:
- **Host**: Domain name or IP address
- **Port**: Specified port or default (4878)
- **Path**: Request path (defaults to `/`)
## Error Handling
### Error Types
```rust
use gurt::GurtError;
match client.get("gurt://invalid-url").await {
Ok(response) => {
// Handle successful response
}
Err(GurtError::InvalidMessage(msg)) => {
println!("Invalid request: {}", msg);
}
Err(GurtError::Connection(msg)) => {
println!("Connection error: {}", msg);
}
Err(GurtError::Timeout(msg)) => {
println!("Request timeout: {}", msg);
}
Err(GurtError::Io(err)) => {
println!("IO error: {}", err);
}
Err(err) => {
println!("Other error: {}", err);
}
}
```
### Timeout Configuration
```rust
let config = GurtClientConfig {
connect_timeout: Duration::from_secs(5), // Connection timeout
request_timeout: Duration::from_secs(30), // Overall request timeout
handshake_timeout: Duration::from_secs(3), // GURT handshake timeout
..Default::default()
};
```
## Why Rust-first?
Rust was chosen for the official GURT protocol implementation due to its embedded nature.
To keep the core organized & not write identical code in GDScript, we used a GDExtension. A GDExtension can be created with a multitude of languages, but Rust was the one that provided the best performance, size, and programming ergonomics.
We expect the community to implement bindings for other languages, such as Python and JavaScript, to make GURT accessible for everybody!
## Example: Building a GURT API Client
```rust
use gurt::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
struct ApiClient {
client: GurtClient,
base_url: String,
}
impl ApiClient {
fn new(base_url: String) -> Self {
Self {
client: GurtClient::new(),
base_url,
}
}
async fn create_user(&self, user: CreateUser) -> Result<User> {
let url = format!("{}/users", self.base_url);
let response = self.client.post_json(&url, &user).await?;
if !response.is_success() {
return Err(GurtError::invalid_message(
format!("API error: {}", response.status_message)
));
}
let user: User = serde_json::from_slice(&response.body)?;
Ok(user)
}
async fn get_user(&self, id: u64) -> Result<User> {
let url = format!("{}/users/{}", self.base_url, id);
let response = self.client.get(&url).await?;
if response.status_code == 404 {
return Err(GurtError::invalid_message("User not found".to_string()));
}
if !response.is_success() {
return Err(GurtError::invalid_message(
format!("API error: {}", response.status_message)
));
}
let user: User = serde_json::from_slice(&response.body)?;
Ok(user)
}
}
#[tokio::main]
async fn main() -> Result<()> {
let api = ApiClient::new("gurt://api.example.com".to_string());
// Create a user
let new_user = CreateUser {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
let user = api.create_user(new_user).await?;
println!("Created user: {} (ID: {})", user.name, user.id);
// Retrieve the user
let retrieved_user = api.get_user(user.id).await?;
println!("Retrieved user: {}", retrieved_user.name);
Ok(())
}
```

272
docs/docs/gurt-protocol.md Normal file
View File

@@ -0,0 +1,272 @@
---
sidebar_position: 2
---
# GURT Protocol
**GURT** (version 1.0.0) is a TCP-based application protocol designed as an HTTP-like alternative with built-in TLS 1.3 encryption. It serves as the foundation for the Gurted ecosystem, enabling secure communication between clients and servers using the `gurt://` URL scheme.
## Overview
GURT provides a familiar HTTP-like syntax while offering security through mandatory TLS 1.3 encryption. Unlike HTTP where encryption is optional (HTTPS), all GURT connections are encrypted by default.
### Key Features
- **HTTP-like syntax** with familiar methods (GET, POST, PUT, DELETE, etc.)
- **Built-in required TLS 1.3 encryption** for all connections
- **Binary and text data support**
- **Status codes** compatible with HTTP semantics
- **Default port**: 4878
- **ALPN identifier**: `GURT/1.0`
## URL Scheme
GURT uses the `gurt://` URL scheme:
```
gurt://example.com/path
gurt://192.168.1.100:4878/api/data
gurt://localhost:4878/hello
```
The protocol automatically defaults to port 4878.
## Communication Flow
Every GURT session must begin with a `HANDSHAKE` request:
```http
HANDSHAKE / GURT/1.0.0\r\n
host: example.com\r\n
user-agent: GURT-Client/1.0.0\r\n
\r\n
```
Server responds with protocol confirmation:
```http
GURT/1.0.0 101 SWITCHING_PROTOCOLS\r\n
gurt-version: 1.0.0\r\n
encryption: TLS/1.3\r\n
alpn: GURT/1.0\r\n
server: GURT/1.0.0\r\n
date: Wed, 01 Jan 2020 00:00:00 GMT\r\n
\r\n
```
## Message Format
### Request Structure
```http
METHOD /path GURT/1.0.0\r\n
header-name: header-value\r\n
content-length: 123\r\n
user-agent: GURT-Client/1.0.0\r\n
\r\n
[message body]
```
**Components:**
- **Method line**: `METHOD /path GURT/1.0.0`
- **Headers**: Lowercase names, colon-separated values
- **Header terminator**: `\r\n\r\n`
- **Body**: Optional message content
### Response Structure
```http
GURT/1.0.0 200 OK\r\n
content-type: application/json\r\n
content-length: 123\r\n
server: GURT/1.0.0\r\n
date: Wed, 01 Jan 2020 00:00:00 GMT\r\n
\r\n
[response body]
```
**Components:**
- **Status line**: `GURT/1.0.0 <code> <message>`
- **Headers**: Lowercase names, required for responses
- **Body**: Optional response content
## HTTP Methods
GURT supports all standard HTTP methods:
| Method | Purpose | Body Allowed |
|--------|---------|--------------|
| `GET` | Retrieve resource | No |
| `POST` | Create/submit data | Yes |
| `PUT` | Update/replace resource | Yes |
| `DELETE` | Remove resource | No |
| `HEAD` | Get headers only | No |
| `OPTIONS` | Get allowed methods | No |
| `PATCH` | Partial update | Yes |
| `HANDSHAKE` | Protocol handshake | No |
## Status Codes
GURT uses HTTP-compatible status codes:
### Success (2xx)
- `200 OK` - Request successful
- `201 CREATED` - Resource created
- `202 ACCEPTED` - Request accepted for processing
- `204 NO_CONTENT` - Success with no response body
### Protocol (1xx)
- `101 SWITCHING_PROTOCOLS` - Handshake successful
### Client Error (4xx)
- `400 BAD_REQUEST` - Invalid request format
- `401 UNAUTHORIZED` - Authentication required
- `403 FORBIDDEN` - Access denied
- `404 NOT_FOUND` - Resource not found
- `405 METHOD_NOT_ALLOWED` - Method not supported
- `408 TIMEOUT` - Request timeout
- `413 TOO_LARGE` - Request too large
- `415 UNSUPPORTED_MEDIA_TYPE` - Unsupported content type
### Server Error (5xx)
- `500 INTERNAL_SERVER_ERROR` - Server error
- `501 NOT_IMPLEMENTED` - Method not implemented
- `502 BAD_GATEWAY` - Gateway error
- `503 SERVICE_UNAVAILABLE` - Service unavailable
- `504 GATEWAY_TIMEOUT` - Gateway timeout
## Security
All connections must use TLS 1.3 for encryption. This means you have to generate and use a valid TLS certificate for your GURT server.
### Setup for Production
For production deployments, you'll need to generate your own certificates since traditional Certificate Authorities don't support custom protocols:
1. **Generate production certificates with OpenSSL:**
```bash
# Generate private key
openssl genpkey -algorithm RSA -out gurt-server.key -pkcs8 -v
# Generate certificate signing request
openssl req -new -key gurt-server.key -out gurt-server.csr
# Generate self-signed certificate (valid for 365 days)
openssl x509 -req -days 365 -in gurt-server.csr -signkey gurt-server.key -out gurt-server.crt
# Or generate both key and certificate in one step
openssl req -x509 -newkey rsa:4096 -keyout gurt-server.key -out gurt-server.crt -days 365 -nodes
```
2. **Deploy with production certificates:**
```bash
cargo run --release serve --cert gurt-server.crt --key gurt-server.key --host 0.0.0.0 --port 4878
```
### Development Environment Setup
To set up a development environment for GURT, follow these steps:
1. **Install mkcert:**
```bash
# Windows (with Chocolatey)
choco install mkcert
# Or download from: https://github.com/FiloSottile/mkcert/releases
```
2. **Install local CA in system:**
```bash
mkcert -install
```
This installs a local CA in your **system certificate store**.
3. **Generate localhost certificates:**
```bash
cd gurted/protocol/cli
mkcert localhost 127.0.0.1 ::1
```
This creates:
- `localhost+2.pem` (certificate)
- `localhost+2-key.pem` (private key)
4. **Start GURT server with certificates:**
```bash
gurty serve --cert localhost+2.pem --key localhost+2-key.pem
```
Install Gurty, the official GURT server tool, [on the Gurted.com download page](https://gurted.com/download/)
## Protocol Limits
| Parameter | Limit |
|-----------|-------|
| Maximum message size | 10 MB |
| Default connection timeout | 10 seconds |
| Default request timeout | 30 seconds |
| Default handshake timeout | 5 seconds |
| Maximum connection pool size | 10 connections |
| Pool idle timeout | 300 seconds |
## Example Session
Complete GURT communication example:
```http
# Client connects and sends handshake
HANDSHAKE / GURT/1.0.0\r\n
host: example.com\r\n
user-agent: GURT-Client/1.0.0\r\n
\r\n
# Server confirms protocol
GURT/1.0.0 101 SWITCHING_PROTOCOLS\r\n
gurt-version: 1.0.0\r\n
encryption: TLS/1.3\r\n
alpn: GURT/1.0\r\n
server: GURT/1.0.0\r\n
date: Wed, 14 Aug 2025 12:00:00 GMT\r\n
\r\n
# All further communication is encrypted
# Client sends JSON data
POST /api/data GURT/1.0.0\r\n
host: example.com\r\n
content-type: application/json\r\n
content-length: 17\r\n
user-agent: GURT-Client/1.0.0\r\n
\r\n
{"foo":"bar","x":1}
# Server responds with JSON
GURT/1.0.0 200 OK\r\n
content-type: application/json\r\n
content-length: 16\r\n
server: GURT/1.0.0\r\n
date: Wed, 14 Aug 2025 12:00:01 GMT\r\n
\r\n
{"result":"ok"}
```
## Domain Resolution
GURT integrates with Gurted's custom DNS system:
### Direct IP Access
```
gurt://192.168.1.100:4878/
gurt://localhost:4878/api
```
### Domain Resolution
```
gurt://example.real/ # Resolves via Gurted DNS
```
The Gurted DNS server resolves domains in the format `name.tld` to IP addresses, enabling human-readable domain names for GURT services. This is done automatically by your GURT browser and is documented in the [DNS System documentation](./dns-system.md).
## Implementation
GURT is implemented in Rust with the following components:
- **Protocol Library**: Core protocol implementation, reusable as a Rust crate
- **CLI Tool (Gurty)**: Server setup and management
- **Godot Extension**: Browser integration for Flumi

471
docs/docs/gurt-server.md Normal file
View File

@@ -0,0 +1,471 @@
---
sidebar_position: 4
---
# GURT Server Library
The GURT server library provides a framework for building HTTP-like servers that use the GURT protocol. It features automatic TLS handling, route-based request handling, and middleware support.
## Installation
Add the GURT library to your `Cargo.toml`:
```toml
[dependencies]
gurt = "0.1"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
serde_json = "1.0"
```
## Quick Start
```rust
use gurt::prelude::*;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let server = GurtServer::with_tls_certificates("cert.pem", "key.pem")?
.get("/", |_ctx| async {
Ok(GurtResponse::ok().with_string_body("<h1>Hello, GURT!</h1>"))
})
.get("/api/users", |_ctx| async {
let users = json!(["Alice", "Bob"]);
Ok(GurtResponse::ok().with_json_body(&users))
});
println!("GURT server starting on gurt://127.0.0.1:4878");
server.listen("127.0.0.1:4878").await
}
```
## Creating a Server
### Basic Server
```rust
let server = GurtServer::new();
```
### Server with TLS Certificates
```rust
// Load TLS certificates during server creation
let server = GurtServer::with_tls_certificates("cert.pem", "key.pem")?;
// Or load certificates later
let mut server = GurtServer::new();
server.load_tls_certificates("cert.pem", "key.pem")?;
```
## Route Handlers
### Method-Specific Routes
```rust
let server = GurtServer::with_tls_certificates("cert.pem", "key.pem")?
.get("/", |_ctx| async {
Ok(GurtResponse::ok().with_string_body("GET request"))
})
.post("/submit", |ctx| async {
let body = ctx.text()?;
println!("Received: {}", body);
Ok(GurtResponse::new(GurtStatusCode::Created).with_string_body("Created"))
})
.put("/update", |_ctx| async {
Ok(GurtResponse::ok().with_string_body("Updated"))
})
.delete("/delete", |_ctx| async {
Ok(GurtResponse::new(GurtStatusCode::NoContent))
})
.patch("/partial", |_ctx| async {
Ok(GurtResponse::ok().with_string_body("Patched"))
});
```
### Any Method Route
```rust
let server = server.any("/webhook", |ctx| async {
match ctx.method() {
GurtMethod::GET => Ok(GurtResponse::ok().with_string_body("GET webhook")),
GurtMethod::POST => Ok(GurtResponse::ok().with_string_body("POST webhook")),
_ => Ok(GurtResponse::new(GurtStatusCode::MethodNotAllowed)),
}
});
```
### Route Patterns
```rust
let server = server
.get("/users", |_ctx| async {
Ok(GurtResponse::ok().with_string_body("All users"))
})
.get("/users/*", |ctx| async {
// Matches /users/123, /users/profile, etc.
let path = ctx.path();
Ok(GurtResponse::ok().with_string_body(format!("User path: {}", path)))
})
.get("/api/*", |_ctx| async {
// Matches any path starting with /api/
Ok(GurtResponse::ok().with_string_body("API endpoint"))
});
```
## Server Context
The `ServerContext` provides access to request information:
```rust
.post("/analyze", |ctx| async {
// Client information
println!("Client IP: {}", ctx.client_ip());
println!("Client Port: {}", ctx.client_port());
// Request details
println!("Method: {:?}", ctx.method());
println!("Path: {}", ctx.path());
// Headers
if let Some(content_type) = ctx.header("content-type") {
println!("Content-Type: {}", content_type);
}
// Iterate all headers
for (name, value) in ctx.headers() {
println!("{}: {}", name, value);
}
// Body data
let body_bytes = ctx.body();
let body_text = ctx.text()?;
Ok(GurtResponse::ok().with_string_body("Analyzed"))
})
```
## Response Building
### Basic Responses
```rust
// Success responses
GurtResponse::ok() // 200 OK
GurtResponse::new(GurtStatusCode::Created) // 201 Created
GurtResponse::new(GurtStatusCode::Accepted) // 202 Accepted
GurtResponse::new(GurtStatusCode::NoContent) // 204 No Content
// Client error responses
GurtResponse::bad_request() // 400 Bad Request
GurtResponse::new(GurtStatusCode::Unauthorized) // 401 Unauthorized
GurtResponse::new(GurtStatusCode::Forbidden) // 403 Forbidden
GurtResponse::not_found() // 404 Not Found
GurtResponse::new(GurtStatusCode::MethodNotAllowed) // 405 Method Not Allowed
// Server error responses
GurtResponse::internal_server_error() // 500 Internal Server Error
GurtResponse::new(GurtStatusCode::NotImplemented) // 501 Not Implemented
GurtResponse::new(GurtStatusCode::ServiceUnavailable) // 503 Service Unavailable
```
### Response with Body
```rust
// String body
GurtResponse::ok().with_string_body("Hello, World!")
// JSON body
use serde_json::json;
let data = json!({"message": "Hello", "status": "success"});
GurtResponse::ok().with_json_body(&data)
// Binary body
let image_data = std::fs::read("image.png")?;
GurtResponse::ok()
.with_header("content-type", "image/png")
.with_body(image_data)
```
### Response with Headers
```rust
GurtResponse::ok()
.with_string_body("Custom response")
.with_header("x-custom-header", "custom-value")
.with_header("cache-control", "no-cache")
.with_header("content-type", "text/plain; charset=utf-8")
```
## Advanced Examples
### JSON API Server
```rust
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Serialize, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
let server = GurtServer::with_tls_certificates("cert.pem", "key.pem")?
.get("/api/users", |ctx| async {
let users = vec![
User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
];
Ok(GurtResponse::ok()
.with_header("content-type", "application/json")
.with_json_body(&users))
})
.post("/api/users", |ctx| async {
let body = ctx.text()?;
let user: User = serde_json::from_str(&body)
.map_err(|_| GurtError::invalid_message("Invalid JSON"))?;
// Save user to database here...
println!("Creating user: {}", user.name);
Ok(GurtResponse::new(GurtStatusCode::Created)
.with_header("content-type", "application/json")
.with_json_body(&user)?)
})
.get("/api/users/*", |ctx| async {
let path = ctx.path();
if let Some(user_id) = path.strip_prefix("/api/users/") {
if let Ok(id) = user_id.parse::<u64>() {
// Get user from database here...
let user = User {
id,
name: format!("User {}", id),
email: format!("user{}@example.com", id),
};
Ok(GurtResponse::ok()
.with_header("content-type", "application/json")
.with_json_body(&user))
} else {
Ok(GurtResponse::bad_request()
.with_string_body("Invalid user ID"))
}
} else {
Ok(GurtResponse::not_found())
}
});
```
### File Server
```rust
use std::path::Path;
use tokio::fs;
let server = server.get("/files/*", |ctx| async {
let path = ctx.path();
let file_path = path.strip_prefix("/files/").unwrap_or("");
// Security: prevent directory traversal
if file_path.contains("..") {
return Ok(GurtResponse::new(GurtStatusCode::Forbidden)
.with_string_body("Access denied"));
}
let full_path = format!("./static/{}", file_path);
match fs::read(&full_path).await {
Ok(data) => {
let content_type = match Path::new(&full_path).extension()
.and_then(|ext| ext.to_str()) {
Some("html") => "text/html",
Some("css") => "text/css",
Some("js") => "application/javascript",
Some("json") => "application/json",
Some("png") => "image/png",
Some("jpg") | Some("jpeg") => "image/jpeg",
Some("gif") => "image/gif",
_ => "application/octet-stream",
};
Ok(GurtResponse::ok()
.with_header("content-type", content_type)
.with_body(data))
}
Err(_) => {
Ok(GurtResponse::not_found()
.with_string_body("File not found"))
}
}
});
```
### Middleware Pattern
```rust
// Request logging middleware
async fn log_request(ctx: &ServerContext) -> Result<()> {
println!("{} {} from {}",
ctx.method(),
ctx.path(),
ctx.client_ip()
);
Ok(())
}
// Authentication middleware
async fn require_auth(ctx: &ServerContext) -> Result<()> {
if let Some(auth_header) = ctx.header("authorization") {
if auth_header.starts_with("Bearer ") {
// Validate token here...
return Ok(());
}
}
Err(GurtError::invalid_message("Authentication required"))
}
let server = server
.get("/protected", |ctx| async {
// Apply middleware
log_request(ctx).await?;
require_auth(ctx).await?;
Ok(GurtResponse::ok()
.with_string_body("Protected content"))
})
.post("/api/data", |ctx| async {
log_request(ctx).await?;
// Handle request
Ok(GurtResponse::ok()
.with_string_body("Data processed"))
});
```
### Error Handling
```rust
let server = server.post("/api/process", |ctx| async {
match process_data(ctx).await {
Ok(result) => {
Ok(GurtResponse::ok()
.with_json_body(&result))
}
Err(ProcessError::ValidationError(msg)) => {
Ok(GurtResponse::bad_request()
.with_json_body(&json!({"error": msg})))
}
Err(ProcessError::NotFound) => {
Ok(GurtResponse::not_found()
.with_json_body(&json!({"error": "Resource not found"})))
}
Err(_) => {
Ok(GurtResponse::internal_server_error()
.with_json_body(&json!({"error": "Internal server error"})))
}
}
});
async fn process_data(ctx: &ServerContext) -> Result<serde_json::Value, ProcessError> {
// Your processing logic here
todo!()
}
#[derive(Debug)]
enum ProcessError {
ValidationError(String),
NotFound,
InternalError,
}
```
## TLS Configuration
### Development Certificates
For development, use `mkcert` to generate trusted local certificates:
```bash
# Install mkcert
choco install mkcert # Windows
brew install mkcert # macOS
# or download from GitHub releases
# Install local CA
mkcert -install
# Generate certificates
mkcert localhost 127.0.0.1 ::1
```
### Production Certificates
For production, generate certificates with OpenSSL:
```bash
# Generate private key
openssl genpkey -algorithm RSA -out server.key -pkcs8
# Generate certificate signing request
openssl req -new -key server.key -out server.csr
# Generate self-signed certificate
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
# Or in one step
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 365 -nodes
```
## Listening and Deployment
```rust
// Listen on all interfaces
server.listen("0.0.0.0:4878").await?;
// Listen on specific interface
server.listen("127.0.0.1:8080").await?;
// Listen on IPv6
server.listen("[::1]:4878").await?;
```
## Testing
```rust
#[cfg(test)]
mod tests {
use super::*;
use gurt::GurtClient;
#[tokio::test]
async fn test_server() {
let server = GurtServer::with_tls_certificates("test-cert.pem", "test-key.pem")
.unwrap()
.get("/test", |_ctx| async {
Ok(GurtResponse::ok().with_string_body("test response"))
});
// Start server in background
tokio::spawn(async move {
server.listen("127.0.0.1:9999").await.unwrap();
});
// Give server time to start
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
// Test with client
let client = GurtClient::new();
let response = client.get("gurt://127.0.0.1:9999/test").await.unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.text().unwrap(), "test response");
}
}
```

105
docs/docs/gurty-cli.md Normal file
View File

@@ -0,0 +1,105 @@
---
sidebar_position: 5
---
# Gurty CLI Tool
**Gurty** is a command-line interface tool for setting up and managing GURT protocol servers. It provides an easy way to deploy GURT servers with proper TLS configuration for both development and production environments.
## Installation
Build Gurty from the protocol CLI directory:
```bash
cd protocol/cli
cargo build --release
```
The binary will be available at `target/release/gurty` (or `gurty.exe` on Windows).
## Quick Start
### Development Setup
1. **Install mkcert** for development certificates:
```bash
# Windows (with Chocolatey)
choco install mkcert
# macOS (with Homebrew)
brew install mkcert
# Or download from: https://github.com/FiloSottile/mkcert/releases
```
2. **Install local CA** in your system:
```bash
mkcert -install
```
3. **Generate localhost certificates**:
```bash
cd protocol/cli
mkcert localhost 127.0.0.1 ::1
```
This creates:
- `localhost+2.pem` (certificate)
- `localhost+2-key.pem` (private key)
4. **Start GURT server**:
```bash
cargo run --release serve --cert localhost+2.pem --key localhost+2-key.pem
```
### Production Setup
1. **Generate production certificates** with OpenSSL:
```bash
# Generate private key
openssl genpkey -algorithm RSA -out gurt-server.key -pkcs8
# Generate certificate signing request
openssl req -new -key gurt-server.key -out gurt-server.csr
# Generate self-signed certificate (valid for 365 days)
openssl x509 -req -days 365 -in gurt-server.csr -signkey gurt-server.key -out gurt-server.crt
# Or generate both in one step
openssl req -x509 -newkey rsa:4096 -keyout gurt-server.key -out gurt-server.crt -days 365 -nodes
```
2. **Deploy with production certificates**:
```bash
cargo run --release serve --cert gurt-server.crt --key gurt-server.key --host 0.0.0.0 --port 4878
```
## Commands
### `serve` Command
Start a GURT server with TLS certificates.
```bash
gurty serve [OPTIONS]
```
#### Options
| Option | Description | Default |
|--------|-------------|---------|
| `--cert <FILE>` | Path to TLS certificate file | Required |
| `--key <FILE>` | Path to TLS private key file | Required |
| `--host <HOST>` | Host address to bind to | `127.0.0.1` |
| `--port <PORT>` | Port number to listen on | `4878` |
| `--dir <DIR>` | Directory to serve files from | None |
| `--log-level <LEVEL>` | Logging level (error, warn, info, debug, trace) | `info` |
#### Examples
```bash
gurty serve --cert localhost+2.pem --key localhost+2-key.pem --dir ./public
```
Debug:
```bash
gurty serve --cert dev.pem --key dev-key.pem --log-level debug
```

View File

@@ -15,6 +15,8 @@ sidebar_position: 1
**GURT** is a *content delivery protocol* similar to HTTPS. It's the core of how Gurted applications communicate.
Learn more about the GURT protocol: [Protocol Specification](./gurt-protocol.md)
## Getting Started
Get started by **exploring Gurted sites** or **try creating your first GURT page**.

View File

@@ -137,7 +137,7 @@ const config: Config = {
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
additionalLanguages: ['lua'],
additionalLanguages: ['lua', 'bash', 'http'],
},
} satisfies Preset.ThemeConfig,
};

View File

@@ -13,21 +13,38 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
Create as many sidebars as you want.
*/
const sidebars: SidebarsConfig = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
label: 'GURT Protocol',
items: [
'gurt-protocol',
'gurt-client',
'gurt-server',
'gurty-cli',
],
},
{
type: 'category',
label: 'Ecosystem',
items: [
'dns-system',
'flumi-browser',
],
},
{
type: 'category',
label: 'Web Standards',
items: [
'html',
'css',
],
},
],
*/
// Auto-generated fallback
// tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
};
export default sidebars;