mirror of
https://github.com/kerberos-io/onvif.git
synced 2026-08-23 15:08:33 +00:00
refactor(onvif): add SendSoapWithOptions as the variadic workhorse
Future per-call knobs (timeout, context, custom namespaces) will arrive sooner than later. Adding them as SendSoapWithHeader2 / 3 / N would turn the Device API into a combinatorial mess; adding them as new required positional args breaks every existing consumer. SendSoapWithOptions accepts variadic SoapOption values. SendSoap and SendSoapWithHeader become one-line delegates so all current callers keep their signatures, and the public surface is purely additive. Only WithHeader ships today — wiring the AXIS ReferenceParameters path through the same plumbing. New options land as WithX constructors in this file rather than as new Device methods.
This commit is contained in:
31
Device.go
31
Device.go
@@ -335,7 +335,7 @@ func (dev *Device) GetEndpointByRequestStruct(requestStruct interface{}) (string
|
||||
|
||||
// SendSoap POSTs the given body wrapped in a SOAP envelope.
|
||||
func (dev Device) SendSoap(endpoint string, xmlRequestBody string) (*http.Response, error) {
|
||||
return dev.SendSoapWithHeader(endpoint, xmlRequestBody, "")
|
||||
return dev.SendSoapWithOptions(endpoint, xmlRequestBody)
|
||||
}
|
||||
|
||||
// SendSoapWithHeader is SendSoap plus arbitrary inner-Header XML —
|
||||
@@ -350,11 +350,38 @@ func (dev Device) SendSoap(endpoint string, xmlRequestBody string) (*http.Respon
|
||||
// sourced data inside it. Malformed XML returns an error before any
|
||||
// request is made.
|
||||
func (dev Device) SendSoapWithHeader(endpoint, xmlRequestBody, xmlHeaderContent string) (*http.Response, error) {
|
||||
return dev.SendSoapWithOptions(endpoint, xmlRequestBody, WithHeader(xmlHeaderContent))
|
||||
}
|
||||
|
||||
// SoapOption tweaks a single SendSoapWithOptions call. New options
|
||||
// (per-call timeout, context, custom envelope namespaces, ...) should
|
||||
// be added as WithX constructors here rather than as new method
|
||||
// variants on Device.
|
||||
type SoapOption func(*soapConfig)
|
||||
|
||||
type soapConfig struct {
|
||||
headerContent string
|
||||
}
|
||||
|
||||
// WithHeader adds inner-Header XML to the envelope. See
|
||||
// SendSoapWithHeader for the content contract.
|
||||
func WithHeader(xml string) SoapOption {
|
||||
return func(c *soapConfig) { c.headerContent = xml }
|
||||
}
|
||||
|
||||
// SendSoapWithOptions is the workhorse behind SendSoap and
|
||||
// SendSoapWithHeader; call it directly when you need to combine
|
||||
// options or pass options not surfaced by the convenience wrappers.
|
||||
func (dev Device) SendSoapWithOptions(endpoint, xmlRequestBody string, opts ...SoapOption) (*http.Response, error) {
|
||||
var cfg soapConfig
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
soap := gosoap.NewEmptySOAP()
|
||||
soap.AddStringBodyContent(xmlRequestBody)
|
||||
soap.AddRootNamespaces(Xlmns)
|
||||
soap.AddAction()
|
||||
if err := addHeaderChildren(&soap, xmlHeaderContent); err != nil {
|
||||
if err := addHeaderChildren(&soap, cfg.headerContent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dev.params.Username != "" && dev.params.Password != "" {
|
||||
|
||||
@@ -103,6 +103,49 @@ func TestDevice_SendSoapWithHeader_AcceptsMultipleTopLevelChildren(t *testing.T)
|
||||
assert.Contains(t, headerSlice, "Bar")
|
||||
}
|
||||
|
||||
// SendSoapWithOptions is the variadic shape that future per-call
|
||||
// options (timeout, context, ...) will hang off. SendSoap and
|
||||
// SendSoapWithHeader stay as thin convenience wrappers so existing
|
||||
// callers are not forced to migrate.
|
||||
func TestDevice_SendSoapWithOptions_WithHeaderMatchesSendSoapWithHeader(t *testing.T) {
|
||||
const headerXML = `<dom0:SubscriptionId xmlns:dom0="urn:test">42</dom0:SubscriptionId>`
|
||||
const bodyXML = `<tev:PullMessages xmlns:tev="http://www.onvif.org/ver10/events/wsdl"/>`
|
||||
|
||||
var captured string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
captured = string(b)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
dev := Device{params: DeviceParams{HttpClient: srv.Client()}}
|
||||
resp, err := dev.SendSoapWithOptions(srv.URL, bodyXML, WithHeader(headerXML))
|
||||
require.NoError(t, err)
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
assert.Contains(t, captured, "SubscriptionId")
|
||||
assert.Contains(t, captured, "42")
|
||||
}
|
||||
|
||||
func TestDevice_SendSoapWithOptions_NoOptsMatchesSendSoap(t *testing.T) {
|
||||
var captured string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
captured = string(b)
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
dev := Device{params: DeviceParams{HttpClient: srv.Client()}}
|
||||
resp, err := dev.SendSoapWithOptions(srv.URL, `<tev:Body xmlns:tev="x"/>`)
|
||||
require.NoError(t, err)
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
assert.NotContains(t, captured, "IsReferenceParameter",
|
||||
"no opts should produce a header-less envelope")
|
||||
}
|
||||
|
||||
// Digest auth fallback path: the camera 401s the first POST and the
|
||||
// retry computes a digest. The ref-params header must survive the
|
||||
// retry — losing it would silently re-introduce the AXIS regression
|
||||
|
||||
Reference in New Issue
Block a user