Skip to content

Commit

Permalink
Convert to HTTP handlers inside outside of the main package
Browse files Browse the repository at this point in the history
Previously, the top-level package exported functions with the
signature `func(*util.Context) util.StatusError`, and the `main`
package was responsible for wrapping these and producing normal
HTTP handlers. However, the Google Cloud Functions execution
environment expects to be able to invoke HTTP handlers which are
exported directly from the top-level package.

We move the logic of converting these inner handlers into HTTP
handlers to the `internal/util` package, allowing us to expose
HTTP handlers from the top-level package
  • Loading branch information
joshlf committed May 12, 2020
1 parent f73fab2 commit f9f3a06
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 32 deletions.
4 changes: 3 additions & 1 deletion functions/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

// ChallengeHandler is a handler for the /challenge endpoint.
func ChallengeHandler(ctx *util.Context) util.StatusError {
var ChallengeHandler = util.MakeHTTPHandler(challengeHandler)

func challengeHandler(ctx *util.Context) util.StatusError {
if err := util.ValidateRequestMethod(ctx, "GET", ""); err != nil {
return err
}
Expand Down
32 changes: 1 addition & 31 deletions functions/cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"

"functions"
"functions/internal/util"

"github.com/GoogleCloudPlatform/functions-framework-go/funcframework"
)

func main() {
funcframework.RegisterHTTPFunction("/challenge", makeHTTPHandler(functions.ChallengeHandler))
funcframework.RegisterHTTPFunction("/challenge", functions.ChallengeHandler)
// Use PORT environment variable, or default to 8080.
port := "8080"
if envPort := os.Getenv("PORT"); envPort != "" {
Expand All @@ -26,30 +23,3 @@ func main() {
log.Fatalf("funcframework.Start: %v\n", err)
}
}

func makeHTTPHandler(handler func(ctx *util.Context) util.StatusError) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx, err := util.NewContext(w, r)
if err != nil {
writeStatusError(w, r, err)
return
}

if err := handler(&ctx); err != nil {
writeStatusError(w, r, err)
}
}
}

func writeStatusError(w http.ResponseWriter, r *http.Request, err util.StatusError) {
type response struct {
Message string `json:"message"`
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(err.HTTPStatusCode())
json.NewEncoder(w).Encode(response{Message: err.Message()})

log.Printf("[%v %v %v]: responding with error code %v and message \"%v\"",
r.RemoteAddr, r.Method, r.URL, err.HTTPStatusCode(), err.Message())
}
43 changes: 43 additions & 0 deletions functions/internal/util/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package util

import (
"encoding/json"
"log"
"net/http"
)

// Handler is a handler for a request to this service. Use MakeHTTPHandler to
// wrap a Handler with the logic necessary to produce a handler which can be
// registered with the "net/http" package.
type Handler = func(ctx *Context) StatusError

// MakeHTTPHandler wraps a Handler, producing a handler which can be registered
// with the "net/http" package. The returned handler is responsible for:
// - Constructing a *Context
// - Converting any errors into an HTTP response
func MakeHTTPHandler(handler func(ctx *Context) StatusError) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx, err := NewContext(w, r)
if err != nil {
writeStatusError(w, r, err)
return
}

if err := handler(&ctx); err != nil {
writeStatusError(w, r, err)
}
}
}

func writeStatusError(w http.ResponseWriter, r *http.Request, err StatusError) {
type response struct {
Message string `json:"message"`
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(err.HTTPStatusCode())
json.NewEncoder(w).Encode(response{Message: err.Message()})

log.Printf("[%v %v %v]: responding with error code %v and message \"%v\" (error: %v)",
r.RemoteAddr, r.Method, r.URL, err.HTTPStatusCode(), err.Message(), err)
}

0 comments on commit f9f3a06

Please sign in to comment.