A local emulator for cloudflare workers
npm install --save-dev cf-emu- requests can consume arbitrary amounts of CPU time
- while
eval()andFunction()are disabled to prevent accidental use, this can be easily bypassed and should not be relied upon Date.now()is not throttledsetTimeout()& co are supported regardless of contextcachesare memory only and flush upon restart- secret bindings are not persisted across deployments
- cloudflare headers are added, though the
CF-IPCountryis always hardcoded andCF-Rayends with-DEV - streams are not implemented
- kv bindings are not implemented
HTMLRewriteris not supportedTextEncoder,TextDecoder,fetch()& friends (URL,Request,Response,Headers,FormData,File,Blob), `crypto`` and others are using alternative implementations that are almost certainly not bug-for-bug compatible with cloudflare's; there is sufficient overlap to make this project useful, but more in a WHATWG than a W3C sort of way- APIs released in the last 5 years are almost certainly not supported
While primarily a command line interface, cf-emu supports some additional usage patterns which are described in this document. For a list of supported options, see the CLI definitions or run:
cf-emu --helpStart an API server on localhost:1234 that will (re)deploy cloudflare workers
on localhost:8080 when new code is sent or if they crash:
cf-emu --api 1234 --port 8080 --watchdogSince this is a RCE vector, the API server will refuse to start if neither
CF_TOKEN (for token based authentication) nor CF_EMAIL and CF_APIKEY (for
api key based authentication) environment variables are set. They don't have to
be valid cloudflare credentials (a simple string match is performed) however
they need to be defined and sent when deploying new code. You may bypass this
behavior by setting the --unsafe flag though you should be aware that
fetch() works by default across domain if the destination is localhost
If you import cf-emu/runtime in your unit tests and assign it to global, the
workers runtime API will be globally available to the code that you are testing
(assuming you do this before importing any code to be tested, for example in a
test config file).
If you want to manually invoke your fetch listeners (e.g. for unit tests), a
list of currently registered handlers is available as the non-enumerable
handlers export of the runtime module (it's an Array of functions). Note
that this Array will be mutated over time by calls to addEventListener and /
or removeEventListener, though you can safely discard all registered handlers
(e.g. for test clean-up) by setting its length to 0 (assigning it to [] will
not work as that just replaces your imported copy):
handler.js:
addEventListener('fetch', ev => {
ev.respondWith(new Response('hello world'))
})handler.test.js:
let {handlers} = require('cf-emu/runtime')
let {assert} = require('chai')
describe('handler.js', () => {
before(() => require('./handler.js'))
afterEach(() => handlers.length = 0) // flush all handlers for the next test
it('returns hello world', () => {
let [first] = handlers
assert.isFunction(first, 'did not define an event handler')
first({
respondWith(text) {
assert.equal(text, 'hello world', 'bad response body')
}
})
})
})Alternatively, you can call cf-emu programatically and use fetch() to
interact with your deployed worker, though this is significantly slower as it
involves subprocesses and should only be used for end-to-end tests:
integration.test.js:
let emu = require('cf-emu')
describe('handler.js', () => {
let instance
before((next) => {
instance = emu({
input: './handler.js', // you can use multipart here instead
port: 8080
})
setTimeout(next, 1000) // wait 1 second for the server to start (YMMV!)
})
after(() => instance.close())
// hint: there is also instance.kill(), but that could break code coverage
it('returns hello world', async () => {
let res = await fetch('http://localhost:8080/')
assert.equal(await res.text(), 'hello world')
})
})I guess if you really want to, I can't exactly stop you from putting a bunch of these in standalone mode behind a load balancer, but I definitely won't encourage you to do so:
- for one, while this module is somewhat tested for conformance, no real thought
has been given to performance, security and memory usage; then again, the same
was true for
node.jsitself when it first released so YMMV - some parts of the runtime (e.g. the
cachesobject) could be considered mocks as they don't really do anything useful; this can however be mitigated with the--requireoption if you want to implement your own versions - error propagation is probably not perfect; I'm not currently aware of any classes of errors that fail silently or result in a DoS, but I make no claim to have exhausted all possible error cases