- Building Microservices with Go
- Nic Jackson
- 853字
- 2021-07-15 17:28:05
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.
- Hands-On Image Processing with Python
- JavaScript+jQuery開發實戰
- Vue.js 3.0源碼解析(微課視頻版)
- Django:Web Development with Python
- C語言程序設計實踐教程
- CKA/CKAD應試教程:從Docker到Kubernetes完全攻略
- 正則表達式經典實例(第2版)
- 微信小程序開發解析
- Windows Server 2016 Automation with PowerShell Cookbook(Second Edition)
- Learning Python Design Patterns
- 圖數據庫實戰
- Mastering Elixir
- Android Studio開發實戰:從零基礎到App上線 (移動開發叢書)
- Blender 3D Cookbook
- Beginning PHP