diff --git a/Makefile b/Makefile index 90930f3b2..87795751f 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ build: ${GO} build -o bin/go-mysqldump cmd/go-mysqldump/main.go ${GO} build -o bin/go-canal cmd/go-canal/main.go ${GO} build -o bin/go-binlogparser cmd/go-binlogparser/main.go + ${GO} build -o bin/go-mysqlserver cmd/go-mysqlserver/main.go test: ${GO} test --race -timeout 2m ./... diff --git a/README.md b/README.md index 8d9dc1777..7790c88b2 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,6 @@ func main() { } } } - ``` Another shell diff --git a/cmd/go-mysqlserver/main.go b/cmd/go-mysqlserver/main.go new file mode 100644 index 000000000..bf663976f --- /dev/null +++ b/cmd/go-mysqlserver/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + "net" + + "github.com/go-mysql-org/go-mysql/server" +) + +func main() { + // Listen for connections on localhost port 4000 + l, err := net.Listen("tcp", "127.0.0.1:4000") + if err != nil { + log.Fatal(err) + } + + log.Println("Listening on port 4000, connect with 'mysql -h 127.0.0.1 -P 4000 -u root'") + + // Accept a new connection once + c, err := l.Accept() + if err != nil { + log.Fatal(err) + } + + log.Println("Accepted connection") + + // Create a connection with user root and an empty password. + // You can use your own handler to handle command here. + conn, err := server.NewConn(c, "root", "", server.EmptyHandler{}) + if err != nil { + log.Fatal(err) + } + + log.Println("Registered the connection with the server") + + // as long as the client keeps sending commands, keep handling them + for { + if err := conn.HandleCommand(); err != nil { + log.Fatal(err) + } + } +} diff --git a/server/command.go b/server/command.go index 78a0ea03d..3c73697ae 100644 --- a/server/command.go +++ b/server/command.go @@ -3,12 +3,15 @@ package server import ( "bytes" "fmt" + "log" + "github.com/go-mysql-org/go-mysql/mysql" . "github.com/go-mysql-org/go-mysql/mysql" "github.com/go-mysql-org/go-mysql/replication" "github.com/siddontang/go/hack" ) +// Handler is what a server needs to implement the client-server protocol type Handler interface { //handle COM_INIT_DB command, you can check whether the dbName is valid, or other. UseDB(dbName string) error @@ -31,6 +34,7 @@ type Handler interface { HandleOtherCommand(cmd byte, data []byte) error } +// ReplicationHandler is for handlers that want to implement the replication protocol type ReplicationHandler interface { // handle Replication command HandleRegisterSlave(data []byte) error @@ -38,6 +42,8 @@ type ReplicationHandler interface { HandleBinlogDumpGTID(gtidSet *MysqlGTIDSet) (*replication.BinlogStreamer, error) } +// HandleCommand is handling commands received by the server +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_command_phase.html func (c *Conn) HandleCommand() error { if c.Conn == nil { return fmt.Errorf("connection closed") @@ -178,47 +184,101 @@ func (c *Conn) dispatch(data []byte) interface{} { } } +// EmptyHandler is a mostly empty implementation for demonstration purposes type EmptyHandler struct { } +// EmptyReplicationHandler is a empty handler that implements the replication protocol type EmptyReplicationHandler struct { EmptyHandler } +// UseDB is called for COM_INIT_DB func (h EmptyHandler) UseDB(dbName string) error { + log.Printf("Received: UseDB %s", dbName) return nil } + +// HandleQuery is called for COM_QUERY func (h EmptyHandler) HandleQuery(query string) (*Result, error) { + log.Printf("Received: Query: %s", query) + + // These two queries are implemented for minimal support for MySQL Shell + if query == `SET NAMES 'utf8mb4';` { + return nil, nil + } + if query == `select concat(@@version, ' ', @@version_comment)` { + r, err := mysql.BuildSimpleResultset([]string{"concat(@@version, ' ', @@version_comment)"}, [][]interface{}{ + {"8.0.11"}, + }, false) + if err != nil { + return nil, err + } + return &mysql.Result{ + Status: 0, + Warnings: 0, + InsertId: 0, + AffectedRows: 0, + Resultset: r, + }, nil + } + return nil, fmt.Errorf("not supported now") } +// HandleFieldList is called for COM_FIELD_LIST packets +// Note that COM_FIELD_LIST has been deprecated since MySQL 5.7.11 +// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_field_list.html func (h EmptyHandler) HandleFieldList(table string, fieldWildcard string) ([]*Field, error) { + log.Printf("Received: FieldList: table=%s, fieldWildcard:%s", table, fieldWildcard) return nil, fmt.Errorf("not supported now") } + +// HandleStmtPrepare is called for COM_STMT_PREPARE func (h EmptyHandler) HandleStmtPrepare(query string) (int, int, interface{}, error) { + log.Printf("Received: StmtPrepare: %s", query) return 0, 0, nil, fmt.Errorf("not supported now") } + +// 'context' isn't used but replacing it with `_` would remove important information for who +// wants to extend this later. +//revive:disable:unused-parameter + +// HandleStmtExecute is called for COM_STMT_EXECUTE func (h EmptyHandler) HandleStmtExecute(context interface{}, query string, args []interface{}) (*Result, error) { + log.Printf("Received: StmtExecute: %s (args: %v)", query, args) return nil, fmt.Errorf("not supported now") } +// HandleStmtClose is called for COM_STMT_CLOSE func (h EmptyHandler) HandleStmtClose(context interface{}) error { + log.Println("Received: StmtClose") return nil } +//revive:enable:unused-parameter + +// HandleRegisterSlave is called for COM_REGISTER_SLAVE func (h EmptyReplicationHandler) HandleRegisterSlave(data []byte) error { + log.Printf("Received: RegisterSlave: %x", data) return fmt.Errorf("not supported now") } +// HandleBinlogDump is called for COM_BINLOG_DUMP (non-GTID) func (h EmptyReplicationHandler) HandleBinlogDump(pos Position) (*replication.BinlogStreamer, error) { + log.Printf("Received: BinlogDump: pos=%s", pos.String()) return nil, fmt.Errorf("not supported now") } +// HandleBinlogDumpGTID is called for COM_BINLOG_DUMP_GTID func (h EmptyReplicationHandler) HandleBinlogDumpGTID(gtidSet *MysqlGTIDSet) (*replication.BinlogStreamer, error) { + log.Printf("Received: BinlogDumpGTID: gtidSet=%s", gtidSet.String()) return nil, fmt.Errorf("not supported now") } +// HandleOtherCommand is called for commands not handled elsewhere func (h EmptyHandler) HandleOtherCommand(cmd byte, data []byte) error { + log.Printf("Received: OtherCommand: cmd=%x, data=%x", cmd, data) return NewError( ER_UNKNOWN_ERROR, fmt.Sprintf("command %d is not supported now", cmd), diff --git a/server/example/server_example.go b/server/example/server_example.go deleted file mode 100644 index 2af545566..000000000 --- a/server/example/server_example.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "net" - - "github.com/go-mysql-org/go-mysql/mysql" - "github.com/go-mysql-org/go-mysql/server" - "github.com/go-mysql-org/go-mysql/test_util/test_keys" - "github.com/siddontang/go-log/log" - - "crypto/tls" - "time" -) - -type RemoteThrottleProvider struct { - *server.InMemoryProvider - delay int // in milliseconds -} - -func (m *RemoteThrottleProvider) GetCredential(username string) (password string, found bool, err error) { - time.Sleep(time.Millisecond * time.Duration(m.delay)) - return m.InMemoryProvider.GetCredential(username) -} - -func main() { - l, _ := net.Listen("tcp", "127.0.0.1:3306") - // user either the in-memory credential provider or the remote credential provider (you can implement your own) - //inMemProvider := server.NewInMemoryProvider() - //inMemProvider.AddUser("root", "123") - remoteProvider := &RemoteThrottleProvider{server.NewInMemoryProvider(), 10 + 50} - remoteProvider.AddUser("root", "123") - var tlsConf = server.NewServerTLSConfig(test_keys.CaPem, test_keys.CertPem, test_keys.KeyPem, tls.VerifyClientCertIfGiven) - for { - c, _ := l.Accept() - go func() { - // Create a connection with user root and an empty password. - // You can use your own handler to handle command here. - svr := server.NewServer("8.0.12", mysql.DEFAULT_COLLATION_ID, mysql.AUTH_CACHING_SHA2_PASSWORD, test_keys.PubPem, tlsConf) - conn, err := server.NewCustomizedConn(c, svr, remoteProvider, server.EmptyHandler{}) - - if err != nil { - log.Errorf("Connection error: %v", err) - return - } - - for { - err = conn.HandleCommand() - if err != nil { - log.Errorf(`Could not handle command: %v`, err) - return - } - } - }() - } -} diff --git a/server/server_conf.go b/server/server_conf.go index 0328bbe95..bff1967c9 100644 --- a/server/server_conf.go +++ b/server/server_conf.go @@ -48,7 +48,7 @@ func NewDefaultServer() *Server { certPem, keyPem := generateAndSignRSACerts(caPem, caKey) tlsConf := NewServerTLSConfig(caPem, certPem, keyPem, tls.VerifyClientCertIfGiven) return &Server{ - serverVersion: "5.7.0", + serverVersion: "8.0.11", protocolVersion: 10, capability: CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | CLIENT_PROTOCOL_41 | CLIENT_TRANSACTIONS | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | CLIENT_SSL | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA,