Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Building Microservices with Go

You're reading from   Building Microservices with Go Develop seamless, efficient, and robust microservices with Go

Arrow left icon
Product type Paperback
Published in Jul 2017
Publisher
ISBN-13 9781786468666
Length 358 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Nic Jackson Nic Jackson
Author Profile Icon Nic Jackson
Nic Jackson
Arrow right icon
View More author details
Toc

Context

The problem with the previous pattern is that there is no way that you can pass the validated request from one handler to the next without breaking the http.Handler interface, but guess what Go has us covered. The context package was listed as experimental for several years before finally making it in to the standard package with Go 1.7. The Context type implements a safe method for accessing request-scoped data that is safe to use simultaneously by multiple Go routines. Let’s take a quick look at this package and then update our example to see it in use.

Background

The Background method returns an empty context that has no values; it is typically used by the main function and as the top-level Context:

func Background() Context 

WithCancel

The WithCancel method returns a copy of the parent context with a cancel function, calling the cancel function releases resources associated with the context and should be called as soon as operations running in the Context type are complete:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 

WithDeadline

The WithDeadline method returns a copy of the parent context that expires after the current time is greater than deadline. At this point, the context's Done channel is closed and the resources associated are released. It also passes back a CancelFunc method that allows manual cancellation of the context:

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 

WithTimeout

The WithTimeout method is similar to WithDeadline except you pass it a duration for which the Context type should exist. Once this duration has elapsed, the Done channel is closed and the resources associated with the context are released:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 

WithValue

The WithValue method returns a copy of the parent Context in which the val value is associated with the key. The Context values are perfect to be used for request-scoped data:

func WithValue(parent Context, key interface{}, val interface{}) Context 

Why not attempt to modify example 1.7 to implement a request scoped context. The key could be in the previous sentence; every request needs its own context.

Using contexts

You probably found that rather painful, especially if you come from a background in a framework such as Rails or Spring. Writing this kind of code is not really something you want to be spending your time on, building application features is far more important. One thing to note however is that neither Ruby or Java have anything more advanced in their base packages. Thankfully for us, over the seven years that Go has been in existence, many excellent people have done just that, and when looking at frameworks in Chapter 3, Introducing Docker, we will find that all of this complexity has been taken care of by some awesome open source authors.

In addition to the adoption of context into the main Go release version 1.7 implements an important update on the http.Request structure, we have the following additions:

func (r *Request) Context() context.Context

The Context() method gives us access to a context.Context structure which is always non nil as it is populated when the request is originally created. For inbound requests the http.Server manages the lifecycle of the context automatically cancelling it when the client connection closes. For outbound requests, Context controls cancellation, by this we mean that if we cancel the Context() method we can cancel the outgoing request. This concept is illustrated in the following example:

70 func fetchGoogle(t *testing.T) {
71 r, _ := http.NewRequest("GET", "https://google.com", nil)
72
73 timeoutRequest, cancelFunc := context.WithTimeout(r.Context(), 1*time.Millisecond)
74 defer cancelFunc()
75
76 r = r.WithContext(timeoutRequest)
77
78 _, err := http.DefaultClient.Do(r)
79 if err != nil {
80 fmt.Println("Error:", err)
81 }
82 }

 

In line 74, we are creating a timeout context from the original in the request, and unlike an inbound request where the context is automatically cancelled for you we must manually perform this step in an outbound request.

 

Line 77 implements the second of the two new context methods which have been added to the http.Request object:

func (r *Request) WithContext(ctx context.Context) *Request

The WithContext object returns a shallow copy of the original request which has the context changed to the given ctx context.

When we execute this function we will find that after 1 millisecond the request will complete with an error:

Error: Get https://google.com: context deadline exceeded

The context is timing out before the request has a change to complete and the do method immediately returns. This is an excellent technique to use for outbound connections and thanks to the changes in Go 1.7 is now incredibly easy to implement.

What about our inbound connection Let’s see how we can update our previous example. Example 1.9 updates our example to show how we can leverage the context package to implement Go routine safe access to objects. The full example can be found in reading_writing_json_8/reading_writing_json_8.go but all of the modification we need to make are in the two ServeHTTP methods for our handlers:

41 func (h validationHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
42 var request helloWorldRequest
43 decoder := json.NewDecoder(r.Body)
44
45 err := decoder.Decode(&request)
46 if err != nil {
47 http.Error(rw, "Bad request", http.StatusBadRequest)
48 return
49 }
50
51 c := context.WithValue(r.Context(), validationContextKey("name"), request.Name)
52 r = r.WithContext(c)
53
54 h.next.ServeHTTP(rw, r)
55 }

If we take a quick look at our validationHandler you will see that when we have a valid request, we are creating a new context for this request and then setting the value of the Name field in the request into the context. You might also wonder what is going on with line 51. When you add an item to a context such as with the WithValue call, the method returns a copy of the previous context, to save a little time and add a little confusion, we are holding a pointer to the context, so in order to pass this as a copy to WithValue, we must dereference it. To update our pointer, we must also set the returned value to the value referenced by the pointer hence again we need to dereference it. The other think we need to look at with this method call is the key, we are using validationContextKey this is an explicitly declared type of string:

13 type validationContextKey string

The reason we are not just using a simple string is that context often flows across packages and if we just used string then we could end up with a key clash where one package within your control is writing a name key and another package which is outside of your control is also using the context and writing a key called name, in this instance the second package would inadvertently overwrite your context value. By declaring a package level type validationContextKey and using this we can ensure that we avoid these collisions:

64 func (h helloWorldHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
65 name := r.Context().Value(validationContextKey("name")).(string)
66 response := helloWorldResponse{Message: "Hello " + name}
67
68 encoder := json.NewEncoder(rw)
69 encoder.Encode(response)
70 }

To retrieve the value, all we have to do is obtain the context and then call the Value method casting it into a string.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image