Giter VIP home page Giter VIP logo

cf-emu's Introduction

npm tests coverage

cf-emu

A local emulator for cloudflare workers

Installation

npm install --save-dev cf-emu

Known differences

  • requests can consume arbitrary amounts of CPU time
  • while eval() and Function() are disabled to prevent accidental use, this can be easily bypassed and should not be relied upon
  • Date.now() is not throttled
  • setTimeout() & co are supported regardless of context
  • TextDecoder supports utf-8, utf-16le and latin1, cloudflare only advertises utf-8
  • fetch() is probably less restricted than cloudflare, though not completely spec compliant more info
  • the URL class is following the spec, it's unclear what cloudflare implements though I didn't notice any differences
  • cloudflare headers are added, though the CF-IPCountry is always hardcoded and CF-Ray ends with -DEV
  • streams are not implemented
  • kv bindings are not implemented
  • secret bindings are not persisted across deployments
  • crypto might support different algorithms than cloudflare, depending on how you compiled node.js
  • caches are memory only and flush upon restart
  • HTMLRewriter is not supported

Usage

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 --help

As a development API server

Start 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 --watchdog

Since 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

As a test helper

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')
    })
})

As a production server

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.js itself when it first released so YMMV
  • some parts of the runtime (e.g. the caches object) could be considered mocks as they don't really do anything useful; this can however be mitigated with the --require option 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

cf-emu's People

Watchers

 avatar  avatar

Forkers

jimmywarting

cf-emu's Issues

file class

let File = exports.File = global.File || class File extends Blob {
constructor(parts, name, options={}) {
let lastModified
if(options.lastModified) {
lastModified = options.lastModified
delete options.lastModified
} else
lastModified = Date.now()
super(parts, options)
this._lm = lastModified
this._name = name
}
get lastModified() {
return this._lm
}
get lastModifiedDate() {
return new Date(this._lm)
}
get name() {
return this._name
}
}

fetch-blob@v3 got built in File class now

import File from 'fetch-blob/file.js'

dose not work in NodeJS v20

seems to use global fetch when it's available instead of node-fetch which breaks a bunch of stuff.

Should just rip out all formdata/fetch/busboy stuff and just depend on using built in fetch in nodejs instead.
require that user use node v18+

create the vm context based on something like:

class WorkerScript extends EventTarget { ... }

and make a FetchEvent class based on

class FetchEvent extends Event { ... }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.