Skip to content

Instantly share code, notes, and snippets.

@adamyi
Created March 14, 2020 09:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamyi/0ffeb314d6e72580e3be65d46e8fc235 to your computer and use it in GitHub Desktop.
Save adamyi/0ffeb314d6e72580e3be65d46e8fc235 to your computer and use it in GitHub Desktop.
Trivial HTTP Server
/**
* A trivial HTTP Server, written for COMP3331/9331, 20T1
* Adam Yi <adamyi@cse.unsw.edu.au>
*
*/
package main
import (
"bufio"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"path/filepath"
"strconv"
"strings"
)
const ServerName = "Adam's Awesome Server"
const (
StatusOK = 200
StatusBadRequest = 400
StatusForbidden = 403
StatusNotFound = 404
StatusMethodNotAllowed = 405
StatusInternalServerError = 500
)
var statusText = map[int]string{
StatusOK: "OK",
StatusBadRequest: "Bad Request",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusInternalServerError: "Internal Server Error",
}
func StatusText(code int) string {
return statusText[code]
}
func main() {
if len(os.Args) < 2 {
log.Fatalf("Usage: %s port", os.Args[0])
}
port, err := strconv.Atoi(os.Args[1])
if err != nil {
log.Fatalf("Usage: %s port", os.Args[0])
}
socket, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
panic(err)
}
defer socket.Close()
log.Printf("%s listening on port %d", ServerName, port)
for {
conn, err := socket.Accept()
if err != nil {
log.Println(err.Error())
continue
}
go HandleConnection(conn)
}
}
func HandleConnection(conn net.Conn) {
defer conn.Close()
url, method := ParseRequest(conn)
HandleRequest(conn, url, method)
}
func HandleRequest(conn net.Conn, url string, method string) {
if method != "GET" {
ServeResponse(conn, StatusMethodNotAllowed, []byte(fmt.Sprintf("You issued a %s request but we only support GET", method)))
return
}
url = strings.Split(url, "?")[0]
if strings.HasSuffix(url, "/") {
url += "index.html"
}
url = filepath.Clean(url)
if strings.HasPrefix(url, "../") {
ServeResponse(conn, 400, nil)
}
fileContent, err := ioutil.ReadFile("./" + url)
if err != nil {
fmt.Println(err.Error())
if os.IsNotExist(err) {
ServeResponse(conn, StatusNotFound, []byte(fmt.Sprintf("%s does not exist.", url)))
} else if os.IsPermission(err) {
ServeResponse(conn, StatusForbidden, []byte(fmt.Sprintf("You don't have permission to view %s", url)))
} else {
ServeResponse(conn, StatusInternalServerError, []byte("Something went wrong badly..."))
}
return
}
ServeResponse(conn, StatusOK, fileContent)
}
func ServeResponse(conn net.Conn, code int, message []byte) {
fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\n", code, StatusText(code))
fmt.Fprintf(conn, "Server: %s\r\n", ServerName)
fmt.Fprintf(conn, "Connection: close\r\n")
if message != nil && len(message) > 0 {
fmt.Fprintf(conn, "Content-Length: %d\r\n", len(message))
fmt.Fprintf(conn, "\r\n")
conn.Write(message)
} else {
fmt.Fprintf(conn, "\r\n")
}
}
func ParseRequest(conn net.Conn) (url string, method string) {
statusLineParsed := false
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := scanner.Text()
if !statusLineParsed {
statusLine := strings.Split(line, " ")
method = statusLine[0]
url = statusLine[1]
statusLineParsed = true
}
// TODO: parse headers for fun
if line == "" {
break
}
}
return url, method
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment