Feat/proxy devserver (#603)

This commit is contained in:
Lucas Fernandes Nogueira 2020-05-21 11:48:57 -03:00 committed by GitHub
parent 0aeb87a26c
commit fef54ad6e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 58 deletions

View File

@ -1,6 +1,6 @@
{
"name": "tauri",
"version": "0.6.0",
"version": "0.6.1",
"description": "Multi-binding collection of libraries and templates for building Tauri apps",
"bin": {
"tauri": "./bin/tauri.js"
@ -51,6 +51,7 @@
"cross-spawn": "7.0.2",
"fast-glob": "3.2.2",
"fs-extra": "9.0.0",
"http-proxy": "^1.18.0",
"imagemin": "7.0.1",
"imagemin-optipng": "7.1.0",
"imagemin-pngquant": "8.0.0",
@ -73,6 +74,7 @@
"@babel/preset-typescript": "7.9.0",
"@types/cross-spawn": "6.0.1",
"@types/fs-extra": "8.1.0",
"@types/http-proxy": "^1.17.4",
"@types/imagemin": "7.0.0",
"@types/imagemin-optipng": "5.2.0",
"@types/jsdom": "16.2.1",

View File

@ -3,17 +3,16 @@
import net from 'net'
async function findClosestOpenPort(port: number, host: string): Promise<number> {
let portProposal = port
do {
if (await isPortAvailable(portProposal, host)) {
return portProposal
}
portProposal++
}
while (portProposal < 65535)
throw new Error('ERROR_NETWORK_PORT_NOT_AVAIL')
return await isPortAvailable(port, host)
.then(isAvailable => {
if (isAvailable) {
return port
} else if (port < 65535) {
return findClosestOpenPort(port + 1, host)
} else {
throw new Error('ERROR_NETWORK_PORT_NOT_AVAIL')
}
})
}
async function isPortAvailable(port: number, host: string): Promise<boolean> {

View File

@ -14,8 +14,10 @@ import { tauriDir, appDir } from './helpers/app-paths'
import logger from './helpers/logger'
import onShutdown from './helpers/on-shutdown'
import { spawn, spawnSync } from './helpers/spawn'
import { exec } from 'child_process'
import { TauriConfig } from './types/config'
import getTauriConfig from './helpers/tauri-config'
import httpProxy from 'http-proxy'
const log = logger('app:tauri', 'green')
const warn = logger('app:tauri (runner)', 'red')
@ -51,10 +53,18 @@ class Runner {
if (!this.ranBeforeDevCommand && cfg.build.beforeDevCommand) {
this.ranBeforeDevCommand = true // prevent calling it twice on recursive call on our watcher
const [command, ...args] = cfg.build.beforeDevCommand.split(' ')
spawn(command, args, appDir, code => {
process.exit(code)
log('Running `' + cfg.build.beforeDevCommand + '`')
const ls = exec(cfg.build.beforeDevCommand, {
cwd: appDir,
env: process.env
}, error => {
if (error) {
process.exit(1)
}
})
ls.stderr && ls.stderr.pipe(process.stderr)
ls.stdout && ls.stdout.pipe(process.stdout)
}
const tomlContents = this.__getManifest()
@ -68,51 +78,51 @@ class Runner {
let inlinedAssets: string[] = []
if (runningDevServer) {
const self = this
const devUrl = new URL(devPath)
const app = http.createServer((req, res) => {
const options = {
hostname: devUrl.hostname,
port: devUrl.port,
path: req.url,
method: req.method
}
const self = this
const proxy = http.request(options, function (originalResponse) {
if (options.path === '/') {
let body: Uint8Array[] = []
originalResponse.on('data', chunk => {
body.push(chunk)
})
originalResponse.on('end', () => {
const originalHtml = body.join('')
const indexDir = os.tmpdir()
writeFileSync(path.join(indexDir, 'index.html'), originalHtml)
self.__parseHtml(cfg, indexDir, false)
.then(({ html }) => {
res.writeHead(200)
res.end(html)
}).catch(err => {
res.writeHead(500, JSON.stringify(err))
res.end()
})
})
} else {
res.writeHead(res.statusCode, originalResponse.headers)
originalResponse.pipe(res, {
end: true
})
}
})
req.pipe(proxy, {
end: true
})
const proxy = httpProxy.createProxyServer({
ws: true,
target: {
host: devUrl.hostname,
port: devUrl.port
},
selfHandleResponse: true
})
proxy.on('proxyRes', function (proxyRes, req, res) {
if (req.url === '/') {
let body: Uint8Array[] = []
proxyRes.on('data', function (chunk) {
body.push(chunk)
})
proxyRes.on('end', function () {
let bodyStr = body.join('')
const indexDir = os.tmpdir()
writeFileSync(path.join(indexDir, 'index.html'), bodyStr)
self.__parseHtml(cfg, indexDir, false)
.then(({ html }) => {
res.end(html)
}).catch(err => {
res.writeHead(500, JSON.stringify(err))
res.end()
})
})
} else {
proxyRes.pipe(res)
}
})
const proxyServer = http.createServer((req, res) => {
delete req.headers['accept-encoding']
proxy.web(req, res)
})
proxyServer.on('upgrade', (req, socket, head) => {
proxy.ws(req, socket, head)
})
const port = await findClosestOpenPort(parseInt(devUrl.port) + 1, devUrl.hostname)
const server = app.listen(port)
this.devServer = server
const devServer = proxyServer.listen(port)
this.devServer = devServer
devPath = `${devUrl.protocol}//localhost:${port}`
cfg.build.devPath = devPath
process.env.TAURI_CONFIG = JSON.stringify(cfg)

View File

@ -1234,6 +1234,13 @@
dependencies:
"@types/node" "*"
"@types/http-proxy@^1.17.4":
version "1.17.4"
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b"
integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==
dependencies:
"@types/node" "*"
"@types/imagemin-optipng@5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@types/imagemin-optipng/-/imagemin-optipng-5.2.0.tgz#83046e0695739661fa738ad253bdbf51bc4f9e9d"
@ -3056,6 +3063,13 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@^3.0.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
@ -3776,6 +3790,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
eventemitter3@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
events@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
@ -4195,6 +4214,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.0.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==
dependencies:
debug "^3.0.0"
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -4716,6 +4742,15 @@ http-cache-semantics@^4.0.0:
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-proxy@^1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==
dependencies:
eventemitter3 "^4.0.0"
follow-redirects "^1.0.0"
requires-port "^1.0.0"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@ -7776,6 +7811,11 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"

View File

@ -624,4 +624,4 @@ if (document.readyState === 'complete' || document.readyState === 'interactive')
__openLinks()
}, true)
}
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div style="margin-top:24px"> <select id="request-method"> <option value="GET">GET</option> <option value="POST">POST</option> <option value="PUT">PUT</option> <option value="PATCH">PATCH</option> <option value="DELETE">DELETE</option> </select> <input id="request-url" placeholder="Type the request URL..."> <textarea id="request-body" placeholder="Request body"></textarea> <button id="make-request">Make request</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var dirSelect=document.getElementById("dir");for(var key in window.tauri.Dir){var value=window.tauri.Dir[key],opt=document.createElement("option");opt.value=value,opt.innerHTML=key,dirSelect.appendChild(opt)}</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> <script></script> </body></html>
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var dirSelect=document.getElementById("dir");for(var key in window.tauri.Dir){var value=window.tauri.Dir[key],opt=document.createElement("option");opt.value=value,opt.innerHTML=key,dirSelect.appendChild(opt)}</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> </body></html>