diff --git a/http-auth/README.md b/http-auth/IMPLEMENTATION.md similarity index 91% rename from http-auth/README.md rename to http-auth/IMPLEMENTATION.md index 7c04fe45b..f2dca5d28 100644 --- a/http-auth/README.md +++ b/http-auth/IMPLEMENTATION.md @@ -19,4 +19,4 @@ Learning objectives: ## Project -TODO +See the `main` branch for instructions for this project. diff --git a/http-auth/go.mod b/http-auth/go.mod new file mode 100644 index 000000000..21cf751d3 --- /dev/null +++ b/http-auth/go.mod @@ -0,0 +1,5 @@ +module http-auth + +go 1.18 + +require golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect diff --git a/http-auth/go.sum b/http-auth/go.sum new file mode 100644 index 000000000..00176fd22 --- /dev/null +++ b/http-auth/go.sum @@ -0,0 +1,2 @@ +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/http-auth/main.go b/http-auth/main.go new file mode 100644 index 000000000..9f129c276 --- /dev/null +++ b/http-auth/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "html" + "io" + "log" + "net/http" + "os" + "strings" + + "golang.org/x/time/rate" +) + +func authOk(user string, pass string) bool { + return user == os.Getenv("AUTH_USERNAME") && pass == os.Getenv("AUTH_PASSWORD") +} + +// Take a rate.Limiter instance and a http.HandlerFunc and return another http.HandlerFunc that +// checks the rate limiter using `Allow()` before calling the supplied handler. If the request +// is not allowed by the limiter, a `503 Service Unavailable` Error is returned. +func rateLimit(limiter *rate.Limiter, next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if limiter.Allow() { + next.ServeHTTP(w, r) + } else { + http.Error(w, "Service Unavailable", http.StatusServiceUnavailable) + } + }) +} + +// writeStartOfHTML is a function we can call from both the POST and GET path to start off the HTML response. +func writeStartOfHTML(w http.ResponseWriter) { + // Indicate that we are sending back HTML + w.Header().Add("Content-Type", "text/html") + // Write the doctype and opening tag + w.Write([]byte("\n\n")) +} + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // If the request is POSTing data, return what they sent back + if r.Method == "POST" { + // The request (r) body is an io.Reader so we can copy it into the + // string builder and handle errors + body := new(strings.Builder) + if _, err := io.Copy(body, r.Body); err != nil { + // In the case of an error in this copying process, return a server error + log.Printf("Error copying request body: %v", err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Internal server error")) + return + } + writeStartOfHTML(w) + // Write the body back to the requester in a safe way + w.Write([]byte(html.EscapeString(body.String()))) + } else { + writeStartOfHTML(w) + // In all other cases, just say hello + w.Write([]byte("Hello, world\n")) + w.Write([]byte("
Query parameters:\n