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
|
return urls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updater provides a common interface for any backend that can respond to
|
||||||
|
// update requests made to an Omaha server.
|
||||||
type Updater interface {
|
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