Created
March 14, 2020 09:06
-
-
Save adamyi/0ffeb314d6e72580e3be65d46e8fc235 to your computer and use it in GitHub Desktop.
Trivial HTTP Server
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
/** | |
* 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