Skip to content

support option timeout #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gotty
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
// To enable reconnection, set `true` to `enable_reconnect`
// reconnect_time = false

// [int] Interval time to auto disconnect command when there is no input operation.
// 0(default) means never automatically disconnect.
// timeout = 0

// [int] Maximum connection to gotty, 0(default) means no limit.
// max_connection = 0

// [bool] Accept only one client and exit gotty once the client exits
// once = false

Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ tools:
go get github.com/mitchellh/gox
go get github.com/tcnksm/ghr
go get github.com/jteeuwen/go-bindata/...
go get github.com/zyfdegh/boomer

test:
if [ `go fmt $(go list ./... | grep -v /vendor/) | wc -l` -gt 0 ]; then echo "go fmt error"; exit 1; fi
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ You can build a binary using the following commands. Windows is not supported no
# Install tools
go get github.com/jteeuwen/go-bindata/...
go get github.com/tools/godep
go get github.com/zyfdegh/boomer

# Checkout hterm
git submodule sync && git submodule update --init --recursive
Expand Down
23 changes: 21 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"text/template"

"github.com/braintree/manners"
"github.com/elazarl/go-bindata-assetfs"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/websocket"
"github.com/kr/pty"
"github.com/yudai/hcl"
Expand All @@ -43,6 +43,8 @@ type App struct {
titleTemplate *template.Template

onceMutex *umutex.UnblockingMutex

connections int
}

type Options struct {
Expand All @@ -62,6 +64,8 @@ type Options struct {
TitleFormat string `hcl:"title_format"`
EnableReconnect bool `hcl:"enable_reconnect"`
ReconnectTime int `hcl:"reconnect_time"`
Timeout int `hcl:"timeout"`
MaxConnection int `hcl:"max_connection"`
Once bool `hcl:"once"`
PermitArguments bool `hcl:"permit_arguments"`
CloseSignal int `hcl:"close_signal"`
Expand All @@ -88,6 +92,7 @@ var DefaultOptions = Options{
TitleFormat: "GoTTY - {{ .Command }} ({{ .Hostname }})",
EnableReconnect: false,
ReconnectTime: 10,
MaxConnection: 0,
Once: false,
CloseSignal: 1, // syscall.SIGHUP
Preferences: HtermPrefernces{},
Expand Down Expand Up @@ -274,6 +279,12 @@ func (app *App) makeServer(addr string, handler *http.Handler) (*http.Server, er
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
if app.options.MaxConnection != 0 {
if app.connections >= app.options.MaxConnection {
log.Printf("Reached max connection: %d", app.options.MaxConnection)
return
}
}
log.Printf("New client connected: %s", r.RemoteAddr)

if r.Method != "GET" {
Expand Down Expand Up @@ -342,7 +353,15 @@ func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
log.Print("Failed to execute command")
return
}
log.Printf("Command is running for client %s with PID %d (args=%q)", r.RemoteAddr, cmd.Process.Pid, strings.Join(argv, " "))

app.connections++
if app.options.MaxConnection != 0 {
log.Printf("Command is running for client %s with PID %d (args=%q), connections: %d/%d",
r.RemoteAddr, cmd.Process.Pid, strings.Join(argv, " "), app.connections, app.options.MaxConnection)
} else {
log.Printf("Command is running for client %s with PID %d (args=%q), connections: %d",
r.RemoteAddr, cmd.Process.Pid, strings.Join(argv, " "), app.connections)
}

context := &clientContext{
app: app,
Expand Down
43 changes: 42 additions & 1 deletion app/client_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/fatih/structs"
"github.com/gorilla/websocket"
"github.com/zyfdegh/boomer"
)

type clientContext struct {
Expand All @@ -24,6 +25,7 @@ type clientContext struct {
command *exec.Cmd
pty *os.File
writeMutex *sync.Mutex
boomer *boomer.Boomer
}

const (
Expand Down Expand Up @@ -55,6 +57,17 @@ type ContextVars struct {
func (context *clientContext) goHandleClient() {
exit := make(chan bool, 2)

if context.app.options.Timeout > 0 {
timout := uint64(context.app.options.Timeout)
boomer, err := boomer.NewBoomer(timout, context.boom)
if err != nil {
log.Printf("new boomer error: %v", err)
return
}
context.boomer = boomer
context.boomer.Arm()
}

go func() {
defer func() { exit <- true }()

Expand All @@ -79,10 +92,22 @@ func (context *clientContext) goHandleClient() {

context.command.Wait()
context.connection.Close()
log.Printf("Connection closed: %s", context.request.RemoteAddr)
context.app.connections--
if context.app.options.MaxConnection != 0 {
log.Printf("Connection closed: %s, connections: %d/%d",
context.request.RemoteAddr, context.app.connections, context.app.options.MaxConnection)
} else {
log.Printf("Connection closed: %s, connections: %d",
context.request.RemoteAddr, context.app.connections)
}
}()
}

func (context *clientContext) boom() {
context.command.Process.Signal(syscall.Signal(context.app.options.CloseSignal))
log.Printf("Disconnected: no operation for %d seconds", context.app.options.Timeout)
}

func (context *clientContext) processSend() {
if err := context.sendInitialize(); err != nil {
log.Printf(err.Error())
Expand All @@ -97,6 +122,15 @@ func (context *clientContext) processSend() {
log.Printf("Command exited for: %s", context.request.RemoteAddr)
return
}
if size > 0 {
if context.boomer != nil {
err := context.boomer.Rewind()
if err != nil {
log.Printf("rewind boomer error: %v", err)
return
}
}
}
safeMessage := base64.StdEncoding.EncodeToString([]byte(buf[:size]))
if err = context.write(append([]byte{Output}, []byte(safeMessage)...)); err != nil {
log.Printf(err.Error())
Expand Down Expand Up @@ -172,6 +206,13 @@ func (context *clientContext) processReceive() {
break
}

if context.boomer != nil {
err = context.boomer.Rewind()
if err != nil {
log.Printf("rewind boomer error: %v", err)
return
}
}
_, err := context.pty.Write(data[1:])
if err != nil {
return
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ func main() {
flag{"title-format", "", "Title format of browser window"},
flag{"reconnect", "", "Enable reconnection"},
flag{"reconnect-time", "", "Time to reconnect"},
flag{"timeout", "", "Interval in seconds to auto disconnect, 0 means never"},
flag{"max-connection", "", "Maximum connection to gotty, 0(default) means no limit"},
flag{"once", "", "Accept only one client and exit on disconnection"},
flag{"permit-arguments", "", "Permit clients to send command line arguments in URL (e.g. http://example.com:8080/?arg=AAA&arg=BBB)"},
flag{"close-signal", "", "Signal sent to the command process when gotty close it (default: SIGHUP)"},
Expand Down