Error handling on a call stack - http request handler - http

In the below code:
func (p *ProductHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// handle the request for a list of products
if r.Method == http.MethodGet {
p.getProductHandler(w, r)
return
}
if r.Method == http.MethodPost {
p.addProductHandler(w, r)
return
}
if r.Method == http.MethodPut {
id := findID(w, r)
p.updateProductHandler(id, w, r)
return
}
// catch all
// if no method is satisfied return an error
w.WriteHeader(http.StatusMethodNotAllowed)
}
// getProducts returns the products from the data store
func (p *ProductHandler) getProductHandler(w http.ResponseWriter, r *http.Request) {
p.l.Println("Handle GET Products")
// fetch the products from the datastore
productList := data.GetProducts()
// serialize the list to JSON
err := productList.WriteJSON(w)
if err != nil {
http.Error(w, "Unable to marshal json", http.StatusInternalServerError)
return
}
}
func (p *ProductHandler) addProductHandler(w http.ResponseWriter, r *http.Request) {
p.l.Println("Handle POST products")
// Read the item from the incoming request
productItem := &data.Product{}
err := productItem.ReadJSON(r.Body)
if err != nil {
http.Error(w, "Unable to unmarshal JSON", http.StatusBadRequest)
return
}
p.l.Printf("Product item: %#v\n", productItem)
data.AddProductItem(productItem)
}
func (p *ProductHandler) updateProductHandler(id int, w http.ResponseWriter, r *http.Request) {
// whatever
return
}
func findID(w http.ResponseWriter, r *http.Request) int {
// expect the id in the URI
dfa := regexp.MustCompile(`/([0-9]+)`)
matches := dfa.FindAllStringSubmatch(r.URL.Path, -1) // returns [][]string
if len(matches) != 1 {
http.Error(w, "Invalid URI", http.StatusBadRequest)
return
}
if len(matches[0]) != 2 {
http.Error(w, "Invlaid URI", http.StatusBadRequest)
return
}
idString := matches[0][1]
id, err := strconv.Atoi(idString)
if err != nil {
http.Error(w, "Invlaid URI", http.StatusBadRequest)
return
}
return id
}
On error, we use http.Error() within handlers that handle POST & GET and then return
But for PUT request, call stack stack is ServeHTTP -> findID() and then ServeHTTP() -> updateProductHandler(). Error handling is required in findID() but cannot return immediately, because findID() returns int.
What is the design pattern to perform error handling for a call stack? Error wrapping using github.com/pkg/errors....

func (p *ProductHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// handle the request for a list of products
if r.Method == http.MethodGet {
p.getProductHandler(w, r)
return
}
if r.Method == http.MethodPost {
p.addProductHandler(w, r)
return
}
if r.Method == http.MethodPut {
id, err := findID(r.URL.Path)
if err == nil {
p.updateProductHandler(id, w, r)
} else {
http.Error(w, err.Error(), http.StatusBadRequest)
}
return
}
// catch all
// if no method is satisfied return an error
w.WriteHeader(http.StatusMethodNotAllowed)
w.Header().Add("Allow", "GET, POST, PUT")
}
func findID(path string) (int, error) {
// expect the id in the URI
dfa := regexp.MustCompile(`/([0-9]+)`)
matches := dfa.FindAllStringSubmatch(path, -1) // returns [][]string
if len(matches) != 1 {
return 0, fmt.Errorf("Invalid URI %s", path)
}
if len(matches[0]) != 2 {
return 0, fmt.Errorf("Invalid URI %s", path)
}
idString := matches[0][1]
id, err := strconv.Atoi(idString)
if err != nil {
return 0, fmt.Errorf("Invalid URI %s", path)
}
return id, nil
}

Related

How to trace http.Client with httptrace in Go

Per this doc, we can trace http.Client with httptrace in this way
t := &transport{}
req, _ := http.NewRequest("GET", "https://google.com", nil)
trace := &httptrace.ClientTrace{
GotConn: t.GotConn,
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
client := &http.Client{Transport: t}
For google API client, here are the one wrapper codes
func NewWithClient(jsonKey []byte, cli *http.Client) (*Client, error) {
if cli == nil {
return nil, fmt.Errorf("client is nil")
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cli)
conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
if err != nil {
return nil, err
}
service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
if err != nil {
return nil, err
}
return &Client{service}, err
}
We want to apply httptrace to the http.Client argument of NewWithClient to do HTTP trace.
What we have tried
type TraceTransport struct {
}
var traceTransport = &TraceTransport{}
var trace = &httptrace.ClientTrace{
GotConn: traceTransport.GotConn,
}
func (t *TraceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return http.DefaultTransport.RoundTrip(req)
}
func (t *TraceTransport) GotConn(info httptrace.GotConnInfo) {
fmt.Printf("Connection reused for %v \n", info.Reused)
}
type ClientWrapper struct {
defaultClient *http.Client
}
var clientWrapperTrace = &httptrace.ClientTrace{GotConn: traceTransport.GotConn}
func (c *ClientWrapper) Do(req *http.Request) (*http.Response, error) {
req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientWrapperTrace))
return c.defaultClient.Do(req)
}
func NewClientTrace(jsonKey []byte) (*Client, error) {
cli := &http.Client{
Transport: traceTransport,
Timeout: time.Duration(10) * time.Second,
}
cliWrapper := &ClientWrapper{defaultClient: cli}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cliWrapper)
conf, err := google.JWTConfigFromJSON(jsonKey, androidpublisher.AndroidpublisherScope)
if err != nil {
return nil, err
}
service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
if err != nil {
return nil, err
}
return &Client{service}, err
}
type Client struct {
service *androidpublisher.Service
}
func (c *Client) VerifyProduct(
ctx context.Context,
packageName string,
productID string,
token string,
) (*androidpublisher.ProductPurchase, error) {
ps := androidpublisher.NewPurchasesProductsService(c.service)
result, err := ps.Get(packageName, productID, token).Context(ctx).Do()
return result, err
}
// test codes
c, err := NewClientTrace([]byte(privateKey))
if err != nil {
return
}
packageName := "package.name"
productID := "product_id"
token := "xxxxx"
r, err := c.VerifyProduct(context.Background(), packageName, productID, token)
However, it is failed to trace http.Client, There is no output of GotConn. Could someone help us to figure out the issue of the above codes?
Requests from google/oauth2 are not traceable by httptrace. your ClientWrapper passed with context.WithValue will be ignored here, and oauth2 has it's own http.Client, it just use the Transport method of *http.Client from context.Value.
Requests from androidpublisher can be traced by httptrace like this:
ctx := httptrace.WithClientTrace(context.Background(), clientWrapperTrace)
r, err := c.VerifyProduct(ctx, packageName, productID, token)
If you just want to count the requests, i think overwrite the http.Client.Transport is a easy way.
type TraceTransport struct {
}
func (t *TraceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
fmt.Printf("RoundTrip hook %v\n", req.URL)
return http.DefaultTransport.RoundTrip(req)
}
func NewClientTrace(jsonKey []byte) (*Client, error) {
cli := &http.Client{Transport: &TraceTransport{}}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, cli)
// ...
service, err := androidpublisher.NewService(ctx, option.WithHTTPClient(conf.Client(ctx)))
// ....
}

Should I close request body if I use io.TeeReader() or io.ReadAll()?

I have a handler which I call from my main() function:
type requestBody struct {
Query string `json:"query"`
}
func main() {
r := chi.NewRouter()
r.Post("/api", MyHandler(superGraph, gqlGen))
}
func MyHandler(library *MyLibrary, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
buf := bytes.NewBuffer(make([]byte, 0))
reader := io.TeeReader(r.Body, buf)
var reqBody requestBody
err := json.NewDecoder(reader).Decode(&reqBody)
if err != nil {
http.Error(w, "cannot read body", http.StatusBadRequest)
return
}
res, err := library.DoSomething(...)
if err != nil {
log.Error(err)
err := r.Body.Close()
log.ErrorIf(err)
r.Body = ioutil.NopCloser(buf)
next.ServeHTTP(w, r)
return
}
render.JSON(w, r, res) // go-chi "render" pkg
}
}
QUESTION
Do I need the below line?
err := r.Body.Close()
I know https://stackoverflow.com/a/42533540/10088259:
A request body does not need to be closed in the handler. From the http.Request documentation:
The Server will close the request body. The ServeHTTP
Handler does not need to.
but here I'm using:
reader := io.TeeReader(r.Body, buf)
and if err != nil {
r.Body = ioutil.NopCloser(buf)
So, should I r.Body.Close() it in the if path of my code considering that ioutil.NopCloser() has a "fake" Close() method?

Server returning 400 when user exists

I'm working on some tests in Go and I have spent the past 2 days trying to make it work but I couldn't. My problem is that the test returns 400 even when the user does exist.
This is my getUser function
func (handler *UserHandler) getUser(w http.ResponseWriter, ID int) {
logfile, err := os.OpenFile("events.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Error opening file: %v", err)
}
defer logfile.Close()
log.SetOutput(logfile)
user := db.Fetch(ID)
userJSON, err := json.Marshal(user)
if err != nil {
log.Printf("Error while marshaling the user into JSON: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// userJSON is sent as http Response
w.Write(userJSON)
}
This is my UserHandler
type UserHandler struct{}
func (handle *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var head string
head, r.URL.Path = ShiftPath(r.URL.Path)
id, err := strconv.Atoi(head)
if err != nil {
http.Error(w, fmt.Sprintf("Invalid user ID %q", head), http.StatusBadRequest)
return
}
switch r.Method {
case "GET":
handle.getUser(w, id)
default:
http.Error(w, "Only GET is allowed", http.StatusMethodNotAllowed)
}
}
func ShiftPath(p string) (head, tail string) {
p = path.Clean("/" + p)
i := strings.Index(p[1:], "/") + 1
if i <= 0 {
return p[1:], "/"
}
return p[1:i], p[i:]
}
And this is my test
func TestGetUser(t *testing.T) {
handler := new(UserHandler)
mux := http.NewServeMux()
mux.HandleFunc("/user/", handler.ServeHTTP)
writer := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/user/12", nil)
mux.ServeHTTP(writer, request)
if writer.Code != 200 {
t.Errorf("Response code is %v", writer.Code)
}
}
Issue with code ====> id, err := strconv.Atoi(head)
Due to error you see a return and hence you see 400 error.
Have your server code fully functional with valid logic.
Suggestion: Always print or debug line by line. You can find the issue and root cause.

Go race condition in timeout handler

I can see two main issues in the example code below, but I don't know how to solve them correctly.
If the timeout handler does not get the signal through the errCh that the next handler has completed or an error occured, it will reply "408 Request timeout" to the request.
The problem here is that the ResponseWriter is not safe to be used by multiple goroutines. And the timeout handler starts a new goroutine when executing the next handler.
Issues:
How to prevent the next handler from writing into the ResponseWriter when the ctx's Done channel times out in the timeout handler.
How to prevent the timeout handler from replying 408 status code when the next handler is writing into the ResponseWriter but it has not finished yet and the ctx's Done channel times out in the timeout handler.
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
http.Handle("/race", handlerFunc(timeoutHandler))
http.ListenAndServe(":8080", nil)
}
func timeoutHandler(w http.ResponseWriter, r *http.Request) error {
const seconds = 1
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(seconds)*time.Second)
defer cancel()
r = r.WithContext(ctx)
errCh := make(chan error, 1)
go func() {
// w is not safe for concurrent use by multiple goroutines
errCh <- nextHandler(w, r)
}()
select {
case err := <-errCh:
return err
case <-ctx.Done():
// w is not safe for concurrent use by multiple goroutines
http.Error(w, "Request timeout", 408)
return nil
}
}
func nextHandler(w http.ResponseWriter, r *http.Request) error {
// just for fun to simulate a better race condition
const seconds = 1
time.Sleep(time.Duration(seconds) * time.Second)
fmt.Fprint(w, "nextHandler")
return nil
}
type handlerFunc func(w http.ResponseWriter, r *http.Request) error
func (fn handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, "Server error", 500)
}
}
Here is a possible solution, which is based on #Andy's comment.
A new responseRecorder will be passed to the nextHandler, and the recorded response will be copied back to the client:
func timeoutHandler(w http.ResponseWriter, r *http.Request) error {
const seconds = 1
ctx, cancel := context.WithTimeout(r.Context(),
time.Duration(seconds)*time.Second)
defer cancel()
r = r.WithContext(ctx)
errCh := make(chan error, 1)
w2 := newResponseRecorder()
go func() {
errCh <- nextHandler(w2, r)
}()
select {
case err := <-errCh:
if err != nil {
return err
}
w2.cloneHeader(w.Header())
w.WriteHeader(w2.status)
w.Write(w2.buf.Bytes())
return nil
case <-ctx.Done():
http.Error(w, "Request timeout", 408)
return nil
}
}
And here is the responseRecorder:
type responseRecorder struct {
http.ResponseWriter
header http.Header
buf *bytes.Buffer
status int
}
func newResponseRecorder() *responseRecorder {
return &responseRecorder{
header: http.Header{},
buf: &bytes.Buffer{},
}
}
func (w *responseRecorder) Header() http.Header {
return w.header
}
func (w *responseRecorder) cloneHeader(dst http.Header) {
for k, v := range w.header {
tmp := make([]string, len(v))
copy(tmp, v)
dst[k] = tmp
}
}
func (w *responseRecorder) Write(data []byte) (int, error) {
if w.status == 0 {
w.WriteHeader(http.StatusOK)
}
return w.buf.Write(data)
}
func (w *responseRecorder) WriteHeader(status int) {
w.status = status
}

Submitting data from front end to back end in Go

I have simple website. Go with JavaScript. Now I get this message "http: multiple response.WriteHeader calls" and I know that a have another header open. But I don't know where and I'm struggling to find a solution.
func (t *Server) RootHandler(w http.ResponseWriter, r *http.Request) {
var c *entities.Korisnik
var k *entities.Kilometri
var a *entities.Auto
if c = t.authentication(w, r); c == nil {
return
}
gk, err := t.store.GetKilometri(c)
if errorEval(w, err, http.StatusInternalServerError) {
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if r.FormValue("debug") == "true" {
s, _ := json.MarshalIndent(&Bla{c, gk}, "", " ")
w.Write(s)
return
} else {
w.Header().Set("Content-Type", "application/json")
errorEval(w, json.NewEncoder(w).Encode(&Bla{c, gk}), http.StatusInternalServerError)
}
log.Println("0")
if errorEval(w, json.NewDecoder(r.Body).Decode(&Input{a, k}), http.StatusBadRequest) {
log.Println("1")
return
}
err = t.store.NewKilometri(k, c, a)
log.Println("2")
if errorEval(w, err, http.StatusInternalServerError) {
return
}
}
I get this in my terminal
015/10/20 16:12:32 0
2015/10/20 16:12:32 EOF
2015/10/20 16:12:32 http: multiple response.WriteHeader calls
2015/10/20 16:12:32 1
2015/10/20 16:12:32 0
2015/10/20 16:12:32 EOF
2015/10/20 16:12:32 http: multiple response.WriteHeader calls
2015/10/20 16:12:32 1
func errorEval(w http.ResponseWriter, err error, status int) bool {
if err == nil {
return false
}
log.Println(err)
http.Error(w, errorString[status], status)
return true
}
It looks to me like errorEval likely writes the status code and maybe a body if an error is encountered. Most places you call it you check the return and return from your handler if it handles an error.
In the case of errorEval(w, json.NewEncoder(w).Encode(&Bla{c, gk}), http.StatusInternalServerError) you are not checking the return.
My guess is, there is some json error and the handler is writing a 500 response, and you are continuing to do other things which in turn try to write additional responses.

Resources