Support auto reconnection

A new option `--auto-reconnect` which takes seconds to reconnect is
added.
This commit is contained in:
Iwasaki Yudai 2015-08-24 07:14:24 +09:00
parent 4df9ac8059
commit acacba6f03
6 changed files with 97 additions and 55 deletions

View File

@ -56,7 +56,7 @@ By default, gotty starts a web server at port 8080. Open the URL on your web bro
--random-url, -r Add a random string to the URL [$GOTTY_RANDOM_URL] --random-url, -r Add a random string to the URL [$GOTTY_RANDOM_URL]
--profile-file, -f "~/.gotty" Path to profile file [$GOTTY_PROFILE_FILE] --profile-file, -f "~/.gotty" Path to profile file [$GOTTY_PROFILE_FILE]
--title-format "GoTTY - {{ .Command }} ({{ .Hostname }})" Title format of browser window [$GOTTY_TITLE_FORMAT] --title-format "GoTTY - {{ .Command }} ({{ .Hostname }})" Title format of browser window [$GOTTY_TITLE_FORMAT]
--version, -v print the version --auto-reconnect "-1" Seconds to automatically reconnect to the server when the connection is closed (default: disabled) [$GOTTY_AUTO_RECONNECT]
``` ```
By default, gotty doesn't allow clients to send any keystrokes or commands except terminal window resizing. When you want to permit clients to write input to the PTY, add the `-w` option. However, accepting input from remote clients is dangerous for most commands. Make sure that only trusted clients can connect to your gotty server when you activate this option. If you need interaction with the PTY, consider starting gotty with tmux or GNU Screen and run your main command on it. By default, gotty doesn't allow clients to send any keystrokes or commands except terminal window resizing. When you want to permit clients to write input to the PTY, add the `-w` option. However, accepting input from remote clients is dangerous for most commands. Make sure that only trusted clients can connect to your gotty server when you activate this option. If you need interaction with the PTY, consider starting gotty with tmux or GNU Screen and run your main command on it.

View File

@ -32,14 +32,15 @@ type App struct {
} }
type Options struct { type Options struct {
Address string Address string
Port string Port string
PermitWrite bool PermitWrite bool
Credential string Credential string
RandomUrl bool RandomUrl bool
ProfileFile string ProfileFile string
TitleFormat string TitleFormat string
Command []string AutoReconnect int
Command []string
} }
const DefaultProfileFilePath = "~/.gotty" const DefaultProfileFilePath = "~/.gotty"

View File

@ -28,9 +28,10 @@ const (
) )
const ( const (
Output = '0' Output = '0'
SetWindowTitle = '1' SetWindowTitle = '1'
SetPreferences = '2' SetPreferences = '2'
SetAutoReconnect = '3'
) )
type argResizeTerminal struct { type argResizeTerminal struct {
@ -124,6 +125,17 @@ func (context *clientContext) sendInitialize() error {
writer.Write(prefs) writer.Write(prefs)
writer.Close() writer.Close()
if context.app.options.AutoReconnect >= 0 {
autoReconnect, _ := json.Marshal(context.app.options.AutoReconnect)
writer, err = context.connection.NextWriter(websocket.TextMessage)
if err != nil {
return err
}
writer.Write([]byte{SetAutoReconnect})
writer.Write(autoReconnect)
writer.Close()
}
return nil return nil
} }

View File

@ -71,7 +71,7 @@ func (fi bindataFileInfo) Sys() interface{} {
return nil return nil
} }
var _staticGottyJs = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x54\xc1\x8e\x9b\x30\x10\xbd\xe7\x2b\x2c\x2e\x31\x6a\xe4\xdd\xf4\xd0\x43\xa2\x55\x0f\xab\xed\xa1\xad\xba\x55\x93\x76\x0f\xab\x3d\x18\x33\x09\x6e\x1c\x1b\xd9\x66\x51\x5a\xf1\xef\x1d\x13\x48\x08\x61\x51\xe7\x80\x60\xfc\xde\xf3\xf3\xcc\x18\xba\x29\xb4\xf0\xd2\x68\x1a\x93\xbf\x13\x82\xf1\xca\x2d\xc9\xbc\xcf\xdd\x83\xe6\x89\x82\x94\xdc\x91\x52\xea\xd4\x94\x4c\x19\xc1\x03\x94\xe5\xd6\x78\x23\x8c\x22\x77\x77\x24\xaa\xb1\x8b\x68\x79\x22\x17\x16\x17\x08\xbd\xd0\xf8\x48\xa6\xa5\x73\x8b\x9b\x9b\x29\x59\x84\xd7\xf0\x16\x93\x77\x57\xca\x99\x71\x7e\x20\x9d\x73\x9f\x69\xbe\x07\x5c\x42\xf2\xf4\xbc\x57\xeb\xc4\xe1\x8e\xcf\xd1\xd6\x78\x7f\x88\x5e\xce\xcb\x65\xc8\x6b\x28\xc9\x13\x24\x2b\x23\x76\xe0\x29\xba\x9b\x9d\x69\xf1\x72\x72\x02\x7b\xb0\xfb\xe6\xb3\x74\xcc\x68\x93\x83\x46\xfa\xa9\x40\xf0\x0a\xda\xb7\x55\x0a\x91\x05\x06\x4b\x61\xc3\x0b\xe5\x57\xde\x58\xbe\x85\x66\x3f\x25\x13\xd6\x64\xd8\x57\x3c\x85\xa2\xf1\x72\x94\xc7\x84\x02\x6e\x69\xeb\x27\x44\x40\x35\x72\x47\xc6\x1a\x1f\x52\x1f\xb5\x2e\x50\x68\xb6\x5d\xfb\x01\x3c\x3d\x74\x5d\x77\x0d\xb7\x27\x95\x06\x11\x35\x51\x1a\x96\x17\x2e\xbb\x50\x0c\x81\x79\xa3\x7f\xad\xbf\xc0\xc1\x79\x6b\x76\xd0\x55\xc4\x4c\x5f\xb4\xa9\x99\x03\x9d\xd2\xe8\x36\xc2\x36\x05\xd0\xf2\x02\x53\x5d\x6f\x11\xf0\x2b\x6f\xa5\xde\xa2\x7e\x7f\xcb\x21\x47\xe7\x53\x3a\xf9\xe7\xc2\x14\x36\xb3\xd8\x6b\x37\x23\xd6\x94\x6e\xcc\xde\xd5\x42\x88\x68\x1e\x3c\x7f\x5e\x3d\x7e\x63\xae\xf6\x23\x37\x87\x61\x64\x88\x6b\xf1\x6e\x34\x4e\x16\xed\xcb\x6c\x14\x1d\xec\x2e\xea\xe7\xdb\xb8\x6a\x70\x25\xbe\xca\xc6\xa3\xf5\x3e\xf6\x5b\x3b\xcf\x95\xc2\x22\x27\x86\xdb\xb4\x3b\x95\x55\x7f\xa8\x52\x10\x38\x9a\x1e\x68\x6a\x44\xb1\xc7\xd9\x67\x89\x49\x0f\x0d\xa3\xea\xde\x94\x3d\x38\x77\x9c\xfd\xb7\x2f\x4b\xca\x3d\x47\x40\x9d\x67\xe1\x83\x39\x25\x05\xd0\x79\xc7\x82\x2b\xa5\x17\x19\x3d\x63\x9e\x6f\x5f\xba\x1a\x82\x3b\x20\xd3\xdb\xe9\x62\xe0\x60\x86\x95\x56\x7a\xf8\xb9\xfe\x34\xff\x40\x03\xb7\x37\x7e\x89\x05\xbe\x5b\xf6\xa4\xe6\x43\x52\x0e\xfc\x53\xfd\xff\x59\x4b\xaf\xe0\xbf\xb5\xde\xf7\xb4\x72\x0b\x1b\xb0\xa0\x05\x84\x9f\x50\x3d\x5b\x39\xb7\x6e\x50\xf0\x31\xf9\x0d\xc2\xb3\x1d\xce\x3e\xed\xf0\x62\xb6\x31\xf6\x81\x63\x49\x4e\x75\x45\xc8\xd0\x74\xd7\xc6\xb7\xe0\xbf\x23\xd9\xd1\x38\x9c\x21\x40\x67\x5d\x17\xcf\x98\x78\xe9\x5f\xca\xf1\x93\x1d\x27\xaf\xea\xb4\x5a\x28\xe3\xc6\x1b\xdd\xb6\xc3\x65\xa6\x7c\x7c\x05\xab\xf8\x81\x46\xf7\x46\x6b\xa8\x09\xe4\x3e\x28\xa4\xd1\x8c\xe8\x42\xa9\xce\xf6\x35\xaf\xd0\x6f\x4c\x68\x35\xa9\x62\x1a\x4f\xfe\x05\x00\x00\xff\xff\x0d\xc1\x26\x9b\xaf\x06\x00\x00") var _staticGottyJs = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x94\x55\xc1\x6e\x1a\x31\x10\xbd\xe7\x2b\xac\xbd\xe0\x6d\xa9\x03\xad\xd4\x03\x88\xf6\x10\xa5\x87\xb6\x6a\xaa\x40\x9b\x43\x94\x83\xf1\x0e\xe0\x62\x6c\x64\x7b\xb3\xa2\x15\xff\xde\xf1\xb2\xc0\xb2\x78\x83\xe2\x03\x5a\x3c\x6f\xde\xcc\x1b\xcf\xd8\x74\x96\x6b\xe1\xa5\xd1\x34\x25\xff\xae\x08\xae\x67\x6e\xc9\xc2\xfb\xb5\xbb\xd5\x7c\xaa\x20\x23\x23\x52\x48\x9d\x99\x82\x29\x23\x78\x80\xb2\xb5\x35\xde\x08\xa3\xc8\x68\x44\x92\x12\x3b\x48\x86\x07\xe7\xdc\xa2\x81\xd0\x13\x8e\xcf\xa4\x53\x38\x37\xb8\xbe\xee\x90\x41\xf8\x0c\x5f\x29\x79\x7b\xc6\xbc\x30\xce\x47\xb6\xd7\xdc\x2f\x34\x5f\x01\x9a\xd0\xb9\x73\x8c\xb5\xcf\xc4\x61\xc4\xc7\x64\x6e\xbc\xdf\x24\x4f\x47\x33\xcf\xbd\xb9\x07\x61\xb4\x06\xe1\x11\xf2\xae\x3f\xbc\x3a\x18\xcd\x1a\xf4\x43\x70\x3c\x2b\xc1\x1e\x51\x04\xab\x86\x82\x3c\xc0\x74\x6c\xc4\x12\x3c\x45\x71\xdd\x63\xd4\xb4\xa2\xdb\x3b\x78\xb0\xab\xda\x56\xe1\x98\xd1\x21\x4c\x3d\x08\x3c\x83\xf6\xf5\x48\x61\x2d\x82\x27\xcb\x60\xc6\x73\xe5\xc7\xde\x58\x3e\x87\x2a\xb6\x92\x53\x56\xed\xb0\xef\x58\x10\x45\xd3\xe1\x45\x5f\x26\x14\x70\x4b\xeb\xf9\x85\x15\x90\x15\xed\xce\x6b\x82\x3f\x52\xef\x38\xcf\x90\x98\xfc\xde\x7e\x0f\x3c\xdb\xb4\x95\xaa\x5e\x01\x69\x10\x55\x3a\x4b\xc3\xd6\xb9\x5b\x9c\x31\x87\x85\x36\xa3\x7f\x4f\xbe\xc1\xc6\x79\x6b\x96\x50\x67\xc6\x9d\x18\x79\x55\x4f\x07\x3a\xa3\x49\x2f\xc1\x4e\x08\xc0\xe1\x19\x6e\x1b\x0f\x17\xfc\xc6\xde\x4a\x3d\xc7\x58\xcd\xf0\x6d\x19\x1e\xd5\x3b\xf9\xf7\x24\x49\x3c\xfc\x7c\xa5\x5d\x97\x58\x53\xb8\x4b\xe9\x46\x8d\x61\x25\xfd\xa0\xe3\xeb\xf8\xee\x07\x73\x65\x6e\x72\xb6\x69\x47\x87\x15\x0f\x54\x5f\x55\x66\x83\xfd\x47\xf7\xa2\x47\x90\x30\x28\x7f\x5f\xc6\x6e\x5b\xad\x69\xd4\x72\xbe\x1b\x3b\x9b\x5d\xaf\x68\xe7\xb9\x52\x78\x20\x53\xc3\x6d\xd6\xec\xf0\xa6\x5f\xd5\xf0\x02\x5b\xdd\x03\xcd\x8c\xc8\x57\x38\x53\x6c\x6a\xb2\x4d\xcd\x73\xdb\x9c\xc4\x15\x38\xb7\x9b\xab\x97\x87\x31\xe3\x9e\x23\xa8\xb4\xb1\xf0\x87\x39\x25\x05\xd0\x7e\x23\x2d\x57\x48\x2f\x16\xf4\x88\x7b\xec\x3d\x35\xb9\x04\x77\x40\x3a\xbd\xce\xa0\x45\xb8\x61\x85\x95\x1e\x7e\x4d\xbe\xf4\x3f\xd2\xc0\x11\x69\xe9\xa9\x05\xbe\x1c\x46\x68\xfb\x6d\xb4\x0e\xfc\x43\x79\x85\x4e\xa4\x57\xf0\x6a\xde\xf7\x11\xde\xb5\x85\x19\x58\xd0\x02\xc2\xa5\x58\xf6\xec\x9a\x5b\xd7\x4a\x7e\x37\xfd\x83\x77\x2e\x5b\xe2\x8c\xd1\x9a\x6f\xca\x66\xc6\xde\x72\x2c\xdb\xe1\x0c\x10\xd2\x36\x41\xa5\x98\x39\xf8\x9f\x48\xe0\x68\x1a\x74\x05\x78\xb7\x9e\xcd\x23\x6e\x3c\xc5\x2e\x82\xd7\x28\xfe\x10\x51\xdc\x7c\x3c\x2e\x6b\x8e\xd0\x1f\x87\x66\xdb\xe8\x46\xa1\x8c\xbb\xdc\x8b\x72\x46\x68\xa8\x42\xac\x42\x65\x75\x72\x7d\x61\x78\x0e\xc8\x70\x0b\x2e\x4c\x71\xf7\x0c\x56\xf1\x0d\x4d\x6e\x76\xca\x30\x34\xb9\x09\xb9\x64\x49\x97\xe8\x5c\xa9\xe6\xf0\x9d\x8e\x9e\xdd\x1c\x6a\x52\x8f\xd4\x54\x07\xd6\x1a\x7b\xa2\x2e\x6c\x34\x55\xb4\xb3\xd5\x38\x77\xef\xf4\xe1\x1d\x29\xdf\xd9\x9a\x5f\xdb\xab\x14\x2a\x77\x7a\x84\x9f\x46\xa4\xd7\xcc\x00\x3b\x6a\x22\x57\x60\x72\x4f\x77\x71\xba\x8d\x73\x7f\x43\xfa\xbd\x5e\x2f\x92\xdb\x36\xa5\xe9\xd5\xff\x00\x00\x00\xff\xff\x1e\x3d\x0c\xe8\x3f\x09\x00\x00")
func staticGottyJsBytes() ([]byte, error) { func staticGottyJsBytes() ([]byte, error) {
return bindataRead( return bindataRead(
@ -86,7 +86,7 @@ func staticGottyJs() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "static/gotty.js", size: 1711, mode: os.FileMode(436), modTime: time.Unix(1440336972, 0)} info := bindataFileInfo{name: "static/gotty.js", size: 2367, mode: os.FileMode(436), modTime: time.Unix(1440367388, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }

View File

@ -55,6 +55,12 @@ func main() {
Usage: "Title format of browser window", Usage: "Title format of browser window",
EnvVar: "GOTTY_TITLE_FORMAT", EnvVar: "GOTTY_TITLE_FORMAT",
}, },
cli.IntFlag{
Name: "auto-reconnect",
Value: -1,
Usage: "Seconds to automatically reconnect to the server when the connection is closed (default: disabled)",
EnvVar: "GOTTY_AUTO_RECONNECT",
},
} }
cmd.Action = func(c *cli.Context) { cmd.Action = func(c *cli.Context) {
if len(c.Args()) == 0 { if len(c.Args()) == 0 {
@ -72,6 +78,7 @@ func main() {
c.Bool("random-url"), c.Bool("random-url"),
c.String("profile-file"), c.String("profile-file"),
c.String("title-format"), c.String("title-format"),
c.Int("auto-reconnect"),
c.Args(), c.Args(),
}, },
) )

View File

@ -2,62 +2,84 @@
var httpsEnabled = window.location.protocol == "https:"; var httpsEnabled = window.location.protocol == "https:";
var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws'; var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws';
var protocols = ["gotty"]; var protocols = ["gotty"];
var ws = new WebSocket(url, protocols); var autoReconnect = -1;
var term; var openWs = function() {
var ws = new WebSocket(url, protocols);
ws.onopen = function(event) { var term;
hterm.defaultStorage = new lib.Storage.Local();
hterm.defaultStorage.clear();
term = new hterm.Terminal(); ws.onopen = function(event) {
hterm.defaultStorage = new lib.Storage.Local();
hterm.defaultStorage.clear();
term.onTerminalReady = function() { term = new hterm.Terminal();
var io = term.io.push();
io.onVTKeystroke = function(str) { term.onTerminalReady = function() {
ws.send("0" + str); var io = term.io.push();
};
io.sendString = io.onVTKeystroke; io.onVTKeystroke = function(str) {
ws.send("0" + str);
};
io.onTerminalResize = function(columns, rows) { io.sendString = io.onVTKeystroke;
ws.send(
"1" + JSON.stringify( io.onTerminalResize = function(columns, rows) {
{ ws.send(
columns: columns, "1" + JSON.stringify(
rows: rows, {
} columns: columns,
rows: rows,
}
)
) )
) };
term.installKeyboard();
}; };
term.installKeyboard(); term.decorate(document.body);
}; };
term.decorate(document.body); ws.onmessage = function(event) {
}; data = event.data.slice(1);
switch(event.data[0]) {
case '0':
term.io.writeUTF16(data);
break;
case '1':
term.setWindowTitle(data);
break;
case '2':
preferences = JSON.parse(data);
Object.keys(preferences).forEach(function(key) {
term.getPrefs().set(key, preferences[key]);
});
break;
case '3':
autoReconnect = JSON.parse(data);
break;
}
}
ws.onmessage = function(event) { ws.onclose = function(event) {
data = event.data.slice(1); if (term) {
switch(event.data[0]) { term.uninstallKeyboard();
case '0': term.io.showOverlay("Connection Closed", null);
term.io.writeUTF16(data); }
break; tryReconnect();
case '1': }
term.setWindowTitle(data);
break; ws.onerror = function(error) {
case '2': tryReconnect();
preferences = JSON.parse(data);
Object.keys(preferences).forEach(function(key) {
term.getPrefs().set(key, preferences[key]);
});
break;
} }
} }
ws.onclose = function(event) { openWs();
term.io.showOverlay("Connection Closed", null);
term.uninstallKeyboard(); var tryReconnect = function() {
if (autoReconnect >= 0) {
setTimeout(openWs, autoReconnect * 1000);
}
} }
})() })()