go-cdn/main.go
2023-06-03 22:23:41 +03:00

207 lines
5.5 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/sirupsen/logrus"
)
// Config represents the configuration structure
type Config struct {
Port string `json:"port"`
MaxUploadSize int64 `json:"max_upload_size"`
Heartbeat Duration `json:"heartbeat"`
RequireAuth bool `json:"require_auth"`
AuthUsername string `json:"auth_username"`
AuthPassword string `json:"auth_password"`
UploadDirectory string `json:"upload_directory"`
}
// Duration is a custom type for decoding time.Duration from JSON
type Duration time.Duration
// UnmarshalJSON unmarshals a JSON string into a Duration
func (d *Duration) UnmarshalJSON(data []byte) error {
var durationStr string
if err := json.Unmarshal(data, &durationStr); err != nil {
return fmt.Errorf("error decoding Duration: %w", err)
}
parsedDuration, err := time.ParseDuration(durationStr)
if err != nil {
return fmt.Errorf("error parsing Duration: %w", err)
}
*d = Duration(parsedDuration)
return nil
}
// CDNHandler handles HTTP requests to the CDN server
type CDNHandler struct {
Config Config
Logger *logrus.Logger
}
// ServeHTTP serves HTTP requests
func (c *CDNHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
c.handleGet(w, r)
case http.MethodPost:
c.handlePost(w, r)
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
}
// handleGet handles GET requests
func (c *CDNHandler) handleGet(w http.ResponseWriter, r *http.Request) {
c.Logger.Infof("Received GET request for URL: %s", r.URL.Path)
// Serve file for download
filePath := filepath.Join(c.Config.UploadDirectory, r.URL.Path)
file, err := os.Open(filePath)
if err != nil {
c.Logger.Errorf("Error opening file: %v", err)
http.Error(w, "File Not Found", http.StatusNotFound)
return
}
defer file.Close()
// Set the appropriate Content-Type header based on file extension
contentType := "application/octet-stream"
switch filepath.Ext(filePath) {
case ".jpg", ".jpeg":
contentType = "image/jpeg"
case ".png":
contentType = "image/png"
case ".pdf":
contentType = "application/pdf"
}
w.Header().Set("Content-Type", contentType)
if _, err := io.Copy(w, file); err != nil {
c.Logger.Errorf("Error copying file: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
c.Logger.Infof("File downloaded successfully: %s", r.URL.Path)
}
// handlePost handles POST requests
func (c *CDNHandler) handlePost(w http.ResponseWriter, r *http.Request) {
c.Logger.Info("Received POST request")
// Validate request size
r.Body = http.MaxBytesReader(w, r.Body, c.Config.MaxUploadSize)
if err := r.ParseMultipartForm(c.Config.MaxUploadSize); err != nil {
c.Logger.Errorf("Error parsing multipart form: %v", err)
http.Error(w, "Payload Too Large", http.StatusRequestEntityTooLarge)
return
}
// Get the uploaded file
file, handler, err := r.FormFile("file")
if err != nil {
c.Logger.Errorf("Error retrieving file: %v", err)
http.Error(w, "Error retrieving file", http.StatusInternalServerError)
return
}
defer file.Close()
// Create the upload directory if it doesn't exist
uploadDir := c.Config.UploadDirectory
if uploadDir == "" {
uploadDir = "uploads"
}
if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil {
c.Logger.Errorf("Error creating upload directory: %v", err)
http.Error(w, "Error creating upload directory", http.StatusInternalServerError)
return
}
// Create the file in the upload directory
filePath := filepath.Join(uploadDir, handler.Filename)
newFile, err := os.Create(filePath)
if err != nil {
c.Logger.Errorf("Error creating file: %v", err)
http.Error(w, "Error creating file", http.StatusInternalServerError)
return
}
defer newFile.Close()
// Copy the uploaded file to the new file
if _, err := io.Copy(newFile, file); err != nil {
c.Logger.Errorf("Error copying file: %v", err)
http.Error(w, "Error copying file", http.StatusInternalServerError)
return
}
c.Logger.Infof("File uploaded successfully: %s", handler.Filename)
fmt.Fprint(w, "File uploaded successfully")
}
func main() {
// Parse command line flags
configPath := flag.String("config", "config.json", "Path to the configuration file")
flag.Parse()
// Initialize logrus logger
logger := logrus.New()
logger.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05",
})
// Read the configuration file
configFile, err := os.Open(*configPath)
if err != nil {
logger.Fatalf("Error opening configuration file: %v", err)
}
defer configFile.Close()
// Decode the configuration file
var config Config
err = json.NewDecoder(configFile).Decode(&config)
if err != nil {
logger.Fatalf("Error decoding configuration file: %v", err)
}
// Start a goroutine to restart the server periodically
if config.Heartbeat > 0 {
go func() {
for range time.Tick(time.Duration(config.Heartbeat)) {
logger.Info("Server heartbeat")
os.Exit(0)
}
}()
}
// Create a new CDNHandler with the configuration
cdnHandler := &CDNHandler{
Config: config,
Logger: logger,
}
// Create a new HTTP server
server := &http.Server{
Addr: ":" + config.Port,
Handler: cdnHandler,
ErrorLog: log.New(logger.Writer(), "", 0),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
logger.Infof("Starting CDN server on port %s", config.Port)
log.Fatal(server.ListenAndServe())
}