{"id":19376610,"url":"https://github.com/gavinray97/polyglot-wasm-faas","last_synced_at":"2025-02-24T15:45:56.352Z","repository":{"id":50692534,"uuid":"519852233","full_name":"GavinRay97/polyglot-wasm-faas","owner":"GavinRay97","description":"A Quarkus app that allows deploying WASM \u0026 and other language functions as API handlers at runtime, using GraalVM","archived":false,"fork":false,"pushed_at":"2022-07-31T19:02:54.000Z","size":2318,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-16T10:56:54.857Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GavinRay97.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-31T18:09:18.000Z","updated_at":"2023-03-03T07:59:46.000Z","dependencies_parsed_at":"2022-08-17T16:05:55.553Z","dependency_job_id":null,"html_url":"https://github.com/GavinRay97/polyglot-wasm-faas","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fpolyglot-wasm-faas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fpolyglot-wasm-faas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fpolyglot-wasm-faas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinRay97%2Fpolyglot-wasm-faas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GavinRay97","download_url":"https://codeload.github.com/GavinRay97/polyglot-wasm-faas/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240508817,"owners_count":19813042,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T08:44:30.905Z","updated_at":"2025-02-24T15:45:56.332Z","avatar_url":"https://github.com/GavinRay97.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Build-your-own (shitty) AWS Lambda/OpenFaaS with Quarkus + GraalVM\n\nThis project is an example of how you can use GraalVM's `Polyglot` functionality to deploy API handlers from WASM modules or scripting languages while the application is live.\n\n\u003e Disclaimer: This project is not production-ready. In particular, there are several optimizations which can be made in regards to GraalVM evaluation by defining a shared `Engine`, better caching of `Source` values, etc.\n\nBy the end of this 5-minute read, we'll have an app that can deploy both the below Rust function (as WASM), and the below JavaScript function to an API endpoint:\n- Rust: https://github.com/GavinRay97/polyglot-wasm-faas/blob/2760f118753b29e51e1b25a7a2d9cbc6808f30b5/src/test/resources/rust-wasm-samples/hello-wasm/src/lib.rs#L17-L25\n- JavaScript: https://github.com/GavinRay97/polyglot-wasm-faas/blob/2760f118753b29e51e1b25a7a2d9cbc6808f30b5/src/test/resources/bundles/javascript/handler.js#L1-L4\n\n## How it works\n\nAt the end, what we'd like to end up with is:\n\n1. A way for users to package up some source code along with a bit of metadata\n2. Have that code be deployable to a user-defined API endpoint, e.g. `/myhandler`\n\nA simple design for this would be to have a bundle of the following:\n- Source code files\n- A metadata file, declaring the language of the code, and the entrypoint file for the program\n\nYou might imagine something like:\n```json\n{\n  \"language\": \"js\",\n  \"entrypointFile\": \"main.js\"\n}\n```\n```js\n// main.js\nfunction handler(ctx) {\n    ctx.response().end(\"Hello world\")\n}\n// Return handler as last line of code so a reference to function is passed\nhandler\n```\n\nThese files could be zipped up into a \"bundle\", and uploaded to the server for the server to deploy.\n\nTo solve this problem, we can start by defining data models. We want an entity to represent the above-mentioned metadata about a handler, and we want an entity to glue together that metadata with the physical files and location on-disk.\n\n```java\nenum PolyglotLanguage {\n    JAVASCRIPT(\"js\"),\n    PYTHON(\"python\"),\n    RUBY(\"ruby\"),\n    WASM(\"wasm\")\n}\n\nrecord PolyglotFunctionMetadata(\n        PolyglotLanguage language,\n        String entrypointFile) {\n}\n\nrecord PolyglotFunctionBundle(\n        String name,\n        PolyglotFunctionMetadata metadata,\n        Path directory) {\n    \n}\n```\n\nThe next thing we need is an API handler that can consume a `.zip` file upload and extract the contents + map them into these entities.\nThat looks something like:\n\n```java\n@ApplicationScoped\n@RouteBase(path = \"/api/v1/handler\")\npublic class HandlerResource {\n\n    ConcurrentHashMap\u003cString, PolyglotFunctionBundle\u003e bundles = new ConcurrentHashMap\u003c\u003e();\n\n    @Route(path = \"/:name\", methods = Route.HttpMethod.POST, consumes = \"multipart/form-data\")\n    void uploadHandler(@Param String name, RoutingContext ctx) {\n        // Error-handling omitted\n        FileUpload fileUpload = ctx.fileUploads().get(0);\n\n        // Extract zip file to temporary directory\n        Path tempDirectory = Files.createTempDirectory(\"polyglot-faas-\");\n        Path outDir = Paths.get(tempDirectory.toString(), name);\n        try (ZipFile zipFile = new ZipFile(fileUpload.uploadedFileName())) {\n            zipFile.extractAll(outDir.toString());\n        }\n\n        // Load handler metadata\n        File metadataFile = outDir.resolve(\"metadata.json\").toFile();\n        PolyglotFunctionMetadata metadata = objectMapper.readValue(metadataFile, PolyglotFunctionMetadata.class);\n\n        // Create handler bundle\n        PolyglotFunctionBundle bundle = new PolyglotFunctionBundle(name, metadata, outDir);\n\n        // Store handler bundle\n        bundles.put(name, bundle);\n        ctx.response().end(\"{\\\"status\\\":\\\"ok\\\", \\\"message\\\":\\\"Handler \" + name + \" uploaded\\\"}\");\n    }\n}\n```\n\nThe last thing we need is an API endpoint that allows invoking the uploaded handlers. This is where it gets a little bit trickier.\n\nAt a high level, there are two \"strategies\" for executing user code. One for scripting languages, and one for WASM modules.\n\nThis is due to how different the execution environments and exposed API's are for those targets. Passing values to-and-from WASM is much more difficult than with traditional Truffle guest languages.\n\nIn WASM, we use stdin and stdout as proxies for arguments. This requires compiling against WASI, otherwise these aren't accessible.\n\nI won't paste the entirety of the underlying code here, but if you're curious check `PolyglotFunctionBundle.java` for `runWasmHandler()` and `loadRequestHandler()`:\n\n```java\nclass HandlerResource {\n\n    @Route(path = \"/:name\", methods = Route.HttpMethod.GET, consumes = \"application/json\", produces = \"application/json\")\n    void getHandler(@Param String name, RoutingContext ctx) {\n        PolyglotFunctionBundle bundle = bundles.get(name);\n        // Error handling omitted to simplify code example\n        if (bundle.metadata().language() == PolyglotLanguage.WASM) {\n            ctx.request().body().onSuccess(body -\u003e {\n                String jsonInput = body.toString();\n                try {\n                    String jsonOutput = bundle.runWasmHandler(jsonInput);\n                    ctx.response().end(jsonOutput);\n                }\n            });\n        } else {\n            bundle.loadRequestHandler().handle(ctx);\n        }\n    }\n}\n```\n\n## Test code to show the rough idea\n\nThe following tests show the overall upload -\u003e invoke flow:\n\n```java\nclass HandlerResourceTest {\n\n    @Test\n    @Order(1)\n    void testUploadHandler() {\n        given()\n                .multiPart(new File(\"src/test/resources/bundles/rust-wasm/rust-wasm.zip\"))\n                .when().post(\"/api/v1/handler/rust-wasm\")\n                .then()\n                .statusCode(200)\n                .body(\"status\", is(\"ok\"))\n                .body(\"message\", is(\"Handler rust-wasm uploaded\"));\n\n        given()\n                .multiPart(new File(\"src/test/resources/bundles/javascript/javascript-handler.zip\"))\n                .when().post(\"/api/v1/handler/javascript-example\")\n                .then()\n                .statusCode(200)\n                .body(\"status\", is(\"ok\"))\n                .body(\"message\", is(\"Handler javascript-example uploaded\"));\n    }\n\n    @Test\n    @Order(2)\n    void testInvokeRustWasmHandler() {\n        given()\n                .contentType(\"application/json\")\n                .body(\"{\\\"name\\\":\\\"John\\\"}\")\n                .when().get(\"/api/v1/handler/rust-wasm\")\n                .then()\n                .statusCode(200)\n                .body(\"name_twice\", is(\"John John\"));\n    }\n\n    @Test\n    @Order(3)\n    void testInvokeJavaScriptHandler() {\n        given()\n                .contentType(\"application/json\")\n                .when().get(\"/api/v1/handler/javascript-example\")\n                .then()\n                .statusCode(200)\n                .body(\"msg\", is(\"Hello from javascript-example\"));\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgavinray97%2Fpolyglot-wasm-faas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgavinray97%2Fpolyglot-wasm-faas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgavinray97%2Fpolyglot-wasm-faas/lists"}