From 9df0423aeae27ae548fe64f953d0f82c6a37a711 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Sun, 26 Feb 2023 14:12:35 -0500 Subject: [PATCH] fix: pinging the server at 127.0.0.1 when using just the port number (#357) * add 0.0.0.0 server ci example * add explicit zero test * add timeout to the waiting failing demo * add test and explanation * run more tests in Node v16 * add debugging * update what we run on node v16 * switch from localhost to 127.0.0.1 for default host * prepend localhost and common host names with http * update readme * ipv6 test * update localhost in README snippets --- .github/workflows/ci.yml | 39 ++++++++++++++++++++++++++++++++++++- README.md | 37 ++++++++++++++++++++++++++--------- __snapshots__/utils-spec.js | 26 ++++++++++++------------- package.json | 7 +++++-- src/utils-spec.js | 23 ++++++++++++++++++---- src/utils.js | 19 +++++++++++++++--- test/zero.mjs | 17 ++++++++++++++++ 7 files changed, 136 insertions(+), 32 deletions(-) create mode 100644 test/zero.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6153fb3..df464a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,34 @@ jobs: - name: Run demo expect 403 code 📊 run: npm run demo-expect-403 + tests-node-v16: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎 + uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Node version 🖨️ + run: node -v + + - name: NPM install + uses: bahmutov/npm-install@v1 + + - name: Run json-server test + run: npm run demo-json-server + + - name: Run explicit 0.0.0.0 host example + run: npm run demo-zero-explicit + + - name: Run 0.0.0.0 host with 127.0.0.1 url + run: npm run demo-zero-127 + + - name: Run 0.0.0.0 host example + run: npm run demo-zero + tests-node-v18: runs-on: ubuntu-latest steps: @@ -93,8 +121,17 @@ jobs: - name: Run ::1 host example run: npm run demo-ip6 + - name: Run explicit 0.0.0.0 host example + run: npm run demo-zero-explicit + + - name: Run 0.0.0.0 host with 127.0.0.1 url + run: npm run demo-zero-127 + + - name: Run 0.0.0.0 host example + run: npm run demo-zero + release: - needs: ['tests1', 'tests2', 'tests-node-v18'] + needs: ['tests1', 'tests2', 'tests-node-v16', 'tests-node-v18'] if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index ddd2450..d54ee9e 100644 --- a/README.md +++ b/README.md @@ -80,14 +80,17 @@ You can use either `start-server-and-test`, `server-test` or `start-test` comman You can use `:` in front of port number like `server-test :8080`, so all these are equivalent ``` -start-server-and-test start http://localhost:8080 test -server-test start http://localhost:8080 test -server-test http://localhost:8080 test +start-server-and-test start http://127.0.0.1:8080 test +server-test start http://127.0.0.1:8080 test +server-test http://127.0.0.1:8080 test +server-test 127.0.0.1:8080 test start-test :8080 test start-test 8080 test start-test 8080 ``` +**Tip:** I highly recommend you specify the full url instead of the port, see the `localhost vs 0.0.0.0 vs 127.0.0.1` section later in this README. + ### Options If you use convention and name your scripts "start" and "test" you can simply provide URL @@ -102,7 +105,7 @@ If you use convention and name your scripts "start" and "test" you can simply pr } ``` -You can also shorten local url to just port, the code below is equivalent to checking `http://localhost:8080`. +You can also shorten local url to just port, the code below is equivalent to checking `http://127.0.0.1:8080`. ```json { @@ -165,7 +168,7 @@ If you want to start the server, wait for it to respond, and then run multiple t } ``` -The above script `ci` after the `localhost:9000` responds executes the `npm run test:unit` command. Then when it finishes it runs `npm run test:e2e`. If the first or second command fails, the `ci` script fails. Of course, your mileage on Windows might vary. +The above script `ci` after the `127.0.0.1:9000` responds executes the `npm run test:unit` command. Then when it finishes it runs `npm run test:e2e`. If the first or second command fails, the `ci` script fails. Of course, your mileage on Windows might vary. #### expected @@ -198,7 +201,7 @@ Then you can execute tests simply: ```text $ npx start-test 'http-server -c-1 .' 8080 'cypress run' starting server using command "http-server -c-1 ." -and when url "http://localhost:8080" is responding +and when url "http://127.0.0.1:8080" is responding running tests using command "cypress run" Starting up http-server, serving . ... @@ -211,12 +214,28 @@ $ yarn start-test 'http-server -c-1 .' 8080 'cypress run' yarn run v1.13.0 $ /private/tmp/test-t/node_modules/.bin/start-test 'http-server -c-1 .' 8080 'cypress run' starting server using command "http-server -c-1 ." -and when url "http://localhost:8080" is responding +and when url "http://127.0.0.1:8080" is responding running tests using command "cypress run" Starting up http-server, serving . ... ``` +## localhost vs 0.0.0.0 vs 127.0.0.1 + +The latest versions of Node and some web servers listen on host `0.0.0.0` which _no longer means localhost_. Thus if you specify _just the port number_, like `:3000`, this package will try `http://127.0.0.1:3000` to ping the server. A good practice is to specify the full URL you would like to ping. + +``` +# same as "http://127.0.0.1:3000" +start-server start 3000 test +# better +start-server start http://127.0.0.1:3000 test +# or +start-server start http://0.0.0.0:3000 test +# of course, if your server is listening on localhost +# you can still set the URL +start-server start http://localhost:3000 test +``` + ## Note for yarn users By default, npm is used to run scripts, however you can specify that yarn is used as follows: @@ -279,8 +298,8 @@ $ DEBUG=start-server-and-test npm run test start-server-and-test parsing CLI arguments: [ 'dev', '3000', 'subtask' ] +0ms start-server-and-test parsed args: { services: [ { start: 'npm run dev', url: [Array] } ], test: 'npm run subtask' } ... -making HTTP(S) head request to url:http://localhost:3000 ... - HTTP(S) error for http://localhost:3000 Error: Request failed with status code 404 +making HTTP(S) head request to url:http://127.0.0.1:3000 ... + HTTP(S) error for http://127.0.0.1:3000 Error: Request failed with status code 404 ``` ### Disable HTTPS certificate checks diff --git a/__snapshots__/utils-spec.js b/__snapshots__/utils-spec.js index 34b26b8..34bf6b4 100644 --- a/__snapshots__/utils-spec.js +++ b/__snapshots__/utils-spec.js @@ -53,13 +53,13 @@ exports['utils getArguments allows 5 arguments 1'] = { { "start": "npm run start", "url": [ - "http://localhost:6000" + "http://127.0.0.1:6000" ] }, { "start": "start:web", "url": [ - "http://localhost:6010" + "http://127.0.0.1:6010" ] } ], @@ -80,13 +80,13 @@ exports['utils getArguments determines NPM script for each command 1'] = { { "start": "npm run startA", "url": [ - "http://localhost:6000" + "http://127.0.0.1:6000" ] }, { "start": "npm run startB", "url": [ - "http://localhost:6010" + "http://127.0.0.1:6010" ] } ], @@ -117,7 +117,7 @@ exports['utils getArguments returns 3 arguments 1'] = { { "start": "npm run start", "url": [ - "http://localhost:8080" + "http://127.0.0.1:8080" ] } ], @@ -142,7 +142,7 @@ exports['utils getArguments understands custom commands 1'] = { { "start": "custom-command --with argument", "url": [ - "http://localhost:3000" + "http://127.0.0.1:3000" ] } ], @@ -154,9 +154,9 @@ exports['utils getArguments understands several ports 1'] = { { "start": "npm run start", "url": [ - "http://localhost:3000", - "http://localhost:4000", - "http://localhost:5000" + "http://127.0.0.1:3000", + "http://127.0.0.1:4000", + "http://127.0.0.1:5000" ] } ], @@ -168,7 +168,7 @@ exports['utils getArguments understands single :port 1'] = { { "start": "npm run start", "url": [ - "http://localhost:3000" + "http://127.0.0.1:3000" ] } ], @@ -180,7 +180,7 @@ exports['utils getArguments understands single port 1'] = { { "start": "npm run start", "url": [ - "http://localhost:3000" + "http://127.0.0.1:3000" ] } ], @@ -192,7 +192,7 @@ exports['utils getArguments understands start plus url 1'] = { { "start": "start-server", "url": [ - "http://localhost:6000" + "http://127.0.0.1:6000" ] } ], @@ -204,7 +204,7 @@ exports['utils getArguments understands url plus test 1'] = { { "start": "npm run start", "url": [ - "http://localhost:6000" + "http://127.0.0.1:6000" ] } ], diff --git a/package.json b/package.json index 14d2746..91dd020 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,11 @@ "demo-commands": "node src/bin/start.js 'node test/server.js --port 8800' 8800 'node test/client --port 8800'", "demo-multiple": "node src/bin/start.js 'node test/server --port 6000' 6000 'node test/server --port 6010' 6010 'curl http://127.0.0.1:6000 && curl http://127.0.0.1:6010'", "demo-multiple-test-commands": "node src/bin/start.js 9000 'npm run message && npm run message2'", - "demo-json-server": "node src/bin/start.js 'json-server test/data.json' 3000 'echo json-server working'", - "demo-ip6": "node src/bin/start.js 'node test/ip6.mjs' 8000 'echo server with ::1 working'" + "demo-json-server": "WAIT_ON_TIMEOUT=10000 DEBUG=start-server-and-test node src/bin/start.js 'json-server test/data.json' localhost:3000 'echo json-server working'", + "demo-ip6": "WAIT_ON_TIMEOUT=10000 DEBUG=start-server-and-test node src/bin/start.js 'node test/ip6.mjs' localhost:8000 'echo server with ::1 working'", + "demo-zero": "WAIT_ON_TIMEOUT=10000 node src/bin/start.js 'node test/zero.mjs' 8000 'echo server with 0.0.0.0 working'", + "demo-zero-127": "WAIT_ON_TIMEOUT=10000 node src/bin/start.js 'node test/zero.mjs' http://127.0.0.1:8000 'echo server with 0.0.0.0 working'", + "demo-zero-explicit": "node src/bin/start.js 'node test/zero.mjs' http://0.0.0.0:8000 'echo server with 0.0.0.0 working'" }, "devDependencies": { "@types/node": "^18.14.1", diff --git a/src/utils-spec.js b/src/utils-spec.js index b3efd15..2fff793 100644 --- a/src/utils-spec.js +++ b/src/utils-spec.js @@ -201,12 +201,27 @@ describe('utils', () => { }) it('changes port to localhost', () => { - la(arrayEq(normalizeUrl('6006'), ['http://localhost:6006'])) - la(arrayEq(normalizeUrl(8080), ['http://localhost:8080'])) + la(arrayEq(normalizeUrl('6006'), ['http://127.0.0.1:6006'])) + la(arrayEq(normalizeUrl(8080), ['http://127.0.0.1:8080'])) }) it('changes :port to localhost', () => { - la(arrayEq(normalizeUrl(':6006'), ['http://localhost:6006'])) + la(arrayEq(normalizeUrl(':6006'), ['http://127.0.0.1:6006'])) + }) + + it('appends http to localhost', () => { + la(arrayEq(normalizeUrl('localhost'), ['http://localhost'])) + la(arrayEq(normalizeUrl('localhost:3030'), ['http://localhost:3030'])) + }) + + it('appends http to 127.0.0.1', () => { + la(arrayEq(normalizeUrl('127.0.0.1'), ['http://127.0.0.1'])) + la(arrayEq(normalizeUrl('127.0.0.1:3030'), ['http://127.0.0.1:3030'])) + }) + + it('appends http to 0.0.0.0', () => { + la(arrayEq(normalizeUrl('0.0.0.0'), ['http://0.0.0.0'])) + la(arrayEq(normalizeUrl('0.0.0.0:3030'), ['http://0.0.0.0:3030'])) }) it('returns original argument if does not know what to do', () => { @@ -217,7 +232,7 @@ describe('utils', () => { it('parses multiple resources', () => { la( arrayEq(normalizeUrl(':6006|http://foo.com'), [ - 'http://localhost:6006', + 'http://127.0.0.1:6006', 'http://foo.com' ]) ) diff --git a/src/utils.js b/src/utils.js index 6062237..94c0037 100644 --- a/src/utils.js +++ b/src/utils.js @@ -189,8 +189,17 @@ const isUrlOrPort = input => { }) } +/** + * Returns the host to ping if the user specified just the port. + * For a long time, the safest bet was "localhost", but now modern + * web servers seem to bind to "0.0.0.0", which means + * the "127.0.0.1" works better + */ +const getHost = () => '127.0.0.1' + const normalizeUrl = input => { const str = is.string(input) ? input.split('|') : [input] + const defaultHost = getHost() return str.map(s => { if (is.url(s)) { @@ -198,19 +207,23 @@ const normalizeUrl = input => { } if (is.number(s) && is.port(s)) { - return `http://localhost:${s}` + return `http://${defaultHost}:${s}` } if (!is.string(s)) { return s } + if (s.startsWith('localhost') || s.startsWith('127.0.0.1') || s.startsWith('0.0.0.0')) { + return `http://${s}` + } + if (is.port(parseInt(s))) { - return `http://localhost:${s}` + return `http://${defaultHost}:${s}` } if (s[0] === ':') { - return `http://localhost${s}` + return `http://${defaultHost}${s}` } // for anything else, return original argument return s diff --git a/test/zero.mjs b/test/zero.mjs new file mode 100644 index 0000000..b68e390 --- /dev/null +++ b/test/zero.mjs @@ -0,0 +1,17 @@ +import http from 'node:http'; + +// Create a local server to receive data from +const server = http.createServer(); + +// Listen to the request event +server.on('request', (request, res) => { + console.log('server responding') + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + data: 'Hello World!', + })); +}); + +server.listen(8000, '0.0.0.0', () => { + console.log('server is listening on 0.0.0.0:8000') +});