From f85afce94940c0025fe656e4e26462c112757ee9 Mon Sep 17 00:00:00 2001 From: Tilo K Date: Sun, 15 Feb 2026 16:31:49 +0100 Subject: [PATCH] feat: basic file handling --- .gitignore | 2 + config/config.go | 14 ++++++ http_handle.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++ main.go | 15 +++++-- 4 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 config/config.go create mode 100644 http_handle.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98b2a71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +./htdocs +htdocs diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..f7c37f4 --- /dev/null +++ b/config/config.go @@ -0,0 +1,14 @@ +package config + +type Config struct { + NetInterface string + Htdocs string +} + +func GetConfig() *Config { + //TODO: Load config from file + return &Config{ + NetInterface: "127.0.0.1:80", + Htdocs: "./htdocs", + } +} diff --git a/http_handle.go b/http_handle.go new file mode 100644 index 0000000..1316efa --- /dev/null +++ b/http_handle.go @@ -0,0 +1,115 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/fs" + "log/slog" + "mime" + "net" + "os" + "path" + "strings" + + "tilok.dev/go-http-server/config" + rh "tilok.dev/go-http-server/response_helper" +) + +func HandleHTTPRequest(headers Headers, conn net.Conn) { + if strings.Contains(headers.Uri, "..") { + conn.Write([]byte("HTTP/1.1 403 Fuck you\r\n\r\n")) + conn.Close() + return + } + config := config.GetConfig() + _, err := os.Stat(config.Htdocs) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + CreateDefaultHtdocs(config.Htdocs) + } else { + slog.Error("Failed to stat directory", "error", err) + panic("Failed to stat directory") + } + } + + desiredPath := path.Join(config.Htdocs, headers.Uri) + if headers.Uri == "/" { + desiredPath = path.Join(config.Htdocs, "index.html") + } + + file, err := os.Open(desiredPath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + rh.RespondWithStatusCode(404, conn) + return + } + if errors.Is(err, fs.ErrPermission) { + rh.RespondWithStatusCode(403, conn) + return + } + + slog.Error("Unhandled error case", "err", err) + rh.RespondWithStatusCode(500, conn) + return + } + + defer file.Close() + sendFile(file, conn) + + conn.Close() +} + +func sendFile(file *os.File, conn net.Conn) { + reader := bufio.NewReader(file) + defer file.Close() + + fileStat, err := file.Stat() + if err != nil { + slog.Error("Failed to stat file", "error", err) + rh.RespondWithStatusCode(500, conn) + return + } + + parts := strings.Split(fileStat.Name(), ".") + ext := "." + parts[len(parts)-1] + mime_type := mime.TypeByExtension(ext) + length := fileStat.Size() + + if mime_type == "" { + mime_type = "application/octet-stream" + } + + header := fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %d\r\nServer: Tilo's Go HTTP Server\r\n\r\n", mime_type, length) + conn.Write([]byte(header)) + + buf := make([]byte, 4096) + for { + n, err := reader.Read(buf) + if err != nil { + if err == io.EOF { + break + } + slog.Error("Failed to read file", "error", err) + rh.RespondWithStatusCode(500, conn) + return + } + conn.Write(buf[:n]) + } + +} + +func CreateDefaultHtdocs(dirPath string) error { + err := os.MkdirAll(dirPath, 0755) + if err != nil { + return err + } + + err = os.WriteFile(path.Join(dirPath, "index.html"), []byte("Hello World

Hello World

"), 0644) + if err != nil { + return err + } + + return nil +} diff --git a/main.go b/main.go index 5c10d62..43ee636 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,15 @@ import ( "bufio" "log/slog" "net" + "time" + "tilok.dev/go-http-server/config" rh "tilok.dev/go-http-server/response_helper" ) func main() { - //TODO: Make interface configurable - interf := "127.0.0.1:8080" - listener, err := net.Listen("tcp", interf) + conf := config.GetConfig() + listener, err := net.Listen("tcp", conf.NetInterface) if err != nil { slog.Error("Could not create listener", "err", err.Error()) } @@ -30,6 +31,11 @@ func main() { } func handleConnection(conn net.Conn) { + startTime := time.Now() + defer func() { + slog.Info("Request handled", "duration", time.Since(startTime).String()) + }() + defer conn.Close() reader := bufio.NewReader(conn) headers := readHeaders(reader) @@ -39,5 +45,6 @@ func handleConnection(conn net.Conn) { } slog.Info("Received request", "method", headers.Method, "uri", headers.Uri, "proto", headers.Proto, "client", conn.RemoteAddr().String()) - headers.debugPrintHeaders() + + HandleHTTPRequest(*headers, conn) }