github.com/dnutiu/simplFT@v0.0.0-20181023061248-df4b14cdce57/server/connection.go (about) 1 package server 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "log" 8 "net" 9 10 "os" 11 12 "time" 13 14 "os/signal" 15 "syscall" 16 17 "sync" 18 19 "github.com/dnutiu/simplFT/server/config" 20 "github.com/fsnotify/fsnotify" 21 "github.com/spf13/viper" 22 ) 23 24 // ConfigPath is used by the config package to find the path of the config file. 25 var ConfigPath string 26 27 // ConfigName is used by the config package to find the config file. 28 var ConfigName string 29 30 // uploadDirectory is the directory where the files will be uploaded 31 var uploadDirectory string 32 33 // uploadTimeout is the amount in seconds the server will wait for a file to be uploaded 34 var uploadTimeout time.Duration 35 36 // uploadEnabled holds true of false, if in the config upload was enabled when the upload server was started. 37 var uploadEnabled bool 38 39 // Shutdown is the shutdown where SIGINT and SIGTERM is send too 40 var Shutdown = make(chan os.Signal) 41 var ftpShutdown = make(chan struct{}) 42 var uploadShutdown = make(chan struct{}) 43 44 var uploadListener net.Listener 45 var ftpListener net.Listener 46 47 // All connected clients 48 var clients map[Client]bool 49 50 // Client interface provides the blueprints for the Client that is used by the server. 51 type Client interface { 52 Connection() net.Conn // Connection returns the connection stream. 53 SetConnection(conn net.Conn) // SetConnection sets the connection for the client. 54 Disconnect() // Disconnect closes the Client's connections and clears up resources. 55 Stack() *StringStack // Returns the underlying String Stack. 56 } 57 58 // FTPClient represents a FTPClient connection, it holds a root cage and the underlying connection. 59 type FTPClient struct { 60 rootCage *StringStack // rootCage is a StringStack that is used to represent the current directory the client is in. 61 connection net.Conn 62 } 63 64 // Stack returns the root cage stack. 65 func (c *FTPClient) Stack() *StringStack { 66 return c.rootCage 67 } 68 69 // SetStack sets the stack for the FTPClient. 70 func (c *FTPClient) SetStack(stack *StringStack) { 71 c.rootCage = stack 72 } 73 74 // Connection returns the Connection of the client. 75 func (c *FTPClient) Connection() net.Conn { 76 return c.connection 77 } 78 79 // SetConnection sets the given connection to the client. 80 func (c *FTPClient) SetConnection(conn net.Conn) { 81 c.connection = conn 82 } 83 84 // Disconnects the client. 85 func (c *FTPClient) Disconnect() { 86 c.connection.Close() 87 } 88 89 func shutdownHandler() { 90 for { 91 select { 92 case <-Shutdown: 93 log.Println("Shutdown signal received") 94 var wg sync.WaitGroup 95 wg.Add(1) 96 97 go func() { // Disconnect all the clients. 98 for k := range clients { 99 k.Disconnect() 100 } 101 wg.Done() 102 }() 103 wg.Wait() 104 105 ShutdownUploadServer() 106 ShutdownFtpServer() 107 return 108 } 109 } 110 } 111 112 func ShutdownUploadServer() { 113 if uploadEnabled == true { 114 if uploadListener != nil { 115 uploadListener.Close() 116 } 117 uploadShutdown <- struct{}{} 118 } 119 } 120 121 func ShutdownFtpServer() { 122 if ftpListener != nil { 123 ftpListener.Close() 124 } 125 ftpShutdown <- struct{}{} 126 } 127 128 func Init() { 129 signal.Notify(Shutdown, syscall.SIGINT, syscall.SIGTERM) 130 131 clients = make(map[Client]bool) 132 go shutdownHandler() 133 134 config.InitializeConfiguration(ConfigName, ConfigPath) 135 config.ChangeCallback(func(event fsnotify.Event) { 136 log.Println("Configuration reloaded successfully!") 137 }) 138 } 139 140 func HandleConnection(client Client) { 141 defer client.Disconnect() 142 defer func() { 143 if r := recover(); r != nil { 144 log.Println("PANIC: ", r) 145 146 recoveryError, ok := r.(string) 147 if ok { 148 io.WriteString(client.Connection(), fmt.Sprintf("PANIC: %s", recoveryError)) 149 } 150 } 151 }() 152 153 log.Println(client.Connection().RemoteAddr(), "has connected.") 154 clients[client] = true 155 156 // Process input 157 input := bufio.NewScanner(client.Connection()) 158 159 for input.Scan() { 160 log.Println(client.Connection().RemoteAddr(), ":", input.Text()) 161 162 err := ProcessInput(client, input.Text()) 163 if err != nil { 164 log.Println(err) 165 io.WriteString(client.Connection(), err.Error()+"\n") 166 } 167 } 168 169 // Client has left. 170 delete(clients, client) 171 log.Println(client.Connection().RemoteAddr(), "has disconnected.") 172 } 173 174 func StartFtpServer(wg *sync.WaitGroup) error { 175 defer wg.Done() 176 Addr := viper.GetString("address") 177 Port := viper.GetInt("port") 178 DirDepth := viper.GetInt("maxDirDepth") 179 BasePath = viper.GetString("absoluteServePath") 180 181 // Start the server 182 var err error 183 ftpListener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", Addr, Port)) 184 if err != nil { 185 log.Fatal(err) 186 return err 187 } 188 defer ftpListener.Close() 189 190 log.Println("Hello world!") 191 log.Println("Ftp server running on:", Addr, "port", Port) 192 193 for { 194 conn, err := ftpListener.Accept() 195 196 // Handle shutdown 197 select { 198 case <-ftpShutdown: 199 goto exit 200 default: 201 if err != nil { 202 log.Print(err) 203 continue 204 } 205 206 client := FTPClient{} 207 client.SetStack(MakeStringStack(DirDepth)) 208 client.SetConnection(conn) 209 210 go HandleConnection(&client) 211 } 212 } 213 exit: 214 log.Println("Ftp server exited.") 215 return nil 216 } 217 218 func HandleUpload(conn net.Conn) { 219 // Initialize Client 220 client := FTPClient{} 221 client.SetStack(MakeStringStack(2)) 222 223 // Upload directory 224 client.Stack().Push(uploadDirectory) 225 client.SetConnection(conn) 226 defer client.Disconnect() 227 228 // Create the file on the disk and make sure that the filename is random. 229 var filename = randSeq(10) 230 var _, err = os.Stat(MakePathFromStringStack(client.Stack()) + filename) 231 232 for !os.IsNotExist(err) { 233 filename = randSeq(10) 234 _, err = os.Stat(MakePathFromStringStack(client.Stack()) + filename) 235 } 236 237 // This channel will be used to store the uploadResult 238 c1 := make(chan error, 1) 239 log.Println(conn.RemoteAddr().String() + " is uploading something.") 240 clients[&client] = true 241 242 // Create a new Go routine for uploading 243 go func() { 244 err := UploadFile(&client, filename) 245 c1 <- err 246 }() 247 248 // Wait for either UploadResult or Timeout 249 select { 250 case result := <-c1: 251 { 252 if result == nil { 253 io.WriteString(conn, filename) 254 log.Println(conn.RemoteAddr().String() + "'s upload finished.") 255 } else { 256 log.Println(fmt.Sprintf("%s: %s %s", "HandleUpload", conn.RemoteAddr().String(), result.Error())) 257 258 client.Stack().Push(filename) 259 os.Remove(MakePathFromStringStack(client.Stack())) 260 261 io.WriteString(conn, result.Error()) 262 } 263 264 conn.Close() 265 } 266 case <-time.After(time.Second * uploadTimeout): 267 { 268 io.WriteString(conn, "Timeout") 269 conn.Close() 270 } 271 } 272 273 delete(clients, &client) 274 } 275 276 // StartUploadServer starts the uploading server 277 func StartUploadServer(wg *sync.WaitGroup) error { 278 defer wg.Done() 279 var err error 280 uploadEnabled = viper.GetBool("upload.enabled") 281 if uploadEnabled == false { 282 log.Println("Uploading not enabled. To enable modify the config file and restart the server") 283 return ErrUploadServerFailure 284 } 285 286 addr := viper.GetString("upload.address") 287 port := viper.GetInt("upload.port") 288 uploadDirectory = viper.GetString("upload.directory") 289 uploadTimeout = time.Duration(viper.GetInt("upload.timeout")) 290 291 uploadListener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) 292 if err != nil { 293 log.Println(err) 294 goto exit 295 } 296 defer uploadListener.Close() 297 298 err = os.Mkdir(BasePath+"/"+uploadDirectory, 0740) 299 if err != nil { 300 if _, err := os.Stat(BasePath + "/" + uploadDirectory); err != nil { 301 if os.IsNotExist(err) { 302 log.Println("Can't create upload directory!") 303 goto exit 304 } 305 } 306 } 307 308 log.Println("Upload server running on:", addr, "port", port) 309 310 for { 311 conn, err := uploadListener.Accept() 312 // Handle shutdown 313 select { 314 case <-uploadShutdown: 315 goto exit 316 default: 317 if err != nil { 318 log.Print(err) 319 continue 320 } 321 322 go HandleUpload(conn) 323 } 324 } 325 326 exit: 327 log.Println("Upload server exited.") 328 return nil 329 }