Skip to content

Instantly share code, notes, and snippets.

@aidenfoxivey
Created March 28, 2023 15:25
Show Gist options
  • Save aidenfoxivey/ffa8ee83870e46782accd3c41641eb15 to your computer and use it in GitHub Desktop.
Save aidenfoxivey/ffa8ee83870e46782accd3c41641eb15 to your computer and use it in GitHub Desktop.
A simple Go server I wrote in ~1 hour.
package main
import (
"bufio"
"flag"
"fmt"
"log"
"net"
"os"
"strings"
"time"
)
const (
YEAR = 2023
VERSION = 0.1
)
type httpVer uint8
const (
HTTP1_0 httpVer = iota
HTTP1_1
HTTP2
)
type httpHeader struct {
version httpVer
verb string
url string
}
// header strings are in the form of:
// VERB URL VERSION
func buildHeaderObject(headerStr string) httpHeader {
firstSpaceIdx := strings.IndexByte(headerStr, ' ')
lastSpaceIdx := strings.LastIndexByte(headerStr, ' ')
verb := headerStr[:firstSpaceIdx]
url := headerStr[firstSpaceIdx+1 : lastSpaceIdx]
versionStr := headerStr[lastSpaceIdx+1:]
var edition httpVer
switch versionStr {
case "HTTP/1.1":
edition = HTTP1_1
case "HTTP/1":
edition = HTTP1_0
case "HTTP/2":
edition = HTTP2
default:
log.Fatal("Unable to parse header obj: ", headerStr)
}
return httpHeader{edition, verb, url}
}
func main() {
portPtr := flag.Uint("port", 8080, "port to receive connections on")
flag.Parse()
logName := fmt.Sprint("./logs/log-", time.Now().Unix(), ".txt")
file, err := os.OpenFile(logName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
fmt.Printf("Listening on port %d.\n", *portPtr)
ipAddr := fmt.Sprintf("127.0.0.1:%d", *portPtr)
listener, err := net.Listen("tcp", ipAddr)
log.SetOutput(file)
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
defer listener.Close()
for {
connection, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
err = connection.SetDeadline(time.Now().Add(3 * time.Second))
if err != nil {
log.Fatal(err)
}
go processConnection(connection)
}
}
func processConnection(conn net.Conn) {
defer log.Println("Closed connection:", conn.RemoteAddr().String())
defer conn.Close()
log.Println(conn.RemoteAddr().String(), " connected.")
streamReader := bufio.NewReader(conn)
request, err := streamReader.ReadString('\n')
request = strings.Trim(request, "\r\n")
if err != nil {
log.Fatal(err)
}
statusLine := ""
fileName := ""
switch request {
case "GET / HTTP/1.1":
statusLine = "HTTP/1.1 200 OK"
fileName = "index.html"
case "GET /sleep HTTP/1.1":
// sleep to simulate long request
time.Sleep(time.Second * 2)
statusLine = "HTTP/1.1 200 OK"
fileName = "index.html"
case "GET /sleepier HTTP/1.1":
// this will time out
time.Sleep(time.Second * 5)
statusLine = "HTTP/1.1 200 OK"
fileName = "index.html"
default:
statusLine = "HTTP/1.1 404 NOT FOUND"
fileName = "404.html"
}
contents, _ := os.ReadFile(fileName)
str := string(contents)
length := len(str)
payload := fmt.Sprintf("%s\r\nContent-Length: %d\r\n\r\n%s", statusLine, length, contents)
conn.Write([]byte(payload))
}
type handled struct {
content string
notFound bool
}
type packet struct {
ctrl_msg string
}
// return contents on file to handler
func handleHeader(hdr httpHeader) handled {
if hdr.version == HTTP1_0 || hdr.version == HTTP2 {
panic("Not implemented for HTTP1 or HTTP2 yet.")
}
if hdr.verb != "GET" {
panic("Not implemented anything other than GET yet.")
}
path := findFile(hdr.url)
notfound := false
contents, err := os.ReadFile(path)
if err != nil {
notfound = true
}
return handled{
content: string(contents),
notFound: notfound,
}
}
func findFile(requested string) string {
sysPath := "./documentRoot/"
// if request the directory, search for an index.html in that directory
if strings.HasSuffix(requested, "/") {
return sysPath + requested + "index.html"
}
return sysPath + requested
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment