Ver código fonte

Initial commit

Billy Barrow 5 dias atrás
commit
b09948f5a9
8 arquivos alterados com 304 adições e 0 exclusões
  1. 18 0
      examples/SimpleApi.vala
  2. 5 0
      examples/meson.build
  3. 15 0
      meson.build
  4. 40 0
      src/Context.vala
  5. 37 0
      src/Router.vala
  6. 88 0
      src/Server.vala
  7. 17 0
      src/meson.build
  8. 84 0
      vapi/libmicrohttpd.vapi

+ 18 - 0
examples/SimpleApi.vala

@@ -0,0 +1,18 @@
+using Astralis;
+
+void main() {
+    var server = new Server(8080);
+    
+    server.map_get("/hello", (context) => {
+        print("Handling /hello\n");
+        context.response.body = "Hello from Astralis!";
+        context.response.content_type = "text/plain";
+    });
+
+    server.map_get("/json", (context) => {
+        context.response.body = "{ \"message\": \"Hello JSON\" }";
+        context.response.content_type = "application/json";
+    });
+
+    server.run();
+}

+ 5 - 0
examples/meson.build

@@ -0,0 +1,5 @@
+executable('simple-api',
+    'SimpleApi.vala',
+    dependencies: [astralis_dep, invercargill_dep],
+    install: false
+)

+ 15 - 0
meson.build

@@ -0,0 +1,15 @@
+project('astralis', ['c', 'vala'],
+  version: '0.1.0',
+)
+
+# Dependencies
+glib_dep = dependency('glib-2.0')
+gobject_dep = dependency('gobject-2.0')
+mhd_dep = dependency('libmicrohttpd')
+invercargill_dep = dependency('invercargill-1')
+
+# VAPI Directory
+add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi')], language: 'vala')
+
+subdir('src')
+subdir('examples')

+ 40 - 0
src/Context.vala

@@ -0,0 +1,40 @@
+using MHD;
+
+namespace Astralis {
+
+    public class HttpRequest : Object {
+        public string url { get; private set; }
+        public string method { get; private set; }
+        public string version { get; private set; }
+        
+        // In a real implementation we would have headers, query params etc map
+        // but for now we keep it simple to just wrappers.
+        private Connection connection;
+
+        public HttpRequest(Connection connection, string url, string method, string version) {
+            this.connection = connection;
+            this.url = url;
+            this.method = method;
+            this.version = version;
+        }
+    }
+
+    public class HttpResponse : Object {
+        public uint status_code { get; set; default = 200; }
+        public string content_type { get; set; default = "text/plain"; }
+        public string body { get; set; default = ""; }
+
+        public HttpResponse() {
+        }
+    }
+
+    public class HttpContext : Object {
+        public HttpRequest request { get; private set; }
+        public HttpResponse response { get; private set; }
+
+        public HttpContext(HttpRequest request, HttpResponse response) {
+            this.request = request;
+            this.response = response;
+        }
+    }
+}

+ 37 - 0
src/Router.vala

@@ -0,0 +1,37 @@
+using Invercargill.DataStructures;
+
+namespace Astralis {
+
+    public delegate void RouteHandler(HttpContext context);
+
+
+    public class RouteEntry {
+        public RouteHandler handler;
+        public RouteEntry(owned RouteHandler handler) {
+            this.handler = (owned) handler;
+        }
+    }
+
+    public class Router : Object {
+        private Dictionary<string, RouteEntry> routes = new Dictionary<string, RouteEntry>();
+
+        public void map_get(string path, owned RouteHandler handler) {
+            // In a real router, we'd handle methods separately.
+            // For now, simple path mapping.
+            routes.set(path, new RouteEntry((owned) handler));
+        }
+
+        public bool handle(HttpContext context) {
+            if (routes.has(context.request.url)) {
+                try {
+                    routes.get(context.request.url).handler(context);
+                    return true;
+                } catch (Invercargill.IndexError e) {
+                    // This should not happen since we checked has() first
+                    return false;
+                }
+            }
+            return false;
+        }
+    }
+}

+ 88 - 0
src/Server.vala

@@ -0,0 +1,88 @@
+using MHD;
+
+namespace Astralis {
+
+    public class Server : Object {
+        private Daemon daemon;
+        private Router router;
+        private int port;
+
+        public Server(int port) {
+            this.port = port;
+            this.router = new Router();
+        }
+
+        public void map_get(string path, owned RouteHandler handler) {
+            router.map_get(path, (owned) handler);
+        }
+
+        private int access_handler (Connection connection, string? url, string? method, string? version, string? upload_data, size_t* upload_data_size, void** con_cls) {
+            
+            // Initial call for this request
+            if (con_cls[0] == null) {
+                // Determine request type, allocate context etc.
+                // For this simple example, we just mark it as seen (non-null).
+                 con_cls[0] = (void*) 1; 
+                 return Result.YES;
+            }
+
+            if (upload_data_size[0] != 0) {
+                // We don't handle upload data in this scaffold yet.
+                upload_data_size[0] = 0;
+                return Result.YES;
+            }
+
+            var request = new HttpRequest(connection, url ?? "", method ?? "", version ?? "");
+            var response = new HttpResponse();
+            var context = new HttpContext(request, response);
+
+            bool handled = router.handle(context);
+            
+            if (!handled) {
+                response.status_code = 404;
+                response.body = "Not Found";
+            }
+
+            // Create MHD response
+            var mhd_response = new Response.from_buffer(
+                response.body.length, 
+                response.body.data, 
+                ResponseMemoryMode.RESPMEM_MUST_COPY
+            );
+            
+            mhd_response.add_header("Content-Type", response.content_type);
+            
+            int ret = MHD.queue_response(connection, response.status_code, mhd_response);
+            
+            // In C we'd destroy response here, but Vala handles memory management via the binding's free_function hopefully, 
+            // OR we'd need to be careful. The VAPI says free_function = MHD_destroy_response, so Vala will call it when the object goes out of scope.
+            // However, MHD_queue_response usually increments refcount or copies? 
+            // Actually MHD_create_response.. returns a response with refcount 1.
+            // MHD_queue_response increments it.
+            // So if our wrapper object dies, it calls destroy, which decrements.
+            // That should be correct.
+            
+            return ret;
+        }
+
+        public void run() {
+            daemon = Daemon.start(
+                MHD.USE_SELECT_INTERNALLY | MHD.USE_DEBUG, 
+                (uint16) this.port, 
+                null, 
+                (connection, url, method, version, upload_data, upload_data_size, con_cls) => {
+                    // Trampoline to instance method if needed, or just use lambda capturing 'this'
+                    return this.access_handler(connection, url, method, version, upload_data, upload_data_size, con_cls);
+                }, 
+                MHD.OPTION_END
+            );
+
+            if (daemon == null) {
+                error("Failed to start daemon");
+            }
+
+            print(@"Server running on port $port\nPress Ctrl+C to stop...\n");
+            new MainLoop().run();
+        }
+    }
+}

+ 17 - 0
src/meson.build

@@ -0,0 +1,17 @@
+sources = files(
+    'Context.vala',
+    'Router.vala',
+    'Server.vala'
+)
+
+libastralis = shared_library('astralis',
+    sources,
+    dependencies: [glib_dep, gobject_dep, mhd_dep, invercargill_dep],
+    install: true
+)
+
+astralis_dep = declare_dependency(
+    link_with: libastralis,
+    include_directories: include_directories('.'),
+    dependencies: [glib_dep, gobject_dep, invercargill_dep, mhd_dep] # Users of astralis need glib, gobject, invercargill and mhd
+)

+ 84 - 0
vapi/libmicrohttpd.vapi

@@ -0,0 +1,84 @@
+[CCode (cheader_filename = "microhttpd.h")]
+namespace MHD {
+    [CCode (cname = "enum MHD_ValueKind", cprefix = "MHD_")]
+    public enum ValueKind {
+        RESPONSE_HEADER_KIND,
+        HEADER_KIND,
+        COOKIE_KIND,
+        POSTDATA_KIND,
+        GET_ARGUMENT_KIND,
+        FOOTER_KIND
+    }
+
+    [CCode (cname = "enum MHD_RequestTerminationCode", cprefix = "MHD_REQUEST_TERMINATED_")]
+    public enum RequestTerminationCode {
+        COMPLETED_OK,
+        WITH_ERROR,
+        TIMEOUT_REACHED,
+        DAEMON_SHUTDOWN,
+        READ_ERROR,
+        CLIENT_ABORT
+    }
+
+    [CCode (cname = "enum MHD_ResponseMemoryMode", cprefix = "MHD_")]
+    public enum ResponseMemoryMode {
+        RESPMEM_PERSISTENT,
+        RESPMEM_MUST_FREE,
+        RESPMEM_MUST_COPY
+    }
+
+    [CCode (cname = "MHD_USE_SELECT_INTERNALLY")]
+    public const uint USE_SELECT_INTERNALLY;
+    [CCode (cname = "MHD_USE_THREAD_PER_CONNECTION")]
+    public const uint USE_THREAD_PER_CONNECTION;
+    [CCode (cname = "MHD_USE_DEBUG")]
+    public const uint USE_DEBUG;
+
+    [CCode (cname = "MHD_OPTION_END")]
+    public const int OPTION_END;
+
+    [SimpleType]
+    [CCode (cname = "struct MHD_Connection*")]
+    public struct Connection {}
+
+    [CCode (cname = "int", cprefix = "MHD_")]
+    public enum Result {
+        YES = 1,
+        NO = 0
+    }
+
+    [Compact]
+    [CCode (cname = "struct MHD_Response", free_function = "MHD_destroy_response")]
+    public class Response {
+        [CCode (cname = "MHD_create_response_from_buffer")]
+        public Response.from_buffer (size_t size, [CCode(array_length=false)] uint8[] buffer, ResponseMemoryMode mode);
+        
+        [CCode (cname = "MHD_add_response_header")]
+        public int add_header (string header, string content);
+    }
+
+    [Compact]
+    [CCode (cname = "struct MHD_Daemon", free_function = "MHD_stop_daemon")]
+    public class Daemon {
+        [CCode (cname = "MHD_start_daemon")]
+        public static Daemon start (uint flags, uint16 port, 
+            AcceptPolicyCallback? apc, 
+            AccessHandlerCallback dh, 
+            ...);
+    }
+
+    [CCode (instance_pos = 0)]
+    public delegate int AcceptPolicyCallback (void* cls, void* addr, uint addrlen);
+
+    [CCode (instance_pos = 0)]
+    public delegate int AccessHandlerCallback (Connection connection, 
+        string? url, string? method, string? version, 
+        string? upload_data, [CCode(array_length=false)] size_t* upload_data_size, 
+        void** con_cls);
+    
+    [CCode (cname = "MHD_queue_response")]
+    public int queue_response (Connection connection, uint status_code, Response response);
+
+    [CCode (cname = "MHD_lookup_connection_value")]
+    public unowned string? lookup_connection_value (Connection connection, ValueKind kind, string key);
+}