@fastly/http-compute-js
A library aiming to provide Node.js-compatible request and response objects.
Compute provides Request and Response objects,
but these are based on the modern Fetch standard, rather than the req
and res
objects
traditionally seen in Node.js programs. If you are more familiar with using the Node.js request and response objects, or
have some libraries that work with them, this library aims to let you do that.
Usage
To your Compute JavaScript project (which you can create using fastly compute init
and the
Compute JavaScript Starter Kit),
add the @fastly/http-compute-js
package as a development dependency.
yarn add --dev @fastly/http-compute-js
or
npm install --save-dev @fastly/http-compute-js
In your program:
import http from '@fastly/http-compute-js';
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!'
}));
});
server.listen();
req
and res
are implementations of IncomingMessage
and
ServerResponse
, respectively, and
can be used as in a Node.js program.
req
is an IncomingMessage
object whose Readable
interface has been wired to the body of the Compute request's body
stream. As such, you can read from it using the standard on('data')
/on('end')
mechanisms, or using
libraries such as parse-body
. You can also read the
headers and other information using the standard interface.
res
is a ServerResponse
object whose Writable
interface is wired to an in-memory buffer.
Write to it normally using res.write()
/ res.end()
or pipe to it using res.pipe()
. You can also set
headers and status code using the standard interfaces.
Notes / Known Issues
- The aim of this library is to provide compatibility where practical. Please understand that some features are not possible
to achieve 100% compatibility with Node.js, due to platform differences.
- Other libraries that consume
IncomingMessage
and ServerResponse
may or may not be compatible with Compute. Some
may work with the use of polyfills, applied during module bundling. - HTTP Version is currently always reported as
1.1
. - Unlike in Node.js, the
socket
property of these objects is always null
, and cannot be assigned. - Some functionality is not (yet) supported:
http.Agent
, http.ClientRequest
, http.get()
, http.request()
, to name a few. - Transfer-Encoding: chunked does not work at the moment and has been disabled.
- At the current time, the
ServerResponse
write stream must be finished before the Response
object is generated.
Webpack polyfills
In order to use this library you must add a number of polyfill configurations to the
webpack.config.js
of your Compute project. Specifically, add the following webpack.ProvidePlugin
to the plugins
array, and add the following items to the alias
and fallback
sections,
creating the resolve
, alias
, and fallback
properties as needed if they do not exist.
module.exports = {
plugins: [
new webpack.ProvidePlugin({
Buffer: [ 'buffer', 'Buffer' ],
process: 'process',
}),
],
resolve: {
fallback: {
'buffer': require.resolve('buffer/'),
'process': require.resolve('process/browser'),
'stream': require.resolve('stream-browserify'),
}
},
};
Example
The following is an example that reads the URL, method, headers, and body from the
request, and writes a response.
import http from '@fastly/http-compute-js';
const server = http.createServer(async (req, res) => {
const url = req.url;
const method = req.method;
const headers = {};
for (let [key, value] of Object.entries(req.headers)) {
if(!Array.isArray(value)) {
value = [String(value)];
}
headers[key] = value.join(', ');
}
let body = null;
if (method !== 'GET' && method !== 'HEAD') {
body = await new Promise(resolve => {
const data = [];
req.on('data', (chunk) => {
data.push(chunk);
});
req.on('end', () => {
resolve(data.join(''));
});
});
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
url,
method,
headers,
body,
}));
});
server.listen();
The server
object
server
is an instance of HttpServer
, modeled after http.Server
.
It is created using the createServer()
function, usually passing in your request handler.
The server
begins to listen for fetch
events once the listen()
function is called.
createServer([onRequest])
- Instantiates an
HttpServer
instance, optionally passing in an onRequest listener. - Parameters:
onRequest
- (optional) supplying this is equivalent to calling server.on('request', onRequest)
after instantiation.
- Returns: an
HttpServer
instance.
HttpServer
class members:
listen([port][,onListening])
- Starts the
server
listening for fetch
events. - Parameters:
port
- (optional) a port number. This argument is purely for API compatibility with Node's server.listen()
,
and is ignored by Compute.onListening
- (optional) supplying this is equivalent to calling server.on('listening', onListening)
before
calling this method.
- event:
'listening'
- Emitted when the
fetch
event handler has been established after calling server.listen()
.
- event:
'request'
- Emitted each time there is a request.
- Parameters:
request
- http.IncomingMessage
response
- http.ServerResponse
Manual instantiation of req
and res
Sometimes, you may need to use Node.js-compatible request and response objects for only some parts of your
application. Or, you may be working with an existing application or package (for example, "middleware")
designed to interact with these objects.
@fastly/http-compute-js
provides utility functions that help in this case to help you go back
and forth between the Request
and Response
objects used in Compute and their Node.js-compatible
counterparts.
toReqRes(request)
- Converts from a Compute-provided
Request
object to a pair of Node.js-compatible
request and response objects. - Parameters:
request
- A Request
object. You would
typically obtain this from the request
property of the event
object received by your fetch
event
handler.
- Returns: an object with the following properties.
req
- An http.IncomingMessage
object whose Readable
interface has been wired to the Request
object's body
. NOTE: This is an error
if the Request
's body
has already been used.res
- An http.ServerResponse
object whose Writable
interface has been wired to an in-memory buffer.
toComputeResponse(res)
- Creates a new
Response
object from the res
object above, based on the status code, headers, and body that has been
written to it. This Response
object is typically used as the return value from a Compute fetch
handler. - Parameters:
res
- An http.ServerResponse
object created by toReqRes()
.
- Returns: a promise that resolves to a
Response
object. - NOTE: This function returns a
Promise
that resolves to a Response
once the res
object emits the
'finish'
event, which typically happens when you call
res.end()
. If your application never signals the
end of output, this promise will never resolve, and your application will likely time out. - If an error occurs, the promise will reject with that error.
Example
The following is an example that shows the use of the manual instantiation functions in a Compute
JavaScript application written using a fetch
event listener. Node.js-compatible req
and res
objects are produced from event.request
. After having some output written, a Response
object is
created from the res
object and returned from the event listener.
import { toReqRes, toComputeResponse } from '@fastly/http-compute-js';
addEventListener('fetch', (event) => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
const { req, res } = toReqRes(event.request);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!'
}));
return await toComputeResponse(res);
}
Issues
If you encounter any non-security-related bug or unexpected behavior, please file an issue
using the bug report template.
Security issues
Please see our SECURITY.md for guidance on reporting security-related issues.
License
MIT.
In order for this library to function without requiring a direct dependency on Node.js itself,
portions of the code in this library are adapted / copied from Node.js.
Those portions are Copyright Joyent, Inc. and other Node contributors.
See the LICENSE file for details.