Skip to content

Commit

Permalink
Tunnel fix (#143)
Browse files Browse the repository at this point in the history
* upgrade action job versions

* fix curl command

* pull out logic

* minor fixes
  • Loading branch information
mifi authored Aug 1, 2022
1 parent 40cfdb3 commit bd631b1
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 110 deletions.
13 changes: 9 additions & 4 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}

- run: |
curl https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 --output cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64

- run: yarn
- run: npm run lint

- name: Download cloudflared
run: |
curl https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -L --output cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
# can be used for debugging:
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3

- run: npm run test-all
env:
TRANSLOADIT_KEY: ${{ secrets.TRANSLOADIT_KEY }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:
node: ['10', '12', '14']

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@
"lint": "eslint .",
"next:update": "next-update --keep true --tldr",
"test-unit": "jest --coverage ./test/unit",
"test-integration": "jest ./test/integration",
"test-integration": "jest --runInBand ./test/integration",
"tsd": "tsd",
"test-all": "npm run tsd && jest --coverage --coverageReporters json lcov text clover json-summary --forceExit",
"test-all": "npm run tsd && jest --runInBand --coverage --coverageReporters json lcov text clover json-summary --forceExit",
"test": "npm run tsd && npm run test-unit"
},
"license": "MIT",
Expand Down
99 changes: 3 additions & 96 deletions test/integration/__tests__/live-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @jest-environment node
*/
// https://github.com/axios/axios/issues/2654
const http = require('http')
const keyBy = require('lodash/keyBy')
const querystring = require('querystring')
const temp = require('temp')
Expand All @@ -19,7 +18,7 @@ const pipeline = promisify(streamPipeline)

const Transloadit = require('../../../src/Transloadit')

const createTunnel = require('../../tunnel')
const { startTestServer } = require('../../testserver')

async function downloadTmpFile (url) {
const { path } = await temp.open('transloadit')
Expand Down Expand Up @@ -69,98 +68,6 @@ function createAssembly (client, params) {
return promise
}

const startServerAsync = async (handler2) => {
let customHandler

function handler (...args) {
if (customHandler) return customHandler(...args)
return handler2(...args)
}

const server = http.createServer(handler)

// Find a port to use
let port = 8000
await new Promise((resolve, reject) => {
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
if (++port >= 65535) {
server.close()
return reject(new Error('Failed to bind to port'))
}
return server.listen(port, '127.0.0.1')
}
return reject(err)
})

server.listen(port, '127.0.0.1')

server.on('listening', resolve)
})

let tunnel
try {
tunnel = createTunnel({ cloudFlaredPath: process.env.CLOUDFLARED_PATH, port })

// eslint-disable-next-line no-console
tunnel.process.on('error', console.error)
tunnel.process.on('close', () => {
// console.log('tunnel closed')
server.close()
})

const url = await tunnel.urlPromise
// console.log('tunnel created', url)

try {
let requestPromise
await new Promise((resolve, reject) => {
let curPath
let done = false

customHandler = (req, res) => {
// console.log('handler', req.url)

if (req.url !== curPath) throw new Error(`Unexpected path ${req.url}`)

done = true
res.end()
resolve()
}

;(async () => {
for (let i = 0; i < 10; i += 1) {
if (done) return
curPath = `/check${i}`
try {
await got(`${url}${curPath}`, { timeout: { request: 2000 } })
} catch (err) {
// console.error(err)
// eslint-disable-next-line no-shadow
await new Promise((resolve) => setTimeout(resolve, 3000))
}
}
reject(new Error('Timed out checking for a functioning tunnel'))
})()
})
await requestPromise
} finally {
customHandler = undefined
}

// console.log('Tunnel ready')

return {
url,
close: () => tunnel.close(),
}
} catch (err) {
if (tunnel) tunnel.close()
server.close()
throw err
}
}

// https://transloadit.com/demos/importing-files/import-a-file-over-http
const genericImg = 'https://demos.transloadit.com/66/01604e7d0248109df8c7cc0f8daef8/snowflake.jpg'
const sampleSvg = '<?xml version="1.0" standalone="no"?><svg height="100" width="100"><circle cx="50" cy="50" r="40" fill="red" /></svg>'
Expand Down Expand Up @@ -488,7 +395,7 @@ describe('API integration', () => {
got.stream(genericImg).pipe(res)
}

const server = await startServerAsync(handleRequest)
const server = await startTestServer(handleRequest)

try {
const params = {
Expand Down Expand Up @@ -640,7 +547,7 @@ describe('API integration', () => {
}

try {
server = await startServerAsync(onNotificationRequest)
server = await startTestServer(onNotificationRequest)
await createAssembly(client, { params: { ...genericParams, notify_url: server.url } })
} catch (err) {
onError(err)
Expand Down
103 changes: 103 additions & 0 deletions test/testserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const http = require('http')
const got = require('got')

const createTunnel = require('./tunnel')

async function startTestServer (handler2) {
let customHandler

function handler (...args) {
if (customHandler) return customHandler(...args)
return handler2(...args)
}

const server = http.createServer(handler)

// Find a free port to use
let port = 8000
await new Promise((resolve, reject) => {
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
if (++port >= 65535) {
server.close()
return reject(new Error('Failed to bind to port'))
}
return server.listen(port, '127.0.0.1')
}
return reject(err)
})

server.listen(port, '127.0.0.1')

server.on('listening', resolve)
})

let tunnel
try {
tunnel = createTunnel({ cloudFlaredPath: process.env.CLOUDFLARED_PATH, port })

// eslint-disable-next-line no-console
tunnel.process.on('error', console.error)
tunnel.process.on('close', () => {
// console.log('tunnel closed')
server.close()
})

// console.log('waiting for tunnel to be created')
const url = await tunnel.urlPromise
// console.log('tunnel created', url)

try {
let curPath
let done = false

const promise1 = new Promise((resolve) => {
customHandler = (req, res) => {
// console.log('handler', req.url)

if (req.url !== curPath) throw new Error(`Unexpected path ${req.url}`)

done = true
res.end()
resolve()
}
})

const promise2 = (async () => {
// try connecting to the tunnel and resolve when connection successfully passed through
for (let i = 0; i < 10; i += 1) {
if (done) return
curPath = `/check${i}`
try {
await got(`${url}${curPath}`, { timeout: { request: 2000 } })
return
} catch (err) {
// console.error(err.message)
// eslint-disable-next-line no-shadow
await new Promise((resolve) => setTimeout(resolve, 3000))
}
}
throw new Error('Timed out checking for a functioning tunnel')
})()

await Promise.all([promise1, promise2])
} finally {
customHandler = undefined
}

// console.log('Tunnel ready')

return {
url,
close: () => tunnel.close(),
}
} catch (err) {
if (tunnel) tunnel.close()
server.close()
throw err
}
}

module.exports = {
startTestServer,
}
14 changes: 8 additions & 6 deletions test/tunnel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ const { Resolver } = require('dns')
const { promisify } = require('util')

module.exports = ({ cloudFlaredPath = 'cloudflared', port }) => {
// console.log('starting tunnel', port)
const process = execa(cloudFlaredPath, ['tunnel', '--url', `http://localhost:${port}`, '--no-autoupdate'], { buffer: false, stdout: 'ignore' })
const rl = readline.createInterface({ input: process.stderr })

process.on('error', (err) => console.error(err))

const urlPromise = (async () => {
const url = await new Promise((resolve) => {
let foundUrl
rl.on('line', (line) => {
// console.log('line', line)
if (!foundUrl) {
const match = line.match(/(https:\/\/[^.]+\.trycloudflare\.com)/)
if (!match) return
Expand All @@ -23,24 +23,26 @@ module.exports = ({ cloudFlaredPath = 'cloudflared', port }) => {
}
})
})
// console.log('Found url')

// We need to wait for DNS to be resolvable.
// If we don't, the operating system dns cache will be poisoned by the not yet valid dns
// and forever fail for that subdomain name
// If we don't, the operating system's dns cache will be poisoned by the not yet valid resolved entry
// and it will forever fail for that subdomain name...
const resolver = new Resolver()
resolver.setServers(['8.8.8.8']) // if we don't explicitly specify DNS server, it will also poison the OS dns cache
const resolve4 = promisify(resolver.resolve4.bind(resolver))
for (let i = 0; i < 10; i += 1) {
try {
const host = new URL(url).hostname
// console.log('checking dns', host)
await resolve4(host)
return url
} catch (err) {
// console.error(err)
// console.error(err.message)
await new Promise((resolve) => setTimeout(resolve, 3000))
}
}
throw new Error('Timed out trying to resolve tunnel host')
throw new Error('Timed out trying to resolve tunnel dns')
})()

function close () {
Expand Down

0 comments on commit bd631b1

Please sign in to comment.