using Astralis; using Invercargill; using Invercargill.DataStructures; /** * FormData Example * * Demonstrates handling POST form data in Astralis using async Endpoint. * Shows both application/x-www-form-urlencoded and multipart/form-data handling. */ // HTML form page handler class FormPageEndpoint : Object, Endpoint { public string route { get { return "/"; } } public Method[] methods { owned get { return { Method.GET }; } } public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error { var res = new HttpStringResult(""" Form Data Example

Form Data Examples

Simple Form (URL Encoded)

Registration Form (URL Encoded)

Search Form (URL Encoded)

File Upload (Multipart)

Links

Form Debug Tool

""") .set_header("Content-Type", "text/html"); return res; } } // Simple form submission handler class SimpleFormEndpoint : Object, Endpoint { public string route { get { return "/submit-simple"; } } public Method[] methods { owned get { return { Method.POST }; } } public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error { // 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(); 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("", (acc, s) => acc + s); return new HttpStringResult(result); } } // Registration form submission handler class RegisterFormEndpoint : Object, Endpoint { public string route { get { return "/submit-register"; } } public Method[] methods { owned get { return { Method.POST }; } } public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error { 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 == "") { return new HttpStringResult(@"{ \"error\": \"Username is required\" }") .set_header("Content-Type", "application/json"); } if (password == null || password == "") { return new HttpStringResult(@"{ \"error\": \"Password is required\" }") .set_header("Content-Type", "application/json"); } var age = int.parse(age_str); if (age < 18) { return new HttpStringResult(@"{ \"error\": \"You must be at least 18 years old\" }") .set_header("Content-Type", "application/json"); } // Build JSON response using Series var json_parts = new Series(); 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("", (acc, s) => acc + s); return new HttpStringResult(json_string) .set_header("Content-Type", "application/json"); } } // Search form submission handler class SearchFormEndpoint : Object, Endpoint { public string route { get { return "/submit-search"; } } public Method[] methods { owned get { return { Method.POST }; } } public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error { 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 == "") { return new HttpStringResult(@"{ \"error\": \"Search query is required\" }") .set_header("Content-Type", "application/json"); } // Simulated search results using Enumerable operations var all_products = new Series(); 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(); 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("", (acc, s) => acc + s); return new HttpStringResult(json_string) .set_header("Content-Type", "application/json"); } } // File upload handler (multipart/form-data) class FileUploadEndpoint : Object, Endpoint { public string route { get { return "/submit-file"; } } public Method[] methods { owned get { return { Method.POST }; } } public async HttpResult handle_request(HttpContext context, RouteInformation route) throws Error { 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(); 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("", (acc, s) => acc + s); return new HttpStringResult(result); } } // Form debug tool handler class FormDebugEndpoint : Object, Endpoint { public string route { get { return "/form-debug"; } } public Method[] methods { owned get { return { Method.GET, Method.POST }; } } public async HttpResult handle_request(HttpContext context, RouteInformation route) 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.method == Method.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(); parts.add(""" Form Debug Tool

Form Debug Tool

Test Form

Request Information

Method$(context.request.method)
Path$(context.request.raw_path)
Content Type$(context.request.content_type)
Content Length$(context.request.content_length)
Is Form URL Encoded$(context.request.is_form_urlencoded())
Is Multipart$(context.request.is_multipart())
"""); if (form_data != null) { parts.add(@"
\n"); parts.add(@"

Form Fields ($field_count fields)

\n"); if (field_count == 0) { parts.add(@"

No form fields received.

\n"); } else { parts.add(@" \n"); parts.add(@" \n"); form_data.fields.to_immutable_buffer() .iterate((grouping) => { grouping.iterate((value) => { parts.add(@" \n"); }); }); parts.add(@"
Field NameValue
$(grouping.key)$(value.escape(""))
\n"); } parts.add(@"
\n"); parts.add(@"
\n"); parts.add(@"

File Uploads ($file_count files)

\n"); if (file_count == 0) { parts.add(@"

No files uploaded.

\n"); } else { parts.add(@" \n"); parts.add(@" \n"); form_data.files.to_immutable_buffer() .iterate((grouping) => { grouping.iterate((file) => { parts.add(@" \n"); }); }); parts.add(@"
Field NameFilenameContent-TypeSize
$(file.field_name)$(file.filename)$(file.content_type)$(file.data.length) bytes
\n"); } parts.add(@"
\n"); } else if (body_text != null) { parts.add(@"
\n"); parts.add(@"

Error

\n"); parts.add(@"
$(body_text.escape(""))
\n"); parts.add(@"
\n"); } parts.add("""

Query Parameters

"""); var query_count = context.request.query_params.to_immutable_buffer().count(); if (query_count == 0) { parts.add(@"

No query parameters.

\n"); } else { parts.add(@" \n"); parts.add(@" \n"); context.request.query_params.to_immutable_buffer() .iterate((grouping) => { grouping.iterate((value) => { parts.add(@" \n"); }); }); parts.add(@"
ParameterValue
$(grouping.key)$(value.escape(""))
\n"); } parts.add("""

Back to Forms

"""); var result = parts.to_immutable_buffer() .aggregate("", (acc, s) => acc + s); return new HttpStringResult(result) .set_header("Content-Type", "text/html"); } } // 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 EndpointRouter() .add_endpoint(new FormPageEndpoint()) .add_endpoint(new SimpleFormEndpoint()) .add_endpoint(new RegisterFormEndpoint()) .add_endpoint(new SearchFormEndpoint()) .add_endpoint(new FileUploadEndpoint()) .add_endpoint(new FormDebugEndpoint()); var pipeline = new Pipeline() .add_component(new GzipCompressor()) .add_component(new ZstdCompressor()) .add_component(new BrotliCompressor()) .add_component(router); var server = new Server(8084, pipeline); 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(); }