I haven’t tried it. But with the help of the Gemini AI, (and a lot of corrections to it’s dumb goofs,) here is some test code that might help.
NOTE: There is a corrected file in post 9 below.
http_request.zip (2.5 KB)
#include "ruby.h"
// Helper function to print a C string to the Ruby Console via Kernel#puts
void ruby_puts_cstr(const char *c_str) {
// 1. Create a new Ruby String VALUE from the C string
VALUE r_string = rb_str_new_cstr(c_str);
// 2. Call the Ruby method Kernel#puts with the Ruby String
rb_funcall(rb_mKernel, rb_intern("puts"), 1, r_string);
}
// The C block function for Hash#each. It receives an array [key, value] as 'yieldarg'.
VALUE header_print_block(VALUE yieldarg, VALUE data, int argc, const VALUE *argv, VALUE passed_block) {
// Unpack the yielded array [key, value]
if (TYPE(yieldarg) == T_ARRAY && RARRAY_LEN(yieldarg) >= 2) {
VALUE key = rb_ary_entry(yieldarg, 0);
VALUE value = rb_ary_entry(yieldarg, 1);
// Ensure key and value are string objects before getting the C string pointer.
StringValue(&key);
StringValue(&value);
// Convert key and value to C strings for printing
char *key_str = RSTRING_PTR(key);
char *value_str = RSTRING_PTR(value);
// Format the output string in C and then print via Ruby
char buffer[256];
snprintf(buffer, sizeof(buffer), " Header: %s: %s", key_str, value_str);
ruby_puts_cstr(buffer);
}
return Qnil;
}
// Helper function to print the status code and headers when a request fails.
// Parameters: status_code (Fixnum VALUE), headers (Hash VALUE)
void print_headers(VALUE status_code, VALUE headers) {
char status_buffer[128];
// Convert Fixnum VALUE to Ruby String using rb_inspect for printing
VALUE status_str = rb_inspect(status_code);
// Print the status code label on line one
snprintf(status_buffer, sizeof(status_buffer), "❌ HTTP Failed! Status Code: %s", StringValueCStr(&status_str));
ruby_puts_cstr(status_buffer);
// Iterate over the headers Hash using rb_block_call to invoke Hash#each
rb_block_call(
headers, // The receiver: the headers Hash
rb_intern("each"), // The method: "each"
0, // 0 arguments for Hash#each
NULL,
header_print_block, // C function to handle each iteration
Qnil // Custom data (not used here)
);
}
// The C block function for Sketchup::Http::Request#start.
// It is invoked when the HTTP request completes.
// It expects the yielded value (yieldarg) to be an array [request, response].
VALUE my_callback(VALUE yieldarg, VALUE data, int argc, const VALUE *argv, VALUE passed_block) {
// Get the persistent module for IVAR access
VALUE mLightUp = rb_const_get(rb_cObject, rb_intern("LightUp"));
VALUE mMyExtension = rb_const_get(mLightUp, rb_intern("MyExtension"));
if (TYPE(yieldarg) == T_ARRAY && RARRAY_LEN(yieldarg) >= 2) {
VALUE response_obj = rb_ary_entry(yieldarg, 1);
// Get status_code from the response object
VALUE status_code = rb_funcall(response_obj, rb_intern("status_code"), 0);
// Use FIX2INT for the comparison check
int status_int = FIX2INT(status_code);
char status_buffer[128];
if (status_int >= 200 && status_int < 300) {
// ✅ Success Branch: Print Status Code and Body Content
VALUE body = rb_funcall(response_obj, rb_intern("body"), 0);
// 1. Print status message
VALUE status_str = rb_inspect(status_code);
snprintf(status_buffer, sizeof(status_buffer), "✅ HTTP Request Succeeded! Status: %s", StringValueCStr(&status_str));
ruby_puts_cstr(status_buffer);
// 2. Print body length header
snprintf(status_buffer, sizeof(status_buffer), "--- Response Body (Length: %ld) ---", RSTRING_LEN(body));
ruby_puts_cstr(status_buffer);
// 3. Print the actual body content directly:
rb_funcall(rb_mKernel, rb_intern("puts"), 1, body);
ruby_puts_cstr("------------------------------------");
} else {
// ❌ Failure Branch: Get headers and call print_headers
VALUE headers = rb_funcall(response_obj, rb_intern("headers"), 0);
print_headers(status_code, headers);
}
// Store the response object on the persistent module
rb_ivar_set(mMyExtension, rb_intern("@response"), response_obj);
} else {
rb_warn("Callback did not receive the expected [request, response] array.");
}
// Clear the request reference on the persistent module
rb_ivar_set(mMyExtension, rb_intern("@request"), Qnil);
return Qnil;
}
// C function used internally by the extension to initiate the request.
// It creates a Sketchup::Http::Request object and calls the asynchronous #start method.
// Parameters: self (the containing Ruby module or Qnil), url_c_str (the C string URL)
VALUE c_make_request(VALUE self, const char *url_c_str) {
// Get the persistent module for IVAR access
VALUE mLightUp = rb_const_get(rb_cObject, rb_intern("LightUp"));
VALUE mMyExtension = rb_const_get(mLightUp, rb_intern("MyExtension"));
// Validate that the C string pointer is not NULL
if (url_c_str == NULL) {
rb_raise(rb_eArgError, "The URL argument (C string) cannot be NULL.");
}
// Convert the C string URL to a Ruby String VALUE
VALUE url_str = rb_str_new_cstr(url_c_str);
// Get required classes/modules
VALUE mSketchup = rb_const_get(rb_cObject, rb_intern("Sketchup"));
VALUE mHttp = rb_const_get(mSketchup, rb_intern("Http"));
VALUE cRequest = rb_const_get(mHttp, rb_intern("Request"));
// Define the array of arguments for the constructor
VALUE request_args[] = { url_str };
// Create a new instance
VALUE request_obj = rb_class_new_instance(1, request_args, cRequest);
// Store the request object as an instance variable on the persistent module
rb_ivar_set(mMyExtension, rb_intern("@request"), request_obj);
// Clear the old response variable
rb_ivar_set(mMyExtension, rb_intern("@response"), Qnil);
// Call the start method with the C block using rb_block_call
// rb_block_call(receiver, method_id, argc, argv, func_pointer, data)
rb_block_call(
request_obj, // Receiver: the new request object
rb_intern("start"),// Method ID: "start"
0, // Arg count for #start (none)
NULL, // Arg array (none)
my_callback, // The C function to use as the block
Qnil // Custom data for the block (not used here)
);
// Returns the request object
return request_obj;
}
// Assume your extension so file is named "MyLightUpExtension.so"
void Init_MyLightUpExtension() {
// 1. Define the main namespace module: LightUp
VALUE mLightUp = rb_define_module("LightUp");
// 2. Define the generic data submodule: LightUp::MyExtension
// NOTE: Modules can hold instance variables, making them suitable for this purpose.
VALUE mMyExtension = rb_define_module_under(mLightUp, "MyExtension");
// (Other module definitions, like exposing c_make_request, would go here)
// or other initializations ....
}