~ci-train-bot/ubuntu-push/ubuntu-push-ubuntu-yakkety-2021

« back to all changes in this revision

Viewing changes to server/api/handlers.go

  • Committer: john.lenton at canonical
  • Date: 2014-03-20 12:15:47 UTC
  • mfrom: (81 ubuntu-push)
  • mto: This revision was merged to the branch mainline in revision 85.
  • Revision ID: john.lenton@canonical.com-20140320121547-i544is8492ulfykx
merged trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
        "encoding/json"
23
23
        "fmt"
24
24
        "io"
 
25
        "net/http"
 
26
        "time"
 
27
 
25
28
        "launchpad.net/ubuntu-push/logger"
26
29
        "launchpad.net/ubuntu-push/server/broker"
27
30
        "launchpad.net/ubuntu-push/server/store"
28
 
        "net/http"
29
31
)
30
32
 
31
33
const MaxRequestBodyBytes = 4 * 1024
45
47
const (
46
48
        ioError        = "io-error"
47
49
        invalidRequest = "invalid-request"
48
 
        unknownChannel = "unknown channel"
 
50
        unknownChannel = "unknown-channel"
49
51
        unavailable    = "unavailable"
50
52
        internalError  = "internal"
51
53
)
96
98
                invalidRequest,
97
99
                "Missing data field",
98
100
        }
 
101
        ErrInvalidExpiration = &APIError{
 
102
                http.StatusBadRequest,
 
103
                invalidRequest,
 
104
                "Invalid expiration date",
 
105
        }
 
106
        ErrPastExpiration = &APIError{
 
107
                http.StatusBadRequest,
 
108
                invalidRequest,
 
109
                "Past expiration date",
 
110
        }
99
111
        ErrUnknownChannel = &APIError{
100
112
                http.StatusBadRequest,
101
113
                unknownChannel,
106
118
                internalError,
107
119
                "Unknown error",
108
120
        }
 
121
        ErrStoreUnavailable = &APIError{
 
122
                http.StatusServiceUnavailable,
 
123
                unavailable,
 
124
                "Message store unavailable",
 
125
        }
109
126
        ErrCouldNotStoreNotification = &APIError{
110
127
                http.StatusServiceUnavailable,
111
128
                unavailable,
121
138
 
122
139
// Broadcast request JSON object.
123
140
type Broadcast struct {
124
 
        Channel     string          `json:"channel"`
125
 
        ExpireAfter uint8           `json:"expire_after"`
126
 
        Data        json.RawMessage `json:"data"`
 
141
        Channel  string          `json:"channel"`
 
142
        ExpireOn string          `json:"expire_on"`
 
143
        Data     json.RawMessage `json:"data"`
127
144
}
128
145
 
129
 
func respondError(writer http.ResponseWriter, apiErr *APIError) {
 
146
// RespondError writes back a JSON error response for a APIError.
 
147
func RespondError(writer http.ResponseWriter, apiErr *APIError) {
130
148
        wireError, err := json.Marshal(apiErr)
131
149
        if err != nil {
132
150
                panic(fmt.Errorf("couldn't marshal our own errors: %v", err))
136
154
        writer.Write(wireError)
137
155
}
138
156
 
139
 
func checkContentLength(request *http.Request) *APIError {
 
157
func checkContentLength(request *http.Request, maxBodySize int64) *APIError {
140
158
        if request.ContentLength == -1 {
141
159
                return ErrNoContentLengthProvided
142
160
        }
143
161
        if request.ContentLength == 0 {
144
162
                return ErrRequestBodyEmpty
145
163
        }
146
 
        if request.ContentLength > MaxRequestBodyBytes {
 
164
        if request.ContentLength > maxBodySize {
147
165
                return ErrRequestBodyTooLarge
148
166
        }
149
167
        return nil
150
168
}
151
169
 
152
 
func checkRequestAsPost(request *http.Request) *APIError {
153
 
        if err := checkContentLength(request); err != nil {
 
170
func checkRequestAsPost(request *http.Request, maxBodySize int64) *APIError {
 
171
        if err := checkContentLength(request, maxBodySize); err != nil {
154
172
                return err
155
173
        }
156
174
        if request.Header.Get("Content-Type") != JSONMediaType {
162
180
        return nil
163
181
}
164
182
 
165
 
func readBody(request *http.Request) ([]byte, *APIError) {
166
 
        if err := checkRequestAsPost(request); err != nil {
 
183
// ReadBody checks that a POST request is well-formed and reads its body.
 
184
func ReadBody(request *http.Request, maxBodySize int64) ([]byte, *APIError) {
 
185
        if err := checkRequestAsPost(request, maxBodySize); err != nil {
167
186
                return nil, err
168
187
        }
169
188
 
177
196
        return body, nil
178
197
}
179
198
 
180
 
func checkBroadcast(bcast *Broadcast) *APIError {
 
199
var zeroTime = time.Time{}
 
200
 
 
201
func checkBroadcast(bcast *Broadcast) (time.Time, *APIError) {
181
202
        if len(bcast.Data) == 0 {
182
 
                return ErrMissingData
183
 
        }
184
 
        return nil
185
 
}
186
 
 
187
 
// state holds the interfaces to delegate to serving requests
188
 
type state struct {
189
 
        store  store.PendingStore
190
 
        broker broker.BrokerSending
191
 
        logger logger.Logger
192
 
}
193
 
 
194
 
type BroadcastHandler state
195
 
 
196
 
func (h *BroadcastHandler) doBroadcast(bcast *Broadcast) *APIError {
197
 
        apiErr := checkBroadcast(bcast)
 
203
                return zeroTime, ErrMissingData
 
204
        }
 
205
        expire, err := time.Parse(time.RFC3339, bcast.ExpireOn)
 
206
        if err != nil {
 
207
                return zeroTime, ErrInvalidExpiration
 
208
        }
 
209
        if expire.Before(time.Now()) {
 
210
                return zeroTime, ErrPastExpiration
 
211
        }
 
212
        return expire, nil
 
213
}
 
214
 
 
215
type StoreForRequest func(w http.ResponseWriter, request *http.Request) (store.PendingStore, error)
 
216
 
 
217
// context holds the interfaces to delegate to serving requests
 
218
type context struct {
 
219
        storeForRequest StoreForRequest
 
220
        broker          broker.BrokerSending
 
221
        logger          logger.Logger
 
222
}
 
223
 
 
224
func (ctx *context) getStore(w http.ResponseWriter, request *http.Request) (store.PendingStore, *APIError) {
 
225
        sto, err := ctx.storeForRequest(w, request)
 
226
        if err != nil {
 
227
                apiErr, ok := err.(*APIError)
 
228
                if ok {
 
229
                        return nil, apiErr
 
230
                }
 
231
                ctx.logger.Errorf("failed to get store: %v", err)
 
232
                return nil, ErrUnknown
 
233
        }
 
234
        return sto, nil
 
235
}
 
236
 
 
237
type BroadcastHandler struct {
 
238
        *context
 
239
}
 
240
 
 
241
func (h *BroadcastHandler) doBroadcast(sto store.PendingStore, bcast *Broadcast) *APIError {
 
242
        expire, apiErr := checkBroadcast(bcast)
198
243
        if apiErr != nil {
199
244
                return apiErr
200
245
        }
201
 
        chanId, err := h.store.GetInternalChannelId(bcast.Channel)
 
246
        chanId, err := sto.GetInternalChannelId(bcast.Channel)
202
247
        if err != nil {
203
248
                switch err {
204
249
                case store.ErrUnknownChannel:
207
252
                        return ErrUnknown
208
253
                }
209
254
        }
210
 
        // xxx ignoring expiration for now
211
 
        err = h.store.AppendToChannel(chanId, bcast.Data)
 
255
        err = sto.AppendToChannel(chanId, bcast.Data, expire)
212
256
        if err != nil {
213
 
                // assume this for now
 
257
                h.logger.Errorf("could not store notification: %v", err)
214
258
                return ErrCouldNotStoreNotification
215
259
        }
216
260
 
219
263
}
220
264
 
221
265
func (h *BroadcastHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
222
 
        body, apiErr := readBody(request)
223
 
 
224
 
        if apiErr != nil {
225
 
                respondError(writer, apiErr)
226
 
                return
227
 
        }
 
266
        var apiErr *APIError
 
267
        defer func() {
 
268
                if apiErr != nil {
 
269
                        RespondError(writer, apiErr)
 
270
                }
 
271
        }()
 
272
 
 
273
        body, apiErr := ReadBody(request, MaxRequestBodyBytes)
 
274
        if apiErr != nil {
 
275
                return
 
276
        }
 
277
 
 
278
        sto, apiErr := h.getStore(writer, request)
 
279
        if apiErr != nil {
 
280
                return
 
281
        }
 
282
        defer sto.Close()
228
283
 
229
284
        broadcast := &Broadcast{}
230
285
        err := json.Unmarshal(body, broadcast)
231
 
 
232
286
        if err != nil {
233
 
                respondError(writer, ErrMalformedJSONObject)
 
287
                apiErr = ErrMalformedJSONObject
234
288
                return
235
289
        }
236
290
 
237
 
        apiErr = h.doBroadcast(broadcast)
 
291
        apiErr = h.doBroadcast(sto, broadcast)
238
292
        if apiErr != nil {
239
 
                respondError(writer, apiErr)
240
293
                return
241
294
        }
242
295
 
245
298
}
246
299
 
247
300
// MakeHandlersMux makes a handler that dispatches for the various API endpoints.
248
 
func MakeHandlersMux(store store.PendingStore, broker broker.BrokerSending, logger logger.Logger) http.Handler {
 
301
func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux {
 
302
        ctx := &context{
 
303
                storeForRequest: storeForRequest,
 
304
                broker:          broker,
 
305
                logger:          logger,
 
306
        }
249
307
        mux := http.NewServeMux()
250
 
        mux.Handle("/broadcast", &BroadcastHandler{
251
 
                store:  store,
252
 
                broker: broker,
253
 
                logger: logger,
254
 
        })
 
308
        mux.Handle("/broadcast", &BroadcastHandler{context: ctx})
255
309
        return mux
256
310
}