HFish/core/protocol/ftp/graval/ftpserver.go
2019-08-11 20:14:28 +08:00

145 lines
4.0 KiB
Go

// An experimental FTP server framework. By providing a simple driver class that
// responds to a handful of methods you can have a complete FTP server.
//
// Some sample use cases include persisting data to an Amazon S3 bucket, a
// relational database, redis or memory.
//
// There is a sample in-memory driver available - see the documentation for the
// graval-mem package or the graval READEME for more details.
package graval
import (
"net"
"strconv"
"strings"
)
// serverOpts contains parameters for graval.NewFTPServer()
type FTPServerOpts struct {
// The factory that will be used to create a new FTPDriver instance for
// each client connection. This is a mandatory option.
Factory FTPDriverFactory
// The hostname that the FTP server should listen on. Optional, defaults to
// "::", which means all hostnames on ipv4 and ipv6.
Hostname string
// The port that the FTP should listen on. Optional, defaults to 3000. In
// a production environment you will probably want to change this to 21.
Port int
}
// FTPServer is the root of your FTP application. You should instantiate one
// of these and call ListenAndServe() to start accepting client connections.
//
// Always use the NewFTPServer() method to create a new FTPServer.
type FTPServer struct {
name string
listenTo string
driverFactory FTPDriverFactory
logger *ftpLogger
}
// serverOptsWithDefaults copies an FTPServerOpts struct into a new struct,
// then adds any default values that are missing and returns the new data.
func serverOptsWithDefaults(opts *FTPServerOpts) *FTPServerOpts {
var newOpts FTPServerOpts
if opts == nil {
opts = &FTPServerOpts{}
}
if opts.Hostname == "" {
newOpts.Hostname = "::"
} else {
newOpts.Hostname = opts.Hostname
}
if opts.Port == 0 {
newOpts.Port = 3000
} else {
newOpts.Port = opts.Port
}
newOpts.Factory = opts.Factory
return &newOpts
}
// NewFTPServer initialises a new FTP server. Configuration options are provided
// via an instance of FTPServerOpts. Calling this function in your code will
// probably look something like this:
//
// factory := &MyDriverFactory{}
// server := graval.NewFTPServer(&graval.FTPServerOpts{ Factory: factory })
//
// or:
//
// factory := &MyDriverFactory{}
// opts := &graval.FTPServerOpts{
// Factory: factory,
// Port: 2000,
// Hostname: "127.0.0.1",
// }
// server := graval.NewFTPServer(opts)
//
func NewFTPServer(opts *FTPServerOpts) *FTPServer {
opts = serverOptsWithDefaults(opts)
s := new(FTPServer)
s.listenTo = buildTcpString(opts.Hostname, opts.Port)
s.name = "Go FTP Server"
s.driverFactory = opts.Factory
s.logger = newFtpLogger("")
return s
}
// ListenAndServe asks a new FTPServer to begin accepting client connections. It
// accepts no arguments - all configuration is provided via the NewFTPServer
// function.
//
// If the server fails to start for any reason, an error will be returned. Common
// errors are trying to bind to a privileged port or something else is already
// listening on the same port.
//
func (ftpServer *FTPServer) ListenAndServe() error {
laddr, err := net.ResolveTCPAddr("tcp", ftpServer.listenTo)
if err != nil {
return err
}
listener, err := net.ListenTCP("tcp", laddr)
if err != nil {
return err
}
for {
tcpConn, err := listener.AcceptTCP()
if err != nil {
ftpServer.logger.Print("listening error")
break
}
driver, err := ftpServer.driverFactory.NewDriver()
if err != nil {
ftpServer.logger.Print("Error creating driver, aborting client connection")
} else {
ftpConn := newftpConn(tcpConn, driver)
go ftpConn.Serve()
}
}
return nil
}
func buildTcpString(hostname string, port int) (result string) {
if strings.Contains(hostname, ":") {
// ipv6
if port == 0 {
result = "["+hostname+"]"
} else {
result = "["+hostname +"]:" + strconv.Itoa(port)
}
} else {
// ipv4
if port == 0 {
result = hostname
} else {
result = hostname +":" + strconv.Itoa(port)
}
}
return
}