[HN Gopher] REST Servers in Go: Part 1 - standard library
       ___________________________________________________________________
        
       REST Servers in Go: Part 1 - standard library
        
       Author : tutfbhuf
       Score  : 62 points
       Date   : 2021-01-16 20:21 UTC (2 hours ago)
        
 (HTM) web link (eli.thegreenplace.net)
 (TXT) w3m dump (eli.thegreenplace.net)
        
       | jrockway wrote:
       | Good introduction. A few thoughts:
       | 
       | 1) Be careful with locks in the form "x.Lock(); x.DoSomething();
       | x.Unlock()". If DoSomething panics, you will still be holding the
       | lock, and that's pretty much the end of your program. ("x.Lock();
       | defer x.Unlock(); x.DoSomething()" avoids this problem, but
       | obviously in the non-panic case, the lock is released at a
       | different time than in this implementation. Additional tweaking
       | is required.)
       | 
       | Generally I don't like locks in the request critical path because
       | waiting for a lock is uncancelable, but in this very simple case
       | it doesn't matter. For more complicated concurrency requirements,
       | consider the difference between x.Lock()/x.Do()/x.Unlock vs.
       | select { case x := <-ch: doSomethingWithX(x); case
       | <-request.Context().Done(): error(request.Context().Err()) }. The
       | channel wait can be cancelled when the user disconnects, or hits
       | the stop button in the error, or the request timeout is reached.
       | 
       | 2) Long if/else statements are harder to read than a switch
       | statement. Instead of:                  if(foo == "bar") {
       | // Bar        } else if (foo == "baz") {           // Baz
       | } else {           // Error        }
       | 
       | You might like:                  switch(foo) {        case "bar":
       | // Bar        case "baz":          // Baz        default:
       | // Error        }
       | 
       | These are exactly semantically equivalent, and neither protect
       | you at compile-time from forgetting a case, but there is slightly
       | less visual noise. Worth considering.
       | 
       | 3) I have always found that error handling in http.HandlerFunc-
       | tions cumbersome. The author runs into this, with code like:
       | foo, err := Foo()        if err != nil {           http.Error(w,
       | ...)           return        }        bar, err := Bar()        if
       | err != nil {           http.Error(w, ...)           return
       | }
       | 
       | Basically, you end up writing the error handling code a number of
       | times, and you have to do two things in the "err != nil" block,
       | which is annoying. I prefer:                  func
       | DoTheActualThing() ([]byte, error) {           if
       | everythingIsFine {               return []byte(`{"result":"it
       | worked and you are cool"}`), nil           }           return
       | nil, errors.New("not everything is okay, feels sad")        }
       | 
       | Then in your handler function:                  func ServeHTTP(w
       | http.ResponseWriter, req *http.Request) {           result, err
       | := DoTheActualThing()           if err != nil {
       | http.Error(w, ...)              return           }
       | w.Header().Set("content-type", "application/json")
       | w.WriteHeader(http.StatusOK)           w.Write(result)        }
       | 
       | In this simple example, it doesn't matter, but when you do more
       | than one thing that can cause an error, you'll like it better.
        
         | makeworld wrote:
         | > _Be careful with locks in the form "x.Lock();
         | x.DoSomething(); x.Unlock()". If DoSomething panics, you will
         | still be holding the lock, and that's pretty much the end of
         | your program._
         | 
         | Interesting, thanks. But isn't panicking the end of your
         | program anyway? Could you provide another example where no
         | using defer causes problems?
        
           | acrispino wrote:
           | Not necessarily. Panics can be recovered and the stdlib http
           | server recovers panics from handlers.
        
         | klohto wrote:
         | Would you please expand more on your first point regarding
         | using channels instead of Locks? It's hard for me to wrap a
         | head around it without practical example.
        
           | [deleted]
        
           | Philip-J-Fry wrote:
           | Not the OP but basically imagine that instead of locking a
           | mutex to handle synchronised writes, you spawn a goroutine
           | which just reads from a channel and writes the data.
           | 
           | If that goroutine hasn't finished processing then the channel
           | will be blocked, just like a mutex.
           | 
           | So in your handler you can use a select statement to either
           | write to the channel OR read from the
           | request.Context().Done(). The request context only lives as
           | long as the request. So if the connection drops or times out
           | then the context gets cancelled and a value is pushed onto
           | the done channel and your read is unblocked.
           | 
           | Because you use a select statement then which ever operation
           | unblocks first is what happens. If the write channel unblocks
           | then you get to write your value. If your request context
           | gets cancelled then you can report an error. The request
           | context will always get cancelled eventually, unlike a mutex
           | which will wait forever.
        
       | anderspitman wrote:
       | I think I've managed to get by with less dependencies in Go than
       | any other language. It somehow walks the line between JavaScript
       | leftpad and Python "stdlib is where modules go to die".
       | 
       | I don't think there's been a single instance where I've thought
       | "why can't stdlib do this?" nor "why the heck is this in stdlib?"
        
       | samuelroth wrote:
       | Nice article! This is an interesting approach, much less likely
       | to make Go devs' blood boil over unnecessary libraries.
       | 
       | My only question is why the server / HTTP handlers have to deal
       | with the Mutex. That seems like a "leak" from the `TaskStore`
       | abstraction, which otherwise I really like. (Thank you for not
       | using channels in that interface!)
        
         | jrockway wrote:
         | I think it's necessary to leak the details of the mutex until
         | you have some sort of transaction object to abstract that away.
         | In a concurrent workload, these two things are different:
         | store.Lock()        store.WriteKey("foo", "bar")        x :=
         | store.ReadKey("foo")        store.Unlock()        // x is
         | always "bar"
         | 
         | And:                  store.Lock()        store.WriteKey("foo",
         | "bar")        store.Unlock()             store.Lock()        x
         | := store.ReadKey("foo")        store.Unlock()        // x could
         | be whatever another goroutine set "foo" to, not the "bar" that
         | you just wrote.
         | 
         | In a more complicated app, you'll have library that acts as the
         | datastore, with transaction objects that abstract away the
         | actual mutex (which will be something more complicated):
         | var x string        err := db.DoTx(func(tx *Tx) {
         | tx.Write("foo", "bar")          x = tx.Read("foo")        })
         | if err != nil { ... }        // what x is depends on the
         | details of your database; maybe you're running at "read
         | uncommitted", maybe you're running at "serializable".
         | 
         | But, even in the simple examples, it's worth thinking about the
         | difference between lock { write; read } and lock { write };
         | lock { read }.
        
       | tptacek wrote:
       | With respect to DRY'ing the JSON code, isn't something like this
       | workable:                   err =
       | json.NewEncoder(w).Encode(&task)
       | 
       | I know there used to be a reason why this was disfavored but
       | thought it had been addressed in the stdlib.
        
       ___________________________________________________________________
       (page generated 2021-01-16 23:00 UTC)