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:
@@ -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()),
|
||||
|
||||
Reference in New Issue
Block a user