From 14b87d21c3b8264fdfaaa8ce4a0b65f5418e3b3d Mon Sep 17 00:00:00 2001 From: Jack Kavanagh Date: Tue, 9 Jan 2024 16:09:49 +0100 Subject: [PATCH] Support pathing (#153) * can use id to match paths * full path in header * validation * make redis errors clearer * rotate log url structure * support empty path * rotate paths * fix * remove unused parsers * validate * remove 400s * remove validation --- README.md | 25 ++++++++++++------------- biome.json | 3 ++- docs/api/bins.md | 35 ++++++++++++++++++----------------- lib/routes/bins.js | 12 ++++++------ lib/routes/bins/create.js | 2 +- lib/routes/bins/log.js | 4 ++-- lib/routes/bins/run.js | 8 +++++--- lib/routes/bins/update.js | 34 ++++++++++++++-------------------- src/views/bin/log.pug | 4 ++-- src/views/bin/view.pug | 2 +- src/views/redirect.pug | 4 ++-- 11 files changed, 65 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 999d3ea3..ada405d2 100644 --- a/README.md +++ b/README.md @@ -26,21 +26,8 @@ Mockbin is used internally and maintained by [Kong](https://github.com/Kong), wh ## Installation -```shell -git clone https://github.com/Kong/mockbin.git ./mockbin -cd mockbin -cp .env.sample .env -brew install fnm -fnm use -npm install -``` - -Note: nvm, n or volta can be used instead of fnm. - ### Requirements -other than the dependencies listed in [package.json](package.json) The following are required: - - [Redis](http://redis.io/) ```shell @@ -49,6 +36,18 @@ brew services start redis ``` Redis should be now running on localhost:6379 +Mockbin will start without redis but you wont be able to set or get response bins. + +```shell +git clone https://github.com/Kong/mockbin.git ./mockbin +cd mockbin +cp .env.sample .env +brew install fnm +fnm use +npm install +``` + +Note: nvm, n or volta can be used instead of fnm. ### Running with Node diff --git a/biome.json b/biome.json index c53397b1..8973bdcb 100644 --- a/biome.json +++ b/biome.json @@ -8,7 +8,8 @@ "rules": { "recommended": true, "complexity": { - "noForEach": "off" + "noForEach": "off", + "useArrowFunction": "off" } } } diff --git a/docs/api/bins.md b/docs/api/bins.md index 1c4356bf..e3322a35 100644 --- a/docs/api/bins.md +++ b/docs/api/bins.md @@ -93,16 +93,16 @@ Responds with a `Location` header with the newly created **Bin**, e.g. `Location #### Update Bin -> ##### `PUT /bin/:id` +> ##### `PUT /bin/:id/a/b/c` -Updates a new **Bin** with a mock HTTP response as described by a [HAR Response Object](http://www.softwareishard.com/blog/har-12-spec/#response) body. +Creates or updates a **Bin** with a mock HTTP response as described by a [HAR Response Object](http://www.softwareishard.com/blog/har-12-spec/#response) body. /a/b/c represeent any following paths than will be combined with the id for response matching. -Responds with a `Location` header with the updated **Bin**, e.g. `Location: /bin/3c149e20-bc9c-4c68-8614-048e6023a108` *(the Bin ID is also repeated in the body)* +Responds with a `Location` header with the updated **Bin**, e.g. `Location: /bin/3c149e20-bc9c-4c68-8614-048e6023a108/a/b/c` *(the Bin ID is also repeated in the body)* ###### Request > ```http -> PUT /bin/3c149e20-bc9c-4c68-8614-048e6023a108 HTTP/1.1 +> PUT /bin/3c149e20-bc9c-4c68-8614-048e6023a108/a/b/c HTTP/1.1 > Host: mockbin.org > Content-Type: application/json > Accept: application/json @@ -162,7 +162,7 @@ Responds with a `Location` header with the updated **Bin**, e.g. `Location: /bin > ```http > HTTP/1.1 200 OK -> Location: /bin/3c149e20-bc9c-4c68-8614-048e6023a108 +> Location: /bin/3c149e20-bc9c-4c68-8614-048e6023a108/a/b/c > Content-Type: application/json; charset=utf-8 > Content-Length: 38 > @@ -173,14 +173,14 @@ Responds with a `Location` header with the updated **Bin**, e.g. `Location: /bin #### Inspect Bin -> ##### `GET /bin/:id/view` +> ##### `GET /bin/view/:id` Respondes with the [HAR Response Object](http://www.softwareishard.com/blog/har-12-spec/#response) sent at time of [creation](#create-bin). ###### Request > ```http -> GET /bin/3c149e20-bc9c-4c68-8614-048e6023a108/view HTTP/1.1 +> GET /bin/view/3c149e20-bc9c-4c68-8614-048e6023a108 HTTP/1.1 > Host: mockbin.org > Accept: application/json > ``` @@ -252,16 +252,17 @@ The [HAR Response Object](http://www.softwareishard.com/blog/har-12-spec/#respon Each call to this endpoint will be [logged](#bin-log) *(max of 100 requests)*. You can request this endpoint with *any* combination of the following: - - HTTP methods *(e.g. `POST`, `XXPUT`)* - - HTTP headers *(e.g. `X-My-Header-Name: Value`)* - - body content *(max of 100mb)* - - query string *(e.g. `?foo=bar`)* - - path arguments *(e.g. `/bin/3c149e20-bc9c-4c68-8614-048e6023a108/any/extra/path/`)* + +- HTTP methods *(e.g. `POST`, `XXPUT`)* +- HTTP headers *(e.g. `X-My-Header-Name: Value`)* +- body content *(max of 100mb)* +- query string *(e.g. `?foo=bar`)* +- path arguments *(e.g. `/bin/3c149e20-bc9c-4c68-8614-048e6023a108/any/extra/path/`)* ###### Request > ```http -> GET /bin/3c149e20-bc9c-4c68-8614-048e6023a108/view HTTP/1.1 +> GET /bin/view/3c149e20-bc9c-4c68-8614-048e6023a108 HTTP/1.1 > Host: mockbin.org > Accept: application/json > ``` @@ -284,14 +285,14 @@ You can request this endpoint with *any* combination of the following: #### Bin Access Log -> ##### `GET /bin/:id/log` +> ##### `GET /bin/log/:id` List all requests made to this Bin, using [HAR](http://www.softwareishard.com/blog/har-12-spec/) log format. ###### Request > ```http -> GET /bin/3c149e20-bc9c-4c68-8614-048e6023a108/log HTTP/1.1 +> GET /bin/log/3c149e20-bc9c-4c68-8614-048e6023a108 HTTP/1.1 > Host: mockbin.org > Accept: application/json > ``` @@ -316,14 +317,14 @@ List all requests made to this Bin, using [HAR](http://www.softwareishard.com/bl #### Delete Bin -> ##### `DELETE /bin/:id/delete` +> ##### `DELETE /bin/delete/:id` Deletes the bin and all of its logs ###### Request > ```http -> GET /bin/3c149e20-bc9c-4c68-8614-048e6023a108/view HTTP/1.1 +> GET /bin/view/3c149e20-bc9c-4c68-8614-048e6023a108 HTTP/1.1 > ``` ###### Response diff --git a/lib/routes/bins.js b/lib/routes/bins.js index 6b95846e..196bb8c3 100644 --- a/lib/routes/bins.js +++ b/lib/routes/bins.js @@ -25,7 +25,7 @@ module.exports = function bins(dsnStr) { } this.client.on("error", (err) => { - debug("redis error:", err); + console.log("redis error:", err); }); const router = express.Router(); @@ -42,15 +42,15 @@ module.exports = function bins(dsnStr) { const endpoints = [ { action: "get", path: "/create", route: routes.form.bind(this) }, { action: "post", path: "/create", route: routes.create.bind(this) }, - { action: "get", path: "/:uuid/view", route: routes.view.bind(this) }, - { action: "get", path: "/:uuid/sample", route: routes.sample.bind(this) }, - { action: "get", path: "/:uuid/log", route: routes.log.bind(this) }, + { action: "get", path: "/view/:uuid*", route: routes.view.bind(this) }, + { action: "get", path: "/sample/:uuid*", route: routes.sample.bind(this) }, + { action: "get", path: "/log/:uuid*", route: routes.log.bind(this) }, { action: "delete", - path: "/:uuid/delete", + path: "/delete/:uuid*", route: routes.delete.bind(this), }, - { action: "put", path: "/:uuid", route: routes.update.bind(this) }, + { action: "put", path: "/:uuid*", route: routes.update.bind(this) }, { action: "all", path: "/:uuid*", route: routes.run.bind(this) }, ]; diff --git a/lib/routes/bins/create.js b/lib/routes/bins/create.js index 44cee6b8..2ec0eea1 100644 --- a/lib/routes/bins/create.js +++ b/lib/routes/bins/create.js @@ -56,7 +56,7 @@ module.exports = async function (req, res, next) { .catch((err) => { res.body = { - errors: err.errors, + errors: err.message, }; }) diff --git a/lib/routes/bins/log.js b/lib/routes/bins/log.js index 6f1b27bb..4ebf3240 100644 --- a/lib/routes/bins/log.js +++ b/lib/routes/bins/log.js @@ -3,8 +3,8 @@ const pkg = require("../../../package.json"); module.exports = function (req, res, next) { res.view = "bin/log"; - - this.client.lrange(`log:${req.params.uuid}`, 0, -1, (err, history) => { + const compoundId = req.params.uuid + req.params[0]; + this.client.lrange(`log:${compoundId}`, 0, -1, (err, history) => { if (err) { debug(err); diff --git a/lib/routes/bins/run.js b/lib/routes/bins/run.js index b193efa2..9b086da3 100644 --- a/lib/routes/bins/run.js +++ b/lib/routes/bins/run.js @@ -1,8 +1,10 @@ const debug = require("debug")("mockbin"); module.exports = function (req, res, next) { + // compoundId allows us to provide paths in the id to resolve to a specific bin + const compoundId = req.params.uuid + req.params[0]; this.client.get( - `bin:${req.params.uuid}`, + `bin:${compoundId}`, function (err, value) { if (err) { debug(err); @@ -15,10 +17,10 @@ module.exports = function (req, res, next) { // log interaction & send the appropriate response based on HAR this.client.rpush( - `log:${req.params.uuid}`, + `log:${compoundId}`, JSON.stringify(req.har.log.entries[0]), ); - this.client.ltrim(`log:${req.params.uuid}`, 0, 100); + this.client.ltrim(`log:${compoundId}`, 0, 100); // headers har.headers.map((header) => { diff --git a/lib/routes/bins/update.js b/lib/routes/bins/update.js index ffd900c5..519bf0a4 100644 --- a/lib/routes/bins/update.js +++ b/lib/routes/bins/update.js @@ -1,25 +1,13 @@ const debug = require("debug")("mockbin"); -const util = require("util"); const validate = require("har-validator"); +const path = require("path"); module.exports = function (req, res, next) { const id = req.params.uuid; - let mock = req.jsonBody; - - // check for full HAR - if (req.jsonBody?.response) { - mock = req.jsonBody.response; - } + const path = req.params[0]; + const compoundId = id + path; - // exception for the web Form - // TODO eliminate this and rely on req.simple.postData.text - if (req.simple.postData.params?.response) { - try { - mock = JSON.parse(req.simple.postData.params.response); - } catch (e) { - debug(e); - } - } + let mock = req.jsonBody; // overritten by application/x-www-form-urlencoded or multipart/form-data if (req.simple.postData.text) { @@ -29,6 +17,13 @@ module.exports = function (req, res, next) { debug(e); } } + if (!mock) { + res.body = { + errors: "Response HAR is required", + }; + next(); + return; + } // provide optional values before validation mock.redirectURL = ""; @@ -45,16 +40,15 @@ module.exports = function (req, res, next) { .response(mock) .then( function () { - this.client.set(`bin:${id}`, JSON.stringify(mock)); + this.client.set(`bin:${compoundId}`, JSON.stringify(mock)); res.view = "redirect"; - res.status(200).location(util.format("/bin/%s", id)).body = id; + res.status(200).location(`/bin/${compoundId}`).body = id; }.bind(this), ) - .catch((err) => { res.body = { - errors: err.errors, + errors: err.message, }; }) diff --git a/src/views/bin/log.pug b/src/views/bin/log.pug index c62b57dd..d52a84a1 100644 --- a/src/views/bin/log.pug +++ b/src/views/bin/log.pug @@ -9,12 +9,12 @@ mixin method (method) block content div(data-page="bin/log").container div.btn-group.pull-right.hidden-xs - a(href= '/bin/' + req.params.uuid + '/view').btn.btn-primary View Details + a(href= '/bin/view/' + req.params.uuid).btn.btn-primary View Details h3 Bin History: #[code= req.params.uuid] div.visible-xs - a(href= '/bin/' + req.params.uuid + '/view').btn.btn-block.btn-primary View Details + a(href= '/bin/view' + req.params.uuid).btn.btn-block.btn-primary View Details hr diff --git a/src/views/bin/view.pug b/src/views/bin/view.pug index 8159d550..182b10c3 100644 --- a/src/views/bin/view.pug +++ b/src/views/bin/view.pug @@ -14,7 +14,7 @@ block content a(href= '#apiembed').btn.btn-block.btn-primary #[span.badge 1]   Send Some Requests a(href= '/bin/' + req.params.uuid).btn.btn-block.btn-primary #[span.badge 2]   Visit in Browser - a(href= '/bin/' + req.params.uuid + '/log').btn.btn-block.btn-primary #[span.badge 3]   View History + a(href= '/bin/log/' + req.params.uuid).btn.btn-block.btn-primary #[span.badge 3]   View History br diff --git a/src/views/redirect.pug b/src/views/redirect.pug index eb40b8e7..8a1867af 100644 --- a/src/views/redirect.pug +++ b/src/views/redirect.pug @@ -1,7 +1,7 @@ doctype html html head - meta(http-equiv='refresh', content=`0; url=${res.getHeaders().location}/view`) + meta(http-equiv='refresh', content=`0; url=${res.getHeaders().location.replace('/bin','/bin/view')}`) body - p Redirecting to #[a(href=res.getHeaders().location)= res.getHeaders().location + '/view'] + p Redirecting to #[a(href=res.getHeaders().location)= res.getHeaders().location.replace('/bin','/bin/view')]