官术网_书友最值得收藏!

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.

主站蜘蛛池模板: 宁明县| 四川省| 泸州市| 随州市| 吉安市| 富源县| 黎城县| 抚宁县| 奉新县| 柳河县| 合肥市| 乐平市| 乐亭县| 广元市| 德钦县| 新余市| 南投市| 南城县| 定日县| 波密县| 江达县| 司法| 托克托县| 蓬溪县| 册亨县| 高碑店市| 孝义市| 措勤县| 沅陵县| 饶河县| 伊宁县| 舒城县| 青龙| 个旧市| 卓资县| 青河县| 芦山县| 汽车| 吉隆县| 锡林浩特市| 富平县|