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 {
return new HttpStringResult("""
Form Data Example
Form Data Examples
Simple Form (URL Encoded)
Registration Form (URL Encoded)
Search Form (URL Encoded)
Search Query:
Category:
All Categories
Books
Electronics
Clothing
Min Price:
Max Price:
Search
File Upload (Multipart)
Description:
File:
Upload
""")
.set_header("Content-Type", "text/html");
}
}
// 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
Text Field:
File:
Submit Test
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(@" Field Name Value \n");
form_data.fields.to_immutable_buffer()
.iterate((grouping) => {
grouping.iterate((value) => {
parts.add(@" $(grouping.key) $(value.escape("")) \n");
});
});
parts.add(@"
\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(@" Field Name Filename Content-Type Size \n");
form_data.files.to_immutable_buffer()
.iterate((grouping) => {
grouping.iterate((file) => {
parts.add(@" $(file.field_name) $(file.filename) $(file.content_type) $(file.data.length) bytes \n");
});
});
parts.add(@"
\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(@" Parameter Value \n");
context.request.query_params.to_immutable_buffer()
.iterate((grouping) => {
grouping.iterate((value) => {
parts.add(@" $(grouping.key) $(value.escape("")) \n");
});
});
parts.add(@"
\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(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();
}