+69
| #!/usr/bin/env node | ||
| // Native | ||
| const {resolve} = require('path') | ||
| // Packages | ||
| const asyncToGen = require('async-to-gen/register') | ||
| const updateNotifier = require('update-notifier') | ||
| const nodeVersion = require('node-version') | ||
| const args = require('args') | ||
| const isAsyncSupported = require('is-async-supported') | ||
| // Ours | ||
| const pkg = require('../package') | ||
| // Throw an error if node version is too low | ||
| if (nodeVersion.major < 6) { | ||
| console.error(`Error! Micro requires at least version 6 of Node. Please upgrade!`) | ||
| process.exit(1) | ||
| } | ||
| // Let user know if there's an update | ||
| // This isn't important when deployed to Now | ||
| if (!process.env.NOW && pkg.dist) { | ||
| updateNotifier({pkg}).notify() | ||
| } | ||
| args | ||
| .option('port', 'Port to listen on', process.env.PORT || 3000) | ||
| .option(['H', 'host'], 'Host to listen on', '0.0.0.0') | ||
| const flags = args.parse(process.argv) | ||
| let file = args.sub[0] | ||
| if (!file) { | ||
| try { | ||
| const packageJson = require(resolve(process.cwd(), 'package.json')) | ||
| file = packageJson.main || 'index.js' | ||
| } catch (err) { | ||
| if ('MODULE_NOT_FOUND' !== err.code) { | ||
| console.error(`micro: Could not read \`package.json\`: ${err.message}`) | ||
| process.exit(1) | ||
| } | ||
| } | ||
| } | ||
| if (!file) { | ||
| console.error('micro: Please supply a file.') | ||
| args.showHelp() | ||
| } | ||
| if ('/' !== file[0]) { | ||
| file = resolve(process.cwd(), file) | ||
| } | ||
| if (!isAsyncSupported()) { | ||
| // Support for keywords "async" and "await" | ||
| const pathSep = process.platform === 'win32' ? '\\\\' : '/' | ||
| asyncToGen({ | ||
| includes: new RegExp(`.*micro?${pathSep}(lib|bin)|${file}.*`), | ||
| excludes: null, | ||
| sourceMaps: false | ||
| }) | ||
| } | ||
| // Load package core with async/await support | ||
| // If needed... Otherwise use the native implementation | ||
| require('../lib/index')(file, flags) |
+61
| // Packages | ||
| const detect = require('detect-port') | ||
| // Ours | ||
| const serve = require('./server') | ||
| const listening = require('./listening') | ||
| const getMod = file => { | ||
| let mod | ||
| try { | ||
| mod = require(file) | ||
| if (mod && 'object' === typeof mod) { | ||
| mod = mod.default | ||
| } | ||
| } catch (err) { | ||
| console.error(`micro: Error when importing ${file}: ${err.stack}`) | ||
| process.exit(1) | ||
| } | ||
| if ('function' !== typeof mod) { | ||
| console.error(`micro: "${file}" does not export a function.`) | ||
| process.exit(1) | ||
| } | ||
| return mod | ||
| } | ||
| module.exports = (file, flags) => { | ||
| const server = serve(getMod(file)) | ||
| let port = flags.port | ||
| let host = flags.host | ||
| detect(port).then(open => { | ||
| let inUse = open !== port | ||
| if (inUse) { | ||
| port = open | ||
| inUse = { | ||
| old: flags.port, | ||
| open | ||
| } | ||
| } | ||
| if (host === '0.0.0.0') { | ||
| host = null | ||
| } | ||
| server.listen(port, host, async err => { | ||
| if (err) { | ||
| console.error('micro:', err.stack) | ||
| process.exit(1) | ||
| } | ||
| return await listening(server, inUse) | ||
| }) | ||
| }) | ||
| } |
| // Packages | ||
| const {copy} = require('copy-paste') | ||
| const ip = require('ip') | ||
| const chalk = require('chalk') | ||
| const copyToClipboard = async text => { | ||
| try { | ||
| await copy(text) | ||
| return true | ||
| } catch (err) { | ||
| return false | ||
| } | ||
| } | ||
| module.exports = async (server, inUse) => { | ||
| const details = server.address() | ||
| const ipAddress = ip.address() | ||
| const url = `http://${ipAddress}:${details.port}` | ||
| process.on('SIGINT', () => { | ||
| server.close() | ||
| process.exit(0) | ||
| }) | ||
| if (!process.env.NOW) { | ||
| let message = chalk.green('Micro is running!') | ||
| if (inUse) { | ||
| message += ' ' + chalk.red(`(on port ${inUse.open},` + | ||
| ` because ${inUse.old} is already in use)`) | ||
| } | ||
| message += '\n\n' | ||
| const localURL = `http://localhost:${details.port}` | ||
| message += `• ${chalk.bold('Local: ')} ${localURL}\n` | ||
| message += `• ${chalk.bold('On Your Network: ')} ${url}\n\n` | ||
| const copied = await copyToClipboard(localURL) | ||
| if (copied) { | ||
| message += `${chalk.grey('Copied local address to clipboard!')}\n\n` | ||
| } | ||
| process.stdout.write('\x1Bc') | ||
| process.stdout.write(message) | ||
| } | ||
| } |
+19
| // Native | ||
| const {resolve} = require('path') | ||
| // Packages | ||
| const isAsyncSupported = require('is-async-supported') | ||
| const asyncToGen = require('async-to-gen/register') | ||
| // Support for keywords "async" and "await" | ||
| if (!isAsyncSupported()) { | ||
| const path = resolve(__dirname, './server') | ||
| asyncToGen({ | ||
| includes: new RegExp(`.*${path}.*`), | ||
| excludes: null, | ||
| sourceMaps: false | ||
| }) | ||
| } | ||
| module.exports = require('./server') |
+135
| // Native | ||
| const server = require('http').Server | ||
| // Packages | ||
| const getRawBody = require('raw-body') | ||
| const typer = require('media-typer') | ||
| const isStream = require('isstream') | ||
| const DEV = 'development' === process.env.NODE_ENV | ||
| const TESTING = 'test' === process.env.NODE_ENV | ||
| const serve = fn => server((req, res) => { | ||
| run(req, res, fn, sendError) | ||
| }) | ||
| module.exports = exports = serve | ||
| exports.run = run | ||
| exports.json = json | ||
| exports.send = send | ||
| exports.sendError = sendError | ||
| exports.createError = createError | ||
| async function run(req, res, fn, onError) { | ||
| try { | ||
| const val = await fn(req, res) | ||
| // Return 204 No Content if value is null | ||
| if (null === val) { | ||
| send(res, 204, null) | ||
| } | ||
| // Return a undefined-null value -> send | ||
| if (undefined !== val) { | ||
| send(res, res.statusCode || 200, val) | ||
| } | ||
| } catch (err) { | ||
| await onError(req, res, err) | ||
| } | ||
| } | ||
| // maps requests to buffered raw bodies so that | ||
| // multiple calls to `json` work as expected | ||
| const rawBodyMap = new WeakMap() | ||
| async function json(req, {limit = '1mb'} = {}) { | ||
| try { | ||
| const type = req.headers['content-type'] | ||
| const length = req.headers['content-length'] | ||
| const encoding = typer.parse(type).parameters.charset | ||
| let str = rawBodyMap.get(req) | ||
| if (!str) { | ||
| str = await getRawBody(req, {limit, length, encoding}) | ||
| rawBodyMap.set(req, str) | ||
| } | ||
| try { | ||
| return JSON.parse(str) | ||
| } catch (err) { | ||
| throw createError(400, 'Invalid JSON', err) | ||
| } | ||
| } catch (err) { | ||
| if ('entity.too.large' === err.type) { | ||
| throw createError(413, `Body exceeded ${limit} limit`, err) | ||
| } else { | ||
| throw createError(400, 'Invalid body', err) | ||
| } | ||
| } | ||
| } | ||
| function send(res, code, obj = null) { | ||
| res.statusCode = code | ||
| if (null !== obj) { | ||
| if (Buffer.isBuffer(obj)) { | ||
| if (!res.getHeader('Content-Type')) { | ||
| res.setHeader('Content-Type', 'application/octet-stream') | ||
| } | ||
| res.setHeader('Content-Length', obj.length) | ||
| res.end(obj) | ||
| } else if (isStream(obj)) { | ||
| if (!res.getHeader('Content-Type')) { | ||
| res.setHeader('Content-Type', 'application/octet-stream') | ||
| } | ||
| obj.pipe(res) | ||
| } else { | ||
| let str | ||
| if ('object' === typeof obj) { | ||
| // we stringify before setting the header | ||
| // in case `JSON.stringify` throws and a | ||
| // 500 has to be sent instead | ||
| // the `JSON.stringify` call is split into | ||
| // two cases as `JSON.stringify` is optimized | ||
| // in V8 if called with only one argument | ||
| if (DEV) { | ||
| str = JSON.stringify(obj, null, 2) | ||
| } else { | ||
| str = JSON.stringify(obj) | ||
| } | ||
| res.setHeader('Content-Type', 'application/json') | ||
| } else { | ||
| str = obj | ||
| } | ||
| res.setHeader('Content-Length', Buffer.byteLength(str)) | ||
| res.end(str) | ||
| } | ||
| } else { | ||
| res.end() | ||
| } | ||
| } | ||
| function sendError(req, res, {statusCode, message, stack}) { | ||
| if (statusCode) { | ||
| send(res, statusCode, DEV ? stack : message) | ||
| } else { | ||
| send(res, 500, DEV ? stack : 'Internal Server Error') | ||
| } | ||
| if (!TESTING) { | ||
| console.error(stack) | ||
| } | ||
| } | ||
| function createError(code, msg, orig) { | ||
| const err = new Error(msg) | ||
| err.statusCode = code | ||
| err.originalError = orig | ||
| return err | ||
| } |
+29
-25
| { | ||
| "name": "micro", | ||
| "version": "6.2.1", | ||
| "version": "7.0.0", | ||
| "description": "Asynchronous HTTP microservices", | ||
| "main": "./dist/index.js", | ||
| "main": "./lib/load.js", | ||
| "files": [ | ||
| "dist", | ||
| "bin" | ||
| "bin", | ||
| "lib" | ||
| ], | ||
| "greenkeeper": { | ||
| "emails": false | ||
| }, | ||
| "scripts": { | ||
| "prepublish": "npm run build", | ||
| "pretest": "npm run build", | ||
| "build": "mkdir -p dist && async-to-gen lib/index.js > dist/index.js", | ||
| "test": "xo && ava" | ||
| "precommit": "npm run lint", | ||
| "lint": "xo", | ||
| "test": "npm run lint && ava" | ||
| }, | ||
@@ -22,5 +18,2 @@ "ava": { | ||
| "async-to-gen/register" | ||
| ], | ||
| "files": [ | ||
| "test/index.js" | ||
| ] | ||
@@ -32,2 +25,5 @@ }, | ||
| "semicolon": false, | ||
| "ignores": [ | ||
| "examples/**/*" | ||
| ], | ||
| "rules": { | ||
@@ -40,8 +36,9 @@ "max-lines": 0, | ||
| "yoda": 0, | ||
| "no-negated-condition": 0 | ||
| "no-negated-condition": 0, | ||
| "import/no-dynamic-require": 0, | ||
| "unicorn/no-process-exit": 0 | ||
| } | ||
| }, | ||
| "bin": { | ||
| "micro": "./bin/micro", | ||
| "micro-serve": "./bin/micro" | ||
| "micro": "./bin/micro.js" | ||
| }, | ||
@@ -66,6 +63,7 @@ "repository": "zeit/micro", | ||
| "devDependencies": { | ||
| "ava": "^0.17.0", | ||
| "ava": "^0.18.1", | ||
| "husky": "^0.13.1", | ||
| "request": "^2.74.0", | ||
| "request-promise": "^4.1.1", | ||
| "resumer": "0.0.0", | ||
| "resumer": "^0.0.0", | ||
| "then-sleep": "^1.0.1", | ||
@@ -75,9 +73,15 @@ "xo": "^0.17.0" | ||
| "dependencies": { | ||
| "async-to-gen": "1.3.0", | ||
| "is-async-supported": "1.2.0", | ||
| "isstream": "0.1.2", | ||
| "media-typer": "0.3.0", | ||
| "minimist": "1.2.0", | ||
| "raw-body": "2.2.0" | ||
| "args": "^2.2.4", | ||
| "async-to-gen": "^1.3.2", | ||
| "chalk": "^1.1.3", | ||
| "copy-paste": "^1.3.0", | ||
| "detect-port": "^1.1.0", | ||
| "ip": "^1.1.4", | ||
| "is-async-supported": "^1.2.0", | ||
| "isstream": "^0.1.2", | ||
| "media-typer": "^0.3.0", | ||
| "node-version": "^1.0.0", | ||
| "raw-body": "^2.2.0", | ||
| "update-notifier": "^1.0.3" | ||
| } | ||
| } |
+174
-142
@@ -1,2 +0,2 @@ | ||
|  | ||
|  | ||
@@ -18,79 +18,91 @@ _**Micro —** Async ES6 HTTP microservices_ | ||
| * **Standard**. Just HTTP! | ||
| * **Lightweight**. The package is small and the `async` transpilation fast and transparent | ||
| * **Lightweight**. The package is small and the `async` transpilation is fast and transparent | ||
| ## Example | ||
| ## Usage | ||
| The following example `sleep.js` will wait before responding (without blocking!) | ||
| Firstly, install it: | ||
| ```js | ||
| const {send} = require('micro') | ||
| const sleep = require('then-sleep') | ||
| ```bash | ||
| npm install --save micro | ||
| ``` | ||
| module.exports = async function (req, res) { | ||
| await sleep(500) | ||
| send(res, 200, 'Ready!') | ||
| Then add a `start` script to your `package.json` like this: | ||
| ```json | ||
| { | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "start": "micro" | ||
| } | ||
| } | ||
| ``` | ||
| To run the microservice on port `3000`, use the `micro` command: | ||
| After that, we have to create an `index.js` file and populate it: | ||
| ```bash | ||
| micro sleep.js | ||
| ```js | ||
| module.exports = (req, res) => 'Welcome to micro' | ||
| ``` | ||
| To run the microservice on port `3000` and localhost instead of listening on every interface, use the `micro` command: | ||
| Once all of that is done, just start the server: | ||
| ```bash | ||
| micro -H localhost sleep.js | ||
| npm start | ||
| ``` | ||
| ## Usage | ||
| And go to this URL: `http://localhost:3000` - 🎉 | ||
| Install the package (requires at least Node v6): | ||
| ### `async` & `await` | ||
| ```js | ||
| npm install --save micro | ||
| ``` | ||
| <p><details> | ||
| <summary><b>Examples</b></summary> | ||
| <ul><li><a href="./examples/external-api-call">Fetch external api</a></li></ul> | ||
| </details></p> | ||
| And start using it in your `package.json` file: | ||
| Micro is built for usage with async/await. You can read more about async / await [here](https://zeit.co/blog/async-and-await) | ||
| ```js | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "start": "micro" | ||
| const sleep = require('then-sleep') | ||
| module.exports = async (req, res) => { | ||
| await sleep(500) | ||
| return 'Ready!' | ||
| } | ||
| ``` | ||
| Then write your `index.js` (see above for an example). | ||
| #### Transpilation | ||
| After that, you can make the server run by executing the following command: | ||
| We use [is-async-supported](https://github.com/timneutkens/is-async-supported) combined with [async-to-gen](https://github.com/leebyron/async-to-gen), | ||
| so that the we only convert `async` and `await` to generators when needed. | ||
| ```bash | ||
| npm start | ||
| ``` | ||
| If you want to do it manually, you can! `micro(1)` is idempotent and | ||
| should not interfere. | ||
| ### API | ||
| `micro` exclusively supports Node 6+ to avoid a big transpilation | ||
| pipeline. `async-to-gen` is fast and can be distributed with | ||
| the main `micro` package due to its small size. | ||
| #### micro | ||
| **`micro(fn)`** | ||
| ### Body parsing | ||
| - This function is exposed as the `default` export. | ||
| - Use `require('micro')`. | ||
| - Returns a [`http.Server`](https://nodejs.org/dist/latest-v4.x/docs/api/http.html#http_class_http_server) that uses the provided `fn` as the request handler. | ||
| - The supplied function is run with `await`. It can be `async`! | ||
| - Example: | ||
| <p id="body-parsing-examples"><details> | ||
| <summary><b>Examples</b></summary> | ||
| <ul> | ||
| <li><a href="./examples/json-body-parsing">Parse JSON</a></li> | ||
| <li><a href="./examples/urlencoded-body-parsing">Parse urlencoded form (html `form` tag)</a></li> | ||
| </ul> | ||
| </details></p> | ||
| ```js | ||
| const micro = require('micro'); | ||
| const sleep = require('then-sleep'); | ||
| const srv = micro(async function (req, res) { | ||
| await sleep(500); | ||
| res.writeHead(200); | ||
| res.end('woot'); | ||
| }); | ||
| srv.listen(3000); | ||
| ``` | ||
| For parsing the incoming request body we included an async function `json` | ||
| #### json | ||
| ```js | ||
| const {json} = require('micro') | ||
| module.exports = async (req, res) => { | ||
| const data = await json(req) | ||
| console.log(data.price) | ||
| return '' | ||
| } | ||
| ``` | ||
| #### API | ||
| **`json(req, { limit = '1mb' })`** | ||
@@ -104,15 +116,22 @@ | ||
| - If JSON parsing fails, an `Error` is thrown with `statusCode` set to `400` (see [Error Handling](#error-handling)) | ||
| - Example: | ||
| ```js | ||
| const { json, send } = require('micro'); | ||
| module.exports = async function (req, res) { | ||
| const data = await json(req); | ||
| console.log(data.price); | ||
| send(res, 200); | ||
| } | ||
| ``` | ||
| For other types of data check the [examples](#body-parsing-examples) | ||
| #### send | ||
| ### Sending a different status code | ||
| So far we have used `return` to send data to the client. `return 'Hello World'` is the equivalent of `send(res, 200, 'Hello World')`. | ||
| ```js | ||
| const {send} = require('micro') | ||
| module.exports = async (req, res) => { | ||
| const statusCode = 400 | ||
| const data = { error: 'Custom error message' } | ||
| send(res, statusCode, data) | ||
| } | ||
| ``` | ||
| #### API | ||
| **`send(res, statusCode, data = null)`** | ||
@@ -128,59 +147,28 @@ | ||
| - If JSON serialization fails (for example, if a cyclical reference is found), a `400` error is thrown. See [Error Handling](#error-handling). | ||
| - Example | ||
| ```js | ||
| const { send } = require('micro') | ||
| module.exports = async function (req, res) { | ||
| send(res, 400, { error: 'Please use a valid email' }); | ||
| } | ||
| ``` | ||
| ### Programmatic use | ||
| #### return | ||
| You can use micro programmatically by requiring micro directly: | ||
| **`return val;`** | ||
| ```js | ||
| const micro = require('micro') | ||
| const sleep = require('then-sleep') | ||
| - Returning `val` from your function is shorthand for: `send(res, 200, val)`. | ||
| - Example | ||
| const server = micro(async (req, res) => { | ||
| await sleep(500) | ||
| return 'Hello world' | ||
| }) | ||
| ```js | ||
| module.exports = function (req, res) { | ||
| return {message: 'Hello!'}; | ||
| } | ||
| ``` | ||
| server.listen(3000) | ||
| ``` | ||
| - Returning a promise works as well! | ||
| - Example | ||
| #### API | ||
| ```js | ||
| const sleep = require('then-sleep') | ||
| module.exports = async function (req, res) { | ||
| return new Promise(async (resolve) => { | ||
| await sleep(100); | ||
| resolve('I Promised'); | ||
| }); | ||
| } | ||
| ``` | ||
| **`micro(fn)`** | ||
| #### sendError | ||
| - This function is exposed as the `default` export. | ||
| - Use `require('micro')`. | ||
| - Returns a [`http.Server`](https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_class_http_server) that uses the provided `function` as the request handler. | ||
| - The supplied function is run with `await`. So it can be `async` | ||
| **`sendError(req, res, error)`** | ||
| - Use `require('micro').sendError`. | ||
| - Used as the default handler for errors thrown. | ||
| - Automatically sets the status code of the response based on `error.statusCode`. | ||
| - Sends the `error.message` as the body. | ||
| - During development (when `NODE_ENV` is set to `'development'`), stacks are printed out with `console.error` and also sent in responses. | ||
| - Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`. | ||
| #### createError | ||
| **`createError(code, msg, orig)`** | ||
| - Use `require('micro').createError`. | ||
| - Creates an error object with a `statusCode`. | ||
| - Useful for easily throwing errors with HTTP status codes, which are interpreted by the [built-in error handling](#error-handling). | ||
| - `orig` sets `error.originalError` which identifies the original error (if any). | ||
| <a name="error-handling"></a> | ||
| ### Error handling | ||
@@ -190,3 +178,3 @@ | ||
| If an error is thrown and not caught by you, the response will automatically be `500`. **Important:** during development mode (if the env variable `NODE_ENV` is `'development'`), error stacks will be printed as `console.error` and included in the responses. | ||
| If an error is thrown and not caught by you, the response will automatically be `500`. **Important:** Error stacks will be printed as `console.error` and during development mode (if the env variable `NODE_ENV` is `'development'`), they will also be included in the responses. | ||
@@ -197,23 +185,24 @@ If the `Error` object that's thrown contains a `statusCode` property, that's used as the HTTP code to be sent. Let's say you want to write a rate limiting module: | ||
| const rateLimit = require('my-rate-limit') | ||
| module.exports = async function (req, res) { | ||
| await rateLimit(req); | ||
| // … your code | ||
| module.exports = async (req, res) => { | ||
| await rateLimit(req) | ||
| // ... your code | ||
| } | ||
| ``` | ||
| If the API endpoint is abused, it can throw an error like so: | ||
| If the API endpoint is abused, it can throw an error with ``createError`` like so: | ||
| ```js | ||
| if (tooMany) { | ||
| const err = new Error('Rate limit exceeded'); | ||
| err.statusCode = 429; | ||
| throw err; | ||
| throw createError(429, 'Rate limit exceeded') | ||
| } | ||
| ``` | ||
| Alternatively you can use ``createError`` as described above. | ||
| Alternatively you can create the `Error` object yourself | ||
| ```js | ||
| if (tooMany) { | ||
| throw createError(429, 'Rate limit exceeded') | ||
| const err = new Error('Rate limit exceeded') | ||
| err.statusCode = 429 | ||
| throw err | ||
| } | ||
@@ -226,7 +215,7 @@ ``` | ||
| try { | ||
| await rateLimit(req); | ||
| await rateLimit(req) | ||
| } catch (err) { | ||
| if (429 == err.statusCode) { | ||
| // perhaps send 500 instead? | ||
| send(res, 500); | ||
| send(res, 500) | ||
| } | ||
@@ -243,18 +232,36 @@ } | ||
| ```js | ||
| module.exports = handleErrors(async (req, res) => { | ||
| throw new Error('What happened here?'); | ||
| }); | ||
| const {send} = require('micro') | ||
| function handleErrors (fn) { | ||
| return async function (req, res) { | ||
| try { | ||
| return await fn(req, res); | ||
| } catch (err) { | ||
| console.log(err.stack); | ||
| send(res, 500, 'My custom error!'); | ||
| } | ||
| const handleErrors = fn => async (req, res) => { | ||
| try { | ||
| return await fn(req, res) | ||
| } catch (err) { | ||
| console.log(err.stack) | ||
| send(res, 500, 'My custom error!') | ||
| } | ||
| } | ||
| module.exports = handleErrors(async (req, res) => { | ||
| throw new Error('What happened here?') | ||
| }) | ||
| ``` | ||
| #### API | ||
| **`sendError(req, res, error)`** | ||
| - Use `require('micro').sendError`. | ||
| - Used as the default handler for errors thrown. | ||
| - Automatically sets the status code of the response based on `error.statusCode`. | ||
| - Sends the `error.message` as the body. | ||
| - Stacks are printed out with `console.error` and during development (when `NODE_ENV` is set to `'development'`) also sent in responses. | ||
| - Usually, you don't need to invoke this method yourself, as you can use the [built-in error handling](#error-handling) flow with `throw`. | ||
| **`createError(code, msg, orig)`** | ||
| - Use `require('micro').createError`. | ||
| - Creates an error object with a `statusCode`. | ||
| - Useful for easily throwing errors with HTTP status codes, which are interpreted by the [built-in error handling](#error-handling). | ||
| - `orig` sets `error.originalError` which identifies the original error (if any). | ||
| ### Testing | ||
@@ -266,19 +273,22 @@ | ||
| ```js | ||
| const micro = require('micro'); | ||
| const test = require('ava'); | ||
| const listen = require('test-listen'); | ||
| const request = require('request-promise'); | ||
| const micro = require('micro') | ||
| const test = require('ava') | ||
| const listen = require('test-listen') | ||
| const request = require('request-promise') | ||
| test('my endpoint', async t => { | ||
| const service = micro(async function (req, res) { | ||
| micro.send(res, 200, { test: 'woot' }) | ||
| }); | ||
| const service = micro(async (req, res) => { | ||
| micro.send(res, 200, { | ||
| test: 'woot' | ||
| }) | ||
| }) | ||
| const url = await listen(service); | ||
| const body = await request(url); | ||
| t.deepEqual(JSON.parse(body).test, 'woot'); | ||
| }); | ||
| const url = await listen(service) | ||
| const body = await request(url) | ||
| t.deepEqual(JSON.parse(body).test, 'woot') | ||
| }) | ||
| ``` | ||
| Look at the [test-listen](https://github.com/zeit/test-listen) for a | ||
| Look at [test-listen](https://github.com/zeit/test-listen) for a | ||
| function that returns a URL with an ephemeral port every time it's called. | ||
@@ -298,2 +308,8 @@ | ||
| To use native `async/await` on Node v7.x, run `micro` like the following. | ||
| ```bash | ||
| node --harmony-async-await node_modules/.bin/micro . | ||
| ``` | ||
| ### Deployment | ||
@@ -311,3 +327,3 @@ | ||
| "scripts": { | ||
| "start": "micro -p 3000" | ||
| "start": "micro" | ||
| } | ||
@@ -319,2 +335,18 @@ } | ||
| #### Port based on environment variable | ||
| When you want to set the port using an environment variable you can use: | ||
| ``` | ||
| micro -p $PORT | ||
| ``` | ||
| Optionally you can add a default if it suits your use case: | ||
| ``` | ||
| micro -p ${PORT:-3000} | ||
| ``` | ||
| `${PORT:-3000}` will allow a fallback to port `3000` when `$PORT` is not defined | ||
| ## Contribute | ||
@@ -321,0 +353,0 @@ |
Sorry, the diff of this file is not supported yet
-138
| // Native | ||
| const server = require('http').Server | ||
| // Packages | ||
| const getRawBody = require('raw-body') | ||
| const typer = require('media-typer') | ||
| const isStream = require('isstream') | ||
| const DEV = 'development' === process.env.NODE_ENV | ||
| const TESTING = 'test' === process.env.NODE_ENV | ||
| module.exports = exports = serve | ||
| exports.run = run | ||
| exports.json = json | ||
| exports.send = send | ||
| exports.sendError = sendError | ||
| exports.createError = createError | ||
| function serve(fn, {onError = null} = {}) { | ||
| if (onError) { | ||
| console.warn('[DEPRECATED] onError is deprecated and will be removed in a future release. Please use your own try/catch as needed.') | ||
| } | ||
| return server((req, res) => { | ||
| run(req, res, fn, onError || sendError) | ||
| }) | ||
| } | ||
| function run(req, res, fn, onError) {return __async(function*(){ | ||
| try { | ||
| const val = yield fn(req, res) | ||
| // return a non-null value -> send | ||
| if (null !== val && undefined !== val) { | ||
| send(res, res.statusCode || 200, val) | ||
| } | ||
| } catch (err) { | ||
| yield onError(req, res, err) | ||
| } | ||
| }())} | ||
| // maps requests to buffered raw bodies so that | ||
| // multiple calls to `json` work as expected | ||
| const rawBodyMap = new WeakMap() | ||
| function json(req, {limit = '1mb'} = {}) {return __async(function*(){ | ||
| try { | ||
| const type = req.headers['content-type'] | ||
| const length = req.headers['content-length'] | ||
| const encoding = typer.parse(type).parameters.charset | ||
| let str = rawBodyMap.get(req) | ||
| if (!str) { | ||
| str = yield getRawBody(req, {limit, length, encoding}) | ||
| rawBodyMap.set(req, str) | ||
| } | ||
| try { | ||
| return JSON.parse(str) | ||
| } catch (err) { | ||
| throw createError(400, 'Invalid JSON', err) | ||
| } | ||
| } catch (err) { | ||
| if ('entity.too.large' === err.type) { | ||
| throw createError(413, `Body exceeded ${limit} limit`, err) | ||
| } else { | ||
| throw createError(400, 'Invalid body', err) | ||
| } | ||
| } | ||
| }())} | ||
| function send(res, code, obj = null) { | ||
| res.statusCode = code | ||
| if (null !== obj) { | ||
| if (Buffer.isBuffer(obj)) { | ||
| if (!res.getHeader('Content-Type')) { | ||
| res.setHeader('Content-Type', 'application/octet-stream') | ||
| } | ||
| res.setHeader('Content-Length', obj.length) | ||
| res.end(obj) | ||
| } else if (isStream(obj)) { | ||
| if (!res.getHeader('Content-Type')) { | ||
| res.setHeader('Content-Type', 'application/octet-stream') | ||
| } | ||
| obj.pipe(res) | ||
| } else { | ||
| let str | ||
| if ('object' === typeof obj) { | ||
| // we stringify before setting the header | ||
| // in case `JSON.stringify` throws and a | ||
| // 500 has to be sent instead | ||
| // the `JSON.stringify` call is split into | ||
| // two cases as `JSON.stringify` is optimized | ||
| // in V8 if called with only one argument | ||
| if (DEV) { | ||
| str = JSON.stringify(obj, null, 2) | ||
| } else { | ||
| str = JSON.stringify(obj) | ||
| } | ||
| res.setHeader('Content-Type', 'application/json') | ||
| } else { | ||
| str = obj | ||
| } | ||
| res.setHeader('Content-Length', Buffer.byteLength(str)) | ||
| res.end(str) | ||
| } | ||
| } else { | ||
| res.end() | ||
| } | ||
| } | ||
| function sendError(req, res, {statusCode, message, stack}) { | ||
| if (statusCode) { | ||
| send(res, statusCode, DEV ? stack : message) | ||
| } else { | ||
| send(res, 500, DEV ? stack : 'Internal Server Error') | ||
| } | ||
| if (!TESTING) { | ||
| console.error(stack) | ||
| } | ||
| } | ||
| function createError(code, msg, orig) { | ||
| const err = new Error(msg) | ||
| err.statusCode = code | ||
| err.originalError = orig | ||
| return err | ||
| } | ||
| function __async(g){return new Promise(function(s,j){function c(a,x){try{var r=g[x?"throw":"next"](a)}catch(e){j(e);return}r.done?s(r.value):Promise.resolve(r.value).then(c,d)}function d(e){c(e,1)}c()})} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
21574
19.11%8
60%269
133.91%358
9.82%12
100%7
16.67%9
200%2
100%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated