feat: basic file handling
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
./htdocs
|
||||
htdocs
|
||||
14
config/config.go
Normal file
14
config/config.go
Normal file
@@ -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",
|
||||
}
|
||||
}
|
||||
115
http_handle.go
Normal file
115
http_handle.go
Normal file
@@ -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("<DOCTYPE html><html><head><title>Hello World</title></head><body><h1>Hello World</h1></body></html>"), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
15
main.go
15
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user