mirror of
https://gitee.com/lauix/HFish
synced 2025-05-12 04:48:01 +08:00
145 lines
4.0 KiB
Go
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
|
|
}
|