Kaynağa Gözat

Cumulative changes

Billy Barrow 4 hafta önce
ebeveyn
işleme
abdb48fbef

+ 219 - 0
examples/DataStructuresDemo.vala

@@ -0,0 +1,219 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * DataStructuresDemo Example
+ * 
+ * Demonstrates various Invercargill data structures.
+ * Shows how to use Series, Vector, RingBuffer, and other structures
+ * with the Astralis web framework.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8086, router);
+    
+    // Root endpoint
+    router.map_func("/", (context) => {
+        return new BufferedHttpResult.from_string(
+            """Data Structures Demo
+
+This example demonstrates various Invercargill data structures:
+
+Endpoints:
+  GET /series                - Series operations
+  GET /vector                - Vector operations
+  GET /ring-buffer            - RingBuffer operations
+  GET /immutable-buffer      - ImmutableBuffer operations
+  GET /wrap-operations       - Wrap utility functions
+  GET /combined-operations    - Combined operations
+"""
+        );
+    });
+    
+    // Series operations
+    router.map_func("/series", (context) => {
+        var series_array = new string[3];
+        series_array[0] = "First";
+        series_array[1] = "Second";
+        series_array[2] = "Third";
+        
+        var parts = new Series<string>();
+        parts.add_start("Series contents: ");
+        
+        foreach (var item in series_array) {
+            parts.add_start(item + " ");
+        }
+        
+        var count = series_array.length;
+        parts.add_start("\nCount: " + count.to_string());
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Vector operations
+    router.map_func("/vector", (context) => {
+        var numbers = new int[5];
+        numbers[0] = 1;
+        numbers[1] = 2;
+        numbers[2] = 3;
+        numbers[3] = 4;
+        numbers[4] = 5;
+        
+        var vector = Wrap.array<int>(numbers).as_enumerable().to_vector();
+        
+        var count = vector.count();
+        var first = vector.first_or_default(n => true);
+        var last = vector.last_or_default(n => true);
+        
+        var result = @"Vector operations:
+  Count: $count
+  First: $first
+  Last: $last
+  Elements: 1, 2, 3, 4, 5";
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // RingBuffer operations
+    router.map_func("/ring-buffer", (context) => {
+        var buffer_array = new string[5];
+        buffer_array[0] = "Item 1";
+        buffer_array[1] = "Item 2";
+        buffer_array[2] = "Item 3";
+        buffer_array[3] = "Item 4";
+        buffer_array[4] = "Item 5";
+        
+        var buffer = Wrap.array<string>(buffer_array).as_enumerable().to_ring_buffer(3);
+        
+        var count = buffer.count();
+        
+        var result = @"RingBuffer operations:
+  Size: 3
+  Count: $count
+  Note: RingBuffer automatically overwrites old items when full";
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // ImmutableBuffer operations
+    router.map_func("/immutable-buffer", (context) => {
+        var items_array = new string[4];
+        items_array[0] = "Apple";
+        items_array[1] = "Banana";
+        items_array[2] = "Cherry";
+        items_array[3] = "Date";
+        
+        var buffer = Wrap.array<string>(items_array).as_enumerable().to_immutable_buffer();
+        
+        var count = buffer.count();
+        
+        var parts = new Series<string>();
+        parts.add_start("ImmutableBuffer contents: ");
+        
+        buffer.iterate((item) => {
+            parts.add_start(item + " ");
+        });
+        
+        parts.add_start("\nCount: " + count.to_string());
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Wrap utility functions
+    router.map_func("/wrap-operations", (context) => {
+        var numbers = new int[5];
+        numbers[0] = 1;
+        numbers[1] = 2;
+        numbers[2] = 3;
+        numbers[3] = 4;
+        numbers[4] = 5;
+        
+        var strings = new string[5];
+        strings[0] = "one";
+        strings[1] = "two";
+        strings[2] = "three";
+        strings[3] = "four";
+        strings[4] = "five";
+        
+        var num_count = Wrap.array<int>(numbers).as_enumerable().count();
+        var str_count = Wrap.array<string>(strings).as_enumerable().count();
+        
+        var result = @"Wrap operations:
+  Array of ints count: $num_count
+  Array of strings count: $str_count
+  Numbers: 1, 2, 3, 4, 5
+  Strings: one, two, three, four, five";
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Combined operations
+    router.map_func("/combined-operations", (context) => {
+        var numbers1 = new int[3];
+        numbers1[0] = 1;
+        numbers1[1] = 2;
+        numbers1[2] = 3;
+        
+        var numbers2 = new int[3];
+        numbers2[0] = 4;
+        numbers2[1] = 5;
+        numbers2[2] = 6;
+        
+        var concatenated = Wrap.array<int>(numbers1).as_enumerable()
+            .concat(Wrap.array<int>(numbers2).as_enumerable());
+        
+        var sum = concatenated.aggregate<int>(0, (acc, n) => acc + n);
+        var avg = (double)sum / concatenated.count();
+        
+        var filtered = concatenated.where(n => n > 2);
+        var sorted = filtered.order_by<int>(n => n);
+        
+        var parts = new Series<string>();
+        parts.add_start("Combined operations:\n");
+        parts.add_start("Original arrays: [1,2,3] and [4,5,6]\n");
+        parts.add_start("Concatenated: ");
+        parts.add_start(enumerable_int_to_string(concatenated));
+        parts.add_start("\n");
+        parts.add_start(@"Sum: $sum\n");
+        parts.add_start(@"Average: $avg\n");
+        parts.add_start("Filtered (>2): ");
+        parts.add_start(enumerable_int_to_string(filtered));
+        parts.add_start("\n");
+        parts.add_start("Sorted: ");
+        parts.add_start(enumerable_int_to_string(sorted));
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    print("Data Structures Demo Server running on port 8086\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8086/\n");
+    print("  - http://localhost:8086/series\n");
+    print("  - http://localhost:8086/vector\n");
+    print("  - http://localhost:8086/ring-buffer\n");
+    print("  - http://localhost:8086/immutable-buffer\n");
+    print("  - http://localhost:8086/wrap-operations\n");
+    print("  - http://localhost:8086/combined-operations\n");
+    
+    server.run();
+}
+
+// Helper functions
+
+string enumerable_int_to_string(Enumerable<int> enumerable) {
+    return enumerable.to_immutable_buffer()
+        .aggregate<string>("[", (acc, n) => {
+            if (acc == "[") return acc + n.to_string();
+            return acc + ", " + n.to_string();
+        }) + "]";
+}

+ 323 - 0
examples/EnumerableOperations.vala

@@ -0,0 +1,323 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * EnumerableOperations Example
+ * 
+ * Demonstrates various LINQ-like operations available in Invercargill Enumerable.
+ * This example shows filtering, mapping, grouping, and aggregation operations.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8087, router);
+    
+    // Root endpoint
+    router.map_func("/", (context) => {
+        return new BufferedHttpResult.from_string(
+            """Enumerable Operations Demo
+
+This example demonstrates various LINQ-like operations available in Invercargill Enumerable:
+
+Endpoints:
+  GET /filter                - Filtering examples
+  GET /map                   - Mapping/Projection examples
+  GET /group-by              - Grouping examples
+  GET /aggregate              - Aggregation examples
+  GET /sort                  - Sorting examples
+  GET /set-operations        - Set operations (union, intersection, etc.)
+  GET /advanced               - Advanced operations (zip, partition, etc.)
+"""
+        );
+    });
+    
+    // Filtering examples
+    router.map_func("/filter", (context) => {
+        var numbers = Wrap.array<int>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+        
+        var parts = new Series<string>();
+        parts.add_start("Original: ");
+        parts.add_start(enumerable_int_to_string(numbers.as_enumerable()));
+        parts.add_start("\n\n");
+        
+        // Filter even numbers
+        var even = numbers.as_enumerable()
+            .where(n => n % 2 == 0);
+        parts.add_start("Even numbers: ");
+        parts.add_start(enumerable_int_to_string(even));
+        parts.add_start("\n");
+        
+        // Filter odd numbers
+        var odd = numbers.as_enumerable()
+            .where(n => n % 2 != 0);
+        parts.add_start("Odd numbers: ");
+        parts.add_start(enumerable_int_to_string(odd));
+        parts.add_start("\n");
+        
+        // Filter numbers greater than 5
+        var greater_than_5 = numbers.as_enumerable()
+            .where(n => n > 5);
+        parts.add_start("Greater than 5: ");
+        parts.add_start(enumerable_int_to_string(greater_than_5));
+        parts.add_start("\n\n");
+        
+        // Chained filters
+        var filtered = numbers.as_enumerable()
+            .where(n => n > 3)
+            .where(n => n < 8);
+        parts.add_start("Between 3 and 8: ");
+        parts.add_start(enumerable_int_to_string(filtered));
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Mapping/Projection examples
+    router.map_func("/map", (context) => {
+        var numbers = Wrap.array<int>({1, 2, 3, 4, 5});
+        
+        var parts = new Series<string>();
+        parts.add_start("Original: ");
+        parts.add_start(enumerable_int_to_string(numbers.as_enumerable()));
+        parts.add_start("\n\n");
+        
+        // Square each number
+        var squared = numbers.as_enumerable()
+            .select<int>(n => n * n);
+        parts.add_start("Squared: ");
+        parts.add_start(enumerable_int_to_string(squared));
+        parts.add_start("\n");
+        
+        // Double each number
+        var doubled = numbers.as_enumerable()
+            .select<int>(n => n * 2);
+        parts.add_start("Doubled: ");
+        parts.add_start(enumerable_int_to_string(doubled));
+        parts.add_start("\n");
+        
+        // Convert to string
+        var as_strings = numbers.as_enumerable()
+            .select<string>(n => n.to_string());
+        parts.add_start("As strings: ");
+        parts.add_start(enumerable_string_to_string(as_strings));
+        parts.add_start("\n\n");
+        
+        // With index
+        var with_index = numbers.as_enumerable()
+            .with_positions()
+            .select<string>(pair => @"$(pair.item) at index $(pair.position)");
+        parts.add_start("With index: ");
+        parts.add_start(enumerable_string_to_string(with_index));
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Grouping examples
+    router.map_func("/group-by", (context) => {
+        var words = Wrap.array<string>({
+            "apple", "banana", "cherry", "apricot", 
+            "blueberry", "blackberry", "coconut"
+        });
+        
+        var parts = new Series<string>();
+        parts.add_start("Words: ");
+        parts.add_start(enumerable_string_to_string(words.as_enumerable()));
+        parts.add_start("\n\n");
+        
+        // Group by first letter
+        var grouped = words.as_enumerable()
+            .group_by<string>(w => w.get_char(0).to_string());
+        
+        parts.add_start("Grouped by first letter:\n");
+        grouped.iterate((grouping) => {
+            parts.add_start(@"  $(grouping.key): ");
+            parts.add_start(enumerable_string_to_string(grouping.to_immutable_buffer()));
+            parts.add_start("\n");
+        });
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Aggregation examples
+    router.map_func("/aggregate", (context) => {
+        var numbers = Wrap.array<int>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+        
+        var parts = new Series<string>();
+        parts.add_start("Numbers: ");
+        parts.add_start(enumerable_int_to_string(numbers.as_enumerable()));
+        parts.add_start("\n\n");
+        
+        // Count
+        var count = numbers.as_enumerable().count();
+        parts.add_start(@"Count: $count\n");
+        
+        // Sum using aggregate
+        var sum = numbers.as_enumerable()
+            .aggregate<int>(0, (acc, n) => acc + n);
+        parts.add_start(@"Sum: $sum\n");
+        
+        // Average
+        var avg = (double)sum / count;
+        parts.add_start(@"Average: $avg\n");
+        
+        // Min
+        var min = numbers.as_enumerable().min(n => n);
+        parts.add_start(@"Min: $min\n");
+        
+        // Max
+        var max = numbers.as_enumerable().max(n => n);
+        parts.add_start(@"Max: $max\n");
+        
+        // First
+        var first = numbers.as_enumerable().first();
+        parts.add_start(@"First: $first\n");
+        
+        // Last
+        var last = numbers.as_enumerable().last();
+        parts.add_start(@"Last: $last\n");
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Sorting examples
+    router.map_func("/sort", (context) => {
+        var numbers = Wrap.array<int>({5, 2, 8, 1, 9, 3, 7, 4, 6, 10});
+        
+        var parts = new Series<string>();
+        parts.add_start("Original: ");
+        parts.add_start(enumerable_int_to_string(numbers.as_enumerable()));
+        parts.add_start("\n\n");
+        
+        // Sort ascending
+        var sorted_asc = numbers.as_enumerable()
+            .order_by<int>(n => n);
+        parts.add_start("Ascending: ");
+        parts.add_start(enumerable_int_to_string(sorted_asc));
+        parts.add_start("\n");
+        
+        // Sort descending
+        var sorted_desc = numbers.as_enumerable()
+            .order_by_descending<int>(n => n);
+        parts.add_start("Descending: ");
+        parts.add_start(enumerable_int_to_string(sorted_desc));
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Set operations
+    router.map_func("/set-operations", (context) => {
+        var set1 = Wrap.array<int>({1, 2, 3, 4, 5});
+        var set2 = Wrap.array<int>({4, 5, 6, 7, 8});
+        
+        var parts = new Series<string>();
+        parts.add_start("Set 1: ");
+        parts.add_start(enumerable_int_to_string(set1.as_enumerable()));
+        parts.add_start("\n");
+        parts.add_start("Set 2: ");
+        parts.add_start(enumerable_int_to_string(set2.as_enumerable()));
+        parts.add_start("\n\n");
+        
+        // Union (concat)
+        var union = set1.as_enumerable().concat(set2.as_enumerable());
+        parts.add_start("Union: ");
+        parts.add_start(enumerable_int_to_string(union));
+        parts.add_start("\n");
+        
+        // Common (intersection)
+        var common = set1.as_enumerable().common(set2.as_enumerable());
+        parts.add_start("Common: ");
+        parts.add_start(enumerable_int_to_string(common));
+        parts.add_start("\n");
+        
+        // Non-common (difference)
+        var diff = set1.as_enumerable().non_common(set2.as_enumerable());
+        parts.add_start("In set1 but not set2: ");
+        parts.add_start(enumerable_int_to_string(diff));
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Advanced operations
+    router.map_func("/advanced", (context) => {
+        var numbers = Wrap.array<int>({1, 2, 3, 4, 5});
+        
+        var parts = new Series<string>();
+        
+        // Take
+        var taken = numbers.as_enumerable().take(3);
+        parts.add_start("Take 3: ");
+        parts.add_start(enumerable_int_to_string(taken));
+        parts.add_start("\n");
+        
+        // Skip
+        var skipped = numbers.as_enumerable().skip(2);
+        parts.add_start("Skip 2: ");
+        parts.add_start(enumerable_int_to_string(skipped));
+        parts.add_start("\n");
+        
+        // Distinct
+        var with_dupes = Wrap.array<int>({1, 2, 2, 3, 3, 3, 4});
+        var distinct = with_dupes.as_enumerable().distinct();
+        parts.add_start("Distinct from {1,2,2,3,3,3,4}: ");
+        parts.add_start(enumerable_int_to_string(distinct));
+        parts.add_start("\n");
+        
+        // Reverse
+        var reversed = numbers.as_enumerable().reverse();
+        parts.add_start("Reversed: ");
+        parts.add_start(enumerable_int_to_string(reversed));
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    print("Enumerable Operations Demo Server running on port 8087\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8087/\n");
+    print("  - http://localhost:8087/filter\n");
+    print("  - http://localhost:8087/map\n");
+    print("  - http://localhost:8087/group-by\n");
+    print("  - http://localhost:8087/aggregate\n");
+    print("  - http://localhost:8087/sort\n");
+    print("  - http://localhost:8087/set-operations\n");
+    print("  - http://localhost:8087/advanced\n");
+    
+    server.run();
+}
+
+// Helper functions
+
+string enumerable_int_to_string(Enumerable<int> enumerable) {
+    return enumerable.to_immutable_buffer()
+        .aggregate<string>("[", (acc, n) => {
+            if (acc == "[") return acc + n.to_string();
+            return acc + ", " + n.to_string();
+        }) + "]";
+}
+
+string enumerable_string_to_string(Enumerable<string> enumerable) {
+    return enumerable.to_immutable_buffer()
+        .aggregate<string>("[", (acc, s) => {
+            if (acc == "[") return acc + "\"" + s + "\"";
+            return acc + ", \"" + s + "\"";
+        }) + "]";
+}

+ 361 - 0
examples/ErrorHandling.vala

@@ -0,0 +1,361 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * ErrorHandling Example
+ * 
+ * Demonstrates error handling and status codes in Astralis.
+ * Uses Invercargill data structures for error logging and management.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8088, router);
+    
+    // Root endpoint
+    router.map_func("/", (context) => {
+        return new BufferedHttpResult.from_string(
+            """Error Handling Demo
+
+This example demonstrates various error handling patterns in Astralis:
+
+Endpoints:
+  GET /ok                    - 200 OK response
+  GET /not-found             - 404 Not Found
+  GET /bad-request           - 400 Bad Request
+  GET /internal-error         - 500 Internal Server Error
+  GET /validation            - Input validation example
+  GET /resource/{id}         - Resource lookup with error handling
+  GET /api/{endpoint}        - API endpoint simulation
+  GET /custom-error          - Custom error response
+  GET /timeout               - Simulated timeout
+  GET /unauthorized          - 401 Unauthorized
+  GET /forbidden             - 403 Forbidden
+  GET /method-not-allowed    - 405 Method Not Allowed
+"""
+        );
+    });
+    
+    // 200 OK response
+    router.map_func("/ok", (context) => {
+        return new BufferedHttpResult.from_string(
+            @"{ \"status\": \"success\", \"message\": \"Request completed successfully\" }",
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // 404 Not Found
+    router.map_func("/not-found", (context) => {
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"Not Found\", \"message\": \"The requested resource was not found\" }",
+            StatusCode.NOT_FOUND,
+            get_json_headers()
+        );
+    });
+    
+    // 400 Bad Request
+    router.map_func("/bad-request", (context) => {
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"Bad Request\", \"message\": \"The request could not be understood\" }",
+            StatusCode.BAD_REQUEST,
+            get_json_headers()
+        );
+    });
+    
+    // 500 Internal Server Error
+    router.map_func("/internal-error", (context) => {
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"Internal Server Error\", \"message\": \"An unexpected error occurred\" }",
+            StatusCode.INTERNAL_SERVER_ERROR,
+            get_json_headers()
+        );
+    });
+    
+    // Input validation example
+    router.map_func("/validation", (context) => {
+        var email = context.request.get_query("email");
+        var age_str = context.request.get_query("age");
+        
+        var error_list = new List<string>();
+        
+        if (email == null || !is_valid_email(email)) {
+            error_list.append("Invalid email address");
+        }
+        
+        if (age_str == null) {
+            error_list.append("Age parameter is required");
+        } else {
+            var age = int.parse(age_str);
+            if (age < 0 || age > 150) {
+                error_list.append("Age must be between 0 and 150");
+            }
+        }
+        
+        if (error_list.length() > 0) {
+            var json_parts = new StringBuilder();
+            json_parts.append("{ \"error\": \"Validation Failed\", \"errors\": [");
+            
+            bool first = true;
+            error_list.foreach((err) => {
+                if (!first) {
+                    json_parts.append(", ");
+                }
+                json_parts.append("\"");
+                json_parts.append(err);
+                json_parts.append("\"");
+                first = false;
+            });
+            
+            json_parts.append("] }");
+            
+            var json_string = json_parts.str;
+            
+            return new BufferedHttpResult.from_string(
+                json_string,
+                StatusCode.BAD_REQUEST,
+                get_json_headers()
+            );
+        }
+        
+        return new BufferedHttpResult.from_string(
+            @"{ \"status\": \"success\", \"email\": \"$email\", \"age\": $age_str }",
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Resource lookup with error handling
+    router.map_func("/resource/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 2) {
+            return error_response("Resource ID is required", StatusCode.BAD_REQUEST);
+        }
+        
+        var id_str = components[1];
+        int id;
+        
+        if (!int.try_parse(id_str, out id)) {
+            return error_response(@"Invalid resource ID: $id_str", StatusCode.BAD_REQUEST);
+        }
+        
+        // Simulate resource lookup using array
+        var resource_array = new Resource[3];
+        resource_array[0] = new Resource(1, "Resource One", "Description of resource one");
+        resource_array[1] = new Resource(2, "Resource Two", "Description of resource two");
+        resource_array[2] = new Resource(3, "Resource Three", "Description of resource three");
+        
+        Resource? resource = null;
+        foreach (var r in resource_array) {
+            if (r.id == id) {
+                resource = r;
+                break;
+            }
+        }
+        
+        if (resource == null) {
+            return error_response(@"Resource with ID $id not found", StatusCode.NOT_FOUND);
+        }
+        
+        return new BufferedHttpResult.from_string(
+            resource.to_json(),
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // API endpoint simulation
+    router.map_func("/api/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 2) {
+            return error_response("API endpoint required", StatusCode.BAD_REQUEST);
+        }
+        
+        var endpoint = components[1];
+        
+        // Simulate different API responses
+        switch (endpoint) {
+            case "users":
+                return new BufferedHttpResult.from_string(
+                    @"{ \"users\": [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}] }",
+                    StatusCode.OK,
+                    get_json_headers()
+                );
+            
+            case "posts":
+                return new BufferedHttpResult.from_string(
+                    @"{ \"posts\": [{\"id\": 1, \"title\": \"First Post\"}, {\"id\": 2, \"title\": \"Second Post\"}] }",
+                    StatusCode.OK,
+                    get_json_headers()
+                );
+            
+            case "invalid":
+                return error_response("Invalid API endpoint", StatusCode.NOT_FOUND);
+            
+            default:
+                return error_response(@"Unknown endpoint: $endpoint", StatusCode.NOT_FOUND);
+        }
+    });
+    
+    // Custom error response
+    router.map_func("/custom-error", (context) => {
+        var error_code = context.request.get_query_or_default("code", "CUSTOM_ERROR");
+        var message = context.request.get_query_or_default("message", "A custom error occurred");
+        
+        var timestamp = new DateTime.now_local().format_iso8601();
+        var request_id = generate_request_id();
+        var json = @"{
+  \"error\": \"$error_code\",
+  \"message\": \"$message\",
+  \"timestamp\": \"$timestamp\",
+  \"request_id\": \"$request_id\"
+}";
+        
+        return new BufferedHttpResult.from_string(
+            json,
+            StatusCode.BAD_REQUEST,
+            get_json_headers()
+        );
+    });
+    
+    // Simulated timeout (actually returns 408)
+    router.map_func("/timeout", (context) => {
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"Request Timeout\", \"message\": \"The request took too long to complete\" }",
+            StatusCode.INTERNAL_SERVER_ERROR,
+            get_json_headers()
+        );
+    });
+    
+    // 401 Unauthorized
+    router.map_func("/unauthorized", (context) => {
+        var auth_header = context.request.get_header("Authorization");
+        
+        if (auth_header == null) {
+            var headers = new Catalogue<string, string>();
+            headers.add("WWW-Authenticate", "Bearer realm=\"api\"");
+            
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Unauthorized\", \"message\": \"Authentication required\" }",
+                StatusCode.UNAUTHORIZED,
+                headers
+            );
+        }
+        
+        // Check auth token (simplified)
+        if (!auth_header.contains("valid-token")) {
+            var headers = new Catalogue<string, string>();
+            headers.add("WWW-Authenticate", "Bearer error=\"invalid_token\"");
+            
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Unauthorized\", \"message\": \"Invalid authentication token\" }",
+                StatusCode.UNAUTHORIZED,
+                headers
+            );
+        }
+        
+        return new BufferedHttpResult.from_string(
+            @"{ \"status\": \"success\", \"message\": \"Authenticated\" }",
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // 403 Forbidden
+    router.map_func("/forbidden", (context) => {
+        var user_role = context.request.get_cookie("role") ?? "guest";
+        
+        if (user_role != "admin") {
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Forbidden\", \"message\": \"You don't have permission to access this resource\" }",
+                StatusCode.FORBIDDEN,
+                get_json_headers()
+            );
+        }
+        
+        return new BufferedHttpResult.from_string(
+            @"{ \"status\": \"success\", \"message\": \"Admin access granted\" }",
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // 405 Method Not Allowed
+    router.map_func("/method-not-allowed", (context) => {
+        var headers = new Catalogue<string, string>();
+        headers.add("Allow", "POST, PUT, DELETE");
+        
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"Method Not Allowed\", \"message\": \"GET method is not allowed for this endpoint\" }",
+            StatusCode.METHOD_NOT_ALLOWED,
+            headers
+        );
+    });
+    
+    print("Error Handling Demo Server running on port 8088\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8088/\n");
+    print("  - http://localhost:8088/ok\n");
+    print("  - http://localhost:8088/not-found\n");
+    print("  - http://localhost:8088/bad-request\n");
+    print("  - http://localhost:8088/internal-error\n");
+    print("  - http://localhost:8088/validation?email=test&age=25\n");
+    print("  - http://localhost:8088/resource/1\n");
+    print("  - http://localhost:8088/resource/999\n");
+    print("  - http://localhost:8088/api/users\n");
+    print("  - http://localhost:8088/custom-error?code=TEST&message=Test+error\n");
+    print("  - http://localhost:8088/timeout\n");
+    print("  - http://localhost:8088/unauthorized\n");
+    print("  - http://localhost:8088/forbidden\n");
+    print("  - http://localhost:8088/method-not-allowed\n");
+    
+    server.run();
+}
+
+// Helper functions
+
+BufferedHttpResult error_response(string message, int status) {
+    var json = @"{ \"error\": \"Error\", \"message\": \"$message\" }";
+    return new BufferedHttpResult.from_string(
+        json,
+        status,
+        get_json_headers()
+    );
+}
+
+Catalogue<string, string> get_json_headers() {
+    var headers = new Catalogue<string, string>();
+    headers.add("Content-Type", "application/json");
+    return headers;
+}
+
+string generate_request_id() {
+    var timestamp = new DateTime.now_local().to_unix();
+    var random = Random.next_int();
+    return @"req-$timestamp-$random";
+}
+
+bool is_valid_email(string email) {
+    // Simple email validation
+    return email.contains("@") && email.contains(".") && email.length > 5;
+}
+
+// Helper classes
+
+class Resource {
+    public int id { get; private set; }
+    public string name { get; private set; }
+    public string description { get; private set; }
+    
+    public Resource(int id, string name, string description) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+    }
+    
+    public string to_json() {
+        return @"{ \"id\": $id, \"name\": \"$name\", \"description\": \"$description\" }";
+    }
+}

+ 554 - 0
examples/FormData.vala

@@ -0,0 +1,554 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * FormData Example
+ * 
+ * Demonstrates handling POST form data in Astralis using async HttpHandler.
+ * Shows both application/x-www-form-urlencoded and multipart/form-data handling.
+ */
+
+// HTML form page handler
+class FormPageHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) throws Error {
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "text/html");
+        return new BufferedHttpResult.from_string(
+            """<!DOCTYPE html>
+<html>
+<head>
+    <title>Form Data Example</title>
+    <style>
+        body { font-family: Arial, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; }
+        h1 { color: #333; }
+        .form-section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
+        label { display: block; margin: 10px 0 5px; font-weight: bold; }
+        input, textarea, select { width: 100%; padding: 8px; margin: 5px 0; box-sizing: border-box; }
+        button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; }
+        button:hover { background: #0056b3; }
+        a { color: #007bff; text-decoration: none; }
+        a:hover { text-decoration: underline; }
+    </style>
+</head>
+<body>
+    <h1>Form Data Examples</h1>
+    
+    <div class="form-section">
+        <h2>Simple Form (URL Encoded)</h2>
+        <form action="/submit-simple" method="POST">
+            <label for="name">Name:</label>
+            <input type="text" id="name" name="name" required>
+            
+            <label for="email">Email:</label>
+            <input type="email" id="email" name="email" required>
+            
+            <button type="submit">Submit</button>
+        </form>
+    </div>
+    
+    <div class="form-section">
+        <h2>Registration Form (URL Encoded)</h2>
+        <form action="/submit-register" method="POST">
+            <label for="username">Username:</label>
+            <input type="text" id="username" name="username" required>
+            
+            <label for="password">Password:</label>
+            <input type="password" id="password" name="password" required>
+            
+            <label for="age">Age:</label>
+            <input type="number" id="age" name="age" min="18" max="120">
+            
+            <label for="country">Country:</label>
+            <select id="country" name="country">
+                <option value="us">United States</option>
+                <option value="uk">United Kingdom</option>
+                <option value="nz">New Zealand</option>
+                <option value="au">Australia</option>
+            </select>
+            
+            <label for="bio">Bio:</label>
+            <textarea id="bio" name="bio" rows="4"></textarea>
+            
+            <label>
+                <input type="checkbox" name="newsletter" value="yes"> Subscribe to newsletter
+            </label>
+            
+            <button type="submit">Register</button>
+        </form>
+    </div>
+    
+    <div class="form-section">
+        <h2>Search Form (URL Encoded)</h2>
+        <form action="/submit-search" method="POST">
+            <label for="query">Search Query:</label>
+            <input type="text" id="query" name="query" required>
+            
+            <label for="category">Category:</label>
+            <select id="category" name="category">
+                <option value="">All Categories</option>
+                <option value="books">Books</option>
+                <option value="electronics">Electronics</option>
+                <option value="clothing">Clothing</option>
+            </select>
+            
+            <label for="min_price">Min Price:</label>
+            <input type="number" id="min_price" name="min_price" min="0" step="0.01">
+            
+            <label for="max_price">Max Price:</label>
+            <input type="number" id="max_price" name="max_price" min="0" step="0.01">
+            
+            <button type="submit">Search</button>
+        </form>
+    </div>
+    
+    <div class="form-section">
+        <h2>File Upload (Multipart)</h2>
+        <form action="/submit-file" method="POST" enctype="multipart/form-data">
+            <label for="description">Description:</label>
+            <input type="text" id="description" name="description">
+            
+            <label for="file">File:</label>
+            <input type="file" id="file" name="file">
+            
+            <button type="submit">Upload</button>
+        </form>
+    </div>
+    
+    <div class="form-section">
+        <h2>Links</h2>
+        <p><a href="/form-debug">Form Debug Tool</a></p>
+    </div>
+</body>
+</html>""",
+            StatusCode.OK,
+            headers
+        );
+    }
+}
+
+// Simple form submission handler
+class SimpleFormHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) throws Error {
+        if (!context.request.is_post()) {
+            return new BufferedHttpResult.from_string(
+                "Please use POST method",
+                StatusCode.METHOD_NOT_ALLOWED
+            );
+        }
+        
+        // Parse form data asynchronously from the request body
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var name = form_data.get_field_or_default("name", "Anonymous");
+        var email = form_data.get_field_or_default("email", "no-email@example.com");
+        
+        var parts = new Series<string>();
+        parts.add("Form Submission Received!\n");
+        parts.add("=========================\n\n");
+        parts.add(@"Name: $name\n");
+        parts.add(@"Email: $email\n");
+        parts.add("\nAll form data:\n");
+        
+        form_data.fields.to_immutable_buffer()
+            .iterate((grouping) => {
+                grouping.iterate((value) => {
+                    parts.add(@"  $(grouping.key): $value\n");
+                });
+            });
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    }
+}
+
+// Registration form submission handler
+class RegisterFormHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) throws Error {
+        if (!context.request.is_post()) {
+            return new BufferedHttpResult.from_string(
+                "Please use POST method",
+                StatusCode.METHOD_NOT_ALLOWED
+            );
+        }
+        
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var username = form_data.get_field("username");
+        var password = form_data.get_field("password");
+        var age_str = form_data.get_field_or_default("age", "0");
+        var country = form_data.get_field_or_default("country", "us");
+        var bio = form_data.get_field_or_default("bio", "");
+        var newsletter = form_data.get_field("newsletter");
+        
+        // Validation
+        if (username == null || username == "") {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Username is required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        if (password == null || password == "") {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Password is required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        var age = int.parse(age_str);
+        if (age < 18) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"You must be at least 18 years old\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        // Build JSON response using Series
+        var json_parts = new Series<string>();
+        json_parts.add(@"{ \"success\": true, \"user\": {");
+        json_parts.add(@"  \"username\": \"$username\",");
+        json_parts.add(@"  \"age\": $age,");
+        json_parts.add(@"  \"country\": \"$country\",");
+        json_parts.add(@"  \"bio\": \"$(bio.replace("\"", "\\\""))\",");
+        json_parts.add(@"  \"newsletter\": $(newsletter != null ? "true" : "false")");
+        json_parts.add(@"} }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    }
+}
+
+// Search form submission handler
+class SearchFormHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) throws Error {
+        if (!context.request.is_post()) {
+            return new BufferedHttpResult.from_string(
+                "Please use POST method",
+                StatusCode.METHOD_NOT_ALLOWED
+            );
+        }
+        
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var query = form_data.get_field("query");
+        var category = form_data.get_field_or_default("category", "");
+        var min_price = double.parse(form_data.get_field_or_default("min_price", "0"));
+        var max_price = double.parse(form_data.get_field_or_default("max_price", "999999"));
+        
+        if (query == null || query == "") {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Search query is required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        // Simulated search results using Enumerable operations
+        var all_products = new Series<Product>();
+        all_products.add(new Product(1, "Book A", "books", 15.99));
+        all_products.add(new Product(2, "Book B", "books", 24.99));
+        all_products.add(new Product(3, "Laptop", "electronics", 999.99));
+        all_products.add(new Product(4, "Phone", "electronics", 699.99));
+        all_products.add(new Product(5, "Shirt", "clothing", 29.99));
+        all_products.add(new Product(6, "Pants", "clothing", 49.99));
+        
+        // Filter results using Enumerable operations
+        var results = all_products.to_immutable_buffer()
+            .where(p => {
+                var matches_query = p.name.down().contains(query.down());
+                var matches_category = category == "" || p.category == category;
+                var matches_price = p.price >= min_price && p.price <= max_price;
+                return matches_query && matches_category && matches_price;
+            });
+        
+        var json_parts = new Series<string>();
+        json_parts.add(@"{ \"query\": \"$query\", \"category\": \"$category\", \"min_price\": $min_price, \"max_price\": $max_price, \"results\": [");
+        
+        bool first = true;
+        results.iterate((product) => {
+            if (!first) json_parts.add(", ");
+            json_parts.add(product.to_json());
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    }
+}
+
+// File upload handler (multipart/form-data)
+class FileUploadHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) throws Error {
+        if (!context.request.is_post()) {
+            return new BufferedHttpResult.from_string(
+                "Please use POST method",
+                StatusCode.METHOD_NOT_ALLOWED
+            );
+        }
+        
+        FormData form_data = yield FormDataParser.parse(
+            context.request.request_body,
+            context.request.content_type
+        );
+        
+        var description = form_data.get_field_or_default("description", "");
+        var file = form_data.get_file("file");
+        
+        var parts = new Series<string>();
+        parts.add("File Upload Result\n");
+        parts.add("==================\n\n");
+        parts.add(@"Description: $description\n");
+        
+        if (file != null) {
+            parts.add(@"\nFile Information:\n");
+            parts.add(@"  Field Name: $(file.field_name)\n");
+            parts.add(@"  Filename: $(file.filename)\n");
+            parts.add(@"  Content-Type: $(file.content_type)\n");
+            parts.add(@"  Size: $(file.data.length) bytes\n");
+        } else {
+            parts.add("\nNo file uploaded.\n");
+        }
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    }
+}
+
+// Form debug tool handler
+class FormDebugHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) throws Error {
+        FormData? form_data = null;
+        string? body_text = null;
+        
+        // Try to parse form data if this is a POST with form content
+        if (context.request.is_post() && 
+            (context.request.is_form_urlencoded() || context.request.is_multipart())) {
+            try {
+                form_data = yield FormDataParser.parse(
+                    context.request.request_body,
+                    context.request.content_type
+                );
+            } catch (Error e) {
+                body_text = @"Error parsing form data: $(e.message)";
+            }
+        }
+        
+        // Get counts (uint type)
+        uint field_count = form_data != null ? form_data.field_count() : 0;
+        uint file_count = form_data != null ? form_data.file_count() : 0;
+        
+        var parts = new Series<string>();
+        parts.add("""<!DOCTYPE html>
+<html>
+<head>
+    <title>Form Debug Tool</title>
+    <style>
+        body { font-family: monospace; max-width: 800px; margin: 40px auto; padding: 20px; background: #f5f5f5; }
+        .section { background: white; padding: 20px; margin: 20px 0; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
+        h1 { color: #333; }
+        h2 { color: #666; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
+        table { width: 100%; border-collapse: collapse; margin: 10px 0; }
+        th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
+        th { background: #007bff; color: white; }
+        tr:hover { background: #f9f9f9; }
+        .empty { color: #999; font-style: italic; }
+        a { color: #007bff; text-decoration: none; }
+        a:hover { text-decoration: underline; }
+        form { margin: 20px 0; }
+        input, textarea { width: 100%; padding: 8px; margin: 5px 0; box-sizing: border-box; }
+        button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; }
+    </style>
+</head>
+<body>
+    <h1>Form Debug Tool</h1>
+    
+    <div class="section">
+        <h2>Test Form</h2>
+        <form action="/form-debug" method="POST" enctype="multipart/form-data">
+            <label>Text Field: <input type="text" name="test_field" value="test value"></label>
+            <label>File: <input type="file" name="test_file"></label>
+            <button type="submit">Submit Test</button>
+        </form>
+    </div>
+    
+    <div class="section">
+        <h2>Request Information</h2>
+        <table>
+            <tr><th>Method</th><td>$(context.request.method)</td></tr>
+            <tr><th>Path</th><td>$(context.request.raw_path)</td></tr>
+            <tr><th>Content Type</th><td>$(context.request.content_type)</td></tr>
+            <tr><th>Content Length</th><td>$(context.request.content_length)</td></tr>
+            <tr><th>Is Form URL Encoded</th><td>$(context.request.is_form_urlencoded())</td></tr>
+            <tr><th>Is Multipart</th><td>$(context.request.is_multipart())</td></tr>
+        </table>
+    </div>
+""");
+        
+        if (form_data != null) {
+            parts.add(@"    <div class=\"section\">\n");
+            parts.add(@"        <h2>Form Fields ($field_count fields)</h2>\n");
+            
+            if (field_count == 0) {
+                parts.add(@"        <p class=\"empty\">No form fields received.</p>\n");
+            } else {
+                parts.add(@"        <table>\n");
+                parts.add(@"            <tr><th>Field Name</th><th>Value</th></tr>\n");
+                
+                form_data.fields.to_immutable_buffer()
+                    .iterate((grouping) => {
+                        grouping.iterate((value) => {
+                            parts.add(@"            <tr><td>$(grouping.key)</td><td>$(value.escape(""))</td></tr>\n");
+                        });
+                    });
+                
+                parts.add(@"        </table>\n");
+            }
+            
+            parts.add(@"    </div>\n");
+            
+            parts.add(@"    <div class=\"section\">\n");
+            parts.add(@"        <h2>File Uploads ($file_count files)</h2>\n");
+            
+            if (file_count == 0) {
+                parts.add(@"        <p class=\"empty\">No files uploaded.</p>\n");
+            } else {
+                parts.add(@"        <table>\n");
+                parts.add(@"            <tr><th>Field Name</th><th>Filename</th><th>Content-Type</th><th>Size</th></tr>\n");
+                
+                form_data.files.to_immutable_buffer()
+                    .iterate((grouping) => {
+                        grouping.iterate((file) => {
+                            parts.add(@"            <tr><td>$(file.field_name)</td><td>$(file.filename)</td><td>$(file.content_type)</td><td>$(file.data.length) bytes</td></tr>\n");
+                        });
+                    });
+                
+                parts.add(@"        </table>\n");
+            }
+            
+            parts.add(@"    </div>\n");
+        } else if (body_text != null) {
+            parts.add(@"    <div class=\"section\">\n");
+            parts.add(@"        <h2>Error</h2>\n");
+            parts.add(@"        <pre>$(body_text.escape(""))</pre>\n");
+            parts.add(@"    </div>\n");
+        }
+        
+        parts.add("""    <div class="section">
+        <h2>Query Parameters</h2>
+""");
+        
+        var query_count = context.request.query_params.to_immutable_buffer().count();
+        if (query_count == 0) {
+            parts.add(@"        <p class=\"empty\">No query parameters.</p>\n");
+        } else {
+            parts.add(@"        <table>\n");
+            parts.add(@"            <tr><th>Parameter</th><th>Value</th></tr>\n");
+            
+            context.request.query_params.to_immutable_buffer()
+                .iterate((grouping) => {
+                    grouping.iterate((value) => {
+                        parts.add(@"            <tr><td>$(grouping.key)</td><td>$(value.escape(""))</td></tr>\n");
+                    });
+                });
+            
+            parts.add(@"        </table>\n");
+        }
+        
+        parts.add("""    </div>
+    
+    <p><a href="/">Back to Forms</a></p>
+</body>
+</html>""");
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "text/html");
+        return new BufferedHttpResult.from_string(
+            result,
+            StatusCode.OK,
+            headers
+        );
+    }
+}
+
+// Helper class for product data
+class Product {
+    public int id { get; private set; }
+    public string name { get; private set; }
+    public string category { get; private set; }
+    public double price { get; private set; }
+    
+    public Product(int id, string name, string category, double price) {
+        this.id = id;
+        this.name = name;
+        this.category = category;
+        this.price = price;
+    }
+    
+    public string to_json() {
+        return @"{ \"id\": $id, \"name\": \"$name\", \"category\": \"$category\", \"price\": $price }";
+    }
+}
+
+void main() {
+    var router = new Router();
+    var server = new Server(8084, router);
+    
+    // Register handlers
+    router.map("/", new FormPageHandler());
+    router.map("/submit-simple", new SimpleFormHandler());
+    router.map("/submit-register", new RegisterFormHandler());
+    router.map("/submit-search", new SearchFormHandler());
+    router.map("/submit-file", new FileUploadHandler());
+    router.map("/form-debug", new FormDebugHandler());
+    
+    print("Form Data Example Server running on port 8084\n");
+    print("Open http://localhost:8084/ in your browser to try the forms\n");
+    
+    server.run();
+}

+ 346 - 0
examples/HeadersAndCookies.vala

@@ -0,0 +1,346 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * HeadersAndCookies Example
+ * 
+ * Demonstrates how to access and manipulate HTTP headers and cookies.
+ * Uses Invercargill Dictionary and Enumerable for header/cookie processing.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8083, router);
+    
+    // Display all request headers
+    router.map_func("/headers", (context) => {
+        var parts = new Series<string>();
+        parts.add("Request Headers:\n");
+        parts.add("================\n\n");
+        
+        context.request.headers.to_immutable_buffer()
+            .iterate((grouping) => {
+                grouping.iterate((value) => {
+                    parts.add(@"$(grouping.key): $value\n");
+                });
+            });
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Check for specific header
+    router.map_func("/user-agent", (context) => {
+        var user_agent = context.request.user_agent ?? "Unknown";
+        
+        return new BufferedHttpResult.from_string(
+            @"Your User-Agent is: $user_agent"
+        );
+    });
+    
+    // Check content type and content length
+    router.map_func("/content-info", (context) => {
+        var parts = new Series<string>();
+        parts.add("Content Information:\n");
+        parts.add("===================\n\n");
+        parts.add(@"Content-Type: $(context.request.content_type)\n");
+        parts.add(@"Content-Length: $(context.request.content_length)\n");
+        parts.add(@"Content-Encoding: $(context.request.content_encoding ?? "none")\n");
+        //  parts.add(@"Has Body: $(context.request.has_body())\n");
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Check if header exists
+    router.map_func("/check-header", (context) => {
+        var header_name = context.request.get_query_or_default("name", "Accept");
+        var exists = context.request.has_header(header_name);
+        var value = context.request.get_header(header_name);
+        
+        return new BufferedHttpResult.from_string(
+            @"Header '$header_name': $(exists ? "EXISTS" : "NOT FOUND")\nValue: $(value ?? "N/A")"
+        );
+    });
+    
+    // Set custom response headers
+    router.map_func("/custom-headers", (context) => {
+        var headers = new Catalogue<string, string>();
+        headers.add("X-Custom-Header", "Custom-Value");
+        headers.add("X-Request-Id", generate_request_id());
+        headers.add("X-Powered-By", "Astralis");
+        headers.add("X-Server-Time", new DateTime.now_local().format_iso8601());
+        
+        return new BufferedHttpResult.from_string(
+            "Response with custom headers!",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Display all cookies
+    router.map_func("/cookies", (context) => {
+        var parts = new Series<string>();
+        parts.add("Request Cookies:\n");
+        parts.add("================\n\n");
+        
+        if (context.request.cookies.to_immutable_buffer().count() == 0) {
+            parts.add("(No cookies sent)\n");
+            parts.add("\nTry setting a cookie first: /set-cookie?name=test&value=123\n");
+        } else {
+            context.request.cookies.to_immutable_buffer()
+                .iterate((grouping) => {
+                    grouping.iterate((value) => {
+                        parts.add(@"$(grouping.key): $value\n");
+                    });
+                });
+        }
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Get specific cookie
+    router.map_func("/get-cookie", (context) => {
+        var name = context.request.get_query_or_default("name", "session");
+        var value = context.request.get_cookie(name);
+        
+        if (value == null) {
+            return new BufferedHttpResult.from_string(
+                @"Cookie '$name' not found",
+                StatusCode.NOT_FOUND
+            );
+        }
+        
+        return new BufferedHttpResult.from_string(
+            @"Cookie '$name' = '$value'"
+        );
+    });
+    
+    // Set a cookie (via Set-Cookie header)
+    router.map_func("/set-cookie", (context) => {
+        var name = context.request.get_query_or_default("name", "test");
+        var value = context.request.get_query_or_default("value", "123");
+        var max_age = context.request.get_query_or_default("max_age", "3600");
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Set-Cookie", @"$name=$value; Max-Age=$max_age; Path=/");
+        
+        return new BufferedHttpResult.from_string(
+            @"Cookie '$name' set to '$value'",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Set multiple cookies
+    router.map_func("/set-cookies", (context) => {
+        var headers = new Catalogue<string, string>();
+        headers.add("Set-Cookie", "user=john; Max-Age=3600; Path=/");
+        headers.add("Set-Cookie", "theme=dark; Max-Age=86400; Path=/");
+        headers.add("Set-Cookie", "lang=en; Max-Age=31536000; Path=/");
+        
+        return new BufferedHttpResult.from_string(
+            "Multiple cookies set!",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Delete a cookie
+    router.map_func("/delete-cookie", (context) => {
+        var name = context.request.get_query_or_default("name", "test");
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Set-Cookie", @"$name=; Max-Age=0; Path=/");
+        
+        return new BufferedHttpResult.from_string(
+            @"Cookie '$name' deleted",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Check if cookie exists
+    router.map_func("/has-cookie", (context) => {
+        var name = context.request.get_query_or_default("name", "session");
+        var exists = context.request.has_cookie(name);
+        
+        return new BufferedHttpResult.from_string(
+            @"Cookie '$name': $(exists ? "EXISTS" : "NOT FOUND")"
+        );
+    });
+    
+    // Cookie-based session simulation
+    router.map_func("/session", (context) => {
+        var session_id = context.request.get_cookie("session_id");
+        
+        if (session_id == null) {
+            // Create new session
+            var new_session_id = generate_session_id();
+            
+            var headers = new Catalogue<string, string>();
+            headers.add("Set-Cookie", @"session_id=$new_session_id; Max-Age=3600; Path=/; HttpOnly");
+            
+            return new BufferedHttpResult.from_string(
+                @"New session created!
+Session ID: $new_session_id
+
+Your session will expire in 1 hour.",
+                StatusCode.OK,
+                headers
+            );
+        }
+        
+        // Existing session
+        return new BufferedHttpResult.from_string(
+            @"Welcome back!
+Session ID: $session_id
+
+Your session is active."
+        );
+    });
+    
+    // CORS headers example
+    router.map_func("/cors", (context) => {
+        var origin = context.request.get_header("Origin") ?? "*";
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Access-Control-Allow-Origin", origin);
+        headers.add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+        headers.add("Access-Control-Allow-Headers", "Content-Type, Authorization");
+        headers.add("Access-Control-Max-Age", "86400");
+        
+        return new BufferedHttpResult.from_string(
+            @"CORS enabled for origin: $origin",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // OPTIONS method for CORS preflight
+    router.map_func("/cors", (context) => {
+        var origin = context.request.get_header("Origin") ?? "*";
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Access-Control-Allow-Origin", origin);
+        headers.add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
+        headers.add("Access-Control-Allow-Headers", "Content-Type, Authorization");
+        headers.add("Access-Control-Max-Age", "86400");
+        headers.add("Content-Length", "0");
+        
+        return new BufferedHttpResult.from_string(
+            "",
+            StatusCode.NO_CONTENT,
+            headers
+        );
+    });
+    
+    // Cache control headers
+    router.map_func("/cache", (context) => {
+        var cache_type = context.request.get_query_or_default("type", "public");
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Cache-Control", @"$cache_type, max-age=3600");
+        headers.add("Expires", get_expires_header(3600));
+        headers.add("ETag", generate_etag());
+        
+        return new BufferedHttpResult.from_string(
+            @"This response is cached ($cache_type cache, 1 hour)",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Content negotiation
+    router.map_func("/negotiate", (context) => {
+        var accept = context.request.get_header("Accept") ?? "*/*";
+        
+        var headers = new Catalogue<string, string>();
+        
+        if (accept.contains("application/json")) {
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"message\": \"JSON response\", \"format\": \"json\" }",
+                StatusCode.OK,
+                headers
+            );
+        } else if (accept.contains("text/xml")) {
+            headers.add("Content-Type", "text/xml");
+            return new BufferedHttpResult.from_string(
+                @"<?xml version=\"1.0\"?><response><message>XML response</message><format>xml</format></response>",
+                StatusCode.OK,
+                headers
+            );
+        } else {
+            headers.add("Content-Type", "text/plain");
+            return new BufferedHttpResult.from_string(
+                "Plain text response",
+                StatusCode.OK,
+                headers
+            );
+        }
+    });
+    
+    // Security headers
+    router.map_func("/secure", (context) => {
+        var headers = new Catalogue<string, string>();
+        headers.add("X-Content-Type-Options", "nosniff");
+        headers.add("X-Frame-Options", "DENY");
+        headers.add("X-XSS-Protection", "1; mode=block");
+        headers.add("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
+        headers.add("Content-Security-Policy", "default-src 'self'");
+        headers.add("Referrer-Policy", "strict-origin-when-cross-origin");
+        
+        return new BufferedHttpResult.from_string(
+            "Response with security headers!",
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    print("Headers and Cookies Example Server running on port 8083\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8083/headers\n");
+    print("  - http://localhost:8083/user-agent\n");
+    print("  - http://localhost:8083/cookies\n");
+    print("  - http://localhost:8083/set-cookie?name=test&value=hello\n");
+    print("  - http://localhost:8083/set-cookies\n");
+    print("  - http://localhost:8083/session\n");
+    print("  - http://localhost:8083/cors\n");
+    print("  - http://localhost:8083/cache\n");
+    print("  - http://localhost:8083/negotiate\n");
+    print("  - http://localhost:8083/secure\n");
+    
+    server.run();
+}
+
+// Helper functions
+string generate_request_id() {
+    var timestamp = new DateTime.now_local().to_unix();
+    var random = Random.next_int();
+    return @"req-$timestamp-$random";
+}
+
+string generate_session_id() {
+    var timestamp = new DateTime.now_local().to_unix();
+    var random = Random.next_int();
+    return @"sess-$timestamp-$random";
+}
+
+string generate_etag() {
+    var timestamp = new DateTime.now_local().to_unix();
+    return @"\"$timestamp\"";
+}
+
+string get_expires_header(int seconds) {
+    var now = new DateTime.now_local();
+    var expires = now.add_seconds(seconds);
+    return expires.format("%a, %d %b %Y %H:%M:%S GMT");
+}

+ 519 - 0
examples/JsonApi.vala

@@ -0,0 +1,519 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * JsonApi Example
+ * 
+ * Demonstrates building a JSON API with proper headers.
+ * Uses Invercargill data structures for data management and
+ * Enumerable for JSON serialization.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8085, router);
+    
+    // API root
+    router.map_func("/api", (context) => {
+        var json = @"{
+  \"name\": \"Astralis JSON API\",
+  \"version\": \"1.0.0\",
+  \"endpoints\": {
+    \"users\": \"/api/users\",
+    \"posts\": \"/api/posts\",
+    \"comments\": \"/api/comments\",
+    \"stats\": \"/api/stats\"
+  }
+}";
+        
+        return new BufferedHttpResult.from_string(
+            json,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // List all users
+    router.map_func("/api/users", (context) => {
+        var users = get_users();
+        
+        var json_parts = new Series<string>();
+        json_parts.add_start(@"{ \"users\": [");
+        
+        bool first = true;
+        users.to_immutable_buffer().iterate((user) => {
+            if (!first) json_parts.add_start(", ");
+            json_parts.add_start(user.to_json());
+            first = false;
+        });
+        
+        var user_count = users.to_immutable_buffer().count();
+        json_parts.add("], ");
+        json_parts.add("\"count\": " + user_count.to_string() + " }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Get user by ID
+    router.map_func("/api/users/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 3) {
+            return error_response("User ID required");
+        }
+        
+        var id = int.parse(components[2]);
+        var user = find_user(id);
+        
+        if (user == null) {
+            return error_response(@"User with ID $id not found", StatusCode.NOT_FOUND);
+        }
+        
+        return new BufferedHttpResult.from_string(
+            user.to_json(),
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // List all posts
+    router.map_func("/api/posts", (context) => {
+        var posts = get_posts();
+        
+        // Filter by user_id if provided
+        var user_id_param = context.request.get_query("user_id");
+        if (user_id_param != null) {
+            var user_id = int.parse(user_id_param);
+            posts = posts.to_immutable_buffer()
+                .where(p => p.user_id == user_id)
+                .to_series();
+        }
+        
+        var json_parts = new Series<string>();
+        json_parts.add_start(@"{ \"posts\": [");
+        
+        bool first = true;
+        posts.to_immutable_buffer().iterate((post) => {
+            if (!first) json_parts.add_start(", ");
+            json_parts.add_start(post.to_json());
+            first = false;
+        });
+        
+        var post_count = posts.to_immutable_buffer().count();
+        json_parts.add("], ");
+        json_parts.add("\"count\": " + post_count.to_string() + " }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Get post by ID
+    router.map_func("/api/posts/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 3) {
+            return error_response("Post ID required");
+        }
+        
+        var id = int.parse(components[2]);
+        var post = find_post(id);
+        
+        if (post == null) {
+            return error_response(@"Post with ID $id not found", StatusCode.NOT_FOUND);
+        }
+        
+        // Include user and comments in response
+        var user = find_user(post.user_id);
+        var comments = get_comments_for_post(id);
+        
+        var json_parts = new Series<string>();
+        json_parts.add_start(@"{ \"post\": $(post.to_json()), ");
+        
+        if (user != null) {
+            json_parts.add_start("\"user\": " + user.to_json() + ", ");
+        } else {
+            json_parts.add_start("\"user\": null, ");
+        }
+        
+        json_parts.add_start("\"comments\": [");
+        
+        bool first = true;
+        comments.to_immutable_buffer().iterate((comment) => {
+            if (!first) json_parts.add_start(", ");
+            json_parts.add_start(comment.to_json());
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // List all comments
+    router.map_func("/api/comments", (context) => {
+        var comments = get_comments();
+        
+        // Filter by post_id if provided
+        var post_id_param = context.request.get_query("post_id");
+        if (post_id_param != null) {
+            var post_id = int.parse(post_id_param);
+            comments = comments.to_immutable_buffer()
+                .where(c => c.post_id == post_id)
+                .to_series();
+        }
+        
+        var json_parts = new Series<string>();
+        json_parts.add_start(@"{ \"comments\": [");
+        
+        bool first = true;
+        comments.to_immutable_buffer().iterate((comment) => {
+            if (!first) json_parts.add_start(", ");
+            json_parts.add_start(comment.to_json());
+            first = false;
+        });
+        
+        var comment_count = comments.to_immutable_buffer().count();
+        json_parts.add("], ");
+        json_parts.add("\"count\": " + comment_count.to_string() + " }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Get comment by ID
+    router.map_func("/api/comments/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 3) {
+            return error_response("Comment ID required");
+        }
+        
+        var id = int.parse(components[2]);
+        var comment = find_comment(id);
+        
+        if (comment == null) {
+            return error_response(@"Comment with ID $id not found", StatusCode.NOT_FOUND);
+        }
+        
+        return new BufferedHttpResult.from_string(
+            comment.to_json(),
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // API statistics
+    router.map_func("/api/stats", (context) => {
+        var users = get_users();
+        var posts = get_posts();
+        var comments = get_comments();
+        
+        var user_count = users.to_immutable_buffer().count();
+        var post_count = posts.to_immutable_buffer().count();
+        var comment_count = comments.to_immutable_buffer().count();
+        var posts_per_user = post_count / (double)user_count;
+        var comments_per_post = comment_count / (double)post_count;
+        
+        var json = @"{
+  \"users\": $user_count,
+  \"posts\": $post_count,
+  \"comments\": $comment_count,
+  \"posts_per_user\": $posts_per_user,
+  \"comments_per_post\": $comments_per_post
+}";
+        
+        return new BufferedHttpResult.from_string(
+            json,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Search endpoint
+    router.map_func("/api/search", (context) => {
+        var query = context.request.get_query("q");
+        
+        if (query == null || query == "") {
+            return error_response("Query parameter 'q' is required");
+        }
+        
+        var results = new Series<SearchResult>();
+        
+        // Search users
+        get_users().to_immutable_buffer()
+            .where(u => u.name.down().contains(query.down()))
+            .iterate((user) => {
+                results.add_start(new SearchResult("user", user.id.to_string(), user.name));
+            });
+        
+        // Search posts
+        get_posts().to_immutable_buffer()
+            .where(p => p.title.down().contains(query.down()) || p.content.down().contains(query.down()))
+            .iterate((post) => {
+                results.add_start(new SearchResult("post", post.id.to_string(), post.title));
+            });
+        
+        var json_parts = new Series<string>();
+        json_parts.add_start(@"{ \"query\": \"$query\", \"results\": [");
+        
+        bool first = true;
+        results.to_immutable_buffer().iterate((result) => {
+            if (!first) json_parts.add_start(", ");
+            json_parts.add_start(result.to_json());
+            first = false;
+        });
+        
+        var result_count = results.to_immutable_buffer().count();
+        json_parts.add("], ");
+        json_parts.add("\"count\": " + result_count.to_string() + " }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Pagination example
+    router.map_func("/api/posts/paginated", (context) => {
+        var page = int.parse(context.request.get_query_or_default("page", "1"));
+        var per_page = int.parse(context.request.get_query_or_default("per_page", "10"));
+        
+        var posts = get_posts();
+        var total = posts.to_immutable_buffer().count();
+        var total_pages = (total + per_page - 1) / per_page;
+        
+        // Calculate pagination
+        var offset = (page - 1) * per_page;
+        var paginated_posts = posts.to_immutable_buffer()
+            .skip(offset)
+            .take(per_page);
+        
+        var json_parts = new Series<string>();
+        json_parts.add_start(@"{ \"page\": $page, \"per_page\": $per_page, \"total\": $total, \"total_pages\": $total_pages, \"posts\": [");
+        
+        bool first = true;
+        paginated_posts.iterate((post) => {
+            if (!first) json_parts.add_start(", ");
+            json_parts.add_start(post.to_json());
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            get_json_headers()
+        );
+    });
+    
+    // Error handling endpoint
+    router.map_func("/api/error", (context) => {
+        var error_type = context.request.get_query_or_default("type", "generic");
+        
+        switch (error_type) {
+            case "not_found":
+                return error_response("Resource not found", StatusCode.NOT_FOUND);
+            case "bad_request":
+                return error_response("Invalid request parameters", StatusCode.BAD_REQUEST);
+            case "server_error":
+                return error_response("Internal server error occurred", StatusCode.INTERNAL_SERVER_ERROR);
+            default:
+                return error_response("An error occurred");
+        }
+    });
+    
+    print("JSON API Example Server running on port 8085\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8085/api\n");
+    print("  - http://localhost:8085/api/users\n");
+    print("  - http://localhost:8085/api/users/1\n");
+    print("  - http://localhost:8085/api/posts\n");
+    print("  - http://localhost:8085/api/posts?user_id=1\n");
+    print("  - http://localhost:8085/api/posts/1\n");
+    print("  - http://localhost:8085/api/comments\n");
+    print("  - http://localhost:8085/api/comments?post_id=1\n");
+    print("  - http://localhost:8085/api/stats\n");
+    print("  - http://localhost:8085/api/search?q=test\n");
+    print("  - http://localhost:8085/api/posts/paginated?page=1&per_page=5\n");
+    
+    server.run();
+}
+
+// Helper functions
+
+Catalogue<string, string> get_json_headers() {
+    var headers = new Catalogue<string, string>();
+    headers.add("Content-Type", "application/json");
+    headers.add("Access-Control-Allow-Origin", "*");
+    return headers;
+}
+
+BufferedHttpResult error_response(string message, StatusCode status = StatusCode.BAD_REQUEST) {
+    var json = @"{ \"error\": \"$message\" }";
+    return new BufferedHttpResult.from_string(
+        json,
+        status,
+        get_json_headers()
+    );
+}
+
+// Data models and storage
+
+class User {
+    public int id { get; private set; }
+    public string name { get; private set; }
+    public string email { get; private set; }
+    
+    public User(int id, string name, string email) {
+        this.id = id;
+        this.name = name;
+        this.email = email;
+    }
+    
+    public string to_json() {
+        return @"{ \"id\": $id, \"name\": \"$name\", \"email\": \"$email\" }";
+    }
+}
+
+class Post {
+    public int id { get; private set; }
+    public int user_id { get; private set; }
+    public string title { get; private set; }
+    public string content { get; private set; }
+    
+    public Post(int id, int user_id, string title, string content) {
+        this.id = id;
+        this.user_id = user_id;
+        this.title = title;
+        this.content = content;
+    }
+    
+    public string to_json() {
+        var escaped_content = content.replace("\"", "\\\"").replace("\n", "\\n");
+        return @"{ \"id\": $id, \"user_id\": $user_id, \"title\": \"$title\", \"content\": \"$escaped_content\" }";
+    }
+}
+
+class Comment {
+    public int id { get; private set; }
+    public int post_id { get; private set; }
+    public string author { get; private set; }
+    public string text { get; private set; }
+    
+    public Comment(int id, int post_id, string author, string text) {
+        this.id = id;
+        this.post_id = post_id;
+        this.author = author;
+        this.text = text;
+    }
+    
+    public string to_json() {
+        var escaped_text = text.replace("\"", "\\\"").replace("\n", "\\n");
+        return @"{ \"id\": $id, \"post_id\": $post_id, \"author\": \"$author\", \"text\": \"$escaped_text\" }";
+    }
+}
+
+class SearchResult {
+    public string result_type { get; private set; }
+    public string id { get; private set; }
+    public string title { get; private set; }
+    
+    public SearchResult(string result_type, string id, string title) {
+        this.result_type = result_type;
+        this.id = id;
+        this.title = title;
+    }
+    
+    public string to_json() {
+        return @"{ \"type\": \"$result_type\", \"id\": \"$id\", \"title\": \"$title\" }";
+    }
+}
+
+// Data storage functions
+
+Series<User> get_users() {
+    var users = new Series<User>();
+    users.add_start(new User(1, "Alice Johnson", "alice@example.com"));
+    users.add_start(new User(2, "Bob Smith", "bob@example.com"));
+    users.add_start(new User(3, "Charlie Brown", "charlie@example.com"));
+    users.add_start(new User(4, "Diana Prince", "diana@example.com"));
+    users.add_start(new User(5, "Eve Davis", "eve@example.com"));
+    return users;
+}
+
+Series<Post> get_posts() {
+    var posts = new Series<Post>();
+    posts.add_start(new Post(1, 1, "First Post", "This is my first post!"));
+    posts.add_start(new Post(2, 1, "Second Post", "Another day, another post."));
+    posts.add_start(new Post(3, 2, "Hello World", "Just saying hello to everyone."));
+    posts.add_start(new Post(4, 3, "Vala Programming", "Vala is a great programming language."));
+    posts.add_start(new Post(5, 4, "Web Development", "Building web applications is fun."));
+    posts.add_start(new Post(6, 5, "API Design", "RESTful APIs are the way to go."));
+    return posts;
+}
+
+Series<Comment> get_comments() {
+    var comments = new Series<Comment>();
+    comments.add_start(new Comment(1, 1, "Bob", "Great first post!"));
+    comments.add_start(new Comment(2, 1, "Charlie", "Welcome to the platform!"));
+    comments.add_start(new Comment(3, 2, "Alice", "Keep posting!"));
+    comments.add_start(new Comment(4, 3, "Alice", "Hello to you too!"));
+    comments.add_start(new Comment(5, 4, "Bob", "I agree!"));
+    comments.add_start(new Comment(6, 5, "Charlie", "Good point!"));
+    return comments;
+}
+
+Series<Comment> get_comments_for_post(int post_id) {
+    return get_comments().to_immutable_buffer()
+        .where(c => c.post_id == post_id)
+        .to_series();
+}
+
+User? find_user(int id) {
+    return get_users().to_immutable_buffer()
+        .first_or_default(u => u.id == id);
+}
+
+Post? find_post(int id) {
+    return get_posts().to_immutable_buffer()
+        .first_or_default(p => p.id == id);
+}
+
+Comment? find_comment(int id) {
+    return get_comments().to_immutable_buffer()
+        .first_or_default(c => c.id == id);
+}

+ 441 - 0
examples/PathRouting.vala

@@ -0,0 +1,441 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * PathRouting Example
+ * 
+ * Demonstrates path component parsing and routing in Astralis.
+ * Uses Invercargill Enumerable for path processing.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8082, router);
+    
+    // Root path
+    router.map_func("/", (context) => {
+        return new BufferedHttpResult.from_string(
+            """Welcome to the Path Routing Example!
+            
+Available endpoints:
+  GET /                           - This message
+  GET /hello                      - Simple greeting
+  GET /hello/{name}               - Greeting with name
+  GET /users                      - List all users
+  GET /users/{id}                 - Get user by ID
+  GET /users/{id}/posts           - Get posts for a user
+  GET /api/v1/status              - API status
+  GET /api/v1/items/{id}         - Get item by ID
+  GET /files/{category}/{name}    - Get file by category and name
+"""
+        );
+    });
+    
+    // Simple path
+    router.map_func("/hello", (context) => {
+        return new BufferedHttpResult.from_string("Hello, World!");
+    });
+    
+    // Path with one component (simulated dynamic routing)
+    router.map_func("/hello/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 2) {
+            return new BufferedHttpResult.from_string(
+                "Please provide a name: /hello/{name}",
+                StatusCode.BAD_REQUEST
+            );
+        }
+        
+        var name = components[1];
+        return new BufferedHttpResult.from_string(@"Hello, $name!");
+    });
+    
+    // Users list endpoint
+    router.map_func("/users", (context) => {
+        // Simulated user data using Invercargill data structures
+        var users = new Series<User>();
+        users.add(new User(1, "Alice", "alice@example.com"));
+        users.add(new User(2, "Bob", "bob@example.com"));
+        users.add(new User(3, "Charlie", "charlie@example.com"));
+        
+        var json_parts = new Series<string>();
+        json_parts.add(@"{ \"users\": [");
+        
+        bool first = true;
+        users.to_immutable_buffer().iterate((user) => {
+            if (!first) json_parts.add(", ");
+            json_parts.add(user.to_json());
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // User by ID endpoint (simulated)
+    router.map_func("/users/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 2) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"User ID required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        var id_str = components[1];
+        var id = int.parse(id_str);
+        
+        // Simulated user lookup
+        var users = new Series<User>();
+        users.add(new User(1, "Alice", "alice@example.com"));
+        users.add(new User(2, "Bob", "bob@example.com"));
+        users.add(new User(3, "Charlie", "charlie@example.com"));
+        
+        var user = users.to_immutable_buffer()
+            .first_or_default(u => u.id == id);
+        
+        if (user == null) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"User not found\" }",
+                StatusCode.NOT_FOUND,
+                headers
+            );
+        }
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            user.to_json(),
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // User posts endpoint (nested path)
+    router.map_func("/users/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        // Check for /users/{id}/posts pattern
+        if (components.count() >= 3 && components[2] == "posts") {
+            var id_str = components[1];
+            var id = int.parse(id_str);
+            
+            // Simulated posts for user
+            var posts = new Series<Post>();
+            posts.add(new Post(101, id, "First Post", "This is my first post"));
+            posts.add(new Post(102, id, "Second Post", "This is my second post"));
+            
+            var json_parts = new Series<string>();
+            json_parts.add(@"{ \"user_id\": $id, \"posts\": [");
+            
+            bool first = true;
+            posts.to_immutable_buffer().iterate((post) => {
+                if (!first) json_parts.add(", ");
+                json_parts.add(post.to_json());
+                first = false;
+            });
+            
+            json_parts.add("] }");
+            
+            var json_string = json_parts.to_immutable_buffer()
+                .aggregate<string>("", (acc, s) => acc + s);
+            
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                json_string,
+                StatusCode.OK,
+                headers
+            );
+        }
+        
+        // Fall back to user detail
+        if (components.count() < 2) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"User ID required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        var id_str = components[1];
+        var id = int.parse(id_str);
+        
+        var users = new Series<User>();
+        users.add(new User(1, "Alice", "alice@example.com"));
+        users.add(new User(2, "Bob", "bob@example.com"));
+        users.add(new User(3, "Charlie", "charlie@example.com"));
+        
+        var user = users.to_immutable_buffer()
+            .first_or_default(u => u.id == id);
+        
+        if (user == null) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"User not found\" }",
+                StatusCode.NOT_FOUND,
+                headers
+            );
+        }
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            user.to_json(),
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // API versioned endpoint
+    router.map_func("/api/v1/status", (context) => {
+        var status = new Dictionary<string, string>();
+        status.set("status", "operational");
+        status.set("version", "1.0.0");
+        status.set("timestamp", new DateTime.now_local().format_iso8601());
+        
+        var json_parts = new Series<string>();
+        json_parts.add("{ ");
+        
+        bool first = true;
+        status.to_immutable_buffer().iterate((kv) => {
+            if (!first) json_parts.add(", ");
+            json_parts.add(@"\"$(kv.key)\": \"$(kv.value)\"");
+            first = false;
+        });
+        
+        json_parts.add(" }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // API items endpoint
+    router.map_func("/api/v1/items/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 4) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Item ID required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        var id_str = components[3];
+        var id = int.parse(id_str);
+        
+        var items = new Dictionary<int, Item>();
+        items.set(1, new Item(1, "Widget", "A useful widget", 9.99));
+        items.set(2, new Item(2, "Gadget", "A fancy gadget", 19.99));
+        items.set(3, new Item(3, "Doohickey", "A mysterious doohickey", 29.99));
+        
+        Item? item = null;
+        if (items.try_get(id, out item)) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                item.to_json(),
+                StatusCode.OK,
+                headers
+            );
+        }
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"Item not found\" }",
+            StatusCode.NOT_FOUND,
+            headers
+        );
+    });
+    
+    // Files endpoint with category and name
+    router.map_func("/files/", (context) => {
+        var components = context.request.path_components.to_vector();
+        
+        if (components.count() < 3) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Category and filename required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        var category = components[1];
+        var name = components[2];
+        
+        // Simulated file lookup
+        var files = new Dictionary<string, Dictionary<string, File>>();
+        var docs = new Dictionary<string, File>();
+        docs.set("readme.txt", new File("readme.txt", "docs", "This is the readme file"));
+        docs.set("guide.pdf", new File("guide.pdf", "docs", "User guide"));
+        
+        var images = new Dictionary<string, File>();
+        images.set("logo.png", new File("logo.png", "images", "Company logo"));
+        images.set("banner.jpg", new File("banner.jpg", "images", "Website banner"));
+        
+        files.set("docs", docs);
+        files.set("images", images);
+        
+        Dictionary<string, File>? category_files = null;
+        if (files.try_get(category, out category_files)) {
+            File? file = null;
+            if (category_files.try_get(name, out file)) {
+                var headers = new Catalogue<string, string>();
+                headers.add("Content-Type", "application/json");
+                return new BufferedHttpResult.from_string(
+                    file.to_json(),
+                    StatusCode.OK,
+                    headers
+                );
+            }
+        }
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            @"{ \"error\": \"File not found in category '$category'\" }",
+            StatusCode.NOT_FOUND,
+            headers
+        );
+    });
+    
+    // Path information endpoint - shows all path components
+    router.map_func("/pathinfo", (context) => {
+        var parts = new Series<string>();
+        parts.add("Path Information:\n");
+        parts.add(@"  Raw path: $(context.request.raw_path)\n");
+        parts.add(@"  Path components: $(context.request.path_components.count())\n");
+        parts.add("\n  Components:\n");
+        
+        context.request.path_components
+            .with_positions()
+            .iterate((pair) => {
+                parts.add(@"    [$((int)pair.position)]: $(pair.item)\n");
+            });
+        
+        parts.add(@"\n  Query string: $(context.request.query_string)\n");
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    print("Path Routing Example Server running on port 8082\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8082/\n");
+    print("  - http://localhost:8082/hello\n");
+    print("  - http://localhost:8082/hello/Alice\n");
+    print("  - http://localhost:8082/users\n");
+    print("  - http://localhost:8082/users/1\n");
+    print("  - http://localhost:8082/users/1/posts\n");
+    print("  - http://localhost:8082/api/v1/status\n");
+    print("  - http://localhost:8082/api/v1/items/2\n");
+    print("  - http://localhost:8082/files/docs/readme.txt\n");
+    print("  - http://localhost:8082/pathinfo?test=1\n");
+    
+    server.run();
+}
+
+// Helper classes for the example
+class User {
+    public int id { get; private set; }
+    public string name { get; private set; }
+    public string email { get; private set; }
+    
+    public User(int id, string name, string email) {
+        this.id = id;
+        this.name = name;
+        this.email = email;
+    }
+    
+    public string to_json() {
+        return @"{ \"id\": $id, \"name\": \"$name\", \"email\": \"$email\" }";
+    }
+}
+
+class Post {
+    public int id { get; private set; }
+    public int user_id { get; private set; }
+    public string title { get; private set; }
+    public string content { get; private set; }
+    
+    public Post(int id, int user_id, string title, string content) {
+        this.id = id;
+        this.user_id = user_id;
+        this.title = title;
+        this.content = content;
+    }
+    
+    public string to_json() {
+        return @"{ \"id\": $id, \"user_id\": $user_id, \"title\": \"$title\", \"content\": \"$content\" }";
+    }
+}
+
+class Item {
+    public int id { get; private set; }
+    public string name { get; private set; }
+    public string description { get; private set; }
+    public double price { get; private set; }
+    
+    public Item(int id, string name, string description, double price) {
+        this.id = id;
+        this.name = name;
+        this.description = description;
+        this.price = price;
+    }
+    
+    public string to_json() {
+        return @"{ \"id\": $id, \"name\": \"$name\", \"description\": \"$description\", \"price\": $price }";
+    }
+}
+
+class File {
+    public string name { get; private set; }
+    public string category { get; private set; }
+    public string description { get; private set; }
+    
+    public File(string name, string category, string description) {
+        this.name = name;
+        this.category = category;
+        this.description = description;
+    }
+    
+    public string to_json() {
+        return @"{ \"name\": \"$name\", \"category\": \"$category\", \"description\": \"$description\" }";
+    }
+}

+ 193 - 0
examples/QueryParameters.vala

@@ -0,0 +1,193 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+/**
+ * QueryParameters Example
+ * 
+ * Demonstrates how to handle query parameters in Astralis.
+ * Uses Invercargill Dictionary for parameter storage and
+ * Enumerable for processing parameters.
+ */
+void main() {
+    var router = new Router();
+    var server = new Server(8081, router);
+    
+    // Simple query parameter access
+    router.map_func("/greet", (context) => {
+        var name = context.request.get_query_or_default("name", "World");
+        var greeting = context.request.get_query_or_default("greeting", "Hello");
+        
+        return new BufferedHttpResult.from_string(
+            @"$greeting, $name!"
+        );
+    });
+    
+    // Multiple query parameters with validation
+    router.map_func("/search", (context) => {
+        var query = context.request.get_query("q");
+        var limit = int.parse(context.request.get_query_or_default("limit", "10"));
+        var offset = int.parse(context.request.get_query_or_default("offset", "0"));
+        
+        if (query == null || query == "") {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"Query parameter 'q' is required\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        // Build response using Enumerable operations
+        var results = Wrap.array<string>({"Result 1", "Result 2", "Result 3", "Result 4", "Result 5"})
+            .skip(offset)
+            .take(limit);
+        
+        var json_parts = new Series<string>();
+        json_parts.add(@"{ \"query\": \"$query\", \"results\": [");
+        
+        bool first = true;
+        results.iterate((result) => {
+            if (!first) json_parts.add(", ");
+            json_parts.add(@"\"$result\"");
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Boolean flag query parameters
+    router.map_func("/debug", (context) => {
+        var verbose = context.request.has_query("verbose");
+        var level = context.request.get_query_or_default("level", "info");
+        
+        var parts = new Series<string>();
+        parts.add("Debug Information:\n");
+        parts.add(@"  Verbose mode: $(verbose ? "enabled" : "disabled")\n");
+        parts.add(@"  Log level: $level\n");
+        
+        // Add query parameters listing using Enumerable
+        parts.add("\nAll query parameters:\n");
+        context.request.query_params.to_immutable_buffer()
+            .iterate((grouping) => {
+                grouping.iterate((value) => {
+                    parts.add(@"  - $(grouping.key): $value\n");
+                });
+            });
+        
+        var result = parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        return new BufferedHttpResult.from_string(result);
+    });
+    
+    // Query parameter with multiple values (comma-separated)
+    router.map_func("/filter", (context) => {
+        var tags_param = context.request.get_query("tags");
+        
+        if (tags_param == null) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"message\": \"No tags provided\" }",
+                StatusCode.OK,
+                headers
+            );
+        }
+        
+        // Parse comma-separated tags using Enumerable operations
+        var tags = Wrap.array<string>(tags_param.split(","))
+            .select<string>(tag => tag.strip())
+            .where(tag => tag != "")
+            .to_vector();
+        
+        var json_parts = new Series<string>();
+        json_parts.add(@"{ \"tags\": [");
+        
+        bool first = true;
+        tags.to_immutable_buffer().iterate((tag) => {
+            if (!first) json_parts.add(", ");
+            json_parts.add(@"\"$tag\"");
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    // Numeric query parameters with range validation
+    router.map_func("/range", (context) => {
+        var min = int.parse(context.request.get_query_or_default("min", "0"));
+        var max = int.parse(context.request.get_query_or_default("max", "100"));
+        var step = int.parse(context.request.get_query_or_default("step", "1"));
+        
+        if (min >= max) {
+            var headers = new Catalogue<string, string>();
+            headers.add("Content-Type", "application/json");
+            return new BufferedHttpResult.from_string(
+                @"{ \"error\": \"min must be less than max\" }",
+                StatusCode.BAD_REQUEST,
+                headers
+            );
+        }
+        
+        // Generate range using Iterate.range and Enumerable operations
+        var numbers = Iterate.range(min, max)
+            .where(n => (n - min) % step == 0);
+        
+        var json_parts = new Series<string>();
+        json_parts.add(@"{ \"min\": $min, \"max\": $max, \"step\": $step, \"numbers\": [");
+        
+        bool first = true;
+        numbers.iterate((n) => {
+            if (!first) json_parts.add(", ");
+            json_parts.add(n.to_string());
+            first = false;
+        });
+        
+        json_parts.add("] }");
+        
+        var json_string = json_parts.to_immutable_buffer()
+            .aggregate<string>("", (acc, s) => acc + s);
+        
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string(
+            json_string,
+            StatusCode.OK,
+            headers
+        );
+    });
+    
+    print("Query Parameters Example Server running on port 8081\n");
+    print("Try these endpoints:\n");
+    print("  - http://localhost:8081/greet?name=Alice\n");
+    print("  - http://localhost:8081/search?q=test&limit=3\n");
+    print("  - http://localhost:8081/debug?verbose&level=debug\n");
+    print("  - http://localhost:8081/filter?tags=foo,bar,baz\n");
+    print("  - http://localhost:8081/range?min=1&max=20&step=2\n");
+    
+    server.run();
+}

+ 57 - 0
examples/RemoteAddress.vala

@@ -0,0 +1,57 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+
+public class RemoteAddressExample : Object {
+    public static int main(string[] args) {
+        // Create a simple handler that logs the client's remote address
+        var handler = new SimpleHttpHandler();
+        
+        // Start the server on port 8080
+        var server = new Server(8080, handler);
+        server.run();
+        
+        return 0;
+    }
+}
+
+// Simple handler that responds with client information
+public class SimpleHttpHandler : Object, HttpHandler {
+    public async HttpResult handle(HttpContext context) {
+        var request = context.request;
+        
+        // Access the remote address
+        var remote_address = request.remote_address;
+        
+        // Build JSON response with client info using string interpolation
+        var json = @"{
+  \"method\": \"$(request.method)\",
+  \"path\": \"$(request.raw_path)\",
+  \"remote_address\": \"$(remote_address ?? "unknown")\",
+  \"user_agent\": \"$(request.user_agent ?? "unknown")\"
+}";
+        
+        var response = new BufferedHttpResult.from_string(
+            json,
+            StatusCode.OK,
+            get_json_headers()
+        );
+        
+        // Log the request to console
+        print(@"[$(get_current_time())] $(request.method) $(request.raw_path) from $(remote_address ?? "unknown")");
+        
+        return response;
+    }
+    
+    private static string get_current_time() {
+        var now = new DateTime.now_local();
+        return now.format("%Y-%m-%d %H:%M:%S");
+    }
+    
+    private static Catalogue<string, string> get_json_headers() {
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        headers.add("Access-Control-Allow-Origin", "*");
+        return headers;
+    }
+}

+ 6 - 3
examples/SimpleApi.vala

@@ -1,18 +1,21 @@
 using Astralis;
 using Invercargill;
+using Invercargill.DataStructures;
 
 void main() {
     var router = new Router();
     var server = new Server(8080, router);
     
-    router.map_get("/hello", (context) => {
+    router.map_func("/hello", (context) => {
         print("Handling /hello\n");
         return new BufferedHttpResult.from_string("Hello from Astralis!");
     });
 
-    router.map_get("/json", (context) => {
+    router.map_func("/json", (context) => {
         print("Handling /json\n");
-        return new BufferedHttpResult.from_string("{ \"message\": \"Hello JSON\" }", StatusCode.OK, Iterate.single<HttpHeader>(new HttpHeader("Content-Type","application/json")));
+        var headers = new Catalogue<string, string>();
+        headers.add("Content-Type", "application/json");
+        return new BufferedHttpResult.from_string("{ \"message\": \"Hello JSON\" }", StatusCode.OK, headers);
     });
 
     server.run();

+ 64 - 0
examples/meson.build

@@ -1,5 +1,69 @@
+# Query Parameters Example
+executable('query-parameters',
+    'QueryParameters.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Path Routing Example
+executable('path-routing',
+    'PathRouting.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Headers and Cookies Example
+executable('headers-and-cookies',
+    'HeadersAndCookies.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Form Data Example
+executable('form-data',
+    'FormData.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# JSON API Example
+executable('json-api',
+    'JsonApi.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Data Structures Demo Example
+executable('data-structures-demo',
+    'DataStructuresDemo.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Enumerable Operations Example
+executable('enumerable-operations',
+    'EnumerableOperations.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Error Handling Example
+executable('error-handling',
+    'ErrorHandling.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)
+
+# Original Simple API Example
 executable('simple-api',
     'SimpleApi.vala',
     dependencies: [astralis_dep, invercargill_dep],
     install: false
 )
+
+# Remote Address Example
+executable('remote-address',
+    'RemoteAddress.vala',
+    dependencies: [astralis_dep, invercargill_dep, json_glib_dep],
+    install: false
+)

+ 1 - 0
meson.build

@@ -6,6 +6,7 @@ project('astralis', ['c', 'vala'],
 glib_dep = dependency('glib-2.0')
 gobject_dep = dependency('gobject-2.0')
 gio_dep = dependency('gio-2.0')
+gio_unix_dep = dependency('gio-unix-2.0')
 mhd_dep = dependency('libmicrohttpd')
 invercargill_dep = dependency('invercargill-1')
 json_glib_dep = dependency('json-glib-1.0')

+ 0 - 37
src/Context.vala

@@ -1,37 +0,0 @@
-using MHD;
-using Invercargill;
-
-namespace Astralis {
-
-    public class HttpRequest : Object {
-        public string raw_path { get; private set; }
-        public string method { get; private set; }
-        public string version { get; private set; }
-
-        public Enumerable<string> path_components { get; private set; }
-        
-        public Enumerable<HttpHeader> headers { get; private set; }
-    
-        public HttpRequest(string url, string method, string version) {
-            this.raw_path = url;
-            this.method = method;
-            this.version = version;
-
-            
-
-            var path_parts = this.raw_path.split("?");
-            this.path_components = Wrap.array<string>(path_parts[0].split("/"))
-                .select<string>(p => Uri.unescape_string (p) ?? "")
-                .where (p => p != "")
-                .to_immutable_buffer();
-        }
-    }
-
-    public class HttpContext : Object {
-        public HttpRequest request { get; private set; }
-
-        internal HttpContext(HttpRequest request) {
-            this.request = request;
-        }
-    }
-}

+ 66 - 0
src/Core/AsyncPipe.vala

@@ -0,0 +1,66 @@
+using Invercargill;
+using Invercargill.DataStructures;
+namespace Astralis {
+
+    public class AsyncPipe : Object {
+
+        private bool write_occurred;
+        private Series<ByteBuffer> chunks = new Series<ByteBuffer>();
+        private bool writes_complete;
+
+        public ByteBuffer? peek() {
+            return chunks.first_or_default();
+        }
+
+        public async BinaryData? read() {
+            while (chunks.length == 0 && !writes_complete) {
+                Idle.add(read.callback);
+                yield;
+            }
+            if(chunks.length == 0) {
+                return null;
+            }
+            return chunks.pop_start();
+        }
+
+        public async BinaryData read_all() {
+            while (!writes_complete) {
+                Idle.add(read_all.callback);
+                yield;
+            }
+            var data = new ByteComposition();
+            data.append_all(chunks);
+            chunks.clear();
+            return data;
+        }
+
+        private void write(uint8[] data) {
+            print(@"$(data.length) bytes added to chunks\n");
+            chunks.add(new ByteBuffer.from_byte_array(data));
+            write_occurred = true;
+        }
+
+        public static AsyncPipeWriter new_writer() {
+            return new AsyncPipeWriter(new AsyncPipe());
+        }
+
+        public class AsyncPipeWriter {
+
+            public AsyncPipeWriter(AsyncPipe pipe) {
+                this.pipe = pipe;
+            }
+
+            public AsyncPipe pipe { get; private set; }
+            public void write(uint8[] data) {
+                pipe.write(data);
+            }
+
+            public void complete() {
+                pipe.writes_complete = true;
+            }
+
+        }
+
+    }
+
+}

+ 182 - 0
src/Core/Context.vala

@@ -0,0 +1,182 @@
+using MHD;
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Astralis {
+
+    public class HttpRequest : Object {
+        // Basic request information
+        public string raw_path { get; private set; }
+        public string method { get; private set; }
+        public string version { get; private set; }
+
+        // Path and routing
+        public Enumerable<string> path_components { get; private set; }
+        public string query_string { get; private set; }
+
+        // Request data collections
+        public Catalogue<string, string> headers { get; private set; }
+        public Catalogue<string, string> query_params { get; private set; }
+        public Catalogue<string, string> cookies { get; private set; }
+
+        // Request body
+        public AsyncPipe request_body { get; private set; }
+
+        // Content information
+        public string content_type { get; private set; }
+        public int64 content_length { get; private set; }
+        public string? content_encoding { get; private set; }
+
+        // Client information
+        public string? remote_address { get; private set; }
+        public string? user_agent { get; private set; }
+        public string? referer { get; private set; }
+
+        public HttpRequest(
+            string url,
+            string method,
+            string version,
+            Catalogue<string, string> headers,
+            AsyncPipe request_body,
+            Catalogue<string, string> query,
+            Catalogue<string, string> cookies,
+            string? remote_address
+        ) {
+            this.raw_path = url;
+            this.method = method;
+            this.version = version;
+            this.headers = headers;
+            this.request_body = request_body;
+            this.query_params = query;
+            this.cookies = cookies;
+            this.remote_address = remote_address;
+
+            // Parse path components
+            var path_parts = this.raw_path.split("?");
+            this.path_components = Wrap.array<string>(path_parts[0].split("/"))
+                .select<string>(p => Uri.unescape_string(p) ?? "")
+                .where(p => p != "")
+                .to_immutable_buffer();
+
+            // Extract query string
+            this.query_string = path_parts.length > 1 ? path_parts[1] : "";
+
+            // Extract common headers
+            this.content_type = get_header("Content-Type") ?? "";
+            this.content_length = parse_content_length(get_header("Content-Length"));
+            this.content_encoding = get_header("Content-Encoding");
+            this.user_agent = get_header("User-Agent");
+            this.referer = get_header("Referer");
+        }
+
+        // Header access methods
+        public string? get_header(string name) {
+            string? value;
+            if (headers.try_get_any(name, out value)) {
+                return value;
+            }
+            return null;
+        }
+
+        public string get_header_or_default(string name, string default_value) {
+            string? value;
+            if (headers.try_get_any(name, out value)) {
+                return value;
+            }
+            return default_value;
+        }
+
+        public Enumerable<string> get_header_all(string name) {
+            return headers.get_or_empty(name);
+        }
+
+        public bool has_header(string name) {
+            return headers.has(name);
+        }
+
+        // Query parameter access methods
+        public string? get_query(string name) {
+            string? value;
+            if (query_params.try_get_any(name, out value)) {
+                return value;
+            }
+            return null;
+        }
+
+        public string get_query_or_default(string name, string default_value) {
+            string? value;
+            if (query_params.try_get_any(name, out value)) {
+                return value;
+            }
+            return default_value;
+        }
+
+        public Enumerable<string> get_query_all(string name) {
+            return query_params.get_or_empty(name);
+        }
+
+        public bool has_query(string name) {
+            return query_params.has(name);
+        }
+
+        // Cookie access methods
+        public string? get_cookie(string name) {
+            string? value;
+            if (cookies.try_get_any(name, out value)) {
+                return value;
+            }
+            return null;
+        }
+
+        public string get_cookie_or_default(string name, string default_value) {
+            string? value;
+            if (cookies.try_get_any(name, out value)) {
+                return value;
+            }
+            return default_value;
+        }
+
+        public Enumerable<string> get_cookie_all(string name) {
+            return cookies.get_or_empty(name);
+        }
+
+        public bool has_cookie(string name) {
+            return cookies.has(name);
+        }
+
+        // Utility methods
+        public bool is_get() { return method == "GET"; }
+        public bool is_post() { return method == "POST"; }
+        public bool is_put() { return method == "PUT"; }
+        public bool is_delete() { return method == "DELETE"; }
+        public bool is_patch() { return method == "PATCH"; }
+        public bool is_head() { return method == "HEAD"; }
+        public bool is_options() { return method == "OPTIONS"; }
+
+        public bool is_form_urlencoded() {
+            return content_type.contains("application/x-www-form-urlencoded");
+        }
+
+        public bool is_json() {
+            return content_type.contains("application/json");
+        }
+
+        public bool is_multipart() {
+            return content_type.contains("multipart/form-data");
+        }
+
+        // Helper method
+        private static int64 parse_content_length(string? value) {
+            if (value == null) return 0;
+            return int64.parse(value);
+        }
+    }
+
+    public class HttpContext : Object {
+        public HttpRequest request { get; private set; }
+
+        internal HttpContext(HttpRequest request) {
+            this.request = request;
+        }
+    }
+}

+ 5 - 1
src/HttpValues.vala → src/Core/HttpValues.vala

@@ -15,9 +15,13 @@ namespace Astralis {
 
     public enum StatusCode {
         OK = 200,
+        CREATED = 201,
+        NO_CONTENT = 204,
         BAD_REQUEST = 400,
+        UNAUTHORIZED = 401,
+        FORBIDDEN = 403,
         NOT_FOUND = 404,
+        METHOD_NOT_ALLOWED = 405,
         INTERNAL_SERVER_ERROR = 500
-        // TODO More
     }
 }

+ 22 - 0
src/Core/RequestHandler.vala

@@ -0,0 +1,22 @@
+
+namespace Astralis {
+
+    public interface HttpHandler : Object {
+        public abstract async HttpResult handle(HttpContext context) throws Error;
+    }
+
+    public delegate HttpResult HttpHandlerDelegate(HttpContext context) throws Error;
+    public class DelegateHttpHandler : Object, HttpHandler {
+        private HttpHandlerDelegate callback;
+
+        public DelegateHttpHandler(owned HttpHandlerDelegate func) {
+            callback = (owned)func;
+        }
+
+        public async HttpResult handle(HttpContext context) throws Error {
+            return callback(context);
+        }
+
+    }
+
+}

+ 54 - 0
src/Core/Response.vala

@@ -0,0 +1,54 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Astralis {
+
+    public class HttpResult : Object {
+        private Catalogue<string, string> header_catalogue;
+        public StatusCode status { get; set; }
+        public Catalogue<string, string> headers { get; private set; }
+
+        public HttpResult(StatusCode status, Catalogue<string, string>? headers = null) {
+            this.header_catalogue = new Catalogue<string, string>();
+            this.headers = header_catalogue; 
+            this.status = status;
+            if(headers != null) {
+                headers.to_immutable_buffer().iterate((grouping) => {
+                    grouping.iterate((value) => {
+                        header_catalogue.add(grouping.key, value);
+                    });
+                });
+            }
+        }
+
+        public void add_header(string name, string value) {
+            header_catalogue.add(name, value);
+        }
+
+        public void add_headers(Catalogue<string, string> headers) {
+            headers.to_immutable_buffer().iterate((grouping) => {
+                grouping.iterate((value) => {
+                    header_catalogue.add(grouping.key, value);
+                });
+            });
+        }
+    }
+
+    public class BufferedHttpResult : HttpResult {
+
+        public uint8[] buffer { get; set; }
+
+        public BufferedHttpResult(uint8[] buffer, StatusCode status = StatusCode.OK, Catalogue<string, string>? headers = null) {
+            base(status, headers);
+            this.buffer = buffer;
+            add_header("Content-Length", this.buffer.length.to_string());
+        }
+
+        public BufferedHttpResult.from_string(string str, StatusCode status = StatusCode.OK, Catalogue<string, string>? headers = null) {
+            base(status, headers);
+            this.buffer = str.data;
+            add_header("Content-Length", this.buffer.length.to_string());
+        }
+    }
+
+}

+ 380 - 0
src/Data/FormDataParser.vala

@@ -0,0 +1,380 @@
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Astralis {
+
+    /// Represents an uploaded file from multipart form data
+    public class FileUpload : Object {
+        public string field_name { get; private set; }
+        public string filename { get; private set; }
+        public string content_type { get; private set; }
+        public uint8[] data { get; private set; }
+
+        public FileUpload(string field_name, string filename, string content_type, owned uint8[] data) {
+            this.field_name = field_name;
+            this.filename = filename;
+            this.content_type = content_type;
+            this.data = (owned)data;
+        }
+    }
+
+    /// Holds parsed form data from either application/x-www-form-urlencoded or multipart/form-data
+    public class FormData : Object {
+        private Catalogue<string, string> _fields;
+        private Catalogue<string, FileUpload> _files;
+
+        public FormData() {
+            _fields = new Catalogue<string, string>();
+            _files = new Catalogue<string, FileUpload>();
+        }
+
+        /// Get the underlying fields catalogue for iteration
+        public Catalogue<string, string> fields { get { return _fields; } }
+
+        /// Get the underlying files catalogue for iteration  
+        public Catalogue<string, FileUpload> files { get { return _files; } }
+
+        /// Add a field value
+        public void add_field(string name, string value) {
+            _fields.add(name, value);
+        }
+
+        /// Add a file upload
+        public void add_file(FileUpload file) {
+            _files.add(file.field_name, file);
+        }
+
+        /// Get a single field value by name
+        public string? get_field(string name) {
+            string? value;
+            if (_fields.try_get_any(name, out value)) {
+                return value;
+            }
+            return null;
+        }
+
+        /// Get a field value or default if not present
+        public string get_field_or_default(string name, string default_value) {
+            string? value;
+            if (_fields.try_get_any(name, out value)) {
+                return value;
+            }
+            return default_value;
+        }
+
+        /// Get all values for a field (for multi-value fields like checkboxes)
+        public Enumerable<string> get_field_all(string name) {
+            return _fields.get_or_empty(name);
+        }
+
+        /// Check if a field exists
+        public bool has_field(string name) {
+            return _fields.has(name);
+        }
+
+        /// Get a file upload by field name
+        public FileUpload? get_file(string name) {
+            FileUpload? file;
+            if (_files.try_get_any(name, out file)) {
+                return file;
+            }
+            return null;
+        }
+
+        /// Get all file uploads for a field name
+        public Enumerable<FileUpload> get_file_all(string name) {
+            return _files.get_or_empty(name);
+        }
+
+        /// Check if a file upload exists for a field name
+        public bool has_file(string name) {
+            return _files.has(name);
+        }
+
+        /// Get count of field names (unique keys)
+        public uint field_count() {
+            return _fields.to_immutable_buffer().count();
+        }
+
+        /// Get count of file field names (unique keys)
+        public uint file_count() {
+            return _files.to_immutable_buffer().count();
+        }
+    }
+
+    /// Parser for form data from AsyncPipe
+    public class FormDataParser : Object {
+
+        /// Parse form data from an AsyncPipe, auto-detecting content type
+        /// content_type_header should be the full Content-Type header value
+        public static async FormData parse(AsyncPipe pipe, string content_type_header) throws Error {
+            if (content_type_header.contains("multipart/form-data")) {
+                string boundary = extract_boundary(content_type_header);
+                return yield parse_multipart(pipe, boundary);
+            } else if (content_type_header.contains("application/x-www-form-urlencoded")) {
+                return yield parse_urlencoded(pipe);
+            } else {
+                throw new IOError.NOT_SUPPORTED("Unsupported content type: %s", content_type_header);
+            }
+        }
+
+        /// Parse application/x-www-form-urlencoded data from AsyncPipe
+        public static async FormData parse_urlencoded(AsyncPipe pipe) throws Error {
+            var body = yield pipe.read_all();
+            var body_str = body.to_raw_string();
+
+            var form_data = new FormData();
+
+            if (body_str.length == 0) {
+                return form_data;
+            }
+
+            // Parse key=value pairs separated by &
+            var pairs = body_str.split("&");
+            foreach (var pair in pairs) {
+                if (pair.length == 0) continue;
+
+                string key;
+                string value;
+
+                int eq_pos = pair.index_of("=");
+                if (eq_pos >= 0) {
+                    key = pair.substring(0, eq_pos);
+                    value = pair.substring(eq_pos + 1);
+                } else {
+                    key = pair;
+                    value = "";
+                }
+
+                // URL decode
+                key = Uri.unescape_string(key);
+                value = Uri.unescape_string(value);
+
+                // Replace + with space (form encoding)
+                key = key.replace("+", " ");
+                value = value.replace("+", " ");
+
+                form_data.add_field(key, value);
+            }
+
+            return form_data;
+        }
+
+        /// Parse multipart/form-data from AsyncPipe
+        public static async FormData parse_multipart(AsyncPipe pipe, string boundary) throws Error {
+            var body = yield pipe.read_all();
+            
+            // Extract bytes from BinaryData using write_to
+            size_t body_length = (size_t) body.count();
+            
+            if (body_length == 0) {
+                warning("Empty body received in multipart parser");
+                return new FormData();
+            }
+            
+            uint8[] body_data = new uint8[body_length];
+            body.write_to(body_data, body_length);
+
+            var form_data = new FormData();
+
+            // The boundary in the body is prefixed with --
+            string boundary_marker = "--" + boundary;
+            uint8[] boundary_bytes = boundary_marker.data;
+
+            // Split by boundary (binary-safe)
+            int start = 0;
+            while (start < body_data.length) {
+                // Find the next boundary
+                int boundary_pos = find_bytes(body_data, boundary_bytes, start);
+                if (boundary_pos < 0) {
+                    break;
+                }
+
+                // Move past the boundary
+                int part_start = boundary_pos + boundary_bytes.length;
+
+                // Skip CRLF or LF after boundary
+                if (part_start < body_data.length && body_data[part_start] == '\r') {
+                    part_start++;
+                }
+                if (part_start < body_data.length && body_data[part_start] == '\n') {
+                    part_start++;
+                }
+
+                // Find the next boundary to determine part end
+                int next_boundary = find_bytes(body_data, boundary_bytes, part_start);
+                int part_end;
+                if (next_boundary >= 0) {
+                    // Part ends before the next boundary (strip trailing CRLF or LF)
+                    part_end = next_boundary;
+                    if (part_end > 0 && body_data[part_end - 1] == '\n') {
+                        part_end--;
+                        if (part_end > 0 && body_data[part_end - 1] == '\r') {
+                            part_end--;
+                        }
+                    }
+                } else {
+                    part_end = body_data.length;
+                }
+
+                // Extract the part data
+                if (part_end > part_start) {
+                    uint8[] part_data = new uint8[part_end - part_start];
+                    Memory.copy(part_data, body_data[part_start:part_end], part_end - part_start);
+                    parse_multipart_part_binary(part_data, form_data);
+                }
+
+                start = part_end;
+            }
+
+            return form_data;
+        }
+
+        /// Find a byte sequence within a byte array, starting at a given position
+        private static int find_bytes(uint8[] haystack, uint8[] needle, int start_pos = 0) {
+            if (needle.length == 0 || haystack.length < needle.length) {
+                return -1;
+            }
+
+            for (int i = start_pos; i <= haystack.length - needle.length; i++) {
+                bool found = true;
+                for (int j = 0; j < needle.length; j++) {
+                    if (haystack[i + j] != needle[j]) {
+                        found = false;
+                        break;
+                    }
+                }
+                if (found) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        /// Extract the boundary from a Content-Type header
+        public static string extract_boundary(string content_type) throws Error {
+            // Look for boundary= in the content type
+            int boundary_pos = content_type.index_of("boundary=");
+            if (boundary_pos < 0) {
+                throw new IOError.INVALID_ARGUMENT("No boundary found in multipart content type");
+            }
+
+            string boundary = content_type.substring(boundary_pos + 9);
+
+            // Remove any trailing parameters (semicolon separated)
+            int semicolon_pos = boundary.index_of(";");
+            if (semicolon_pos >= 0) {
+                boundary = boundary.substring(0, semicolon_pos);
+            }
+
+            // Remove quotes if present
+            boundary = boundary.strip();
+            if (boundary.has_prefix("\"") && boundary.has_suffix("\"")) {
+                boundary = boundary.substring(1, boundary.length - 2);
+            }
+
+            return boundary;
+        }
+
+        /// Parse a single multipart part (binary-safe for file uploads)
+        private static void parse_multipart_part_binary(uint8[] part_data, FormData form_data) {
+            // A part consists of headers, blank line, then content
+            // Find \r\n\r\n or \n\n to separate headers from content
+            int header_end = -1;
+            int header_line_ending_size = 0;
+
+            // Look for \r\n\r\n
+            for (int i = 0; i < part_data.length - 3; i++) {
+                if (part_data[i] == '\r' && part_data[i + 1] == '\n' &&
+                    part_data[i + 2] == '\r' && part_data[i + 3] == '\n') {
+                    header_end = i;
+                    header_line_ending_size = 4;
+                    break;
+                }
+            }
+
+            // If not found, look for \n\n
+            if (header_end < 0) {
+                for (int i = 0; i < part_data.length - 1; i++) {
+                    if (part_data[i] == '\n' && part_data[i + 1] == '\n') {
+                        header_end = i;
+                        header_line_ending_size = 2;
+                        break;
+                    }
+                }
+            }
+
+            if (header_end < 0) {
+                return; // Invalid part, skip
+            }
+
+            // Extract headers section as string (headers are always ASCII/text)
+            // Create null-terminated copy for safe string conversion
+            uint8[] header_bytes = new uint8[header_end + 1];
+            Memory.copy(header_bytes, part_data, header_end);
+            header_bytes[header_end] = 0;
+            string headers_section = (string) header_bytes;
+
+            // Content starts after the blank line
+            int content_start = header_end + header_line_ending_size;
+            int content_length = part_data.length - content_start;
+
+            // Parse Content-Disposition header
+            string? field_name = null;
+            string? filename = null;
+            string content_type = "text/plain";
+
+            // Parse headers
+            var header_lines = headers_section.split("\n");
+            foreach (var line in header_lines) {
+                line = line.strip();
+                if (line.down().has_prefix("content-disposition:")) {
+                    // Parse Content-Disposition: form-data; name="field"; filename="file.txt"
+                    string disposition = line.substring(20).strip();
+
+                    // Extract name
+                    int name_pos = disposition.index_of("name=\"");
+                    if (name_pos >= 0) {
+                        int name_start = name_pos + 6;
+                        int name_end = disposition.index_of("\"", name_start);
+                        if (name_end > name_start) {
+                            field_name = disposition.substring(name_start, name_end - name_start);
+                        }
+                    }
+
+                    // Extract filename (optional)
+                    int filename_pos = disposition.index_of("filename=\"");
+                    if (filename_pos >= 0) {
+                        int filename_start = filename_pos + 10;
+                        int filename_end = disposition.index_of("\"", filename_start);
+                        if (filename_end > filename_start) {
+                            filename = disposition.substring(filename_start, filename_end - filename_start);
+                        }
+                    }
+                } else if (line.down().has_prefix("content-type:")) {
+                    content_type = line.substring(13).strip();
+                }
+            }
+
+            if (field_name == null) {
+                return; // No field name, skip
+            }
+
+            if (filename != null) {
+                // This is a file upload - copy binary data
+                uint8[] file_data = new uint8[content_length];
+                Memory.copy(file_data, part_data[content_start:part_data.length], content_length);
+                var file = new FileUpload(field_name, filename, content_type, file_data);
+                form_data.add_file(file);
+            } else {
+                // This is a regular field - convert to string
+                // Null-terminate for safe string conversion
+                uint8[] text_data = new uint8[content_length + 1];
+                Memory.copy(text_data, part_data[content_start:part_data.length], content_length);
+                text_data[content_length] = 0;
+                string content = (string) text_data;
+                form_data.add_field(field_name, content);
+            }
+        }
+    }
+}

+ 10 - 11
src/Router.vala → src/Handlers/Router.vala

@@ -2,28 +2,27 @@ using Invercargill.DataStructures;
 
 namespace Astralis {
 
-    public delegate HttpResult RouteHandler(HttpContext context) throws Error;
-
-
     public class RouteEntry {
-        public RouteHandler handler;
-        public RouteEntry(owned RouteHandler handler) {
-            this.handler = (owned) handler;
+        public HttpHandler handler;
+        public RouteEntry(HttpHandler handler) {
+            this.handler = handler;
         }
     }
 
     public class Router : HttpHandler, Object {
         private Dictionary<string, RouteEntry> routes = new Dictionary<string, RouteEntry>();
 
-        public void map_get(string path, owned RouteHandler handler) {
-            // In a real router, we'd handle methods separately.
-            // For now, simple path mapping.
-            routes.set(path, new RouteEntry((owned) handler));
+        public void map_func(string path, owned HttpHandlerDelegate handler) {
+            routes.set(path, new RouteEntry(new DelegateHttpHandler((owned) handler)));
+        }
+
+        public void map(string path, owned HttpHandler handler) {
+            routes.set(path, new RouteEntry(handler));
         }
 
         public async HttpResult handle(HttpContext context) {
             try {
-                return routes.get(context.request.raw_path).handler(context);
+                return yield routes.get(context.request.raw_path).handler.handle(context);
             } catch (Invercargill.IndexError e) {
                 return new BufferedHttpResult.from_string("Not Found", StatusCode.NOT_FOUND);
             }

+ 0 - 14
src/Headers.vala

@@ -1,14 +0,0 @@
-using Invercargill;
-namespace Astralis {
-
-    public class HttpHeader {
-        public string header { get; private set; }
-        public string value { get; private set; }
-        
-        public HttpHeader(string header, string value) {
-            this.header = header;
-            this.value = value;
-        }
-    }
-
-}

+ 0 - 8
src/RequestHandler.vala

@@ -1,8 +0,0 @@
-
-namespace Astralis {
-
-    public interface HttpHandler : Object {
-        public abstract async HttpResult handle(HttpContext context) throws Error;
-    }
-
-}

+ 0 - 50
src/Response.vala

@@ -1,50 +0,0 @@
-using Invercargill;
-using Invercargill.DataStructures;
-
-namespace Astralis {
-
-    public class HttpResult : Object {
-        private Series<HttpHeader> header_list;
-        public StatusCode status { get; set; }
-        public Enumerable<HttpHeader> headers { get; private set; }
-
-        public HttpResult(StatusCode status, Enumerable<HttpHeader>? headers = null) {
-            this.header_list = new Series<HttpHeader>();
-            this.headers = header_list.seal(); 
-            this.status = status;
-            if(headers != null) {
-                header_list.add_all(headers);
-            }
-        }
-
-        public void add_new_header(string name, string value) {
-            header_list.add(new HttpHeader(name, value));
-        }
-
-        public void add_header(HttpHeader header) {
-            header_list.add(header);
-        }
-
-        public void add_headers(Enumerable<HttpHeader> headers) {
-            header_list.add_all(headers);
-        }
-    }
-
-    public class BufferedHttpResult : HttpResult {
-
-        public uint8[] buffer { get; set; }
-
-        public BufferedHttpResult(uint8[] buffer, StatusCode status = StatusCode.OK, Enumerable<HttpHeader>? headers = null) {
-            base(status, headers);
-            this.buffer = buffer;
-            add_new_header("Content-Length", this.buffer.length.to_string());
-        }
-
-        public BufferedHttpResult.from_string(string str, StatusCode status = StatusCode.OK, Enumerable<HttpHeader>? headers = null) {
-            base(status, headers);
-            this.buffer = str.data;
-            add_new_header("Content-Length", this.buffer.length.to_string());
-        }
-    }
-
-}

+ 0 - 104
src/Server.vala

@@ -1,104 +0,0 @@
-using MHD;
-
-namespace Astralis {
-
-    public class Server : Object {
-        private Daemon daemon;
-        private HttpHandler handler;
-        private int port;
-
-        public Server(int port, HttpHandler handler) {
-            this.port = port;
-            this.handler = handler;
-        }
-
-        private int access_handler (Connection connection, string? url, string? method, string? version, string? upload_data, size_t* upload_data_size, void** con_cls) {
-            
-            // Initial call for this request
-            if (con_cls[0] == null) {
-                // Determine request type, allocate context etc.
-                // For this simple example, we just mark it as seen (non-null).
-                 con_cls[0] = (void*) 1; 
-                 return Result.YES;
-            }
-
-            if (upload_data_size[0] != 0) {
-                // We don't handle upload data in this scaffold yet.
-                upload_data_size[0] = 0;
-                return Result.YES;
-            }
-
-            var request = new HttpRequest(url ?? "", method ?? "", version ?? "");
-            var context = new HttpContext(request);
-            MHD.suspend_connection(connection);
-
-            handler.handle.begin(context, (obj, res) => {
-                try {
-                    var result = handler.handle.end(res);
-                    respond(connection, result);
-                }
-                catch(Error e) {
-                    handle_error(e, connection);
-                }
-            });
-
-            return Result.YES;
-        }
-
-        public void run() {
-            daemon = Daemon.start(
-                MHD.USE_SELECT_INTERNALLY | MHD.ALLOW_SUSPEND_RESUME | MHD.USE_DEBUG, 
-                (uint16) this.port, 
-                null, 
-                (connection, url, method, version, upload_data, upload_data_size, con_cls) => {
-                    // Trampoline to instance method if needed, or just use lambda capturing 'this'
-                    return this.access_handler(connection, url, method, version, upload_data, upload_data_size, con_cls);
-                }, 
-                MHD.OPTION_END
-            );
-
-            if (daemon == null) {
-                error("Failed to start daemon");
-            }
-
-            print(@"Server running on port $port\nPress Ctrl+C to stop...\n");
-            new MainLoop().run();
-        }
-
-        private void respond(Connection connection, HttpResult result) {
-            Response response;
-
-            if(result.get_type().is_a(typeof(BufferedHttpResult))) {
-                var buffered = (BufferedHttpResult)result;
-                response = new Response.from_buffer(
-                    buffered.buffer.length,
-                    buffered.buffer, 
-                    ResponseMemoryMode.RESPMEM_MUST_COPY
-                );
-            }
-            else {
-                response = new Response.from_buffer(
-                    0,
-                    new uint8[0], 
-                    ResponseMemoryMode.RESPMEM_PERSISTENT
-                );
-            }
-
-
-            foreach (var header in result.headers) {
-                response.add_header(header.header, header.value);
-            }
-
-            var res = MHD.queue_response(connection, result.status, response);
-            if(res != MHD.Result.YES) {
-                printerr("Astralis Internal Error: Unable to queue response\n");
-            }
-            MHD.resume_connection(connection);
-        }
-
-        private void handle_error(Error error, Connection connection) {
-            printerr(@"Astralis Internal Server Error: Unhandled Error: $(error.message)\n");
-            respond(connection, new BufferedHttpResult.from_string("Internal Server Error", StatusCode.INTERNAL_SERVER_ERROR));
-        }
-    }
-}

+ 39 - 0
src/Server/RequestContext.vala

@@ -0,0 +1,39 @@
+
+namespace Astralis {
+
+    internal class RequestContext {
+
+        public HttpContext? handler_context { get; set; }
+        public AsyncPipe.AsyncPipeWriter request_body_pipe { get; set; }
+        public HttpResult? handler_result { get; set; }
+        public Error? handler_error { get; set; }
+        public bool handler_started { get; set; }
+
+        private Mutex request_lock;
+        private bool request_fully_received;
+        private bool handler_finished_execution;
+
+        public RequestContext() {
+            request_body_pipe = AsyncPipe.new_writer();
+            request_lock = Mutex();
+        }
+
+        public bool handler_finished() {
+            request_lock.lock();
+            handler_finished_execution = true;
+            var result = request_fully_received;
+            request_lock.unlock();
+            return result;
+        }
+
+        public bool request_reception_finished() {
+            request_lock.lock();
+            request_fully_received = true;
+            var result = handler_finished_execution;
+            request_lock.unlock();
+            return result;
+        }
+
+    }
+
+}

+ 251 - 0
src/Server/Server.vala

@@ -0,0 +1,251 @@
+using MHD;
+using Invercargill;
+using Invercargill.DataStructures;
+
+namespace Astralis {
+
+    public class Server : Object {
+        private Daemon daemon;
+        private HttpHandler handler;
+        private int port;
+        private HashSet<RequestContext> request_contexts;
+
+        public Server(int port, HttpHandler handler) {
+            this.port = port;
+            this.handler = handler;
+            this.request_contexts = new HashSet<RequestContext>();
+        }
+
+        private int access_handler (Connection connection, string? url, string? method, string? version, string? upload_data, size_t* upload_data_size, void** con_cls) {
+
+            printerr(@"upload_data_size = $(upload_data_size[0])\n");
+            // On initial call for request, simply set up the context
+            if (con_cls[0] == null) {
+                var context = new RequestContext();
+                request_contexts.add(context);
+                con_cls[0] = (void*) context;
+                return Result.YES;
+            }
+
+            // Subsequent calls are provided the context
+            RequestContext context = (RequestContext) ((Object*) con_cls[0]);
+
+            // On the second call we populate the `HttpRequest` object and begin the handler
+            if (context.handler_context == null) {
+                // Extract all headers from the connection
+                var headers = new Catalogue<string, string>();
+                var headers_collector = new KeyValueCollector(headers);
+                MHD.get_connection_values(connection, MHD.ValueKind.HEADER_KIND, KeyValueCollector.key_value_iterator, (void*)headers_collector);
+    
+                // Extract query parameters from the connection
+                var query_params = new Catalogue<string, string>();
+                var query_collector = new KeyValueCollector(query_params);
+                MHD.get_connection_values(connection, MHD.ValueKind.GET_ARGUMENT_KIND, KeyValueCollector.key_value_iterator, (void*)query_collector);
+    
+                // Extract cookies from the connection
+                var cookies = new Catalogue<string, string>();
+                var cookies_collector = new KeyValueCollector(cookies);
+                MHD.get_connection_values(connection, MHD.ValueKind.COOKIE_KIND, KeyValueCollector.key_value_iterator, (void*)cookies_collector);
+    
+                // Get remote address
+                var remote_address = get_remote_address(connection);
+    
+                // Create HttpRequest with all required parameters
+                var http_request = new HttpRequest(
+                    url ?? "",
+                    method ?? "",
+                    version ?? "",
+                    headers,
+                    context.request_body_pipe.pipe,
+                    query_params,
+                    cookies,
+                    remote_address
+                );
+                var http_context = new HttpContext(http_request);
+                context.handler_context = http_context;
+
+                // Kick off the handler
+                printerr("begin handler...\n");
+                handler.handle.begin(http_context, (obj, res) => {
+                    try {
+                        context.handler_result = handler.handle.end(res);
+                    }
+                    catch(Error e) {
+                        context.handler_error = e;
+                    }
+                    printerr("handler finished...\n");
+                    if(context.handler_finished()) {
+                        respond(connection, context);
+                        MHD.resume_connection(connection);
+                    }
+                });
+            }
+
+            // On the second, and all subsequent calls - read the request body:
+            if (upload_data_size[0] != 0) {
+                printerr("load chunk...\n");
+                var data = new uint8[upload_data_size[0]];
+                Memory.copy(data, upload_data, upload_data_size[0]);
+                context.request_body_pipe.write(data);
+                upload_data_size[0] = 0;
+                printerr("Return to server loop...\n");
+                return Result.YES;
+            }
+            // End of request body data
+            else {
+                printerr("chunks done innit...\n");
+                context.request_body_pipe.complete();
+                if(context.request_reception_finished()) {
+                    return respond(connection, context);
+                }
+                printerr("Suspending for now...\n");
+                MHD.suspend_connection(connection);
+                return Result.YES;
+            }
+        }
+
+        public void run() {
+            daemon = Daemon.start(
+                MHD.USE_SELECT_INTERNALLY | MHD.ALLOW_SUSPEND_RESUME | MHD.USE_DEBUG, 
+                (uint16) this.port, 
+                null, 
+                (connection, url, method, version, upload_data, upload_data_size, con_cls) => {
+                    // Trampoline to instance method if needed, or just use lambda capturing 'this'
+                    return this.access_handler(connection, url, method, version, upload_data, upload_data_size, con_cls);
+                }, 
+                MHD.OPTION_END
+            );
+
+            if (daemon == null) {
+                error("Failed to start daemon");
+            }
+
+            print(@"Server running on port $port\nPress Ctrl+C to stop...\n");
+            new MainLoop().run();
+        }
+
+        private Result respond(Connection connection, RequestContext context) {
+            var result = Result.NO;
+            if(context.handler_error != null) {
+                result = handle_error(context.handler_error, connection);
+            }
+            else if(context.handler_result != null) {
+                result = send_result(connection, context.handler_result);
+            }
+            request_contexts.remove(context);
+            return result;
+        }
+
+        private Result send_result(Connection connection, HttpResult result) {
+            Response response;
+
+            if(result.get_type().is_a(typeof(BufferedHttpResult))) {
+                var buffered = (BufferedHttpResult)result;
+                response = new Response.from_buffer(
+                    buffered.buffer.length,
+                    buffered.buffer, 
+                    ResponseMemoryMode.RESPMEM_MUST_COPY
+                );
+            }
+            else {
+                response = new Response.from_buffer(
+                    0,
+                    new uint8[0], 
+                    ResponseMemoryMode.RESPMEM_PERSISTENT
+                );
+            }
+
+
+            result.headers.to_immutable_buffer().iterate((grouping) => {
+                grouping.iterate((value) => {
+                    response.add_header(grouping.key, value);
+                });
+            });
+
+            var res = MHD.queue_response(connection, result.status, response);
+            if(res != MHD.Result.YES) {
+                printerr("Astralis Internal Error: Unable to queue response\n");
+            }
+            return res;
+        }
+
+        private Result handle_error(Error error, Connection connection) {
+            printerr(@"Astralis Internal Server Error: Unhandled Error: $(error.message)\n");
+            return send_result(connection, new BufferedHttpResult.from_string("Internal Server Error", StatusCode.INTERNAL_SERVER_ERROR));
+        }
+
+        // Simple memory-based InputStream for request body
+        private class MemoryInputStream : InputStream {
+            private uint8[] data;
+            private size_t pos;
+
+            public MemoryInputStream.from_bytes(uint8[] data) {
+                this.data = data;
+                this.pos = 0;
+            }
+
+            public override ssize_t read(uint8[] buffer, Cancellable? cancellable = null) throws IOError {
+                if (pos >= data.length) {
+                    return 0; // EOF
+                }
+                size_t remaining = data.length - pos;
+                size_t to_read = buffer.length < remaining ? buffer.length : remaining;
+                Memory.copy(buffer, data[pos:pos + to_read], to_read);
+                pos += to_read;
+                return (ssize_t)to_read;
+            }
+
+            public override bool close(Cancellable? cancellable = null) throws IOError {
+                return true;
+            }
+        }
+
+        // Helper class to collect key-value pairs (headers, query params, cookies) from MHD connection
+        private class KeyValueCollector {
+            private unowned Catalogue<string, string> catalogue;
+
+            public KeyValueCollector(Catalogue<string, string> catalogue) {
+                this.catalogue = catalogue;
+            }
+
+            public static Result key_value_iterator(void* cls, MHD.ValueKind kind, string key, string? value) {
+                // cls is a pointer to KeyValueCollector instance
+                var collector = (KeyValueCollector*) cls;
+                if (key != null && value != null) {
+                    collector->catalogue.add(key, value);
+                }
+                return MHD.Result.YES;
+            }
+        }
+
+        // Get remote address from connection
+        private string? get_remote_address(Connection connection) {
+            // Get connection info to retrieve client address
+            var info = MHD.get_connection_info(connection, MHD.ConnectionInfoType.CLIENT_ADDRESS);
+            if (info == null) {
+                return null;
+            }
+
+            // The client_addr field points to a sockaddr structure
+            // We need to cast it properly and extract the IP address
+            var addr_ptr = info.client_addr;
+            if (addr_ptr == null) {
+                return null;
+            }
+
+            // Create SocketAddress from native sockaddr structure
+            // Use 128 as the maximum size for sockaddr (IPv6)
+            try {
+                var sock_addr = SocketAddress.from_native((void*)addr_ptr, 128);
+                
+                // Cast to InetSocketAddress to get the address
+                var inet_addr = (InetSocketAddress) sock_addr;
+                
+                // Get the InetAddress and convert to string
+                return inet_addr.address.to_string();
+            } catch (Error e) {
+                return null;
+            }
+        }
+    }
+}

+ 10 - 8
src/meson.build

@@ -1,16 +1,18 @@
 sources = files(
-    'Context.vala',
-    'Router.vala',
-    'Server.vala',
-    'HttpValues.vala',
-    'RequestHandler.vala',
-    'Headers.vala',
-    'Response.vala'
+    'Core/Context.vala',
+    'Core/HttpValues.vala',
+    'Core/RequestHandler.vala',
+    'Core/Response.vala',
+    'Core/AsyncPipe.vala',
+    'Data/FormDataParser.vala',
+    'Handlers/Router.vala',
+    'Server/Server.vala',
+    'Server/RequestContext.vala',
 )
 
 libastralis = shared_library('astralis',
     sources,
-    dependencies: [glib_dep, gobject_dep, mhd_dep, gio_dep, invercargill_dep, invercargill_json_dep, json_glib_dep],
+    dependencies: [glib_dep, gobject_dep, mhd_dep, gio_dep, gio_unix_dep, invercargill_dep, invercargill_json_dep, json_glib_dep],
     install: true
 )
 

+ 50 - 3
vapi/libmicrohttpd.vapi

@@ -90,9 +90,56 @@ namespace MHD {
     [CCode (cname = "MHD_lookup_connection_value")]
     public unowned string? lookup_connection_value (Connection connection, ValueKind kind, string key);
 
-    [CCode (instance_pos = 0)]
-    public delegate Result KeyValueIterator (void* cls, ValueKind kind, string key, string? value, size_t value_size);
+    [CCode (has_target = false)]
+    public delegate Result KeyValueIterator (void* cls, ValueKind kind, string key, string? value);
 
     [CCode (cname = "MHD_get_connection_values")]
-    public int get_connection_values (Connection connection, ValueKind kind, KeyValueIterator? iterator, void* iterator_cls);
+    public int get_connection_values (Connection connection, ValueKind kind, KeyValueIterator? iterator, void* iterator_cls = null);
+
+    [CCode (cname = "enum MHD_ConnectionInfoType", cprefix = "MHD_CONNECTION_INFO_")]
+    public enum ConnectionInfoType {
+        CIPHER_ALGO,
+        PROTOCOL,
+        CLIENT_ADDRESS,
+        GNUTLS_SESSION,
+        GNUTLS_CLIENT_CERT,
+        DAEMON,
+        CONNECTION_FD,
+        SOCKET_CONTEXT,
+        CONNECTION_SUSPENDED,
+        CONNECTION_TIMEOUT,
+        REQUEST_HEADER_SIZE,
+        HTTP_STATUS
+    }
+
+    [CCode (cname = "union MHD_ConnectionInfo", has_type_id = false)]
+    public struct ConnectionInfo {
+        [CCode (cname = "cipher_algorithm")]
+        public int cipher_algorithm;
+        [CCode (cname = "protocol")]
+        public int protocol;
+        [CCode (cname = "client_cert")]
+        public void* client_cert;
+        [CCode (cname = "tls_session")]
+        public void* tls_session;
+        [CCode (cname = "daemon")]
+        public Daemon* daemon;
+        [CCode (cname = "connect_fd")]
+        public int connect_fd;
+        [CCode (cname = "socket_context")]
+        public void* socket_context;
+        [CCode (cname = "suspended")]
+        public int suspended;
+        [CCode (cname = "connection_timeout")]
+        public uint connection_timeout;
+        [CCode (cname = "client_addr")]
+        public void* client_addr;
+        [CCode (cname = "header_size")]
+        public size_t header_size;
+        [CCode (cname = "http_status")]
+        public uint http_status;
+    }
+
+    [CCode (cname = "MHD_get_connection_info")]
+    public unowned ConnectionInfo? get_connection_info (Connection connection, ConnectionInfoType info_type);
 }