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:
@@ -301,11 +301,15 @@ Implemented C versions of:
|
|||||||
|
|
||||||
All effects use evidence passing for handler customization.
|
All effects use evidence passing for handler customization.
|
||||||
|
|
||||||
### Phase 3: Http Effect
|
### Phase 3: Http Effect ✅ COMPLETE
|
||||||
|
|
||||||
Implement HTTP client:
|
HTTP client using POSIX sockets:
|
||||||
- `Http.get(url)` - GET request
|
- `Http.get(url)` - GET request
|
||||||
- `Http.post(url, body)` - POST request
|
- `Http.post(url, body)` - POST request
|
||||||
|
- `Http.put(url, body)` - PUT request
|
||||||
|
- `Http.delete(url)` - DELETE request
|
||||||
|
|
||||||
|
Self-contained implementation (no external dependencies like libcurl).
|
||||||
|
|
||||||
### Phase 5: JavaScript Backend
|
### Phase 5: JavaScript Backend
|
||||||
|
|
||||||
|
|||||||
@@ -467,6 +467,15 @@ impl CBackend {
|
|||||||
self.writeln(" void* env;");
|
self.writeln(" void* env;");
|
||||||
self.writeln("} LuxFileHandler;");
|
self.writeln("} LuxFileHandler;");
|
||||||
self.writeln("");
|
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("// Evidence struct - passed to effectful functions");
|
||||||
self.writeln("// Contains pointers to current handlers for each effect type");
|
self.writeln("// Contains pointers to current handlers for each effect type");
|
||||||
self.writeln("typedef struct {");
|
self.writeln("typedef struct {");
|
||||||
@@ -476,6 +485,7 @@ impl CBackend {
|
|||||||
self.writeln(" LuxRandomHandler* random;");
|
self.writeln(" LuxRandomHandler* random;");
|
||||||
self.writeln(" LuxTimeHandler* time;");
|
self.writeln(" LuxTimeHandler* time;");
|
||||||
self.writeln(" LuxFileHandler* file;");
|
self.writeln(" LuxFileHandler* file;");
|
||||||
|
self.writeln(" LuxHttpHandler* http;");
|
||||||
self.writeln("} LuxEvidence;");
|
self.writeln("} LuxEvidence;");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
self.writeln("// Default Console handler using built-in implementations");
|
self.writeln("// Default Console handler using built-in implementations");
|
||||||
@@ -672,6 +682,142 @@ impl CBackend {
|
|||||||
self.writeln(" .env = NULL");
|
self.writeln(" .env = NULL");
|
||||||
self.writeln("};");
|
self.writeln("};");
|
||||||
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("// Default evidence with built-in handlers");
|
||||||
self.writeln("static LuxEvidence default_evidence = {");
|
self.writeln("static LuxEvidence default_evidence = {");
|
||||||
self.writeln(" .console = &default_console_handler,");
|
self.writeln(" .console = &default_console_handler,");
|
||||||
@@ -679,7 +825,8 @@ impl CBackend {
|
|||||||
self.writeln(" .reader = NULL,");
|
self.writeln(" .reader = NULL,");
|
||||||
self.writeln(" .random = &default_random_handler,");
|
self.writeln(" .random = &default_random_handler,");
|
||||||
self.writeln(" .time = &default_time_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("");
|
self.writeln("");
|
||||||
self.writeln("// === List Types ===");
|
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
|
// For other effects, emit generic evidence-passing call
|
||||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||||
if self.has_evidence {
|
if self.has_evidence {
|
||||||
@@ -1915,6 +2103,11 @@ impl CBackend {
|
|||||||
"exists" | "isDir" => Some("LuxBool".to_string()),
|
"exists" | "isDir" => Some("LuxBool".to_string()),
|
||||||
_ => None,
|
_ => 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" {
|
} else if effect.name == "Console" {
|
||||||
match operation.name.as_str() {
|
match operation.name.as_str() {
|
||||||
"print" => Some("void".to_string()),
|
"print" => Some("void".to_string()),
|
||||||
|
|||||||
Reference in New Issue
Block a user