omaha: add complete http handler implementation

The handler is driven by something implementing the 'Updater' interface.
This commit is contained in:
Michael Marineau 2015-07-25 19:00:42 -07:00
parent 2cf1d8f13e
commit f33cb66abb
3 changed files with 226 additions and 1 deletions

134
omaha/handler.go Normal file
View 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
View 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)
}
}

View File

@ -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
}