Skip to content

Latest commit

 

History

History
139 lines (108 loc) · 4.77 KB

server.md

File metadata and controls

139 lines (108 loc) · 4.77 KB

RESTful Server

Introduction

Server class is designed to receive HTTP requests and send response. Tries to mimic default http module, with the difference of strong JSON support.

For more advanced request serving check out Lambda Server.

Using built-in http package

func userHandler(w http.ResponseWriter, r *http.Request) {
    type user struct{ Name, Address string }
    switch r.Method {
    case http.MethodGet:
        joe := user{Name: "Joe", Address: "Karakaari 7, 02610 Espoo, Suomi"}
        restful.SendJSONResponse(w, 200, &joe, true)
    case http.MethodPost:
        var who user
        restful.GetRequestData(r, 0, &who)
        _ = restful.SendLocationResponse(w, "http://me:8080/user/joe")
    case http.MethodDelete:
        restful.SendEmptyResponse(w, http.StatusNoContent)
    default:
        restful.SendProblemResponse(w, r, http.StatusMethodNotAllowed, "Leave me alone!")
    }
}

func main() {
    http.HandleFunc("/user", userHandler)
    restful.ListenAndServe(":8080", http.DefaultServeMux) // Like http.ListenAndServe(), but logs and handles K8s liveness probe, too.
    panic("Server crashed when printing this line")
}

Using Gorilla/Mux

import "github.com/gorilla/mux"

func getUserHandler(w http.ResponseWriter, r *http.Request) {...}

func main() {
    handler := mux.NewRouter()
    handler.HandleFunc("/user", getUserHandler).Methods(http.MethodGet)
    restful.ListenAndServe(":8080", handler) // Works with Gorilla/Mux, too.
}

Server-Client Trace Example

This tiny example shows how incoming request data are saved to context. Headers may contain Zipkin/Jaeger X-B3-* and OpenTracing traceparent and tracestate. If incoming request contained those, then client generates new span IDs for each request.

func userHandler(w http.ResponseWriter, r *http.Request) {
    ctx := restful.NewRequestCtx(w, r)
    var whatever struct{}
    _ = restful.Get(ctx, "https://example0.com/", &whatever)
    _, _ = restful.Put(ctx, "https://example1.com/", &whatever, nil)
}

You see nothing, just ctx. The rest is automated. Check network traffic. If debug logs are on then you see the incoming parent as well as the 2 distinct span IDs in the logs, too. If tracing headers are not received, debug logs still contain random IDs, so that you can match requests and responses.

HTTPS

// TLS non-OOP way
restful.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
restful.ListenAndServeTLS(":8443", "/etc/own-tls/tls.crt", "/etc/own-tls/tls.crt", nil)

// TLS OOP way
handler := restful.NewRouter()
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
srv := restful.NewServer().Addr(":8443").Handler(handler).TLSServerCert("/etc/own-tls/tls.crt", "/etc/own-tls/tls.crt")
srv.ListenAndServe()

Mutual TLS is very similar, just client CAs are provided. Client CA can be PEM file or a directory containing PEM files case insensitively matching *.crt or *.pem.

// MTLS non-OOP way
restful.ListenAndServeMTLS(":8443", "/etc/own-tls/tls.crt", "/etc/own-tls/tls.crt", "/etc/clientcas", nil)

// MTLS OOP way
srv := restful.NewServer().Addr(":8443").Handler(handler).TLSServerCert("/etc/own-tls/tls.crt", "/etc/own-tls/tls.crt").TLSClientCert("/etc/clientcas")
srv.ListenAndServe()

❗ Note that once the key and certs are loaded they are in the memory. Any update (e.g. cert-manager.io) will not affect that. You may restart your app, or in the cloud you may issue kubectl rollout restart deploy/xxx.

Monitor

Monitor is a unique construct of RESTful package. That can be used to execute functions pre and post calling handlers. That is used by built-in Logger and can be utilized other ways, e.g. to create various metrics, service charging or filters.

Monitor is available for Server and Router classes, including sub-routers.

func pre(w http.ResponseWriter, r *http.Request) *http.Request {
    // Whatever to do before processing the request.
    fmt.Println("Begin")

    // If you write a response here, the handler function is not called. Use it to terminate the request.
    if r.URL.Path == "/error" {
        w.WriteHeader(http.StatusBadRequest)
    }

    // You may return a new Request structure, e.g. altering the original context.
    // Or nil if the original request is fine.
    return nil
}

func post(w http.ResponseWriter, r *http.Request, statusCode int) {
    // Whatever to do after processing the request.
    // You can use the status code.
    fmt.Println("Ended with ", statusCode)
}

func main() {
    ...
    router := restful.NewRouter().Monitor(pre, post)
    ...
}

Monitor is practically a wrapper layer added around the original handler functions. You may apply several such wrappers.

router = router.Monitor(pre1, post1).Monitor(pre2, nil).Monitor(nil, post3)