diff --git a/api/escape.go b/api/escape.go new file mode 100644 index 0000000..a1b03a2 --- /dev/null +++ b/api/escape.go @@ -0,0 +1,49 @@ +package api + +import ( + "fmt" + "net/http" + + "encoding/json" + "github.com/gorilla/mux" + "github.com/xcat2/goconserver/console" +) + +type EscapeApi struct { + routes Routes +} + +func NewEscapeApi(router *mux.Router) *CommandApi { + api := CommandApi{} + routes := Routes{ + Route{"Command", "GET", "/breaksequence", api.listSequence}, + } + api.routes = routes + for _, route := range routes { + router. + Methods(route.Method). + Path(route.Pattern). + Name(route.Name). + Handler(route.HandlerFunc) + } + return &api +} + +func (api *CommandApi) listSequence(w http.ResponseWriter, req *http.Request) { + plog.Debug(fmt.Sprintf("Receive %s request %s from %s.", req.Method, req.URL.Path, req.RemoteAddr)) + var resp []byte + var err error + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + w.WriteHeader(http.StatusOK) + serverEscape := console.GetServerEscape() + if serverEscape == nil { + plog.HandleHttp(w, req, http.StatusInternalServerError, err.Error()) + return + } + seqs := serverEscape.GetSequences() + if resp, err = json.Marshal(seqs); err != nil { + plog.HandleHttp(w, req, http.StatusInternalServerError, err.Error()) + return + } + fmt.Fprintf(w, "%s\n", resp) +} diff --git a/common/conf.go b/common/conf.go index c4a749e..0430f94 100644 --- a/common/conf.go +++ b/common/conf.go @@ -98,6 +98,11 @@ type EtcdCfg struct { RpcPort string `yaml:"rpcport"` } +type BreakSequenceCfg struct { + Sequence string `yaml:"sequence"` + Delay int `yaml:"delay"` +} + type ServerConfig struct { Global struct { Host string `yaml:"host"` @@ -115,16 +120,17 @@ type ServerConfig struct { DistDir string `yaml:"dist_dir"` } Console struct { - Port string `yaml:"port"` - DataDir string `yaml:"datadir"` - LogTimestamp bool `yaml:"log_timestamp"` - TimePrecision string `yaml:"time_precision"` - TimeFormat string `yaml:"-"` - ReplayLines int `yaml:"replay_lines"` - ClientTimeout int `yaml:"client_timeout"` - TargetTimeout int `yaml:"target_timeout"` - ReconnectInterval int `yaml:"reconnect_interval"` - Loggers LoggerCfg `yaml:"logger"` + Port string `yaml:"port"` + DataDir string `yaml:"datadir"` + LogTimestamp bool `yaml:"log_timestamp"` + TimePrecision string `yaml:"time_precision"` + TimeFormat string `yaml:"-"` + ReplayLines int `yaml:"replay_lines"` + ClientTimeout int `yaml:"client_timeout"` + TargetTimeout int `yaml:"target_timeout"` + ReconnectInterval int `yaml:"reconnect_interval"` + Loggers LoggerCfg `yaml:"logger"` + BreakSequences []BreakSequenceCfg `yaml:"break_sequence"` } Etcd EtcdCfg `yaml:"etcd"` } diff --git a/common/signal.go b/common/signal.go index b1f78c2..df0c476 100644 --- a/common/signal.go +++ b/common/signal.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/signal" + "sync" ) var ( @@ -15,9 +16,11 @@ func DoSignal(done <-chan struct{}) { for { c := make(chan os.Signal) var sigs []os.Signal + s.rwLock.RLock() for sig := range s.GetSigMap() { sigs = append(sigs, sig) } + s.rwLock.RUnlock() signal.Notify(c, sigs...) select { case sig := <-c: @@ -35,22 +38,28 @@ func DoSignal(done <-chan struct{}) { type SignalHandler func(s os.Signal, arg interface{}) type SignalSet struct { - m map[os.Signal]SignalHandler + rwLock *sync.RWMutex + m map[os.Signal]SignalHandler } func GetSignalSet() *SignalSet { if signalSet == nil { signalSet = new(SignalSet) signalSet.m = make(map[os.Signal]SignalHandler) + signalSet.rwLock = new(sync.RWMutex) } return signalSet } func (set *SignalSet) Register(s os.Signal, handler SignalHandler) { + set.rwLock.Lock() set.m[s] = handler + set.rwLock.Unlock() } func (set *SignalSet) Handle(sig os.Signal, arg interface{}) (err error) { + set.rwLock.RLock() + defer set.rwLock.RUnlock() if _, found := set.m[sig]; found { set.m[sig](sig, arg) return nil diff --git a/common/utils.go b/common/utils.go index 751a7f6..05f4a7a 100644 --- a/common/utils.go +++ b/common/utils.go @@ -304,3 +304,17 @@ func ReadTail(path string, tail int) (string, error) { } return strings.Join(ret[cur:], "\n"), nil } + +func SafeWrite(writer io.Writer, b []byte) error { + n := len(b) + tmp := 0 + for n > 0 { + count, err := writer.Write(b[tmp:]) + if err != nil { + return err + } + tmp += count + n -= count + } + return nil +} diff --git a/console/cli.go b/console/cli.go index 2704a88..a1cc1f1 100644 --- a/console/cli.go +++ b/console/cli.go @@ -306,7 +306,6 @@ func (c *CongoCli) waitInput(args interface{}) { terminal.Restore(int(os.Stdin.Fd()), client.origState) if exit == true { if err == nil { - fmt.Printf("Disconnected\n") os.Exit(0) } else { fmt.Fprintf(os.Stderr, err.Error()) @@ -345,7 +344,14 @@ func (c *CongoCli) waitInput(args interface{}) { exit = true return } - exit, _ = client.checkEscape(b, n, "") + err = client.processClientSession(nil, b, n, "") + if err != nil { + fmt.Printf("\r\nError : %s\r\n", err.Error()) + return + } + if client.retry == false { + exit = true + } } } @@ -361,6 +367,7 @@ func (c *CongoCli) console(cmd *cobra.Command, args []string) { } retry := true common.NewTaskManager(100, 16) + clientEscape = NewEscapeClientSystem() for retry { client, conn, err := initConsoleSessionClient(args[0], clientConfig.ServerHost, clientConfig.ConsolePort) if err != nil { diff --git a/console/client.go b/console/client.go index 8a0afe3..5c5b809 100644 --- a/console/client.go +++ b/console/client.go @@ -18,8 +18,8 @@ import ( type ConsoleClient struct { host, port string origState *terminal.State - escape int // client exit signal cr int + searcher *EscapeSearcher exit chan struct{} retry bool inputTask *common.Task @@ -35,9 +35,87 @@ func NewConsoleClient(host string, port string) *ConsoleClient { retry: true, sigio: make(chan struct{}, 1), reported: false, + // clientEscape must not be nil + searcher: NewEscapeSearcher(clientEscape.root), } } +func (c *ConsoleClient) transCr(b []byte, n int) []byte { + temp := make([]byte, common.BUF_SIZE) + j := 0 + for i := 0; i < n; i++ { + ch := b[i] + if c.cr == 0 { + if ch == ' ' { + c.cr = 1 + } else { + temp[j] = ch + j++ + } + } else if c.cr == 1 { + if ch == '\r' { + c.cr = 2 + } else { + temp[j], temp[j+1] = ' ', ch + j += 2 + c.cr = 0 + } + } else if c.cr == 2 { + if ch == '\n' { + temp[j], temp[j+1], temp[j+2] = ' ', '\r', ch + j += 3 + } else { + temp[j] = ch // ignore " \r" + j++ + } + c.cr = 0 + } + } + if c.cr == 1 { + c.cr = 0 + temp[j] = ' ' + j++ + } + return temp[0:j] +} + +func (client *ConsoleClient) processClientSession(conn net.Conn, b []byte, n int, node string) error { + j := 0 + for i := 0; i < n; i++ { + ch := b[i] + // NOTE(chenglch): To avoid of the copy of the buffer, control the send buffer with index + buffered, handler, err := clientEscape.Search(conn, ch, client.searcher) + if err != nil { + return err + } + // if the character is buffered, send the buf before current character + if buffered { + if j < i && conn != nil { + err = common.Network.SendByteWithLength(conn.(net.Conn), b[j:i]) + if err != nil { + return err + } + } + j = i + 1 + continue + } + if handler != nil { + err = handler(conn, client, node, ch) + if err != nil { + return err + } + j = i + 1 + } + } + if conn != nil { + err := common.Network.SendByteWithLength(conn.(net.Conn), b[j:n]) + if err != nil { + return err + } + } + return nil +} + func (c *ConsoleClient) input(args ...interface{}) { b := args[0].([]interface{})[2].([]byte) node := args[0].([]interface{})[1].(string) @@ -77,17 +155,7 @@ func (c *ConsoleClient) input(args ...interface{}) { if n == 0 { return } - exit, pos := c.checkEscape(b, n, node) - if exit == true { - b = EXIT_SEQUENCE[0:] - n = len(b) - c.retry = false - printConsoleDisconnectPrompt() - } - if pos >= n { - return - } - common.Network.SendByteWithLength(conn.(net.Conn), b[pos:n]) + c.processClientSession(conn.(net.Conn), b, n, node) } func (c *ConsoleClient) output(args ...interface{}) { @@ -138,110 +206,6 @@ func (c *ConsoleClient) contains(cmds []byte, cmd byte) bool { return false } -func (c *ConsoleClient) runClientCmd(cmd byte, node string) { - if cmd == CLIENT_CMD_HELP { - printConsoleHelpMsg() - } else if cmd == CLIENT_CMD_REPLAY { - congo := NewCongoClient(clientConfig.HTTPUrl) - ret, err := congo.replay(node) - if err != nil { - if ret != "" { - printConsoleCmdErr(ret) - } else { - printConsoleCmdErr(err) - } - return - } - printConsoleReplay(ret) - } else if cmd == CLIENT_CMD_WHO { - congo := NewCongoClient(clientConfig.HTTPUrl) - ret, err := congo.listUser(node) - if err != nil { - if ret != nil { - printConsoleCmdErr(ret) - } else { - printConsoleCmdErr(err) - } - return - } - printCRLF() - for _, v := range ret["users"].([]interface{}) { - printConsoleUser(v.(string)) - } - } -} - -func (c *ConsoleClient) checkEscape(b []byte, n int, node string) (bool, int) { - pos := 0 - for i := 0; i < n; i++ { - ch := b[i] - if c.escape == 0 { - if ch == '\x05' { - c.escape = 1 - pos = i + 1 - } - } else if c.escape == 1 { - if ch == 'c' { - c.escape = 2 - pos = i + 1 - } else { - c.escape = 0 - } - } else if c.escape == 2 { - if ch == CLIENT_CMD_EXIT { - c.close() - return true, 0 - } else if c.contains(CLIENT_CMDS, ch) { - c.runClientCmd(ch, node) - c.escape = 0 - pos = i + 1 - } else { - c.escape = 0 - } - } - } - return false, pos -} - -func (c *ConsoleClient) transCr(b []byte, n int) []byte { - temp := make([]byte, common.BUF_SIZE) - j := 0 - for i := 0; i < n; i++ { - ch := b[i] - if c.cr == 0 { - if ch == ' ' { - c.cr = 1 - } else { - temp[j] = ch - j++ - } - } else if c.cr == 1 { - if ch == '\r' { - c.cr = 2 - } else { - temp[j], temp[j+1] = ' ', ch - j += 2 - c.cr = 0 - } - } else if c.cr == 2 { - if ch == '\n' { - temp[j], temp[j+1], temp[j+2] = ' ', '\r', ch - j += 3 - } else { - temp[j] = ch // ignore " \r" - j++ - } - c.cr = 0 - } - } - if c.cr == 1 { - c.cr = 0 - temp[j] = ' ' - j++ - } - return temp[0:j] -} - func (c *ConsoleClient) transport(conn net.Conn, node string) error { defer conn.Close() var err error @@ -443,3 +407,16 @@ func (c *CongoClient) listUser(node string) (map[string]interface{}, error) { } return ret.(map[string]interface{}), nil } + +func (c *CongoClient) listBreakSequence() ([]string, error) { + url := fmt.Sprintf("%s/breaksequence", c.baseUrl) + ret, err := c.client.Get(url, nil, nil, false) + if err != nil { + return nil, err + } + items := make([]string, 0) + for _, item := range ret.([]interface{}) { + items = append(items, item.(string)) + } + return items, nil +} diff --git a/console/console.go b/console/console.go index d0e52bd..f0858e7 100644 --- a/console/console.go +++ b/console/console.go @@ -12,18 +12,6 @@ import ( "time" ) -const ( - CLIENT_CMD_EXIT = '.' - CLIENT_CMD_HELP = '?' - CLIENT_CMD_REPLAY = 'r' - CLIENT_CMD_WHO = 'w' -) - -var ( - CLIENT_CMDS = []byte{CLIENT_CMD_HELP, CLIENT_CMD_REPLAY, CLIENT_CMD_WHO} - EXIT_SEQUENCE = [...]byte{'\x05', 'c', '.'} // ctrl-e, c -) - type Console struct { bufConn map[net.Conn]chan []byte // build the map for the connection and the channel remoteIn io.Writer @@ -33,6 +21,7 @@ type Console struct { node *Node mutex *sync.RWMutex last *pl.RemainBuffer // the rest of the buffer that has not been emitted + searcher *EscapeSearcher } func NewConsole(baseSession *plugins.BaseSession, node *Node) *Console { @@ -44,6 +33,8 @@ func NewConsole(baseSession *plugins.BaseSession, node *Node) *Console { running: make(chan bool, 0), mutex: new(sync.RWMutex), last: new(pl.RemainBuffer), + // serverEscape must not be nil + searcher: NewEscapeSearcher(serverEscape.root), } } @@ -74,6 +65,38 @@ func (self *Console) Disconnect(conn net.Conn) { } } +func (self *Console) processTargetSession(writer io.Writer, b []byte) error { + var err error + j := 0 + n := len(b) + for i := 0; i < n; i++ { + ch := b[i] + buffered, handler, err := serverEscape.Search(writer, ch, self.searcher) + if err != nil { + return err + } + if buffered { + if j < i { + err = common.SafeWrite(writer, b[j:i]) + return err + } + j = i + 1 + continue + } + if handler != nil { + if err = handler(writer, ch); err != nil { + return err + } + j = i + 1 + } + } + err = common.SafeWrite(writer, b[j:n]) + if err != nil { + return err + } + return nil +} + func (self *Console) writeTarget(conn net.Conn) { plog.DebugNode(self.node.StorageNode.Name, "Create new connection to read message from client.") defer func() { @@ -98,19 +121,15 @@ func (self *Console) writeTarget(conn net.Conn) { plog.WarningNode(self.node.StorageNode.Name, fmt.Sprintf("Failed to receive message from client. Error:%s.", err.Error())) return } + if bytes.Equal(b, EXIT_SEQUENCE[0:]) { plog.InfoNode(self.node.StorageNode.Name, "Received exit signal from client") return } - tmp := 0 - for n > 0 { - count, err := self.remoteIn.Write(b[tmp:]) - if err != nil { - plog.WarningNode(self.node.StorageNode.Name, fmt.Sprintf("Failed to send message to the remote server. Error:%s.", err.Error())) - return - } - tmp += count - n -= count + err = self.processTargetSession(self.remoteIn, b) + if err != nil { + plog.WarningNode(self.node.StorageNode.Name, fmt.Sprintf("Failed to send message to the remote server. Error:%s.", err.Error())) + return } } } diff --git a/console/escape.go b/console/escape.go new file mode 100644 index 0000000..d0c909d --- /dev/null +++ b/console/escape.go @@ -0,0 +1,342 @@ +package console + +import ( + "fmt" + "github.com/xcat2/goconserver/common" + "io" + "net" + "sync" + "time" +) + +const ( + ESCAPE_CTRL_E = '\x05' + ESCAPE_C = 'c' + CLIENT_CMD_EXIT = '.' + CLIENT_CMD_HELP = '?' + CLIENT_CMD_REPLAY = 'r' + CLIENT_CMD_WHO = 'w' + CLIENT_CMD_LOCAL = 'l' + + SEARCH_BUF_SIZE = 8 +) + +var ( + EXIT_SEQUENCE = [...]byte{ESCAPE_CTRL_E, ESCAPE_C, '.'} // ctrl-e, c + clientEscape *EscapeClientSystem + serverEscape *EscapeServerSystem +) + +type EscapeClientHandler func(net.Conn, interface{}, string, byte) error +type EscapeServerHandler func(io.Writer, byte) error + +func serverBreakSequenceHandler(writer io.Writer, last byte) error { + var err error + // slice start from 0, but the escape key start from 1 + sequence := serverEscape.sequences[last-'0'-1] + for _, ch := range sequence.seqs { + err = common.SafeWrite(writer, []byte{byte(ch)}) + if err != nil { + plog.Error(err) + return err + } + time.Sleep(time.Duration(sequence.delay) * time.Microsecond) + } + return nil +} + +func clientBreakSequenceHandler(conn net.Conn, c interface{}, node string, last byte) error { + printBreakSequence(last) + if conn == nil { + return nil + } + if last == '?' { + congo := NewCongoClient(clientConfig.HTTPUrl) + ret, err := congo.listBreakSequence() + if err != nil { + printFatalErr(err) + return err + } + for _, s := range ret { + fmt.Printf("%s\r\n", s) + } + return nil + } + err := common.Network.SendByteWithLength(conn.(net.Conn), []byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_LOCAL, last}) + if err != nil { + return err + } + return nil +} + +func clientEscapeExitHandler(conn net.Conn, c interface{}, node string, last byte) error { + var err error + client := c.(*ConsoleClient) + client.close() + if conn != nil { + err = common.Network.SendByteWithLength(conn.(net.Conn), EXIT_SEQUENCE[0:]) + if err != nil { + return err + } + } + client.retry = false + printConsoleDisconnectPrompt() + return nil +} + +func clientEscapeHelpHandler(conn net.Conn, c interface{}, node string, last byte) error { + printConsoleHelpMsg() + return nil +} + +func clientEscapeReplayHandler(conn net.Conn, c interface{}, node string, last byte) error { + congo := NewCongoClient(clientConfig.HTTPUrl) + ret, err := congo.replay(node) + if err != nil { + if ret != "" { + printConsoleCmdErr(ret) + } else { + printConsoleCmdErr(err) + } + return err + } + printConsoleReplay(ret) + return nil +} + +func clientEscapeWhoHandler(conn net.Conn, c interface{}, node string, last byte) error { + congo := NewCongoClient(clientConfig.HTTPUrl) + ret, err := congo.listUser(node) + if err != nil { + if ret != nil { + printConsoleCmdErr(ret) + } else { + printConsoleCmdErr(err) + } + return err + } + printCRLF() + for _, v := range ret["users"].([]interface{}) { + printConsoleUser(v.(string)) + } + return nil +} + +type EscapeSearcher struct { + node *EscapeNode + len int + buf [SEARCH_BUF_SIZE]byte +} + +func NewEscapeSearcher(root *EscapeNode) *EscapeSearcher { + return &EscapeSearcher{node: root, len: 0} +} + +type EscapeNode struct { + next map[byte]*EscapeNode + refCount int + handler interface{} +} + +func NewEscapeNode() *EscapeNode { + return &EscapeNode{refCount: 1, handler: nil, next: make(map[byte]*EscapeNode)} +} + +type BreakSequence struct { + seqs string + delay int // ms +} + +func NewBreakSequence(seqs string, delay int) *BreakSequence { + return &BreakSequence{ + seqs: seqs, + delay: delay, + } +} + +type EscapeSystem struct { + rwLock *sync.RWMutex + root *EscapeNode +} + +func (self *EscapeSystem) Register(s []byte, handler interface{}) { + if len(s) == 0 { + return + } + var ok bool + var node *EscapeNode + self.root.refCount++ + entry := self.root + self.rwLock.Lock() + defer self.rwLock.Unlock() + for _, ch := range s { + if node, ok = entry.next[ch]; !ok { + node = NewEscapeNode() + entry.next[ch] = node + } else { + node.refCount++ + } + entry = node + } + node.handler = handler +} + +func (self *EscapeSystem) exist(s []byte) bool { + if len(s) == 0 { + return false + } + var ok bool + entry := self.root + self.rwLock.RLock() + defer self.rwLock.RUnlock() + for _, ch := range s { + if entry, ok = entry.next[ch]; !ok { + return false + } + } + return true +} + +func (self *EscapeSystem) Unregister(s []byte) error { + if !self.exist(s) { + return common.ErrNotExist + } + self.root.refCount-- + var ok bool + var node *EscapeNode + self.rwLock.Lock() + defer self.rwLock.Unlock() + entry := self.root + for _, ch := range s { + if node, ok = entry.next[ch]; !ok { + return common.ErrNotExist + } + entry.refCount-- + delete(entry.next, ch) + entry = node + } + return nil +} + +type EscapeClientSystem struct { + *EscapeSystem +} + +func NewEscapeClientSystem() *EscapeClientSystem { + escapeSystem := &EscapeSystem{ + root: NewEscapeNode(), + rwLock: new(sync.RWMutex), + } + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_EXIT}, clientEscapeExitHandler) + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_HELP}, clientEscapeHelpHandler) + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_REPLAY}, clientEscapeReplayHandler) + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_WHO}, clientEscapeWhoHandler) + for i := 1; i <= 9; i++ { + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_LOCAL, byte(i) + '0'}, clientBreakSequenceHandler) + } + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_LOCAL, '?'}, clientBreakSequenceHandler) + return &EscapeClientSystem{escapeSystem} +} + +// Search and cache the charactor +// return buffered, if return true, this character has been buffered in searcher +func (self *EscapeClientSystem) Search(conn net.Conn, b byte, searcher *EscapeSearcher) (bool, EscapeClientHandler, error) { + var ok bool + self.rwLock.RLock() + defer self.rwLock.RUnlock() + if searcher.node, ok = searcher.node.next[b]; !ok { + searcher.node = self.root + if conn != nil && searcher.len != 0 { + // if the character is not found, send the buffer to the remote and clear the buffer + err := common.Network.SendByteWithLength(conn, searcher.buf[:searcher.len]) + if err != nil { + // do not has buffer to send + return false, nil, err + } + } + searcher.len = 0 + return false, nil, nil + } + if searcher.len == SEARCH_BUF_SIZE { + return false, nil, common.ErrOutOfQuota + } + searcher.buf[searcher.len] = b + searcher.len++ + if searcher.node.handler != nil { + searcher.len = 0 + // match the pattern, return handler, no buffer to send + return false, searcher.node.handler.(func(net.Conn, interface{}, string, byte) error), nil + } + // this character has been add to the search buffer + return true, nil, nil +} + +type EscapeServerSystem struct { + *EscapeSystem + sequences []*BreakSequence +} + +func NewEscapeServerSystem() *EscapeServerSystem { + escapeSystem := &EscapeSystem{ + root: NewEscapeNode(), + rwLock: new(sync.RWMutex), + } + breakSequences := make([]*BreakSequence, 9) + i := 1 + for _, sequence := range serverConfig.Console.BreakSequences { + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_LOCAL, byte(i) + '0'}, serverBreakSequenceHandler) + breakSequences[i-1] = NewBreakSequence(sequence.Sequence, sequence.Delay) + i++ + } + for i <= 9 { + escapeSystem.Register([]byte{ESCAPE_CTRL_E, ESCAPE_C, CLIENT_CMD_LOCAL, byte(i) + '0'}, serverBreakSequenceHandler) + breakSequences[i-1] = NewBreakSequence("~B", 250) + i++ + } + return &EscapeServerSystem{escapeSystem, breakSequences} +} + +func (self *EscapeServerSystem) GetSequences() []string { + ret := make([]string, len(self.sequences)) + for i := 0; i < len(self.sequences); i++ { + ret[i] = fmt.Sprintf("key: %d string: %s, delay: %d", i+1, self.sequences[i].seqs, self.sequences[i].delay) + } + return ret +} + +// return buffered, if return true, this character has been buffered in searcher +func (self *EscapeServerSystem) Search(writer io.Writer, b byte, searcher *EscapeSearcher) (bool, EscapeServerHandler, error) { + var ok bool + var err error + self.rwLock.RLock() + defer self.rwLock.RUnlock() + if searcher.node, ok = searcher.node.next[b]; !ok { + searcher.node = self.root + // if the character is not found, send the buffer to the remote and clear the buffer + if searcher.len > 0 { + err = common.SafeWrite(writer, searcher.buf[:searcher.len]) + if err != nil { + plog.Error(err) + return false, nil, err + } + } + searcher.len = 0 + return false, nil, nil + } + if searcher.len == SEARCH_BUF_SIZE { + return false, nil, common.ErrOutOfQuota + } + searcher.buf[searcher.len] = b + searcher.len++ + if searcher.node.handler != nil { + searcher.len = 0 + // match the pattern, return handler, no buffer to send + return false, searcher.node.handler.(func(io.Writer, byte) error), nil + } + // this character has been add to the search buffer + return true, nil, nil +} + +func GetServerEscape() *EscapeServerSystem { + return serverEscape +} diff --git a/console/message.go b/console/message.go index cd5be93..0e95a54 100644 --- a/console/message.go +++ b/console/message.go @@ -32,10 +32,16 @@ func printConsoleSendErr(err error) { func printConsoleHelpMsg() { fmt.Printf("\r\nHelp message from congo:\r\n" + - "Ctrl + e + c + . Exit from console session \r\n" + - "Ctrl + e + c + ? Print the help message for console command \r\n" + - "Ctrl + e + c + r Replay last lines (only for file_logger) \r\n" + - "Ctrl + e + c + w Who is on this session \r\n") + "Ctrl + e + c + . Exit from console session\r\n" + + "Ctrl + e + c + ? Print the help message for console command\r\n" + + "Ctrl + e + c + r Replay last lines (only for file_logger)\r\n" + + "Ctrl + e + c + w Who is on this session\r\n" + + "Ctrl + e + c + l + [1-9]/? Send break sequence to the remote\r\n") +} + +func printBreakSequence(last byte) { + s := string([]byte{'^', 'E', ESCAPE_C, CLIENT_CMD_LOCAL, last}) + fmt.Printf("\r\nBreak sequence %s pressed\r\n", s) } func printConsoleCmdErr(msg interface{}) { diff --git a/console/server.go b/console/server.go index 39b5c05..e2a079c 100644 --- a/console/server.go +++ b/console/server.go @@ -425,6 +425,7 @@ func GetNodeManager() *NodeManager { if err != nil { panic(err) } + serverEscape = NewEscapeServerSystem() // for linelogger to send the last buffer go nodeManager.PeriodicTask() runtime.GOMAXPROCS(serverConfig.Global.Worker) diff --git a/etc/goconserver/server.conf b/etc/goconserver/server.conf index a37ea0a..33a5385 100644 --- a/etc/goconserver/server.conf +++ b/etc/goconserver/server.conf @@ -83,6 +83,15 @@ console: # retry interval in second if console could not be connected. reconnect_interval: 10 + # define break sequences + break_sequence: + # ipmi break sequence, press Ctrl + e + c + l + 1 to activate + - sequence: ~B + delay: 250 + # press Ctrl + e + c + l + 2 to activate + - sequence: "+\d+\d+" + delay: 300 + # below is experimental option for etcd storage etcd: dail_timeout: 5 diff --git a/goconserver.go b/goconserver.go index fd0e4e1..450a543 100644 --- a/goconserver.go +++ b/goconserver.go @@ -71,6 +71,7 @@ func main() { api.Router = mux.NewRouter().StrictSlash(true) api.NewNodeApi(api.Router) api.NewCommandApi(api.Router) + api.NewEscapeApi(api.Router) if serverConfig.API.DistDir != "" { api.RegisterBackendHandler(api.Router) }