Refine API of webtty package

This commit is contained in:
Iwasaki Yudai 2017-08-24 14:40:28 +09:00
parent d1ec7125cf
commit 807bcc25a4
10 changed files with 82 additions and 97 deletions

View File

@ -123,10 +123,6 @@ func (lcmd *LocalCommand) ResizeTerminal(width int, height int) error {
} }
} }
func (lcmd *LocalCommand) GetTerminalSize() (int, int, error) {
return pty.Getsize(lcmd.pty)
}
func (lcmd *LocalCommand) closeTimeoutC() <-chan time.Time { func (lcmd *LocalCommand) closeTimeoutC() <-chan time.Time {
if lcmd.closeTimeout >= 0 { if lcmd.closeTimeout >= 0 {
return time.After(lcmd.closeTimeout) return time.After(lcmd.closeTimeout)

View File

@ -148,29 +148,17 @@ func (server *Server) processWSConn(ctx context.Context, conn *websocket.Conn) e
if server.options.EnableReconnect { if server.options.EnableReconnect {
opts = append(opts, webtty.WithReconnect(server.options.ReconnectTime)) opts = append(opts, webtty.WithReconnect(server.options.ReconnectTime))
} }
if server.options.Width > 0 || server.options.Height > 0 {
width, height, err := slave.GetTerminalSize()
if err != nil {
return errors.Wrapf(err, "failed to get default terminal size")
}
if server.options.Width > 0 { if server.options.Width > 0 {
width = server.options.Width opts = append(opts, webtty.WithFixedColumns(server.options.Width))
} }
if server.options.Height > 0 { if server.options.Height > 0 {
height = server.options.Height opts = append(opts, webtty.WithFixedRows(server.options.Height))
}
err = slave.ResizeTerminal(width, height)
if err != nil {
return errors.Wrapf(err, "failed to resize terminal")
}
opts = append(opts, webtty.WithFixedSize(server.options.Width, server.options.Height))
} }
if server.options.Preferences != nil { if server.options.Preferences != nil {
opts = append(opts, webtty.WithMasterPreferences(server.options.Preferences)) opts = append(opts, webtty.WithMasterPreferences(server.options.Preferences))
} }
tty, err := webtty.New(conn, slave, opts...) tty, err := webtty.New(&wsWrapper{conn}, slave, opts...)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to create webtty") return errors.Wrapf(err, "failed to create webtty")
} }

View File

@ -8,7 +8,7 @@ import (
type Slave interface { type Slave interface {
webtty.Slave webtty.Slave
GetTerminalSize() (width int, height int, err error) Close() error
} }
type Factory interface { type Factory interface {

33
server/ws_wrapper.go Normal file
View File

@ -0,0 +1,33 @@
package server
import (
"github.com/gorilla/websocket"
)
type wsWrapper struct {
*websocket.Conn
}
func (wsw *wsWrapper) Write(p []byte) (n int, err error) {
writer, err := wsw.Conn.NextWriter(websocket.TextMessage)
if err != nil {
return 0, err
}
defer writer.Close()
return writer.Write(p)
}
func (wsw *wsWrapper) Read(p []byte) (n int, err error) {
for {
msgType, reader, err := wsw.Conn.NextReader()
if err != nil {
return 0, err
}
if msgType != websocket.TextMessage {
continue
}
return reader.Read(p)
}
}

View File

@ -5,6 +5,9 @@ import (
) )
var ( var (
// ErrSlaveClosed indicates the function has exited by the slave
ErrSlaveClosed = errors.New("slave closed") ErrSlaveClosed = errors.New("slave closed")
// ErrSlaveClosed is returned when the slave connection is closed.
ErrMasterClosed = errors.New("master closed") ErrMasterClosed = errors.New("master closed")
) )

View File

@ -1,55 +1,8 @@
/*
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package webtty package webtty
// Master represents a PTY master, usually it's a websocket connection. import (
type Master interface { "io"
WriteMessage(messageType int, data []byte) error
ReadMessage() (messageType int, p []byte, err error)
}
// The message types are defined in RFC 6455, section 11.8.
const (
// TextMessage denotes a text data message. The text message payload is
// interpreted as UTF-8 encoded text data.
WSTextMessage = 1
// BinaryMessage denotes a binary data message.
WSBinaryMessage = 2
// CloseMessage denotes a close control message. The optional message
// payload contains a numeric code and text. Use the FormatCloseMessage
// function to format a close message payload.
WSCloseMessage = 8
// PingMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
WSPingMessage = 9
// PongMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
WSPongMessage = 10
) )
// Master represents a PTY master, usually it's a websocket connection.
type Master io.ReadWriter

View File

@ -1,5 +1,7 @@
package webtty package webtty
// Protocols defines the name of this protocol,
// which is supposed to be used to the subprotocol of Websockt streams.
var Protocols = []string{"webtty"} var Protocols = []string{"webtty"}
const ( const (

View File

@ -17,11 +17,18 @@ func WithPermitWrite() Option {
} }
} }
// WithFixedSize sets a fixed size to TTY master. // WithFixedColumns sets a fixed width to TTY master.
func WithFixedSize(width int, height int) Option { func WithFixedColumns(columns int) Option {
return func(wt *WebTTY) error { return func(wt *WebTTY) error {
wt.width = width wt.columns = columns
wt.height = height return nil
}
}
// WithFixedRows sets a fixed height to TTY master.
func WithFixedRows(rows int) Option {
return func(wt *WebTTY) error {
wt.rows = rows
return nil return nil
} }
} }

View File

@ -6,8 +6,12 @@ import (
// Slave represents a PTY slave, typically it's a local command. // Slave represents a PTY slave, typically it's a local command.
type Slave interface { type Slave interface {
io.ReadWriteCloser io.ReadWriter
// WindowTitleVariables returns any values that can be used to fill out
// the title of a terminal.
WindowTitleVariables() map[string]interface{} WindowTitleVariables() map[string]interface{}
// ResizeTerminal sets a new size of the terminal.
ResizeTerminal(columns int, rows int) error ResizeTerminal(columns int, rows int) error
} }

View File

@ -9,7 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// WebTTY bridges sets of a PTY slave and its PTY master. // WebTTY bridges a PTY slave and its PTY master.
// To support text-based streams and side channel commands such as // To support text-based streams and side channel commands such as
// terminal resizing, WebTTY uses an original protocol. // terminal resizing, WebTTY uses an original protocol.
type WebTTY struct { type WebTTY struct {
@ -20,9 +20,9 @@ type WebTTY struct {
windowTitle []byte windowTitle []byte
permitWrite bool permitWrite bool
width int columns int
height int rows int
reconnect int // in milliseconds reconnect int // in seconds
masterPrefs []byte masterPrefs []byte
bufferSize int bufferSize int
@ -39,8 +39,8 @@ func New(masterConn Master, slave Slave, options ...Option) (*WebTTY, error) {
slave: slave, slave: slave,
permitWrite: false, permitWrite: false,
width: 0, columns: 0,
height: 0, rows: 0,
bufferSize: 1024, bufferSize: 1024,
} }
@ -52,11 +52,12 @@ func New(masterConn Master, slave Slave, options ...Option) (*WebTTY, error) {
return wt, nil return wt, nil
} }
// Run starts the WebTTY. // Run starts the main process of the WebTTY.
// This method blocks until the context is canceled. // This method blocks until the context is canceled.
// Note that the master and slave are left intact even // Note that the master and slave are left intact even
// after the context is canceled. Closing them is caller's // after the context is canceled. Closing them is caller's
// responsibility. // responsibility.
// If the connection to one end gets closed, returns ErrSlaveClosed or ErrMasterClosed.
func (wt *WebTTY) Run(ctx context.Context) error { func (wt *WebTTY) Run(ctx context.Context) error {
err := wt.sendInitializeMessage() err := wt.sendInitializeMessage()
if err != nil { if err != nil {
@ -84,16 +85,14 @@ func (wt *WebTTY) Run(ctx context.Context) error {
go func() { go func() {
errs <- func() error { errs <- func() error {
buffer := make([]byte, wt.bufferSize)
for { for {
typ, data, err := wt.masterConn.ReadMessage() n, err := wt.masterConn.Read(buffer)
if err != nil { if err != nil {
return ErrMasterClosed return ErrMasterClosed
} }
if typ != WSTextMessage {
continue
}
err = wt.handleMasterReadEvent(data) err = wt.handleMasterReadEvent(buffer[:n])
if err != nil { if err != nil {
return err return err
} }
@ -148,7 +147,7 @@ func (wt *WebTTY) masterWrite(data []byte) error {
wt.writeMutex.Lock() wt.writeMutex.Lock()
defer wt.writeMutex.Unlock() defer wt.writeMutex.Unlock()
err := wt.masterConn.WriteMessage(WSTextMessage, data) _, err := wt.masterConn.Write(data)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to write to master") return errors.Wrapf(err, "failed to write to master")
} }
@ -183,7 +182,7 @@ func (wt *WebTTY) handleMasterReadEvent(data []byte) error {
} }
case ResizeTerminal: case ResizeTerminal:
if wt.width != 0 && wt.height != 0 { if wt.columns != 0 && wt.rows != 0 {
break break
} }
@ -196,12 +195,12 @@ func (wt *WebTTY) handleMasterReadEvent(data []byte) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "received malformed data for terminal resize") return errors.Wrapf(err, "received malformed data for terminal resize")
} }
rows := wt.height rows := wt.rows
if rows == 0 { if rows == 0 {
rows = int(args.Rows) rows = int(args.Rows)
} }
columns := wt.width columns := wt.columns
if columns == 0 { if columns == 0 {
columns = int(args.Columns) columns = int(args.Columns)
} }