load wasm dynamically
This commit is contained in:
		
							parent
							
								
									6f5e0c361d
								
							
						
					
					
						commit
						cd40e504ab
					
				
					 3 changed files with 100 additions and 7 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -2,3 +2,4 @@ zig-cache/ | |||
| zig-out/ | ||||
| core | ||||
| src/worker_name.txt | ||||
| *.wasm | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								src/demo.wasm
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/demo.wasm
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										106
									
								
								src/main.zig
									
										
									
									
									
								
							
							
						
						
									
										106
									
								
								src/main.zig
									
										
									
									
									
								
							|  | @ -5,7 +5,7 @@ var x_auth_key: [:0]const u8 = undefined; | |||
| 
 | ||||
| const cf_api_base = "https://api.cloudflare.com/client/v4"; | ||||
| 
 | ||||
| pub fn main() !void { | ||||
| pub fn main() !u8 { | ||||
|     var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | ||||
|     defer arena.deinit(); | ||||
|     const allocator = arena.allocator(); | ||||
|  | @ -21,7 +21,7 @@ pub fn main() !void { | |||
|     //       We might actually want a "run this wasm" upload vs a "these are my | ||||
|     //       js files" upload. But for now we'll optimize for wasm | ||||
|     const script = | ||||
|         \\import demoWasm from "./24526702f6c3ed7fb02b15125f614dd38804525f-demo.wasm"; | ||||
|         \\import demoWasm from "demo.wasm"; | ||||
|         \\var src_default = { | ||||
|         \\  async fetch(request, _env2, ctx) { | ||||
|         \\    const stdout = new TransformStream(); | ||||
|  | @ -56,7 +56,8 @@ pub fn main() !void { | |||
|         \\  src_default as default | ||||
|         \\}; | ||||
|     ; | ||||
|     const wasm = @embedFile("demo.wasm"); | ||||
|     var wasm = try loadWasm(allocator, script); | ||||
|     defer wasm.deinit(); | ||||
| 
 | ||||
|     // stdout is for the actual output of your application, for example if you | ||||
|     // are implementing gzip, then only the compressed bytes should be sent to | ||||
|  | @ -79,18 +80,91 @@ pub fn main() !void { | |||
|         .{if (worker_exists) "Worker exists, will not re-enable" else "Worker is new. Will enable after code update"}, | ||||
|     ); | ||||
| 
 | ||||
|     try putNewWorker(allocator, &client, .{ | ||||
|     var worker = Worker{ | ||||
|         .account_id = accountid.?, | ||||
|         .name = worker_name, | ||||
|         .wasm_file_data = wasm, | ||||
|         .wasm_file_data = wasm.data, | ||||
|         .main_module = script, | ||||
|     }); | ||||
|     }; | ||||
|     putNewWorker(allocator, &client, &worker) catch |err| { | ||||
|         if (worker.errors == null) return err; | ||||
|         const stderr = std.io.getStdErr().writer(); | ||||
|         try stderr.print("{d} errors returned from CloudFlare:\n\n", .{worker.errors.?.len}); | ||||
|         for (worker.errors.?) |cf_err| { | ||||
|             try stderr.print("{s}\n", .{cf_err}); | ||||
|             allocator.free(cf_err); | ||||
|         } | ||||
|         return 1; | ||||
|     }; | ||||
|     const subdomain = try getSubdomain(allocator, &client, accountid.?); | ||||
|     defer allocator.free(subdomain); | ||||
|     try stdout.print("Worker available at: https://{s}.{s}.workers.dev/\n", .{ worker_name, subdomain }); | ||||
|     if (!worker_exists) | ||||
|         try enableWorker(allocator, &client, accountid.?, worker_name); | ||||
|     try bw.flush(); // don't forget to flush! | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| const Wasm = struct { | ||||
|     allocator: std.mem.Allocator, | ||||
|     name: []const u8, | ||||
|     data: []const u8, | ||||
| 
 | ||||
|     const Self = @This(); | ||||
| 
 | ||||
|     pub fn deinit(self: *Self) void { | ||||
|         self.allocator.free(self.name); | ||||
|         self.allocator.free(self.data); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| fn loadWasm(allocator: std.mem.Allocator, script: []const u8) !Wasm { | ||||
|     // Looking for a string like this: import demoWasm from "demo.wasm" | ||||
|     // JavaScript may or may not have ; characters. We're not doing | ||||
|     // a full JS parsing here, so this may not be the most robust | ||||
| 
 | ||||
|     var inx: usize = 0; | ||||
| 
 | ||||
|     var name: ?[]const u8 = null; | ||||
|     while (true) { | ||||
|         inx = std.mem.indexOf(u8, script[inx..], "import ") orelse if (inx == 0) return error.NoImportFound else break; | ||||
|         inx += "import ".len; | ||||
| 
 | ||||
|         // oh god, we're not doing this: https://262.ecma-international.org/5.1/#sec-7.5 | ||||
|         // advance to next token - we don't care what the name is | ||||
|         while (inx < script.len and script[inx] != ' ') inx += 1; | ||||
|         // continue past space(s) | ||||
|         while (inx < script.len and script[inx] == ' ') inx += 1; | ||||
|         // We expect "from " to be next | ||||
|         if (!std.mem.startsWith(u8, script[inx..], "from ")) continue; | ||||
|         inx += "from ".len; | ||||
|         // continue past space(s) | ||||
|         while (inx < script.len and script[inx] == ' ') inx += 1; | ||||
|         // We now expect the name of our file... | ||||
|         if (script[inx] != '"' and script[inx] != '\'') continue; // we're not where we think we are | ||||
|         const quote = script[inx]; // there are two possibilities here | ||||
|         // we don't need to advance inx any more, as we're on the name, and if | ||||
|         // we loop, that's ok | ||||
|         inx += 1; // move off the quote onto the name | ||||
|         const end_quote_inx = std.mem.indexOfScalar(u8, script[inx..], quote); | ||||
|         if (end_quote_inx == null) continue; | ||||
|         const candidate_name = script[inx .. inx + end_quote_inx.?]; | ||||
|         if (std.mem.endsWith(u8, candidate_name, ".wasm")) { | ||||
|             if (name != null) // we are importing two wasm files, and we are now lost | ||||
|                 return error.MultipleWasmImportsUnsupported; | ||||
|             name = candidate_name; | ||||
|         } | ||||
|     } | ||||
|     if (name == null) return error.NoWasmImportFound; | ||||
| 
 | ||||
|     const nm = try allocator.dupe(u8, name.?); | ||||
|     errdefer allocator.free(nm); | ||||
|     const data = try std.fs.cwd().readFileAlloc(allocator, nm, std.math.maxInt(usize)); | ||||
|     return Wasm{ | ||||
|         .allocator = allocator, | ||||
|         .name = nm, | ||||
|         .data = data, | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn getAccountId(allocator: std.mem.Allocator, client: *std.http.Client) ![:0]const u8 { | ||||
|  | @ -167,8 +241,9 @@ const Worker = struct { | |||
|     name: []const u8, | ||||
|     main_module: []const u8, | ||||
|     wasm_file_data: []const u8, | ||||
|     errors: ?[][]const u8 = null, | ||||
| }; | ||||
| fn putNewWorker(allocator: std.mem.Allocator, client: *std.http.Client, worker: Worker) !void { | ||||
| fn putNewWorker(allocator: std.mem.Allocator, client: *std.http.Client, worker: *Worker) !void { | ||||
|     const put_script = cf_api_base ++ "/accounts/{s}/workers/scripts/{s}?include_subdomain_availability=true&excludeScript=true"; | ||||
|     const url = try std.fmt.allocPrint(allocator, put_script, .{ worker.account_id, worker.name }); | ||||
|     defer allocator.free(url); | ||||
|  | @ -226,10 +301,27 @@ fn putNewWorker(allocator: std.mem.Allocator, client: *std.http.Client, worker: | |||
|     // std.debug.print("Url is {s}\n", .{url}); | ||||
|     if (req.response.status != .ok) { | ||||
|         std.debug.print("Status is {}\n", .{req.response.status}); | ||||
|         if (req.response.status == .bad_request) | ||||
|             worker.errors = getErrors(allocator, &req) catch null; | ||||
|         return error.RequestFailed; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn getErrors(allocator: std.mem.Allocator, req: *std.http.Client.Request) !?[][]const u8 { | ||||
|     var json_reader = std.json.reader(allocator, req.reader()); | ||||
|     defer json_reader.deinit(); | ||||
|     var body = try std.json.parseFromTokenSource(std.json.Value, allocator, &json_reader, .{}); | ||||
|     defer body.deinit(); | ||||
|     const arr = body.value.object.get("errors").?.array.items; | ||||
|     if (arr.len == 0) return null; | ||||
|     var error_list = try std.ArrayList([]const u8).initCapacity(allocator, arr.len); | ||||
|     defer error_list.deinit(); | ||||
|     for (arr) |item| { | ||||
|         error_list.appendAssumeCapacity(item.object.get("message").?.string); | ||||
|     } | ||||
|     return try error_list.toOwnedSlice(); | ||||
| } | ||||
| 
 | ||||
| fn workerExists(allocator: std.mem.Allocator, client: *std.http.Client, account_id: []const u8, name: []const u8) !bool { | ||||
|     const existence_check = cf_api_base ++ "/accounts/{s}/workers/services/{s}"; | ||||
|     const url = try std.fmt.allocPrint(allocator, existence_check, .{ account_id, name }); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue