Implementing the Upload Server correctly and refactoring config package a bit
This commit is contained in:
parent
96681b07b8
commit
88229fed6d
7 changed files with 129 additions and 67 deletions
|
@ -17,6 +17,11 @@ If the upload server is running, the user will be able to put files
|
||||||
on the **absoluteServePath**. After the file is uploaded successfully,
|
on the **absoluteServePath**. After the file is uploaded successfully,
|
||||||
if the timeout is not reached, the user will get back the filename.
|
if the timeout is not reached, the user will get back the filename.
|
||||||
|
|
||||||
|
To send data to the upload server, the following command can be used:
|
||||||
|
|
||||||
|
```nc ip port < gopher.png```
|
||||||
|
|
||||||
|
|
||||||
#### Sending commands via netcat
|
#### Sending commands via netcat
|
||||||
|
|
||||||
To grab a file the following command can be send:
|
To grab a file the following command can be send:
|
||||||
|
@ -48,6 +53,7 @@ Sample Configuration File:
|
||||||
},
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"directory": "upload",
|
||||||
"timeout": 5,
|
"timeout": 5,
|
||||||
"address": "localhost",
|
"address": "localhost",
|
||||||
"port": 8081
|
"port": 8081
|
||||||
|
|
|
@ -10,9 +10,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"bufio"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/zyxar/image2ascii/ascii"
|
"github.com/zyxar/image2ascii/ascii"
|
||||||
)
|
)
|
||||||
|
@ -27,34 +24,14 @@ func randSeq(n int) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadFile uploads a file to the server
|
// UploadFile uploads a file to the server
|
||||||
func UploadFile(c Client) (string, error) {
|
func UploadFile(c Client, filename string) (string, error) {
|
||||||
|
|
||||||
input := bufio.NewScanner(c.Connection())
|
|
||||||
start := time.Now()
|
|
||||||
timeout := time.Duration(viper.GetInt("upload.timeout")) * time.Second
|
|
||||||
|
|
||||||
var filename = randSeq(10)
|
|
||||||
var _, err = os.Stat(MakePathFromStringStack(c.Stack()) + filename)
|
|
||||||
|
|
||||||
// Make sure that the filename is random.
|
|
||||||
for !os.IsNotExist(err) {
|
|
||||||
filename = randSeq(10)
|
|
||||||
_, err = os.Stat(MakePathFromStringStack(c.Stack()) + filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(MakePathFromStringStack(c.Stack()) + filename)
|
f, err := os.Create(MakePathFromStringStack(c.Stack()) + filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return filename, err
|
return filename, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
for input.Scan() {
|
io.Copy(f, c.Connection())
|
||||||
if time.Since(start) >= timeout {
|
|
||||||
log.Println(c.Connection().RemoteAddr().String() + " has reached timeout!")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.Write(input.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
return filename, nil
|
return filename, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,13 @@ package config
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigPath will be used via cmd to set the configuration path for the config file.
|
|
||||||
var ConfigPath string
|
|
||||||
|
|
||||||
// loadConfigFromFile tries to load the configuration file from the disk.
|
// loadConfigFromFile tries to load the configuration file from the disk.
|
||||||
func loadConfigFromFile() error {
|
func loadConfigFromFile(configName string) error {
|
||||||
viper.SetConfigName("config")
|
viper.SetConfigName(configName)
|
||||||
viper.AddConfigPath(viper.GetString("ConfigPath"))
|
viper.AddConfigPath(viper.GetString("ConfigPath"))
|
||||||
|
|
||||||
err := viper.ReadInConfig() // Find and read the config file
|
err := viper.ReadInConfig() // Find and read the config file
|
||||||
|
@ -26,31 +21,30 @@ func loadConfigFromFile() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaultConfiguration will set the default configuration settings.
|
// setDefaultConfiguration will set the default configuration settings.
|
||||||
func setDefaultConfiguration() {
|
func setDefaultConfiguration(configPath string) {
|
||||||
viper.SetDefault("address", "localhost")
|
viper.SetDefault("address", "localhost")
|
||||||
viper.SetDefault("port", 8080)
|
viper.SetDefault("port", 8080)
|
||||||
viper.SetDefault("configPath", ConfigPath)
|
viper.SetDefault("configPath", configPath)
|
||||||
viper.SetDefault("maxDirDepth", 30)
|
viper.SetDefault("maxDirDepth", 30)
|
||||||
viper.SetDefault("absoluteServePath", "./")
|
viper.SetDefault("absoluteServePath", "./")
|
||||||
viper.SetDefault("pic.x", 0)
|
viper.SetDefault("pic.x", 0)
|
||||||
viper.SetDefault("pic.y", 0)
|
viper.SetDefault("pic.y", 0)
|
||||||
viper.SetDefault("pic.color", false)
|
viper.SetDefault("pic.color", false)
|
||||||
viper.SetDefault("upload.enabled", false)
|
viper.SetDefault("upload.enabled", false)
|
||||||
|
viper.SetDefault("upload.directory", "upload")
|
||||||
|
viper.SetDefault("upload.timeout", 3)
|
||||||
viper.SetDefault("upload.address", "localhost")
|
viper.SetDefault("upload.address", "localhost")
|
||||||
viper.SetDefault("upload.port", 8081)
|
viper.SetDefault("upload.port", 8081)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeConfiguration initializes the configuration for the application.
|
// InitializeConfiguration initializes the configuration for the application.
|
||||||
func InitializeConfiguration() {
|
func InitializeConfiguration(configName string, configPath string) {
|
||||||
flag.StringVar(&ConfigPath, "config", ".", "Set the location of the config file.")
|
setDefaultConfiguration(configPath)
|
||||||
flag.Parse()
|
loadConfigFromFile(configName)
|
||||||
|
|
||||||
setDefaultConfiguration()
|
|
||||||
loadConfigFromFile()
|
|
||||||
|
|
||||||
viper.WatchConfig()
|
viper.WatchConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigChangeCallback(cb func(event fsnotify.Event)) {
|
func ChangeCallback(cb func(event fsnotify.Event)) {
|
||||||
viper.OnConfigChange(cb)
|
viper.OnConfigChange(cb)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
func TestLoadConfigFromFile(t *testing.T) {
|
func TestLoadConfigFromFile(t *testing.T) {
|
||||||
// SetDefaultConfiguration must be called BEFORE LoadConfigFromFile.
|
// SetDefaultConfiguration must be called BEFORE LoadConfigFromFile.
|
||||||
InitializeConfiguration()
|
InitializeConfiguration("config", "./")
|
||||||
|
|
||||||
Address := viper.GetString("address")
|
Address := viper.GetString("address")
|
||||||
if Address == "" {
|
if Address == "" {
|
||||||
|
|
|
@ -7,6 +7,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/metonimie/simpleFTP/server/server/config"
|
"github.com/metonimie/simpleFTP/server/server/config"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -17,6 +21,15 @@ import (
|
||||||
// is also send to the client.
|
// is also send to the client.
|
||||||
const DataBufferSize = 1024 * 1024
|
const DataBufferSize = 1024 * 1024
|
||||||
|
|
||||||
|
// ConfigPath is used by the config package to find the config file.
|
||||||
|
var ConfigPath string
|
||||||
|
|
||||||
|
// uploadDirectory is the directory where the files will be uploaded
|
||||||
|
var uploadDirectory string
|
||||||
|
|
||||||
|
// uploadTimeout is the amount in seconds the server will wait for a file to be uploaded
|
||||||
|
var uploadTimeout time.Duration
|
||||||
|
|
||||||
// Client interface provides the blueprints for the Client that is used by the server.
|
// Client interface provides the blueprints for the Client that is used by the server.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
Connection() net.Conn // Connection returns the connection stream.
|
Connection() net.Conn // Connection returns the connection stream.
|
||||||
|
@ -25,6 +38,11 @@ type Client interface {
|
||||||
Stack() *StringStack // Returns the underlying String Stack.
|
Stack() *StringStack // Returns the underlying String Stack.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UploadResult struct {
|
||||||
|
Filename string // Filename represents the file which was randomly created by the server
|
||||||
|
Err error // Err is the error that occurred while uploading the file.
|
||||||
|
}
|
||||||
|
|
||||||
// FTPClient represents a FTPClient connection, it holds a root cage and the underlying connection.
|
// FTPClient represents a FTPClient connection, it holds a root cage and the underlying connection.
|
||||||
type FTPClient struct {
|
type FTPClient struct {
|
||||||
rootCage *StringStack // rootCage is a StringStack that is used to represent the current directory the client is in.
|
rootCage *StringStack // rootCage is a StringStack that is used to represent the current directory the client is in.
|
||||||
|
@ -89,8 +107,8 @@ func HandleConnection(client Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
config.InitializeConfiguration()
|
config.InitializeConfiguration("config", ConfigPath)
|
||||||
config.ConfigChangeCallback(func(event fsnotify.Event) {
|
config.ChangeCallback(func(event fsnotify.Event) {
|
||||||
log.Println("Configuration reloaded successfully!")
|
log.Println("Configuration reloaded successfully!")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -126,44 +144,101 @@ func StartFtpServer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartUploadServer() {
|
func HandleUpload(conn net.Conn) {
|
||||||
|
// Initialize Client
|
||||||
|
client := FTPClient{}
|
||||||
|
client.SetStack(MakeStringStack(2))
|
||||||
|
|
||||||
|
// Upload directory
|
||||||
|
client.Stack().Push(uploadDirectory)
|
||||||
|
client.SetConnection(conn)
|
||||||
|
|
||||||
|
// Create the file on the disk and make sure that the filename is random.
|
||||||
|
var filename = randSeq(10)
|
||||||
|
var _, err = os.Stat(MakePathFromStringStack(client.Stack()) + filename)
|
||||||
|
|
||||||
|
for !os.IsNotExist(err) {
|
||||||
|
filename = randSeq(10)
|
||||||
|
_, err = os.Stat(MakePathFromStringStack(client.Stack()) + filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This channel will be used to store the uploadResult
|
||||||
|
c1 := make(chan UploadResult, 1)
|
||||||
|
log.Println(conn.RemoteAddr().String() + " is uploading something.")
|
||||||
|
|
||||||
|
// Create a new Go routine for uploading
|
||||||
|
go func() {
|
||||||
|
fname, err := UploadFile(&client, filename)
|
||||||
|
c1 <- UploadResult{fname, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for either UploadResult or Timeout
|
||||||
|
select {
|
||||||
|
case result := <-c1:
|
||||||
|
{
|
||||||
|
filename, err := result.Filename, result.Err
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
io.WriteString(conn, filename)
|
||||||
|
log.Println(conn.RemoteAddr().String() + "'s upload finished.")
|
||||||
|
} else {
|
||||||
|
log.Println(fmt.Sprintf("%s: %s %s", "HandleUpload", conn.RemoteAddr().String(), err.Error()))
|
||||||
|
|
||||||
|
client.Stack().Push(filename)
|
||||||
|
os.Remove(MakePathFromStringStack(client.Stack()))
|
||||||
|
|
||||||
|
io.WriteString(conn, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * uploadTimeout):
|
||||||
|
{
|
||||||
|
io.WriteString(conn, "Timeout")
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartUploadServer starts the uploading server
|
||||||
|
func StartUploadServer() error {
|
||||||
if viper.GetBool("upload.enabled") == false {
|
if viper.GetBool("upload.enabled") == false {
|
||||||
log.Println("Uploading not enabled. To enable modify the config file and restart the server")
|
log.Println("Uploading not enabled. To enable modify the config file and restart the server")
|
||||||
return
|
return ErrUploadServerFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
Addr := viper.GetString("upload.address")
|
addr := viper.GetString("upload.address")
|
||||||
Port := viper.GetInt("upload.port")
|
port := viper.GetInt("upload.port")
|
||||||
|
uploadDirectory = viper.GetString("upload.directory")
|
||||||
|
uploadTimeout = time.Duration(viper.GetInt("upload.timeout"))
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", Addr, Port))
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Println(err)
|
||||||
|
return ErrUploadServerFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Upload server running on:", Addr, "port", Port)
|
err = os.Mkdir(uploadDirectory, 0740)
|
||||||
|
if err != nil {
|
||||||
|
if _, err := os.Stat(uploadDirectory); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Println("Can't create upload directory!")
|
||||||
|
return ErrUploadServerFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Upload server running on:", addr, "port", port)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
client := FTPClient{}
|
go HandleUpload(conn)
|
||||||
client.SetStack(MakeStringStack(1))
|
|
||||||
client.SetConnection(conn)
|
|
||||||
|
|
||||||
log.Println(conn.RemoteAddr().String() + " is uploading something.")
|
|
||||||
|
|
||||||
filename, err := UploadFile(&client)
|
|
||||||
if err == nil {
|
|
||||||
io.WriteString(conn, filename)
|
|
||||||
} else {
|
|
||||||
log.Print(conn.RemoteAddr().String())
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println(conn.RemoteAddr().String() + "'s upload finished.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,3 +52,8 @@ var (
|
||||||
ErrAlreadyAtBaseDirectory = PathError{errors.New("can't go past beyond root directory")}
|
ErrAlreadyAtBaseDirectory = PathError{errors.New("can't go past beyond root directory")}
|
||||||
ErrSlashNotAllowed = PathError{errors.New("slash is not allowed in file names")}
|
ErrSlashNotAllowed = PathError{errors.New("slash is not allowed in file names")}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// General Errors
|
||||||
|
var (
|
||||||
|
ErrUploadServerFailure = errors.New("upload server failed to start")
|
||||||
|
)
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
"github.com/metonimie/simpleFTP/server/server"
|
"github.com/metonimie/simpleFTP/server/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
flag.StringVar(&server.ConfigPath, "config", ".", "Set the location of the config file.")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
server.Init()
|
server.Init()
|
||||||
|
|
||||||
go server.StartUploadServer()
|
go server.StartUploadServer()
|
||||||
|
|
Loading…
Reference in a new issue