Listings Go, Frank Müller


Listing 1: Hello, World!
import "net/http"

type HelloSayer string

func (hs HelloSayer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Content-Type", "text/plain")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Hello, " + hs + "!"))
}

func main() {
    var myHandler HelloSayer = "World"
    
    http.ListenAndServe(":80", myHandler)
}

--------

Listing 2: HandlerFunc für sehr einfache Handler
import "net/http"

func myHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Content-Type", "text/plain")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Hello, " + hs + "!"))
}

func main() {
    // Convert myHandler() to http.HandlerFunc.
    http.ListenAndServe(":80", http.HandlerFunc(myHandler))
}

--------

Listing 3: Multiplexing
http.Handle("/api/orders/", ordersHandler)
http.Handle("/api/orders/items/", ordersItemsHandler)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, "/api/doc/", http.StatusSeeOther)
})

http.ListenAndServe(":80", nil)

--------

Listing 4: Nutzung von http.Server
mux := http.NewServeMux()

mux.Handle("/api/orders/", ordersHandler)
mux.Handle("/api/orders/items/", ordersItemsHandler)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, "/api/doc/", http.StatusSeeOther)
})

srv := &http.Server{
    Addr:     ":80",
    Handler:  mux,
    ErrorLog: log.New(os.Stdout, "shopping", log.Lshortfile),
}

srv.ListenAndServe()

--------

Listing 5: Geschachtelte Handler verwenden
jobsAccumulatorsHandler := infra.NewNestedHandler()

jobsAccumulatorsHandler.AppendHandler(NewJobsHandler())
jobsAccumulatorsHandler.AppendHandler(NewAccumulatorsHandler())

http.Handle(
    "/api/jobs/",
    http.StripPrefix("/api/", jobsAccumulatorsHandler),
)

--------

Listing 6: Implementierung des NestedHandler
type NestedHandler struct {
    handlerIDs  []string
    handlers    []http.Handler
    handlersLen int
}

func NewNestedHandler() *NestedHandler {
    return &NestedHandler{}
}

func (nh *NestedHandler) AppendHandler(id string, h http.Handler) {
    nh.handlerIDs = append(nh.handlerIDs, id)
    nh.handlers = append(nh.handlers, h)
    nh.handlersLen++
}

func (nh *NestedHandler) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request,
) {
    handler, ok := nh.handler(r.URL.Path)
    if !ok {
        http.Error(
            w,
            "cannot handle request",
            http.StatusRequestURITooLong,
        )
        return
    }
    handler.ServeHTTP(w, r)
}

func (nh *NestedHandler) handler(path string) (http.Handler, bool) {
    if strings.HasSuffix(path, "/") {
        path = strings.TrimSuffix(path, "/")
    }
    fields := strings.Split(path, "/")
    fieldsLen := len(fields)
    index := (fieldsLen - 1) / 2
    if index > nh.handlersLen-1 {
        return nil, false
    }
    if nh.handlerIDs[index] != fields[index*2] {
        return nil, false
    }
    return nh.handlers[index], true
}

--------

Listing 7: Implementierung des MethodHandler
type MethodHandler struct {
    handlers map[string]http.Handler
}

func (mh *MethodHandler) Handle(method string, handler http.Handler) {
    mh.handlers[method] = handler
}

func (mh *MethodHandler) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request,
) {
    handler, ok := mh.handlers[r.Method]
    if !ok {
        handler, ok = mh.handlers[DefaultHandler]
        if !ok {
           http.Error(
                w,
                "cannot handle request",
                http.StatusMethodNotAllowed,
            )
            return
        }
    }
    handler.ServeHTTP(w, r)
}

--------

Listing 8: Geschachtelte Handler mit Methoden verwenden
jobsHandler := infra.NewMethodHandler()

jobsHandler.Handle(http.MethodGet, NewJobsGetHandler())
jobsHandler.Handle(http.MethodPost, NewJobsPostHandler())
jobsHandler.Handle(http.MethodPut, NewJobsPutHandler())
jobsHandler.Handle(http.MethodDelete, NewJobsDeleteHandler())

accumulatorsHandler := infra.NewMethodHandler()

...

jobsAccumulatorsHandler := infra.NewNestedHandler()

jobsAccumulatorsHandler.AppendHandler(jobsHandler)
jobsAccumulatorsHandler.AppendHandler(accumulatorsHandler)

http.Handle(
    "/api/jobs/",
    http.StripPrefix("/api/", jobsAccumulatorsHandler),
)

--------

Listing 9: Requests auf Methoden verteilen
type GetHandler interface {
    ServeHTTPGet(w http.ResponseWriter, r *http.Request)
}

type PostHandler interface {
    ServeHTTPPost(w http.ResponseWriter, r *http.Request)
}

...

type MetaMethodHandler struct {
    handler http.Handler
}

func (mmh *MetaMethodHandler) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request,
) {
    switch r.Method {
    case http.MethodGet:
        if h, ok := mmh.handler.(GetHandler); ok {
            h.ServeHTTPGet(w, r)
            return
        }
    case http.MethodPost:
        if h, ok := mmh.handler.(PostHandler); ok {
            h.ServeHTTPPost(w, r)
            return
        }
    case ...:
        ...
    }
    mmh.handler.ServeHTTP(w, r)
}

--------

Listing 10: MetaMethodHandler nutzen
type jobsHandler struct { ... }

func (jh *jobsHandler) ServeHTTPGet(
    w http.ResponseWriter,
    r *http.Request,
) { ... }

func (jh *jobsHandler) ServeHTTPPost(
    w http.ResponseWriter,
    r *http.Request,
) { ... }

...

func (jh *jobsHandler) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request,
) {
    http.Error(
        w,
        "cannot handle jobs request",
        http.StatusMethodNotAllowed,
    )
}

...

http.Handle(
    "/api/jobs/",
    http.StripPrefix(
        "/api/",
        infra.NewMetaMethodHandler(NewJobHandler()),
    ),
)

--------

Listing 11: Identity and Access Management via JWT
type JWTHandler struct {
    handler   http.Handler
    isAllowed func(token jwt.Token) bool
    authURL   url.URL
}

func (jw *JWTHandler) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request,
) {
    token, ok := jw.retrieveToken(r)
    if !ok {
        http.Redirect(w, r, aw.authURL.String(), http.StatusUnauthorized)
        return
    }
    if !jw.isAllowed(token) {
        http.Error(w, "access not allowed", http.StatusUnauthorized)
        return
    }
    jw.handler.ServeHTTP(w, r)
}

func (jw *JWTHandler) retrieveToken(r *http.Request) (jwt.JWT, bool) {
    // Retrieve token from header and decode token
    // with imported JWT package.
    
    ...
}

...

http.Handle(
    "/api/jobs/",
    http.StripPrefix(
        "/api/",
        NewJWTHandler(
            NewMetaMethodHandler(NewJobHandler()),
            myAllowanceFunc,
            myAuthURL,
        ),
    ),
)

--------

Listing 12: GET-Methode des JobHandlers
func (jh *JobsHandler) ServeHTTPGet(
    w http.ResponseWriter,
    r *http.Request,
) {
    jobID, ok := infra.PathAt(r.URL.Path, 1)
    if ok {
        // Got a job ID.
        log.Printf("requested job %q", jobID)
        job := jh.collector.GetJob(jobID)
        if job == nil {
            http.Error(w, "job not found", http.StatusNotFound)
            return
        }
        infra.ReplyJSON(w, job)
        return
    }
    // Requesting list of job IDs.
    jobIDs := jh.collector.GetJobIDs()
    log.Printf("requested %d job IDs", len(jobIDs))
    infra.ReplyJSON(w, jobIDs)
}

--------

Listing 13: Daten als JSON antworten
func ReplyJSON(
    w http.ResponseWriter,
    reply interface{},
) {
    b, err := json.Marshal(reply)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write(b)
}
