feat: add Http effect to C backend

Implement HTTP client using POSIX sockets:
- Http.get(url) - GET request
- Http.post(url, body) - POST request
- Http.put(url, body) - PUT request
- Http.delete(url) - DELETE request

Features:
- Self-contained implementation (no libcurl dependency)
- URL parsing for host, port, and path
- HTTP/1.1 protocol with Connection: close
- Response body extraction

All Http operations use evidence passing for handler customization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 12:14:06 -05:00
parent 3c7d72f663
commit 2a2c6d2760
2 changed files with 200 additions and 3 deletions

View File

@@ -467,6 +467,15 @@ impl CBackend {
self.writeln(" void* env;");
self.writeln("} LuxFileHandler;");
self.writeln("");
self.writeln("// Handler struct for Http effect");
self.writeln("typedef struct {");
self.writeln(" LuxString (*get)(void* env, LuxString url);");
self.writeln(" LuxString (*post)(void* env, LuxString url, LuxString body);");
self.writeln(" LuxString (*put)(void* env, LuxString url, LuxString body);");
self.writeln(" LuxString (*delete_req)(void* env, LuxString url);");
self.writeln(" void* env;");
self.writeln("} LuxHttpHandler;");
self.writeln("");
self.writeln("// Evidence struct - passed to effectful functions");
self.writeln("// Contains pointers to current handlers for each effect type");
self.writeln("typedef struct {");
@@ -476,6 +485,7 @@ impl CBackend {
self.writeln(" LuxRandomHandler* random;");
self.writeln(" LuxTimeHandler* time;");
self.writeln(" LuxFileHandler* file;");
self.writeln(" LuxHttpHandler* http;");
self.writeln("} LuxEvidence;");
self.writeln("");
self.writeln("// Default Console handler using built-in implementations");
@@ -672,6 +682,142 @@ impl CBackend {
self.writeln(" .env = NULL");
self.writeln("};");
self.writeln("");
self.writeln("// === Http Effect Built-in Implementations ===");
self.writeln("");
self.writeln("#include <sys/socket.h>");
self.writeln("#include <netinet/in.h>");
self.writeln("#include <netdb.h>");
self.writeln("#include <arpa/inet.h>");
self.writeln("");
self.writeln("// Parse URL into host, port, and path");
self.writeln("static int lux_http_parse_url(const char* url, char* host, int* port, char* path) {");
self.writeln(" *port = 80;");
self.writeln(" path[0] = '/'; path[1] = '\\0';");
self.writeln(" const char* start = url;");
self.writeln(" if (strncmp(url, \"http://\", 7) == 0) start = url + 7;");
self.writeln(" else if (strncmp(url, \"https://\", 8) == 0) { start = url + 8; *port = 443; }");
self.writeln(" const char* slash = strchr(start, '/');");
self.writeln(" const char* colon = strchr(start, ':');");
self.writeln(" if (colon && (!slash || colon < slash)) {");
self.writeln(" strncpy(host, start, colon - start);");
self.writeln(" host[colon - start] = '\\0';");
self.writeln(" *port = atoi(colon + 1);");
self.writeln(" } else if (slash) {");
self.writeln(" strncpy(host, start, slash - start);");
self.writeln(" host[slash - start] = '\\0';");
self.writeln(" } else {");
self.writeln(" strcpy(host, start);");
self.writeln(" }");
self.writeln(" if (slash) strcpy(path, slash);");
self.writeln(" return 0;");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString lux_http_request(const char* method, LuxString url, LuxString body) {");
self.writeln(" char host[256], path[1024];");
self.writeln(" int port;");
self.writeln(" lux_http_parse_url(url, host, &port, path);");
self.writeln("");
self.writeln(" struct hostent* server = gethostbyname(host);");
self.writeln(" if (!server) return strdup(\"Error: Could not resolve host\");");
self.writeln("");
self.writeln(" int sockfd = socket(AF_INET, SOCK_STREAM, 0);");
self.writeln(" if (sockfd < 0) return strdup(\"Error: Could not create socket\");");
self.writeln("");
self.writeln(" struct sockaddr_in serv_addr;");
self.writeln(" memset(&serv_addr, 0, sizeof(serv_addr));");
self.writeln(" serv_addr.sin_family = AF_INET;");
self.writeln(" memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);");
self.writeln(" serv_addr.sin_port = htons(port);");
self.writeln("");
self.writeln(" if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {");
self.writeln(" close(sockfd);");
self.writeln(" return strdup(\"Error: Connection failed\");");
self.writeln(" }");
self.writeln("");
self.writeln(" char request[4096];");
self.writeln(" if (body && strlen(body) > 0) {");
self.writeln(" snprintf(request, sizeof(request),");
self.writeln(" \"%s %s HTTP/1.1\\r\\n\"");
self.writeln(" \"Host: %s\\r\\n\"");
self.writeln(" \"Content-Type: application/x-www-form-urlencoded\\r\\n\"");
self.writeln(" \"Content-Length: %zu\\r\\n\"");
self.writeln(" \"Connection: close\\r\\n\\r\\n%s\",");
self.writeln(" method, path, host, strlen(body), body);");
self.writeln(" } else {");
self.writeln(" snprintf(request, sizeof(request),");
self.writeln(" \"%s %s HTTP/1.1\\r\\n\"");
self.writeln(" \"Host: %s\\r\\n\"");
self.writeln(" \"Connection: close\\r\\n\\r\\n\",");
self.writeln(" method, path, host);");
self.writeln(" }");
self.writeln("");
self.writeln(" send(sockfd, request, strlen(request), 0);");
self.writeln("");
self.writeln(" char* response = malloc(65536);");
self.writeln(" size_t total = 0;");
self.writeln(" ssize_t n;");
self.writeln(" while ((n = recv(sockfd, response + total, 65536 - total - 1, 0)) > 0) {");
self.writeln(" total += n;");
self.writeln(" if (total >= 65535) break;");
self.writeln(" }");
self.writeln(" response[total] = '\\0';");
self.writeln(" close(sockfd);");
self.writeln("");
self.writeln(" // Find body (after \\r\\n\\r\\n)");
self.writeln(" char* body_start = strstr(response, \"\\r\\n\\r\\n\");");
self.writeln(" if (body_start) {");
self.writeln(" body_start += 4;");
self.writeln(" char* result = strdup(body_start);");
self.writeln(" free(response);");
self.writeln(" return result;");
self.writeln(" }");
self.writeln(" return response;");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString lux_http_get(LuxString url) {");
self.writeln(" return lux_http_request(\"GET\", url, NULL);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString lux_http_post(LuxString url, LuxString body) {");
self.writeln(" return lux_http_request(\"POST\", url, body);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString lux_http_put(LuxString url, LuxString body) {");
self.writeln(" return lux_http_request(\"PUT\", url, body);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString lux_http_delete(LuxString url) {");
self.writeln(" return lux_http_request(\"DELETE\", url, NULL);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString default_http_get(void* env, LuxString url) {");
self.writeln(" (void)env;");
self.writeln(" return lux_http_get(url);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString default_http_post(void* env, LuxString url, LuxString body) {");
self.writeln(" (void)env;");
self.writeln(" return lux_http_post(url, body);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString default_http_put(void* env, LuxString url, LuxString body) {");
self.writeln(" (void)env;");
self.writeln(" return lux_http_put(url, body);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxString default_http_delete(void* env, LuxString url) {");
self.writeln(" (void)env;");
self.writeln(" return lux_http_delete(url);");
self.writeln("}");
self.writeln("");
self.writeln("static LuxHttpHandler default_http_handler = {");
self.writeln(" .get = default_http_get,");
self.writeln(" .post = default_http_post,");
self.writeln(" .put = default_http_put,");
self.writeln(" .delete_req = default_http_delete,");
self.writeln(" .env = NULL");
self.writeln("};");
self.writeln("");
self.writeln("// Default evidence with built-in handlers");
self.writeln("static LuxEvidence default_evidence = {");
self.writeln(" .console = &default_console_handler,");
@@ -679,7 +825,8 @@ impl CBackend {
self.writeln(" .reader = NULL,");
self.writeln(" .random = &default_random_handler,");
self.writeln(" .time = &default_time_handler,");
self.writeln(" .file = &default_file_handler");
self.writeln(" .file = &default_file_handler,");
self.writeln(" .http = &default_http_handler");
self.writeln("};");
self.writeln("");
self.writeln("// === List Types ===");
@@ -1409,6 +1556,47 @@ impl CBackend {
}
}
// Built-in Http effect
if effect.name == "Http" {
match operation.name.as_str() {
"get" => {
let url = self.emit_expr(&args[0])?;
if self.has_evidence {
return Ok(format!("ev->http->get(ev->http->env, {})", url));
} else {
return Ok(format!("lux_http_get({})", url));
}
}
"post" => {
let url = self.emit_expr(&args[0])?;
let body = self.emit_expr(&args[1])?;
if self.has_evidence {
return Ok(format!("ev->http->post(ev->http->env, {}, {})", url, body));
} else {
return Ok(format!("lux_http_post({}, {})", url, body));
}
}
"put" => {
let url = self.emit_expr(&args[0])?;
let body = self.emit_expr(&args[1])?;
if self.has_evidence {
return Ok(format!("ev->http->put(ev->http->env, {}, {})", url, body));
} else {
return Ok(format!("lux_http_put({}, {})", url, body));
}
}
"delete" => {
let url = self.emit_expr(&args[0])?;
if self.has_evidence {
return Ok(format!("ev->http->delete_req(ev->http->env, {})", url));
} else {
return Ok(format!("lux_http_delete({})", url));
}
}
_ => {}
}
}
// For other effects, emit generic evidence-passing call
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
if self.has_evidence {
@@ -1915,6 +2103,11 @@ impl CBackend {
"exists" | "isDir" => Some("LuxBool".to_string()),
_ => None,
}
} else if effect.name == "Http" {
match operation.name.as_str() {
"get" | "post" | "put" | "delete" => Some("LuxString".to_string()),
_ => None,
}
} else if effect.name == "Console" {
match operation.name.as_str() {
"print" => Some("void".to_string()),