omaha: add complete http handler implementation
The handler is driven by something implementing the 'Updater' interface.
This commit is contained in:
parent
2cf1d8f13e
commit
f33cb66abb
3 changed files with 226 additions and 1 deletions
134
omaha/handler.go
Normal file
134
omaha/handler.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type OmahaHandler struct {
|
||||
Updater
|
||||
}
|
||||
|
||||
func (o *OmahaHandler) ServeHTTP(w http.ResponseWriter, httpReq *http.Request) {
|
||||
if httpReq.Method != "POST" {
|
||||
log.Printf("omaha: Unexpected HTTP method: %s", httpReq.Method)
|
||||
http.Error(w, "Expected a POST", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// A request over 1M in size is certainly bogus.
|
||||
reader := http.MaxBytesReader(w, httpReq.Body, 1024*1024)
|
||||
|
||||
decoder := xml.NewDecoder(reader)
|
||||
var omahaReq Request
|
||||
if err := decoder.Decode(&omahaReq); err != nil {
|
||||
log.Printf("omaha: Failed decoding XML: %v", err)
|
||||
http.Error(w, "Invalid XML", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if omahaReq.Protocol != "3.0" {
|
||||
log.Printf("omaha: Unexpected protocol: %q", omahaReq.Protocol)
|
||||
http.Error(w, "Omaha 3.0 Required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
httpStatus := 0
|
||||
omahaResp := NewResponse()
|
||||
for _, appReq := range omahaReq.Apps {
|
||||
appResp := o.serveApp(omahaResp, httpReq, &omahaReq, appReq)
|
||||
if appResp.Status == AppOK {
|
||||
// HTTP is ok if any app is ok.
|
||||
httpStatus = http.StatusOK
|
||||
} else if httpStatus == 0 {
|
||||
// If no app is ok HTTP will use the first error.
|
||||
if appResp.Status == AppInternalError {
|
||||
httpStatus = http.StatusInternalServerError
|
||||
} else {
|
||||
httpStatus = http.StatusBadRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if httpStatus == 0 {
|
||||
httpStatus = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/xml; charset=utf-8")
|
||||
w.WriteHeader(httpStatus)
|
||||
|
||||
if _, err := w.Write([]byte(xml.Header)); err != nil {
|
||||
log.Printf("omaha: Failed writing response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
encoder := xml.NewEncoder(w)
|
||||
encoder.Indent("", "\t")
|
||||
if err := encoder.Encode(omahaResp); err != nil {
|
||||
log.Printf("omaha: Failed encoding response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OmahaHandler) serveApp(omahaResp *Response, httpReq *http.Request, omahaReq *Request, appReq *AppRequest) *AppResponse {
|
||||
if err := o.CheckApp(omahaReq, appReq); err != nil {
|
||||
if appStatus, ok := err.(AppStatus); ok {
|
||||
return omahaResp.AddApp(appReq.Id, appStatus)
|
||||
}
|
||||
log.Printf("omaha: CheckApp failed: %v", err)
|
||||
return omahaResp.AddApp(appReq.Id, AppInternalError)
|
||||
}
|
||||
|
||||
appResp := omahaResp.AddApp(appReq.Id, AppOK)
|
||||
if appReq.UpdateCheck != nil {
|
||||
o.checkUpdate(appResp, httpReq, omahaReq, appReq)
|
||||
}
|
||||
|
||||
if appReq.Ping != nil {
|
||||
o.Ping(omahaReq, appReq)
|
||||
appResp.AddPing()
|
||||
}
|
||||
|
||||
for _, event := range appReq.Events {
|
||||
o.Event(omahaReq, appReq, event)
|
||||
appResp.AddEvent()
|
||||
}
|
||||
|
||||
return appResp
|
||||
}
|
||||
|
||||
func (o *OmahaHandler) checkUpdate(appResp *AppResponse, httpReq *http.Request, omahaReq *Request, appReq *AppRequest) {
|
||||
update, err := o.CheckUpdate(omahaReq, appReq)
|
||||
if err != nil {
|
||||
if updateStatus, ok := err.(UpdateStatus); ok {
|
||||
appResp.AddUpdateCheck(updateStatus)
|
||||
} else {
|
||||
log.Printf("omaha: CheckUpdate failed: %v", err)
|
||||
appResp.AddUpdateCheck(UpdateInternalError)
|
||||
}
|
||||
} else if update != nil {
|
||||
u := appResp.AddUpdateCheck(UpdateOK)
|
||||
fillUpdate(u, update, httpReq)
|
||||
} else {
|
||||
appResp.AddUpdateCheck(NoUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
func fillUpdate(u *UpdateResponse, update *Update, httpReq *http.Request) {
|
||||
u.URLs = update.URLs([]string{"http://" + httpReq.Host})
|
||||
u.Manifest = &update.Manifest
|
||||
}
|
68
omaha/handler_test.go
Normal file
68
omaha/handler_test.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2013-2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package omaha
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/diff"
|
||||
)
|
||||
|
||||
const (
|
||||
testAppId = "{27BD862E-8AE8-4886-A055-F7F1A6460627}"
|
||||
testAppVer = "1.0.0"
|
||||
)
|
||||
|
||||
var (
|
||||
nilRequest *Request
|
||||
nilResponse *Response
|
||||
)
|
||||
|
||||
func init() {
|
||||
nilRequest = NewRequest()
|
||||
nilRequest.AddApp(testAppId, testAppVer)
|
||||
nilResponse = NewResponse()
|
||||
nilResponse.AddApp(testAppId, AppOK)
|
||||
}
|
||||
|
||||
func compareXML(a, b interface{}) error {
|
||||
aXml, err := xml.MarshalIndent(a, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bXml, err := xml.MarshalIndent(b, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d := diff.Diff(string(aXml), string(bXml)); d != "" {
|
||||
err := fmt.Errorf("Unexpected XML:\n%s", d)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHandleNilRequest(t *testing.T) {
|
||||
handler := OmahaHandler{UpdaterStub{}}
|
||||
response := NewResponse()
|
||||
handler.serveApp(response, nil, nilRequest, nilRequest.Apps[0])
|
||||
if err := compareXML(nilResponse, response); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
|
@ -45,6 +45,29 @@ func (u *Update) URLs(prefixes []string) []*URL {
|
|||
return urls
|
||||
}
|
||||
|
||||
// Updater provides a common interface for any backend that can respond to
|
||||
// update requests made to an Omaha server.
|
||||
type Updater interface {
|
||||
Update(os *OS, app *AppRequest) (*Update, error)
|
||||
CheckApp(req *Request, app *AppRequest) error
|
||||
CheckUpdate(req *Request, app *AppRequest) (*Update, error)
|
||||
Event(req *Request, app *AppRequest, event *EventRequest)
|
||||
Ping(req *Request, app *AppRequest)
|
||||
}
|
||||
|
||||
type UpdaterStub struct{}
|
||||
|
||||
func (u UpdaterStub) CheckApp(req *Request, app *AppRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u UpdaterStub) CheckUpdate(req *Request, app *AppRequest) (*Update, error) {
|
||||
return nil, NoUpdate
|
||||
}
|
||||
|
||||
func (u UpdaterStub) Event(req *Request, app *AppRequest, event *EventRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
func (u UpdaterStub) Ping(req *Request, app *AppRequest) {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue