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"
|
"bufio"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tilok.dev/go-http-server/config"
|
||||||
rh "tilok.dev/go-http-server/response_helper"
|
rh "tilok.dev/go-http-server/response_helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
//TODO: Make interface configurable
|
conf := config.GetConfig()
|
||||||
interf := "127.0.0.1:8080"
|
listener, err := net.Listen("tcp", conf.NetInterface)
|
||||||
listener, err := net.Listen("tcp", interf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Could not create listener", "err", err.Error())
|
slog.Error("Could not create listener", "err", err.Error())
|
||||||
}
|
}
|
||||||
@@ -30,6 +31,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleConnection(conn net.Conn) {
|
func handleConnection(conn net.Conn) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
slog.Info("Request handled", "duration", time.Since(startTime).String())
|
||||||
|
}()
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
reader := bufio.NewReader(conn)
|
reader := bufio.NewReader(conn)
|
||||||
headers := readHeaders(reader)
|
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())
|
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