Merge lp:~pedronis/ubuntu-push/api-handling-dry into lp:ubuntu-push/automatic

Proposed by Samuele Pedroni
Status: Merged
Approved by: Samuele Pedroni
Approved revision: 199
Merged at revision: 200
Proposed branch: lp:~pedronis/ubuntu-push/api-handling-dry
Merge into: lp:ubuntu-push/automatic
Diff against target: 594 lines (+133/-233)
2 files modified
server/api/handlers.go (+100/-134)
server/api/handlers_test.go (+33/-99)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/api-handling-dry
Reviewer Review Type Date Requested Status
Guillermo Gonzalez Approve
Review via email: mp+224342@code.launchpad.net

Commit message

refactor api handling to avoid repetition and have just one ServeHTTP

Description of the change

refactor api handling to avoid repetition and have just one ServeHTTP

To post a comment you must log in.
199. By Samuele Pedroni

Merged better-ok-matching into api-handling-dry.

Revision history for this message
Guillermo Gonzalez (verterok) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'server/api/handlers.go'
2--- server/api/handlers.go 2014-06-02 15:32:07 +0000
3+++ server/api/handlers.go 2014-06-25 11:08:44 +0000
4@@ -277,75 +277,88 @@
5 return sto, nil
6 }
7
8-func (ctx *context) prepare(w http.ResponseWriter, request *http.Request, reqObj interface{}) (store.PendingStore, *APIError) {
9+// JSONPostHandler is able to handle POST requests with a JSON body
10+// delegating for the actual details.
11+type JSONPostHandler struct {
12+ *context
13+ parsingBodyObj func() interface{}
14+ doHandle func(ctx *context, sto store.PendingStore, parsedBodyObj interface{}) (map[string]interface{}, *APIError)
15+}
16+
17+func (h *JSONPostHandler) prepare(w http.ResponseWriter, request *http.Request) (interface{}, store.PendingStore, *APIError) {
18 body, apiErr := ReadBody(request, MaxRequestBodyBytes)
19 if apiErr != nil {
20- return nil, apiErr
21+ return nil, nil, apiErr
22 }
23
24- err := json.Unmarshal(body, reqObj)
25+ parsedBodyObj := h.parsingBodyObj()
26+ err := json.Unmarshal(body, parsedBodyObj)
27 if err != nil {
28- return nil, ErrMalformedJSONObject
29- }
30-
31- return ctx.getStore(w, request)
32-}
33-
34-type BroadcastHandler struct {
35- *context
36-}
37-
38-func (h *BroadcastHandler) doBroadcast(sto store.PendingStore, bcast *Broadcast) *APIError {
39+ return nil, nil, ErrMalformedJSONObject
40+ }
41+
42+ sto, apiErr := h.getStore(w, request)
43+ if apiErr != nil {
44+ return nil, nil, apiErr
45+ }
46+ return parsedBodyObj, sto, nil
47+}
48+
49+func (h *JSONPostHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
50+ var apiErr *APIError
51+ defer func() {
52+ if apiErr != nil {
53+ RespondError(writer, apiErr)
54+ }
55+ }()
56+
57+ parsedBodyObj, sto, apiErr := h.prepare(writer, request)
58+ if apiErr != nil {
59+ return
60+ }
61+ defer sto.Close()
62+
63+ res, apiErr := h.doHandle(h.context, sto, parsedBodyObj)
64+ if apiErr != nil {
65+ return
66+ }
67+
68+ writer.Header().Set("Content-Type", "application/json")
69+ if res == nil {
70+ fmt.Fprintf(writer, `{"ok":true}`)
71+ } else {
72+ res["ok"] = true
73+ resp, err := json.Marshal(res)
74+ if err != nil {
75+ panic(fmt.Errorf("couldn't marshal our own response: %v", err))
76+ }
77+ writer.Write(resp)
78+ }
79+}
80+
81+func doBroadcast(ctx *context, sto store.PendingStore, parsedBodyObj interface{}) (map[string]interface{}, *APIError) {
82+ bcast := parsedBodyObj.(*Broadcast)
83 expire, apiErr := checkBroadcast(bcast)
84 if apiErr != nil {
85- return apiErr
86+ return nil, apiErr
87 }
88 chanId, err := sto.GetInternalChannelId(bcast.Channel)
89 if err != nil {
90 switch err {
91 case store.ErrUnknownChannel:
92- return ErrUnknownChannel
93+ return nil, ErrUnknownChannel
94 default:
95- return ErrUnknown
96+ return nil, ErrUnknown
97 }
98 }
99 err = sto.AppendToChannel(chanId, bcast.Data, expire)
100 if err != nil {
101- h.logger.Errorf("could not store notification: %v", err)
102- return ErrCouldNotStoreNotification
103- }
104-
105- h.broker.Broadcast(chanId)
106- return nil
107-}
108-
109-func (h *BroadcastHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
110- var apiErr *APIError
111- defer func() {
112- if apiErr != nil {
113- RespondError(writer, apiErr)
114- }
115- }()
116-
117- broadcast := &Broadcast{}
118-
119- sto, apiErr := h.prepare(writer, request, broadcast)
120- if apiErr != nil {
121- return
122- }
123- defer sto.Close()
124-
125- apiErr = h.doBroadcast(sto, broadcast)
126- if apiErr != nil {
127- return
128- }
129-
130- writer.Header().Set("Content-Type", "application/json")
131- fmt.Fprintf(writer, `{"ok":true}`)
132-}
133-
134-type UnicastHandler struct {
135- *context
136+ ctx.logger.Errorf("could not store notification: %v", err)
137+ return nil, ErrCouldNotStoreNotification
138+ }
139+
140+ ctx.broker.Broadcast(chanId)
141+ return nil, nil
142 }
143
144 func checkUnicast(ucast *Unicast) (time.Time, *APIError) {
145@@ -363,62 +376,34 @@
146 return base64.StdEncoding.EncodeToString(uuid.NewUUID())
147 }
148
149-func (h *UnicastHandler) doUnicast(sto store.PendingStore, ucast *Unicast) *APIError {
150+func doUnicast(ctx *context, sto store.PendingStore, parsedBodyObj interface{}) (map[string]interface{}, *APIError) {
151+ ucast := parsedBodyObj.(*Unicast)
152 expire, apiErr := checkUnicast(ucast)
153 if apiErr != nil {
154- return apiErr
155+ return nil, apiErr
156 }
157 chanId, err := sto.GetInternalChannelIdFromToken(ucast.Token, ucast.AppId, ucast.UserId, ucast.DeviceId)
158 if err != nil {
159 switch err {
160 case store.ErrUnknownToken:
161- return ErrUnknownToken
162+ return nil, ErrUnknownToken
163 case store.ErrUnauthorized:
164- return ErrUnauthorized
165+ return nil, ErrUnauthorized
166 default:
167- h.logger.Errorf("could not resolve token: %v", err)
168- return ErrCouldNotResolveToken
169+ ctx.logger.Errorf("could not resolve token: %v", err)
170+ return nil, ErrCouldNotResolveToken
171 }
172 }
173
174 msgId := generateMsgId()
175 err = sto.AppendToUnicastChannel(chanId, ucast.AppId, ucast.Data, msgId, expire)
176 if err != nil {
177- h.logger.Errorf("could not store notification: %v", err)
178- return ErrCouldNotStoreNotification
179- }
180-
181- h.broker.Unicast(chanId)
182- return nil
183-}
184-
185-func (h *UnicastHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
186- var apiErr *APIError
187- defer func() {
188- if apiErr != nil {
189- RespondError(writer, apiErr)
190- }
191- }()
192-
193- unicast := &Unicast{}
194-
195- sto, apiErr := h.prepare(writer, request, unicast)
196- if apiErr != nil {
197- return
198- }
199- defer sto.Close()
200-
201- apiErr = h.doUnicast(sto, unicast)
202- if apiErr != nil {
203- return
204- }
205-
206- writer.Header().Set("Content-Type", "application/json")
207- fmt.Fprintf(writer, `{"ok":true}`)
208-}
209-
210-type RegisterHandler struct {
211- *context
212+ ctx.logger.Errorf("could not store notification: %v", err)
213+ return nil, ErrCouldNotStoreNotification
214+ }
215+
216+ ctx.broker.Unicast(chanId)
217+ return nil, nil
218 }
219
220 func checkRegister(reg *Registration) *APIError {
221@@ -428,49 +413,18 @@
222 return nil
223 }
224
225-func (h *RegisterHandler) doRegister(sto store.PendingStore, reg *Registration) (string, *APIError) {
226+func doRegister(ctx *context, sto store.PendingStore, parsedBodyObj interface{}) (map[string]interface{}, *APIError) {
227+ reg := parsedBodyObj.(*Registration)
228 apiErr := checkRegister(reg)
229 if apiErr != nil {
230- return "", apiErr
231+ return nil, apiErr
232 }
233 token, err := sto.Register(reg.DeviceId, reg.AppId)
234 if err != nil {
235- h.logger.Errorf("could not make a token: %v", err)
236- return "", ErrCouldNotMakeToken
237- }
238- return token, nil
239-}
240-
241-func (h *RegisterHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
242- var apiErr *APIError
243- defer func() {
244- if apiErr != nil {
245- RespondError(writer, apiErr)
246- }
247- }()
248-
249- reg := &Registration{}
250-
251- sto, apiErr := h.prepare(writer, request, reg)
252- if apiErr != nil {
253- return
254- }
255- defer sto.Close()
256-
257- token, apiErr := h.doRegister(sto, reg)
258- if apiErr != nil {
259- return
260- }
261-
262- writer.Header().Set("Content-Type", "application/json")
263- res, err := json.Marshal(map[string]interface{}{
264- "ok": true,
265- "token": token,
266- })
267- if err != nil {
268- panic(fmt.Errorf("couldn't marshal our own response: %v", err))
269- }
270- writer.Write(res)
271+ ctx.logger.Errorf("could not make a token: %v", err)
272+ return nil, ErrCouldNotMakeToken
273+ }
274+ return map[string]interface{}{"token": token}, nil
275 }
276
277 // MakeHandlersMux makes a handler that dispatches for the various API endpoints.
278@@ -481,8 +435,20 @@
279 logger: logger,
280 }
281 mux := http.NewServeMux()
282- mux.Handle("/broadcast", &BroadcastHandler{context: ctx})
283- mux.Handle("/notify", &UnicastHandler{context: ctx})
284- mux.Handle("/register", &RegisterHandler{context: ctx})
285+ mux.Handle("/broadcast", &JSONPostHandler{
286+ context: ctx,
287+ parsingBodyObj: func() interface{} { return &Broadcast{} },
288+ doHandle: doBroadcast,
289+ })
290+ mux.Handle("/notify", &JSONPostHandler{
291+ context: ctx,
292+ parsingBodyObj: func() interface{} { return &Unicast{} },
293+ doHandle: doUnicast,
294+ })
295+ mux.Handle("/register", &JSONPostHandler{
296+ context: ctx,
297+ parsingBodyObj: func() interface{} { return &Registration{} },
298+ doHandle: doRegister,
299+ })
300 return mux
301 }
302
303=== modified file 'server/api/handlers_test.go'
304--- server/api/handlers_test.go 2014-06-25 11:00:15 +0000
305+++ server/api/handlers_test.go 2014-06-25 11:08:44 +0000
306@@ -162,14 +162,15 @@
307 func (s *handlersSuite) TestDoBroadcast(c *C) {
308 sto := store.NewInMemoryPendingStore()
309 bsend := &checkBrokerSending{store: sto}
310- bh := &BroadcastHandler{&context{nil, bsend, nil}}
311+ ctx := &context{nil, bsend, nil}
312 payload := json.RawMessage(`{"a": 1}`)
313- apiErr := bh.doBroadcast(sto, &Broadcast{
314+ res, apiErr := doBroadcast(ctx, sto, &Broadcast{
315 Channel: "system",
316 ExpireOn: future,
317 Data: payload,
318 })
319- c.Check(apiErr, IsNil)
320+ c.Assert(apiErr, IsNil)
321+ c.Assert(res, IsNil)
322 c.Check(bsend.err, IsNil)
323 c.Check(bsend.chanId, Equals, store.SystemInternalChannelId)
324 c.Check(bsend.top, Equals, int64(1))
325@@ -178,8 +179,7 @@
326
327 func (s *handlersSuite) TestDoBroadcastUnknownChannel(c *C) {
328 sto := store.NewInMemoryPendingStore()
329- bh := &BroadcastHandler{}
330- apiErr := bh.doBroadcast(sto, &Broadcast{
331+ _, apiErr := doBroadcast(nil, sto, &Broadcast{
332 Channel: "unknown",
333 ExpireOn: future,
334 Data: json.RawMessage(`{"a": 1}`),
335@@ -224,8 +224,7 @@
336 return errors.New("other")
337 },
338 }
339- bh := &BroadcastHandler{}
340- apiErr := bh.doBroadcast(sto, &Broadcast{
341+ _, apiErr := doBroadcast(nil, sto, &Broadcast{
342 Channel: "system",
343 ExpireOn: future,
344 Data: json.RawMessage(`{"a": 1}`),
345@@ -244,8 +243,7 @@
346 },
347 }
348 ctx := &context{logger: s.testlog}
349- bh := &BroadcastHandler{ctx}
350- apiErr := bh.doBroadcast(sto, &Broadcast{
351+ _, apiErr := doBroadcast(ctx, sto, &Broadcast{
352 Channel: "system",
353 ExpireOn: future,
354 Data: json.RawMessage(`{"a": 1}`),
355@@ -316,16 +314,17 @@
356 }
357 sto := store.NewInMemoryPendingStore()
358 bsend := &checkBrokerSending{store: sto}
359- bh := &UnicastHandler{&context{nil, bsend, nil}}
360+ ctx := &context{nil, bsend, nil}
361 payload := json.RawMessage(`{"a": 1}`)
362- apiErr := bh.doUnicast(sto, &Unicast{
363+ res, apiErr := doUnicast(ctx, sto, &Unicast{
364 UserId: "user1",
365 DeviceId: "DEV1",
366 AppId: "app1",
367 ExpireOn: future,
368 Data: payload,
369 })
370- c.Check(apiErr, IsNil)
371+ c.Assert(apiErr, IsNil)
372+ c.Check(res, IsNil)
373 c.Check(bsend.err, IsNil)
374 c.Check(bsend.chanId, Equals, store.UnicastInternalChannelId("user1", "DEV1"))
375 c.Check(bsend.top, Equals, int64(0))
376@@ -340,8 +339,7 @@
377
378 func (s *handlersSuite) TestDoUnicastMissingIdField(c *C) {
379 sto := store.NewInMemoryPendingStore()
380- bh := &UnicastHandler{}
381- apiErr := bh.doUnicast(sto, &Unicast{
382+ _, apiErr := doUnicast(nil, sto, &Unicast{
383 ExpireOn: future,
384 Data: json.RawMessage(`{"a": 1}`),
385 })
386@@ -359,8 +357,7 @@
387 },
388 }
389 ctx := &context{logger: s.testlog}
390- bh := &UnicastHandler{ctx}
391- apiErr := bh.doUnicast(sto, &Unicast{
392+ _, apiErr := doUnicast(ctx, sto, &Unicast{
393 UserId: "user1",
394 DeviceId: "DEV1",
395 AppId: "app1",
396@@ -383,24 +380,23 @@
397 },
398 }
399 ctx := &context{logger: s.testlog}
400- bh := &UnicastHandler{ctx}
401 u := &Unicast{
402 Token: "tok",
403 AppId: "app1",
404 ExpireOn: future,
405 Data: json.RawMessage(`{"a": 1}`),
406 }
407- apiErr := bh.doUnicast(sto, u)
408+ _, apiErr := doUnicast(ctx, sto, u)
409 c.Check(apiErr, Equals, ErrCouldNotResolveToken)
410 c.Check(s.testlog.Captured(), Equals, "ERROR could not resolve token: fail\n")
411 s.testlog.ResetCapture()
412
413 fail = store.ErrUnknownToken
414- apiErr = bh.doUnicast(sto, u)
415+ _, apiErr = doUnicast(ctx, sto, u)
416 c.Check(apiErr, Equals, ErrUnknownToken)
417 c.Check(s.testlog.Captured(), Equals, "")
418 fail = store.ErrUnauthorized
419- apiErr = bh.doUnicast(sto, u)
420+ _, apiErr = doUnicast(ctx, sto, u)
421 c.Check(apiErr, Equals, ErrUnauthorized)
422 c.Check(s.testlog.Captured(), Equals, "")
423 }
424@@ -533,7 +529,11 @@
425 return store.NewInMemoryPendingStore(), nil
426 }
427 ctx := &context{stoForReq, nil, nil}
428- testServer := httptest.NewServer(&BroadcastHandler{ctx})
429+ testServer := httptest.NewServer(&JSONPostHandler{
430+ context: ctx,
431+ parsingBodyObj: func() interface{} { return &Broadcast{} },
432+ doHandle: doBroadcast,
433+ })
434 defer testServer.Close()
435
436 packedMessage := []byte(`{"channel": "system"}`)
437@@ -554,7 +554,10 @@
438 return store.NewInMemoryPendingStore(), nil
439 }
440 ctx := &context{stoForReq, nil, nil}
441- testServer := httptest.NewServer(&BroadcastHandler{ctx})
442+ testServer := httptest.NewServer(&JSONPostHandler{
443+ context: ctx,
444+ parsingBodyObj: func() interface{} { return &Broadcast{} },
445+ })
446 defer testServer.Close()
447
448 packedMessage := []byte("{some bogus-message: ")
449@@ -571,7 +574,7 @@
450 }
451
452 func (s *handlersSuite) TestCannotBroadcastTooBigMessages(c *C) {
453- testServer := httptest.NewServer(&BroadcastHandler{})
454+ testServer := httptest.NewServer(&JSONPostHandler{})
455 defer testServer.Close()
456
457 bigString := strings.Repeat("a", MaxRequestBodyBytes)
458@@ -589,7 +592,7 @@
459 }
460
461 func (s *handlersSuite) TestCannotBroadcastWithoutContentLength(c *C) {
462- testServer := httptest.NewServer(&BroadcastHandler{})
463+ testServer := httptest.NewServer(&JSONPostHandler{})
464 defer testServer.Close()
465
466 dataString := `{"foo":"bar"}`
467@@ -607,7 +610,7 @@
468 }
469
470 func (s *handlersSuite) TestCannotBroadcastEmptyMessages(c *C) {
471- testServer := httptest.NewServer(&BroadcastHandler{})
472+ testServer := httptest.NewServer(&JSONPostHandler{})
473 defer testServer.Close()
474
475 packedMessage := make([]byte, 0)
476@@ -624,7 +627,7 @@
477 }
478
479 func (s *handlersSuite) TestCannotBroadcastNonJSONMessages(c *C) {
480- testServer := httptest.NewServer(&BroadcastHandler{})
481+ testServer := httptest.NewServer(&JSONPostHandler{})
482 defer testServer.Close()
483
484 dataString := `{"foo":"bar"}`
485@@ -642,7 +645,7 @@
486 }
487
488 func (s *handlersSuite) TestCannotBroadcastNonPostMessages(c *C) {
489- testServer := httptest.NewServer(&BroadcastHandler{})
490+ testServer := httptest.NewServer(&JSONPostHandler{})
491 defer testServer.Close()
492
493 dataString := `{"foo":"bar"}`
494@@ -703,40 +706,6 @@
495 c.Check(notifications, HasLen, 1)
496 }
497
498-func (s *handlersSuite) TestCannotUnicastTooBigMessages(c *C) {
499- testServer := httptest.NewServer(&UnicastHandler{})
500- defer testServer.Close()
501-
502- bigString := strings.Repeat("a", MaxRequestBodyBytes)
503- dataString := fmt.Sprintf(`"%v"`, bigString)
504-
505- request := newPostRequest("/", &Unicast{
506- ExpireOn: future,
507- Data: json.RawMessage([]byte(dataString)),
508- }, testServer)
509-
510- response, err := s.client.Do(request)
511- c.Assert(err, IsNil)
512- checkError(c, response, ErrRequestBodyTooLarge)
513-}
514-
515-func (s *handlersSuite) TestCannotUnicastWithMissingFields(c *C) {
516- stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
517- return store.NewInMemoryPendingStore(), nil
518- }
519- ctx := &context{stoForReq, nil, nil}
520- testServer := httptest.NewServer(&UnicastHandler{ctx})
521- defer testServer.Close()
522-
523- request := newPostRequest("/", &Unicast{
524- Data: json.RawMessage(`{"foo":"bar"}`),
525- }, testServer)
526-
527- response, err := s.client.Do(request)
528- c.Assert(err, IsNil)
529- checkError(c, response, ErrMissingIdField)
530-}
531-
532 func (s *handlersSuite) TestCheckRegister(c *C) {
533 registration := func() *Registration {
534 return &Registration{
535@@ -761,10 +730,9 @@
536
537 func (s *handlersSuite) TestDoRegisterMissingIdField(c *C) {
538 sto := store.NewInMemoryPendingStore()
539- rh := &RegisterHandler{}
540- token, apiErr := rh.doRegister(sto, &Registration{})
541+ token, apiErr := doRegister(nil, sto, &Registration{})
542 c.Check(apiErr, Equals, ErrMissingIdField)
543- c.Check(token, Equals, "")
544+ c.Check(token, IsNil)
545 }
546
547 func (s *handlersSuite) TestDoRegisterCouldNotMakeToken(c *C) {
548@@ -778,8 +746,7 @@
549 },
550 }
551 ctx := &context{logger: s.testlog}
552- rh := &RegisterHandler{ctx}
553- _, apiErr := rh.doRegister(sto, &Registration{
554+ _, apiErr := doRegister(ctx, sto, &Registration{
555 DeviceId: "DEV1",
556 AppId: "app1",
557 })
558@@ -842,36 +809,3 @@
559 c.Check(top, Equals, int64(0))
560 c.Check(notifications, HasLen, 1)
561 }
562-
563-func (s *handlersSuite) TestCannotRegisterWithMissingFields(c *C) {
564- stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
565- return store.NewInMemoryPendingStore(), nil
566- }
567- ctx := &context{stoForReq, nil, nil}
568- testServer := httptest.NewServer(&RegisterHandler{ctx})
569- defer testServer.Close()
570-
571- request := newPostRequest("/", &Registration{
572- DeviceId: "DEV1",
573- }, testServer)
574-
575- response, err := s.client.Do(request)
576- c.Assert(err, IsNil)
577- checkError(c, response, ErrMissingIdField)
578-}
579-
580-func (s *handlersSuite) TestCannotRegisterWithNonPOST(c *C) {
581- stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) {
582- return store.NewInMemoryPendingStore(), nil
583- }
584- ctx := &context{stoForReq, nil, nil}
585- testServer := httptest.NewServer(&RegisterHandler{ctx})
586- defer testServer.Close()
587-
588- request, err := http.NewRequest("GET", testServer.URL, nil)
589- c.Assert(err, IsNil)
590-
591- response, err := s.client.Do(request)
592- c.Assert(err, IsNil)
593- checkError(c, response, ErrWrongRequestMethod)
594-}

Subscribers

People subscribed via source and target branches