Skip to content
File

Blob: src/workerd/io/worker-source.h

cpp251 lines
1#pragma once
2 
3#include <rust/cxx.h>
4 
5#include <capnp/schema.capnp.h>
6#include <kj/one-of.h>
7#include <kj/refcount.h>
8#include <kj/string.h>
9 
10namespace workerd {
11 
12using kj::byte;
13 
14class DynamicEnvBuilder;
15 
16// Represents the source code for a Worker.
17//
18// Typically the Worker's source is delivered in a capnp message structure. However, workerd vs.
19// the edge runtime use different capnp schemas. This is mostly because the edge runtime is much
20// older and its definition is... ugly, so workerd replaced it with something cleaner for public
21// consumption.
22//
23// WorkerSource is a data structure that can be constructed from either representation -- as well
24// as from non-capnp-based sources, like the dynamic worker loader API.
25//
26// Note that this structure contains StringPtrs and ArrayPtrs pointing to external data which must
27// remain alive while the WorkerSource is alive. This is done because the source may be very large,
28// and we don't want to have to copy it all out of the original capnp structure.
29struct WorkerSource {
30 // These structs are the variants of the `ModuleContent` `OneOf`, defining all the different
31 // module types.
32 struct EsModule {
33 kj::ArrayPtr<const char> body;
34 // Owns the body text in case it was transpiled during the load.
35 kj::Maybe<::rust::String> ownBody;
36 };
37 struct CommonJsModule {
38 kj::StringPtr body;
39 kj::Maybe<kj::Array<kj::StringPtr>> namedExports;
40 };
41 struct TextModule {
42 kj::StringPtr body;
43 };
44 struct DataModule {
45 kj::ArrayPtr<const byte> body;
46 };
47 struct WasmModule {
48 // Compiled .wasm file content.
49 kj::ArrayPtr<const byte> body;
50 };
51 struct JsonModule {
52 // JSON-encoded content; will be parsed automatically when imported.
53 kj::StringPtr body;
54 };
55 struct PythonModule {
56 kj::StringPtr body;
57 };
58 
59 // PythonRequirement is a variant of ModuleContent, but has no body. The module name specifies
60 // a Python package to be provided by the system.
61 struct PythonRequirement {};
62 
63 // CapnpModule is a .capnp Cap'n Proto schema file. The original text of the file isn't provided;
64 // instead, `ModulesSource::capnpSchemas` contains all the capnp schemas needed by the Worker,
65 // and the `CapnpModule` only specifies the type ID of a particular file found in there.
66 //
67 // TODO(someday): Support CapnpSchema in workerd. Today, it's only supported in the internal
68 // codebase.
69 struct CapnpModule {
70 uint64_t typeId;
71 };
72 
73 using ModuleContent = kj::OneOf<EsModule,
74 CommonJsModule,
75 TextModule,
76 DataModule,
77 WasmModule,
78 JsonModule,
79 PythonModule,
80 PythonRequirement,
81 CapnpModule>;
82 
83 struct Module {
84 kj::StringPtr name;
85 ModuleContent content;
86 
87 // Hack for tests: register this as an internal module. Not allowed in production.
88 bool treatAsInternalForTest = false;
89 
90 Module clone() {
91 Module result{.name = name};
92 
93 // TODO(cleanup): kj::OneOf should have a clone() method.
94 KJ_SWITCH_ONEOF(content) {
95 KJ_CASE_ONEOF(content, EsModule) {
96 result.content = content;
97 }
98 KJ_CASE_ONEOF(content, TextModule) {
99 result.content = content;
100 }
101 KJ_CASE_ONEOF(content, DataModule) {
102 result.content = content;
103 }
104 KJ_CASE_ONEOF(content, WasmModule) {
105 result.content = content;
106 }
107 KJ_CASE_ONEOF(content, JsonModule) {
108 result.content = content;
109 }
110 KJ_CASE_ONEOF(content, CommonJsModule) {
111 result.content = CommonJsModule{.body = content.body,
112 .namedExports = content.namedExports.map([](const kj::Array<kj::StringPtr>& other) {
113 return KJ_MAP(e, other) { return e; };
114 })};
115 }
116 KJ_CASE_ONEOF(content, PythonModule) {
117 result.content = content;
118 }
119 KJ_CASE_ONEOF(content, PythonRequirement) {
120 result.content = content;
121 }
122 KJ_CASE_ONEOF(content, CapnpModule) {
123 result.content = content;
124 }
125 }
126 
127 return result;
128 }
129 };
130 
131 // Representation of source code for a worker using Service Workers syntax (deprecated, but will
132 // be supported forever).
133 struct ScriptSource {
134 // Content of the script (JavaScript). Pointer is valid only until the Script constructor
135 // returns.
136 kj::StringPtr mainScript;
137 
138 // Name of the script, used as the script origin for stack traces. Pointer is valid only until
139 // the Script constructor returns.
140 kj::StringPtr mainScriptName;
141 
142 // Global variables to inject at startup.
143 //
144 // This is sort of weird and historical. Under the old Service Workers syntax, the entire
145 // Worker is one JavaScript file, so there are no "modules" in the normal sense. However,
146 // there were various extra blobs of data we wanted to distribute with the code: Wasm modules,
147 // as well as large text and data blobs (e.g. embedded asset files). We decided at the time
148 // that these made sense as types of bindings. But in fact they don't fit well in the bindings
149 // abstraction: most bindings are used as configuration, but these are whole files, too big
150 // to be treated like configuration. We ended up creating a mechanism to separate out these
151 // binding types and distribute them with the code rather than the config. We also need them
152 // to be delivered to the `Worker::Script` constructor rather than the `Worker` constructor
153 // (long story).
154 //
155 // When ES modules arrived, it suddenly made sense to just say that these are modules, not
156 // bindings. But of course, we have to keep supporting Service Workers syntax forever.
157 //
158 // Recall that in Service Workers syntax, bindings show up as global variables.
159 //
160 // So, this array contains the set of Service Worker bindings that are module-like (text, data,
161 // or Wasm blobs), which should be injected into the global scope. We reuse the `Module` type
162 // for this because it is convenient, but note that only a subset of types are actually
163 // supported as globals. In this array, the `name` of each `Module` is the global variable
164 // name.
165 kj::Array<Module> globals;
166 
167 // The worker may have a bundle of capnp schemas attached. (In Service Workers syntax, these
168 // can't be referenced directly by the app, but they may be used by bindings.)
169 capnp::List<capnp::schema::Node>::Reader capnpSchemas;
170 
171 ScriptSource clone() {
172 return {
173 .mainScript = mainScript,
174 .mainScriptName = mainScriptName,
175 .globals = KJ_MAP(g, globals) { return g.clone(); },
176 .capnpSchemas = capnpSchemas,
177 };
178 }
179 };
180 
181 // Representation of source code for a worker using ES Modules syntax.
182 struct ModulesSource {
183 // Path to the main module, which can be looked up in the module registry. Pointer is valid
184 // only until the Script constructor returns.
185 kj::StringPtr mainModule;
186 
187 // All the Worker's modules.
188 kj::Array<Module> modules;
189 
190 // The worker may have a bundle of capnp schemas attached.
191 capnp::List<capnp::schema::Node>::Reader capnpSchemas;
192 
193 bool isPython;
194 
195 // Optional Python memory snapshot. The actual capnp type is declared in the internal codebase,
196 // so we use AnyStruct here. This is deprecated anyway.
197 kj::Maybe<capnp::AnyStruct::Reader> pythonMemorySnapshot;
198 
199 ModulesSource clone() {
200 return {
201 .mainModule = mainModule,
202 .modules = KJ_MAP(m, modules) { return m.clone(); },
203 .capnpSchemas = capnpSchemas,
204 .isPython = isPython,
205 .pythonMemorySnapshot = pythonMemorySnapshot,
206 };
207 }
208 };
209 
210 // The overall value is either ScriptSource or ModulesSource.
211 kj::OneOf<ScriptSource, ModulesSource> variant;
212 
213 // See DynamicEnvBuilder, below. Not commonly used.
214 kj::Maybe<kj::Arc<DynamicEnvBuilder>> dynamicEnvBuilder;
215 
216 WorkerSource(ScriptSource source): variant(kj::mv(source)) {}
217 WorkerSource(ModulesSource source): variant(kj::mv(source)) {}
218 
219 // Clones everything owned by the `WorkerSource`. But where it contains external pointers, those
220 // pointers are kept as-is.
221 WorkerSource clone() {
222 KJ_SWITCH_ONEOF(variant) {
223 KJ_CASE_ONEOF(script, ScriptSource) {
224 return WorkerSource(script.clone());
225 }
226 KJ_CASE_ONEOF(modules, ModulesSource) {
227 return WorkerSource(modules.clone());
228 }
229 }
230 KJ_UNREACHABLE;
231 }
232};
233 
234// Bit of a hack: a `WorkerSource` can contain a `DynamicEnvBuilder`, which is an object that
235// has something to do with constructing the `env` object and the `IoChannelFactory`. This
236// mechanism is only used in the edge runtime when using dynamic worker loading, to work around a
237// historical mess that exists there: the script code and `env` (bindings) are loaded from
238// different places and can be mixed and matched, but the (much newer) dynamic worker loader API
239// has both of these coming from the same invocation of the loader callback. To get the correct
240// `env` through the windy passages and to the right place, we encode it in this "attachment" to
241// `WorkerSource`.
242//
243// In `workerd`, this is not needed at all, due to the design being much newer and cleaner.
244// Hopefully, the edge runtime can eventually be refactored to eliminate this!
245class DynamicEnvBuilder: public kj::AtomicRefcounted {
246 // No methods here: This type exists strictly to be downcast to the appropriate subclass in the
247 // internal codebase.
248};
249 
250} // namespace workerd