Created
March 28, 2023 15:25
-
-
Save aidenfoxivey/ffa8ee83870e46782accd3c41641eb15 to your computer and use it in GitHub Desktop.
A simple Go server I wrote in ~1 hour.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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