Przeglądaj źródła

refactor(response): simplify HttpResult API with fluent header setting

Replace BufferedHttpResult.from_string() with HttpStringResult and
introduce fluent .set_header() chaining for cleaner response building.
Update all examples to use the new API pattern.

Internal changes:
- ResponseContext now holds server reference for proper cleanup
- Fix ContentReaderCallback binding to use has_target=false
- HttpDataResult properly initializes bytes field
- Remove redundant get_json_headers() helper functions
Billy Barrow 4 tygodni temu
rodzic
commit
f0b4d65827

+ 8 - 10
examples/DataStructuresDemo.vala

@@ -13,8 +13,7 @@ using Invercargill.DataStructures;
 // Root handler
 class RootHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            """Data Structures Demo
+        return new HttpStringResult("""Data Structures Demo
 
 This example demonstrates various Invercargill data structures:
 
@@ -25,8 +24,7 @@ Endpoints:
   GET /immutable-buffer      - ImmutableBuffer operations
   GET /wrap-operations       - Wrap utility functions
   GET /combined-operations   - Combined operations
-"""
-        );
+""");
     }
 }
 
@@ -51,7 +49,7 @@ class SeriesHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -77,7 +75,7 @@ class VectorHandler : Object, RouteHandler {
   Last: $last
   Elements: 1, 2, 3, 4, 5";
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -100,7 +98,7 @@ class RingBufferHandler : Object, RouteHandler {
   Count: $count
   Note: RingBuffer automatically overwrites old items when full";
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -129,7 +127,7 @@ class ImmutableBufferHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -159,7 +157,7 @@ class WrapOperationsHandler : Object, RouteHandler {
   Numbers: 1, 2, 3, 4, 5
   Strings: one, two, three, four, five";
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -202,7 +200,7 @@ class CombinedOperationsHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 

+ 9 - 11
examples/EnumerableOperations.vala

@@ -12,8 +12,7 @@ using Invercargill.DataStructures;
 // Root handler
 class RootHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            """Enumerable Operations Demo
+        return new HttpStringResult("""Enumerable Operations Demo
 
 This example demonstrates various LINQ-like operations available in Invercargill Enumerable:
 
@@ -25,8 +24,7 @@ Endpoints:
   GET /sort                - Sorting examples
   GET /set-operations      - Set operations (union, intersection, etc.)
   GET /advanced            - Advanced operations (zip, partition, etc.)
-"""
-        );
+""");
     }
 }
 
@@ -71,7 +69,7 @@ class FilterHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -116,7 +114,7 @@ class MapHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -147,7 +145,7 @@ class GroupByHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -193,7 +191,7 @@ class AggregateHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -223,7 +221,7 @@ class SortHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -261,7 +259,7 @@ class SetOperationsHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -299,7 +297,7 @@ class AdvancedHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 

+ 42 - 110
examples/ErrorHandling.vala

@@ -12,8 +12,7 @@ using Invercargill.DataStructures;
 // Root handler
 class RootHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            """Error Handling Demo
+        return new HttpStringResult("""Error Handling Demo
 
 This example demonstrates various error handling patterns in Astralis:
 
@@ -30,52 +29,39 @@ Endpoints:
   GET /unauthorized          - 401 Unauthorized
   GET /forbidden             - 403 Forbidden
   GET /method-not-allowed    - 405 Method Not Allowed
-"""
-        );
+""");
     }
 }
 
 // 200 OK response
 class OkHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            @"{ \"status\": \"success\", \"message\": \"Request completed successfully\" }",
-            StatusCode.OK,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"status\": \"success\", \"message\": \"Request completed successfully\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
 // 404 Not Found
 class NotFoundHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            @"{ \"error\": \"Not Found\", \"message\": \"The requested resource was not found\" }",
-            StatusCode.NOT_FOUND,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"error\": \"Not Found\", \"message\": \"The requested resource was not found\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
 // 400 Bad Request
 class BadRequestHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            @"{ \"error\": \"Bad Request\", \"message\": \"The request could not be understood\" }",
-            StatusCode.BAD_REQUEST,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"error\": \"Bad Request\", \"message\": \"The request could not be understood\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
 // 500 Internal Server Error
 class InternalErrorHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            @"{ \"error\": \"Internal Server Error\", \"message\": \"An unexpected error occurred\" }",
-            StatusCode.INTERNAL_SERVER_ERROR,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"error\": \"Internal Server Error\", \"message\": \"An unexpected error occurred\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -119,18 +105,12 @@ class ValidationHandler : Object, RouteHandler {
             
             var json_string = json_parts.str;
             
-            return new BufferedHttpResult.from_string(
-                json_string,
-                StatusCode.BAD_REQUEST,
-                get_json_headers()
-            );
+            return new HttpStringResult(json_string)
+                .set_header("Content-Type", "application/json");
         }
         
-        return new BufferedHttpResult.from_string(
-            @"{ \"status\": \"success\", \"email\": \"$email\", \"age\": $age_str }",
-            StatusCode.OK,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"status\": \"success\", \"email\": \"$email\", \"age\": $age_str }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -162,11 +142,8 @@ class ResourceHandler : Object, RouteHandler {
             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()
-        );
+        return new HttpStringResult(resource.to_json())
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -178,18 +155,12 @@ class ApiEndpointHandler : Object, RouteHandler {
         // 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()
-                );
+                return new HttpStringResult(@"{ \"users\": [{\"id\": 1, \"name\": \"Alice\"}, {\"id\": 2, \"name\": \"Bob\"}] }")
+                    .set_header("Content-Type", "application/json");
             
             case "posts":
-                return new BufferedHttpResult.from_string(
-                    @"{ \"posts\": [{\"id\": 1, \"title\": \"First Post\"}, {\"id\": 2, \"title\": \"Second Post\"}] }",
-                    StatusCode.OK,
-                    get_json_headers()
-                );
+                return new HttpStringResult(@"{ \"posts\": [{\"id\": 1, \"title\": \"First Post\"}, {\"id\": 2, \"title\": \"Second Post\"}] }")
+                    .set_header("Content-Type", "application/json");
             
             case "invalid":
                 return error_response("Invalid API endpoint", StatusCode.NOT_FOUND);
@@ -215,22 +186,16 @@ class CustomErrorHandler : Object, RouteHandler {
   \"request_id\": \"$request_id\"
 }";
         
-        return new BufferedHttpResult.from_string(
-            json,
-            StatusCode.BAD_REQUEST,
-            get_json_headers()
-        );
+        return new HttpStringResult(json)
+            .set_header("Content-Type", "application/json");
     }
 }
 
 // Simulated timeout (actually returns 408)
 class TimeoutHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            @"{ \"error\": \"Request Timeout\", \"message\": \"The request took too long to complete\" }",
-            StatusCode.INTERNAL_SERVER_ERROR,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"error\": \"Request Timeout\", \"message\": \"The request took too long to complete\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -240,33 +205,20 @@ class UnauthorizedHandler : Object, RouteHandler {
         var auth_header = http_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
-            );
+            return new HttpStringResult(@"{ \"error\": \"Unauthorized\", \"message\": \"Authentication required\" }")
+                .set_header("Content-Type", "application/json")
+                .set_header("WWW-Authenticate", "Bearer realm=\"api\"");
         }
         
         // 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 HttpStringResult(@"{ \"error\": \"Unauthorized\", \"message\": \"Invalid authentication token\" }")
+                .set_header("Content-Type", "application/json")
+                .set_header("WWW-Authenticate", "Bearer error=\"invalid_token\"");
         }
         
-        return new BufferedHttpResult.from_string(
-            @"{ \"status\": \"success\", \"message\": \"Authenticated\" }",
-            StatusCode.OK,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"status\": \"success\", \"message\": \"Authenticated\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -276,32 +228,21 @@ class ForbiddenHandler : Object, RouteHandler {
         var user_role = http_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 HttpStringResult(@"{ \"error\": \"Forbidden\", \"message\": \"You don't have permission to access this resource\" }")
+                .set_header("Content-Type", "application/json");
         }
         
-        return new BufferedHttpResult.from_string(
-            @"{ \"status\": \"success\", \"message\": \"Admin access granted\" }",
-            StatusCode.OK,
-            get_json_headers()
-        );
+        return new HttpStringResult(@"{ \"status\": \"success\", \"message\": \"Admin access granted\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
 // 405 Method Not Allowed
 class MethodNotAllowedHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        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
-        );
+        return new HttpStringResult(@"{ \"error\": \"Method Not Allowed\", \"message\": \"GET method is not allowed for this endpoint\" }")
+            .set_header("Content-Type", "application/json")
+            .set_header("Allow", "POST, PUT, DELETE");
     }
 }
 
@@ -346,19 +287,10 @@ void main() {
 
 // Helper functions
 
-BufferedHttpResult error_response(string message, StatusCode status) {
+HttpResult error_response(string message, StatusCode 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;
+    return new HttpStringResult(json)
+        .set_header("Content-Type", "application/json");
 }
 
 string generate_request_id() {

+ 23 - 75
examples/FormData.vala

@@ -12,10 +12,7 @@ using Invercargill.DataStructures;
 // HTML form page handler
 class FormPageHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
-        var headers = new Catalogue<string, string>();
-        headers.add("Content-Type", "text/html");
-        return new BufferedHttpResult.from_string(
-            """<!DOCTYPE html>
+        return new HttpStringResult("""<!DOCTYPE html>
 <html>
 <head>
     <title>Form Data Example</title>
@@ -120,10 +117,8 @@ class FormPageHandler : Object, RouteHandler {
         <p><a href="/form-debug">Form Debug Tool</a></p>
     </div>
 </body>
-</html>""",
-            StatusCode.OK,
-            headers
-        );
+</html>""")
+            .set_header("Content-Type", "text/html");
     }
 }
 
@@ -131,10 +126,7 @@ class FormPageHandler : Object, RouteHandler {
 class SimpleFormHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
         if (!context.request.is_post()) {
-            return new BufferedHttpResult.from_string(
-                "Please use POST method",
-                StatusCode.METHOD_NOT_ALLOWED
-            );
+            return new HttpStringResult("Please use POST method");
         }
         
         // Parse form data asynchronously from the request body
@@ -163,7 +155,7 @@ class SimpleFormHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -171,10 +163,7 @@ class SimpleFormHandler : Object, RouteHandler {
 class RegisterFormHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
         if (!context.request.is_post()) {
-            return new BufferedHttpResult.from_string(
-                "Please use POST method",
-                StatusCode.METHOD_NOT_ALLOWED
-            );
+            return new HttpStringResult("Please use POST method");
         }
         
         FormData form_data = yield FormDataParser.parse(
@@ -191,34 +180,19 @@ class RegisterFormHandler : Object, RouteHandler {
         
         // 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
-            );
+            return new HttpStringResult(@"{ \"error\": \"Username is required\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         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
-            );
+            return new HttpStringResult(@"{ \"error\": \"Password is required\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         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
-            );
+            return new HttpStringResult(@"{ \"error\": \"You must be at least 18 years old\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         // Build JSON response using Series
@@ -234,13 +208,8 @@ class RegisterFormHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -248,10 +217,7 @@ class RegisterFormHandler : Object, RouteHandler {
 class SearchFormHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
         if (!context.request.is_post()) {
-            return new BufferedHttpResult.from_string(
-                "Please use POST method",
-                StatusCode.METHOD_NOT_ALLOWED
-            );
+            return new HttpStringResult("Please use POST method");
         }
         
         FormData form_data = yield FormDataParser.parse(
@@ -265,13 +231,8 @@ class SearchFormHandler : Object, RouteHandler {
         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
-            );
+            return new HttpStringResult(@"{ \"error\": \"Search query is required\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         // Simulated search results using Enumerable operations
@@ -307,13 +268,8 @@ class SearchFormHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -321,10 +277,7 @@ class SearchFormHandler : Object, RouteHandler {
 class FileUploadHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext context, RouteContext route_context) throws Error {
         if (!context.request.is_post()) {
-            return new BufferedHttpResult.from_string(
-                "Please use POST method",
-                StatusCode.METHOD_NOT_ALLOWED
-            );
+            return new HttpStringResult("Please use POST method");
         }
         
         FormData form_data = yield FormDataParser.parse(
@@ -353,7 +306,7 @@ class FileUploadHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -506,13 +459,8 @@ class FormDebugHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(result)
+            .set_header("Content-Type", "text/html");
     }
 }
 

+ 50 - 123
examples/HeadersAndCookies.vala

@@ -26,7 +26,7 @@ class HeadersHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -35,9 +35,7 @@ class UserAgentHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         var user_agent = http_context.request.user_agent ?? "Unknown";
         
-        return new BufferedHttpResult.from_string(
-            @"Your User-Agent is: $user_agent"
-        );
+        return new HttpStringResult(@"Your User-Agent is: $user_agent");
     }
 }
 
@@ -54,7 +52,7 @@ class ContentInfoHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -65,26 +63,18 @@ class CheckHeaderHandler : Object, RouteHandler {
         var exists = http_context.request.has_header(header_name);
         var value = http_context.request.get_header(header_name);
         
-        return new BufferedHttpResult.from_string(
-            @"Header '$header_name': $(exists ? "EXISTS" : "NOT FOUND")\nValue: $(value ?? "N/A")"
-        );
+        return new HttpStringResult(@"Header '$header_name': $(exists ? "EXISTS" : "NOT FOUND")\nValue: $(value ?? "N/A")");
     }
 }
 
 // Set custom response headers
 class CustomHeadersHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        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
-        );
+        return new HttpStringResult("Response with custom headers!")
+            .set_header("X-Custom-Header", "Custom-Value")
+            .set_header("X-Request-Id", generate_request_id())
+            .set_header("X-Powered-By", "Astralis")
+            .set_header("X-Server-Time", new DateTime.now_local().format_iso8601());
     }
 }
 
@@ -110,7 +100,7 @@ class CookiesHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -121,15 +111,10 @@ class GetCookieHandler : Object, RouteHandler {
         var value = http_context.request.get_cookie(name);
         
         if (value == null) {
-            return new BufferedHttpResult.from_string(
-                @"Cookie '$name' not found",
-                StatusCode.NOT_FOUND
-            );
+            return new HttpStringResult(@"Cookie '$name' not found");
         }
         
-        return new BufferedHttpResult.from_string(
-            @"Cookie '$name' = '$value'"
-        );
+        return new HttpStringResult(@"Cookie '$name' = '$value'");
     }
 }
 
@@ -140,30 +125,20 @@ class SetCookieHandler : Object, RouteHandler {
         var value = http_context.request.get_query_or_default("value", "123");
         var max_age = http_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
-        );
+        return new HttpStringResult(@"Cookie '$name' set to '$value'")
+            .set_header("Set-Cookie", @"$name=$value; Max-Age=$max_age; Path=/");
     }
 }
 
 // Set multiple cookies
 class SetCookiesHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        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
-        );
+        // Note: Setting multiple cookies with the same header name requires
+        // special handling - this example shows the approach
+        return new HttpStringResult("Multiple cookies set!")
+            .set_header("Set-Cookie", "user=john; Max-Age=3600; Path=/");
+        // Additional cookies would need to be set via multiple Set-Cookie headers
+        // which requires extending the HttpResult API or using a different approach
     }
 }
 
@@ -172,14 +147,8 @@ class DeleteCookieHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         var name = http_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
-        );
+        return new HttpStringResult(@"Cookie '$name' deleted")
+            .set_header("Set-Cookie", @"$name=; Max-Age=0; Path=/");
     }
 }
 
@@ -189,9 +158,7 @@ class HasCookieHandler : Object, RouteHandler {
         var name = http_context.request.get_query_or_default("name", "session");
         var exists = http_context.request.has_cookie(name);
         
-        return new BufferedHttpResult.from_string(
-            @"Cookie '$name': $(exists ? "EXISTS" : "NOT FOUND")"
-        );
+        return new HttpStringResult(@"Cookie '$name': $(exists ? "EXISTS" : "NOT FOUND")");
     }
 }
 
@@ -204,26 +171,18 @@ class SessionHandler : Object, RouteHandler {
             // 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!
+            return new HttpStringResult(@"New session created!
 Session ID: $new_session_id
 
-Your session will expire in 1 hour.",
-                StatusCode.OK,
-                headers
-            );
+Your session will expire in 1 hour.")
+                .set_header("Set-Cookie", @"session_id=$new_session_id; Max-Age=3600; Path=/; HttpOnly");
         }
         
         // Existing session
-        return new BufferedHttpResult.from_string(
-            @"Welcome back!
+        return new HttpStringResult(@"Welcome back!
 Session ID: $session_id
 
-Your session is active."
-        );
+Your session is active.");
     }
 }
 
@@ -232,17 +191,11 @@ class CorsHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         var origin = http_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
-        );
+        return new HttpStringResult(@"CORS enabled for origin: $origin")
+            .set_header("Access-Control-Allow-Origin", origin)
+            .set_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
+            .set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
+            .set_header("Access-Control-Max-Age", "86400");
     }
 }
 
@@ -251,16 +204,10 @@ class CacheHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         var cache_type = http_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
-        );
+        return new HttpStringResult(@"This response is cached ($cache_type cache, 1 hour)")
+            .set_header("Cache-Control", @"$cache_type, max-age=3600")
+            .set_header("Expires", get_expires_header(3600))
+            .set_header("ETag", generate_etag());
     }
 }
 
@@ -269,29 +216,15 @@ class NegotiateHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         var accept = http_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
-            );
+            return new HttpStringResult(@"{ \"message\": \"JSON response\", \"format\": \"json\" }")
+                .set_header("Content-Type", "application/json");
         } 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
-            );
+            return new HttpStringResult(@"<?xml version=\"1.0\"?><response><message>XML response</message><format>xml</format></response>")
+                .set_header("Content-Type", "text/xml");
         } else {
-            headers.add("Content-Type", "text/plain");
-            return new BufferedHttpResult.from_string(
-                "Plain text response",
-                StatusCode.OK,
-                headers
-            );
+            return new HttpStringResult("Plain text response")
+                .set_header("Content-Type", "text/plain");
         }
     }
 }
@@ -299,19 +232,13 @@ class NegotiateHandler : Object, RouteHandler {
 // Security headers
 class SecureHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        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
-        );
+        return new HttpStringResult("Response with security headers!")
+            .set_header("X-Content-Type-Options", "nosniff")
+            .set_header("X-Frame-Options", "DENY")
+            .set_header("X-XSS-Protection", "1; mode=block")
+            .set_header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
+            .set_header("Content-Security-Policy", "default-src 'self'")
+            .set_header("Referrer-Policy", "strict-origin-when-cross-origin");
     }
 }
 

+ 34 - 63
examples/JsonApi.vala

@@ -24,11 +24,9 @@ class ApiRootHandler : Object, RouteHandler {
   }
 }";
         
-        return new BufferedHttpResult.from_string(
-            json,
-            StatusCode.OK,
-            get_json_headers()
-        );
+        return new HttpStringResult(json)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -54,11 +52,9 @@ class UsersListHandler : Object, RouteHandler {
         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()
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -73,11 +69,9 @@ class UserByIdHandler : Object, RouteHandler {
             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()
-        );
+        return new HttpStringResult(user.to_json())
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -112,11 +106,9 @@ class PostsListHandler : Object, RouteHandler {
         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()
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -158,11 +150,9 @@ class PostByIdHandler : Object, RouteHandler {
         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()
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -197,11 +187,9 @@ class CommentsListHandler : Object, RouteHandler {
         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()
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -216,11 +204,9 @@ class CommentByIdHandler : Object, RouteHandler {
             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()
-        );
+        return new HttpStringResult(comment.to_json())
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -245,11 +231,9 @@ class StatsHandler : Object, RouteHandler {
   \"comments_per_post\": $comments_per_post
 }";
         
-        return new BufferedHttpResult.from_string(
-            json,
-            StatusCode.OK,
-            get_json_headers()
-        );
+        return new HttpStringResult(json)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -295,11 +279,9 @@ class SearchHandler : Object, RouteHandler {
         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()
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -334,11 +316,9 @@ class PaginatedPostsHandler : Object, RouteHandler {
         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()
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
     }
 }
 
@@ -396,20 +376,11 @@ void main() {
 
 // 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) {
+HttpResult error_response(string message, StatusCode status = StatusCode.BAD_REQUEST) {
     var json = @"{ \"error\": \"$message\" }";
-    return new BufferedHttpResult.from_string(
-        json,
-        status,
-        get_json_headers()
-    );
+    return new HttpStringResult(json)
+        .set_header("Content-Type", "application/json")
+        .set_header("Access-Control-Allow-Origin", "*");
 }
 
 // Data models and storage

+ 23 - 70
examples/PathRouting.vala

@@ -12,8 +12,7 @@ using Invercargill.DataStructures;
 // Root handler - shows available endpoints
 class RootHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string(
-            """Welcome to the Path Routing Example!
+        return new HttpStringResult("""Welcome to the Path Routing Example!
 
 Available endpoints:
   GET /                           - This message
@@ -26,15 +25,14 @@ Available endpoints:
   GET /api/v1/items/{id}          - Get item by ID
   GET /files/{category}/{name}    - Get file by category and name
   GET /pathinfo                   - Path information demo
-"""
-        );
+""");
     }
 }
 
 // Simple hello handler
 class HelloHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
-        return new BufferedHttpResult.from_string("Hello, World!");
+        return new HttpStringResult("Hello, World!");
     }
 }
 
@@ -42,7 +40,7 @@ class HelloHandler : Object, RouteHandler {
 class HelloNameHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         var name = route_context.get_segment("name");
-        return new BufferedHttpResult.from_string(@"Hello, $name!");
+        return new HttpStringResult(@"Hello, $name!");
     }
 }
 
@@ -69,13 +67,8 @@ class UsersHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -95,22 +88,12 @@ class UserByIdHandler : Object, RouteHandler {
             .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
-            );
+            return new HttpStringResult(@"{ \"error\": \"User not found\" }")
+                .set_header("Content-Type", "application/json");
         }
         
-        var headers = new Catalogue<string, string>();
-        headers.add("Content-Type", "application/json");
-        return new BufferedHttpResult.from_string(
-            user.to_json(),
-            StatusCode.OK,
-            headers
-        );
+        return new HttpStringResult(user.to_json())
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -140,13 +123,8 @@ class UserPostsHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -173,13 +151,8 @@ class ApiStatusHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -196,22 +169,12 @@ class ApiItemHandler : Object, RouteHandler {
         
         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
-            );
+            return new HttpStringResult(item.to_json())
+                .set_header("Content-Type", "application/json");
         }
         
-        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
-        );
+        return new HttpStringResult(@"{ \"error\": \"Item not found\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -238,23 +201,13 @@ class FilesHandler : Object, RouteHandler {
         if (files.try_get(category, out category_files)) {
             ExampleFile? 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
-                );
+                return new HttpStringResult(file.to_json())
+                    .set_header("Content-Type", "application/json");
             }
         }
         
-        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
-        );
+        return new HttpStringResult(@"{ \"error\": \"File not found in category '$category'\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -285,7 +238,7 @@ class PathInfoHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 

+ 14 - 46
examples/QueryParameters.vala

@@ -16,9 +16,7 @@ class GreetHandler : Object, RouteHandler {
         var name = http_context.request.get_query_or_default("name", "World");
         var greeting = http_context.request.get_query_or_default("greeting", "Hello");
         
-        return new BufferedHttpResult.from_string(
-            @"$greeting, $name!"
-        );
+        return new HttpStringResult(@"$greeting, $name!");
     }
 }
 
@@ -30,13 +28,8 @@ class SearchHandler : Object, RouteHandler {
         var offset = int.parse(http_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
-            );
+            return new HttpStringResult(@"{ \"error\": \"Query parameter 'q' is required\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         // Build response using Enumerable operations
@@ -59,13 +52,8 @@ class SearchHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -92,7 +80,7 @@ class DebugHandler : Object, RouteHandler {
         var result = parts.to_immutable_buffer()
             .aggregate<string>("", (acc, s) => acc + s);
         
-        return new BufferedHttpResult.from_string(result);
+        return new HttpStringResult(result);
     }
 }
 
@@ -102,13 +90,8 @@ class FilterHandler : Object, RouteHandler {
         var tags_param = http_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
-            );
+            return new HttpStringResult(@"{ \"message\": \"No tags provided\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         // Parse comma-separated tags using Enumerable operations
@@ -132,13 +115,8 @@ class FilterHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 
@@ -150,13 +128,8 @@ class RangeHandler : Object, RouteHandler {
         var step = int.parse(http_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
-            );
+            return new HttpStringResult(@"{ \"error\": \"min must be less than max\" }")
+                .set_header("Content-Type", "application/json");
         }
         
         // Generate range using Iterate.range and Enumerable operations
@@ -178,13 +151,8 @@ class RangeHandler : Object, RouteHandler {
         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
-        );
+        return new HttpStringResult(json_string)
+            .set_header("Content-Type", "application/json");
     }
 }
 

+ 3 - 12
examples/RemoteAddress.vala

@@ -31,11 +31,9 @@ public class SimpleHttpHandler : Object, RequestHandler {
   \"user_agent\": \"$(request.user_agent ?? "unknown")\"
 }";
         
-        var response = new BufferedHttpResult.from_string(
-            json,
-            StatusCode.OK,
-            get_json_headers()
-        );
+        var response = new HttpStringResult(json)
+            .set_header("Content-Type", "application/json")
+            .set_header("Access-Control-Allow-Origin", "*");
         
         // Log the request to console
         print(@"[$(get_current_time())] $(request.method) $(request.raw_path) from $(remote_address ?? "unknown")\n");
@@ -47,11 +45,4 @@ public class SimpleHttpHandler : Object, RequestHandler {
         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;
-    }
 }

+ 3 - 4
examples/SimpleApi.vala

@@ -6,7 +6,7 @@ using Invercargill.DataStructures;
 class HelloHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         print("Handling /hello\n");
-        return new BufferedHttpResult.from_string("Hello from Astralis!");
+        return new HttpStringResult("Hello from Astralis!");
     }
 }
 
@@ -14,9 +14,8 @@ class HelloHandler : Object, RouteHandler {
 class JsonHandler : Object, RouteHandler {
     public async HttpResult handle_route(HttpContext http_context, RouteContext route_context) throws Error {
         print("Handling /json\n");
-        var headers = new Catalogue<string, string>();
-        headers.add("Content-Type", "application/json");
-        return new BufferedHttpResult.from_string("{ \"message\": \"Hello JSON\" }", StatusCode.OK, headers);
+        return new HttpStringResult("{ \"message\": \"Hello JSON\" }")
+            .set_header("Content-Type", "application/json");
     }
 }
 

+ 4 - 3
src/Core/Response.vala

@@ -27,11 +27,12 @@ namespace Astralis {
 
     public class HttpDataResult : HttpResult {
 
-        private ByteBuffer bytes;
+        private BinaryData bytes;
 
         public HttpDataResult(BinaryData data, StatusCode status = StatusCode.OK) {
-            bytes = data.to_byte_buffer();
-            base(status, bytes.length);
+            var buffer = data.to_byte_buffer();
+            base(status, buffer.length);
+            bytes = buffer;
         }
 
         public async override void send_body(AsyncOutput output) {

+ 3 - 1
src/Server/ResponseContext.vala

@@ -3,7 +3,8 @@ namespace Astralis {
 
     internal class ResponseContext {
 
-        public ResponseContext(Connection connection, HttpResult result) {
+        public ResponseContext(Server server, Connection connection, HttpResult result) {
+            this.server = server;
             body_output = new ServerOutput ();
             body_output.on_new_chunk.connect(new_data_available);
             this.result = result;
@@ -46,6 +47,7 @@ namespace Astralis {
         public bool connection_suspended { get; set; }
         public Connection connection { get; set; }
         public Error? send_body_error { get; set; }
+        public Server server { get; set; }
 
     }
 

+ 6 - 5
src/Server/Server.vala

@@ -133,32 +133,32 @@ namespace Astralis {
 
         private Result send_result(Connection connection, HttpResult result) {
             uint64 size = result.content_length ?? -1;
-            var response_context = new ResponseContext(connection, result);
+            var response_context = new ResponseContext(this, connection, result);
             response_contexts.add(response_context);
 
             var response = new Response.from_callback(
                 size,
                 1048576,
                 (cls, pos, buf, max) => {
-                    var context = (ResponseContext) ((Object*) cls[0]);
+                    var context = (ResponseContext) cls;
                     var bytes_read = context.body_output.read_chunk(buf, max);
                     if(bytes_read == 0) {
                         if(!context.send_body_finished) {
                             context.suspend_connection();
                         }
                         else if(context.send_body_error == null) {
-                            response_contexts.remove(context);
+                            context.server.response_contexts.remove(context);
                             return CONTENT_READER_END_OF_STREAM;
                         }
                         else {
                             printerr(@"Astralis Internal Server Error: Unhandled Error Sending HttpResult: $(context.send_body_error.message)\n");
-                            response_contexts.remove(context);
+                            context.server.response_contexts.remove(context);
                             return CONTENT_READER_END_WITH_ERROR;
                         }
                     }
                     return (ssize_t)bytes_read;
                 },
-                &response_context,
+                (void*)response_context,
                 null);
 
 
@@ -166,6 +166,7 @@ namespace Astralis {
                 response.add_header(kv.key, kv.value);
             });
 
+            response_context.begin_response();
             var res = MHD.queue_response(connection, result.status, response);
             if(res != MHD.Result.YES) {
                 printerr("Astralis Internal Error: Unable to queue response\n");

+ 0 - 1
src/Server/ServerOutput.vala

@@ -7,7 +7,6 @@ namespace Astralis {
         const int MAX_CHUNKS = 5;
         private BinaryData current_chunk = null;
         private Series<ByteBuffer> chunks = new Series<ByteBuffer>();
-        private bool writes_complete;
         internal signal void on_new_chunk();
 
         public async void write (Invercargill.BinaryData data) {

+ 1 - 1
vapi/libmicrohttpd.vapi

@@ -86,7 +86,7 @@ namespace MHD {
         string? upload_data, [CCode(array_length=false)] size_t* upload_data_size, 
         void** con_cls);
 
-    [CCode (instance_pos = 0)]
+    [CCode (has_target = false)]
     public delegate ssize_t ContentReaderCallback (void* cls, uint64 pos, char* buf, size_t max);
 
     [CCode (has_target = false, cname = "MHD_ContentReaderFreeCallback")]