From 51ae55e8a04f0dc643ca7ad500b75d1bfab08fac Mon Sep 17 00:00:00 2001 From: kikimor Date: Wed, 24 Feb 2021 21:47:09 +0500 Subject: [PATCH 1/2] External httpClient support --- Device.go | 99 +++++++++++++------------------------- README.md | 7 ++- api/api.go | 4 +- examples/DeviceService.go | 9 ++-- examples/discovery_test.go | 3 +- networking/networking.go | 14 +----- 6 files changed, 47 insertions(+), 89 deletions(-) diff --git a/Device.go b/Device.go index 129a8492..5a77e04a 100644 --- a/Device.go +++ b/Device.go @@ -76,13 +76,18 @@ type DeviceInfo struct { //struct represents an abstract ONVIF device. //It contains methods, which helps to communicate with ONVIF device type Device struct { - xaddr string - login string - password string + params DeviceParams endpoints map[string]string info DeviceInfo } +type DeviceParams struct { + Xaddr string + Username string + Password string + HttpClient *http.Client +} + //GetServices return available endpoints func (dev *Device) GetServices() map[string]string { return dev.endpoints @@ -93,7 +98,6 @@ func (dev *Device) GetDeviceInfo() DeviceInfo { return dev.info } - func readResponse(resp *http.Response) string { b, err := ioutil.ReadAll(resp.Body) if err != nil { @@ -109,52 +113,47 @@ func GetAvailableDevicesAtSpecificEthernetInterface(interfaceName string) []Devi */ devices := wsdiscovery.SendProbe(interfaceName, nil, []string{"dn:" + NVT.String()}, map[string]string{"dn": "http://www.onvif.org/ver10/network/wsdl"}) nvtDevices := make([]Device, 0) - ////fmt.Println(devices) + for _, j := range devices { doc := etree.NewDocument() if err := doc.ReadFromString(j); err != nil { fmt.Errorf("%s", err.Error()) return nil } - ////fmt.Println(j) + endpoints := doc.Root().FindElements("./Body/ProbeMatches/ProbeMatch/XAddrs") for _, xaddr := range endpoints { - //fmt.Println(xaddr.Tag,strings.Split(strings.Split(xaddr.Text(), " ")[0], "/")[2] ) xaddr := strings.Split(strings.Split(xaddr.Text(), " ")[0], "/")[2] fmt.Println(xaddr) c := 0 + for c = 0; c < len(nvtDevices); c++ { - if nvtDevices[c].xaddr == xaddr { - fmt.Println(nvtDevices[c].xaddr, "==", xaddr) + if nvtDevices[c].params.Xaddr == xaddr { + fmt.Println(nvtDevices[c].params.Xaddr, "==", xaddr) break } } + if c < len(nvtDevices) { continue } - dev, err := NewDevice(strings.Split(xaddr, " ")[0]) - //fmt.Println(dev) + + dev, err := NewDevice(DeviceParams{Xaddr: strings.Split(xaddr, " ")[0]}) + if err != nil { fmt.Println("Error", xaddr) fmt.Println(err) continue } else { - ////fmt.Println(dev) nvtDevices = append(nvtDevices, *dev) } } - ////fmt.Println(j) - //nvtDevices[i] = NewDevice() } + return nvtDevices } func (dev *Device) getSupportedServices(resp *http.Response) { - //resp, err := dev.CallMethod(device.GetCapabilities{Category:"All"}) - //if err != nil { - // log.Println(err.Error()) - //return - //} else { doc := etree.NewDocument() data, _ := ioutil.ReadAll(resp.Body) @@ -165,28 +164,27 @@ func (dev *Device) getSupportedServices(resp *http.Response) { } services := doc.FindElements("./Envelope/Body/GetCapabilitiesResponse/Capabilities/*/XAddr") for _, j := range services { - ////fmt.Println(j.Text()) - ////fmt.Println(j.Parent().Tag) dev.addEndpoint(j.Parent().Tag, j.Text()) } - //} } //NewDevice function construct a ONVIF Device entity -func NewDevice(xaddr string) (*Device, error) { +func NewDevice(params DeviceParams) (*Device, error) { dev := new(Device) - dev.xaddr = xaddr + dev.params = params dev.endpoints = make(map[string]string) - dev.addEndpoint("Device", "http://"+xaddr+"/onvif/device_service") + dev.addEndpoint("Device", "http://"+dev.params.Xaddr+"/onvif/device_service") + + if dev.params.HttpClient == nil { + dev.params.HttpClient = new(http.Client) + } getCapabilities := device.GetCapabilities{Category: "All"} resp, err := dev.CallMethod(getCapabilities) - //fmt.Println(resp.Request.Host) - //fmt.Println(readResponse(resp)) + if err != nil || resp.StatusCode != http.StatusOK { - //panic(errors.New("camera is not available at " + xaddr + " or it does not support ONVIF services")) - return nil, errors.New("camera is not available at " + xaddr + " or it does not support ONVIF services") + return nil, errors.New("camera is not available at " + dev.params.Xaddr + " or it does not support ONVIF services") } dev.getSupportedServices(resp) @@ -194,28 +192,18 @@ func NewDevice(xaddr string) (*Device, error) { } func (dev *Device) addEndpoint(Key, Value string) { - //use lowCaseKey //make key having ability to handle Mixed Case for Different vendor devcie (e.g. Events EVENTS, events) lowCaseKey := strings.ToLower(Key) dev.endpoints[lowCaseKey] = Value } -//Authenticate function authenticate client in the ONVIF device. -//Function takes and params. -//You should use this function to allow authorized requests to the ONVIF Device -//To change auth data call this function again. -func (dev *Device) Authenticate(username, password string) { - dev.login = username - dev.password = password -} - //GetEndpoint returns specific ONVIF service endpoint address func (dev *Device) GetEndpoint(name string) string { return dev.endpoints[name] } -func buildMethodSOAP(msg string) (gosoap.SoapMessage, error) { +func (dev Device) buildMethodSOAP(msg string) (gosoap.SoapMessage, error) { doc := etree.NewDocument() if err := doc.ReadFromString(msg); err != nil { //log.Println("Got error") @@ -226,7 +214,6 @@ func buildMethodSOAP(msg string) (gosoap.SoapMessage, error) { soap := gosoap.NewEmptySOAP() soap.AddBodyContent(element) - //soap.AddRootNamespace("onvif", "http://www.onvif.org/ver10/device/wsdl") return soap, nil } @@ -267,41 +254,23 @@ func (dev Device) CallMethod(method interface{}) (*http.Response, error) { //CallMethod functions call an method, defined struct with authentication data func (dev Device) callMethodDo(endpoint string, method interface{}) (*http.Response, error) { - /* - Converting struct to xml string representation - */ output, err := xml.MarshalIndent(method, " ", " ") if err != nil { - //log.Printf("error: %v\n", err.Error()) return nil, err } - //fmt.Println(gosoap.SoapMessage(string(output)).StringIndent()) - /* - Build an SOAP request with - */ - soap, err := buildMethodSOAP(string(output)) + + soap, err := dev.buildMethodSOAP(string(output)) if err != nil { - //log.Printf("error: %v\n", err.Error()) return nil, err } - //fmt.Println(soap.StringIndent()) - /* - Adding namespaces and WS-Security headers - */ soap.AddRootNamespaces(Xlmns) - - //fmt.Println(soap.StringIndent()) - //Header handling soap.AddAction() //Auth Handling - if dev.login != "" && dev.password != "" { - soap.AddWSSecurity(dev.login, dev.password) + if dev.params.Username != "" && dev.params.Password != "" { + soap.AddWSSecurity(dev.params.Username, dev.params.Password) } - //fmt.Println(soap.StringIndent()) - /* - Sending request and returns the response - */ - return networking.SendSoap(endpoint, soap.String()) + + return networking.SendSoap(dev.params.HttpClient, endpoint, soap.String()) } diff --git a/README.md b/README.md index bb52a7c0..ed4b0f73 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The following services are implemented: If there is a device on the network at the address *192.168.13.42*, and its ONVIF services use the *1234* port, then you can connect to the device in the following way: ```go -dev, err := onvif.NewDevice("192.168.13.42:1234") +dev, err := onvif.NewDevice(onvif.DeviceParams{Xaddr: "192.168.13.42:1234"}) ``` *The ONVIF port may differ depending on the device , to find out which port to use, you can go to the web interface of the device. **Usually this is 80 port.*** @@ -48,8 +48,7 @@ dev, err := onvif.NewDevice("192.168.13.42:1234") If any function of the ONVIF services requires authentication, you must use the `Authenticate` method. ```go -device := onvif.NewDevice("192.168.13.42:1234") -device.Authenticate("username", "password") +device := onvif.NewDevice(onvif.DeviceParams{Xaddr: "192.168.13.42:1234", Username: "username", Password: password}) ``` #### Defining Data Types @@ -94,7 +93,7 @@ To perform any function of one of the ONVIF services whose structure has been de ```go createUsers := device.CreateUsers{User: onvif.User{Username:"admin", Password:"qwerty", UserLevel:"User"}} -device := onvif.NewDevice("192.168.13.42:1234") +device := onvif.NewDevice(onvif.DeviceParams{Xaddr: "192.168.13.42:1234", Username: "username", Password: password}) device.Authenticate("username", "password") resp, err := dev.CallMethod(createUsers) ``` diff --git a/api/api.go b/api/api.go index 78ef219a..233585f2 100644 --- a/api/api.go +++ b/api/api.go @@ -147,7 +147,7 @@ func callNecessaryMethod(serviceName, methodName, acceptedData, username, passwo soap.AddRootNamespaces(onvif.Xlmns) soap.AddWSSecurity(username, password) - servResp, err := networking.SendSoap(endpoint, soap.String()) + servResp, err := networking.SendSoap(new(http.Client), endpoint, soap.String()) if err != nil { return "", err } @@ -161,7 +161,7 @@ func callNecessaryMethod(serviceName, methodName, acceptedData, username, passwo } func getEndpoint(service, xaddr string) (string, error) { - dev, err := onvif.NewDevice(xaddr) + dev, err := onvif.NewDevice(onvif.DeviceParams{Xaddr: xaddr}) if err != nil { return "", err } diff --git a/examples/DeviceService.go b/examples/DeviceService.go index d04d0e65..8db0ba61 100644 --- a/examples/DeviceService.go +++ b/examples/DeviceService.go @@ -27,12 +27,15 @@ func readResponse(resp *http.Response) string { func main() { //Getting an camera instance - dev, err := goonvif.NewDevice("192.168.13.14:80") + dev, err := goonvif.NewDevice(goonvif.DeviceParams{ + Xaddr: "192.168.13.14:80", + Username: login, + Password: password, + HttpClient: new(http.Client), + }) if err != nil { panic(err) } - //Authorization - dev.Authenticate(login, password) //Preparing commands systemDateAndTyme := device.GetSystemDateAndTime{} diff --git a/examples/discovery_test.go b/examples/discovery_test.go index fbe6ac86..630b28b3 100644 --- a/examples/discovery_test.go +++ b/examples/discovery_test.go @@ -25,11 +25,10 @@ func TestGetAvailableDevicesAtSpecificEthernetInterface(t *testing.T) { } func client() { - dev, err := onvif.NewDevice("192.168.3.10") + dev, err := onvif.NewDevice(onvif.DeviceParams{Xaddr: "192.168.3.10", Username: "admin", Password: "zsyy12345"}) if err != nil { panic(err) } - dev.Authenticate("admin", "zsyy12345") log.Printf("output %+v", dev.GetServices()) diff --git a/networking/networking.go b/networking/networking.go index f4047ca9..7e1aef20 100644 --- a/networking/networking.go +++ b/networking/networking.go @@ -3,13 +3,10 @@ package networking import ( "bytes" "net/http" - "time" ) // SendSoap send soap message -func SendSoap(endpoint, message string) (*http.Response, error) { - httpClient := new(http.Client) - +func SendSoap(httpClient *http.Client, endpoint, message string) (*http.Response, error) { resp, err := httpClient.Post(endpoint, "application/soap+xml; charset=utf-8", bytes.NewBufferString(message)) if err != nil { return resp, err @@ -17,12 +14,3 @@ func SendSoap(endpoint, message string) (*http.Response, error) { return resp, nil } - -// SendSoapWithTimeout send soap message with timeOut -func SendSoapWithTimeout(endpoint string, message []byte, timeout time.Duration) (*http.Response, error) { - httpClient := &http.Client{ - Timeout: timeout, - } - - return httpClient.Post(endpoint, "application/soap+xml; charset=utf-8", bytes.NewReader(message)) -} From 2fb044d81ca685cd959e26d1822b9bdd52becdbc Mon Sep 17 00:00:00 2001 From: kikimor Date: Sat, 6 Mar 2021 23:33:30 +0500 Subject: [PATCH 2/2] endpoint nat support --- Device.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Device.go b/Device.go index 5a77e04a..a2e175fc 100644 --- a/Device.go +++ b/Device.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" "reflect" "strconv" "strings" @@ -195,6 +196,13 @@ func (dev *Device) addEndpoint(Key, Value string) { //use lowCaseKey //make key having ability to handle Mixed Case for Different vendor devcie (e.g. Events EVENTS, events) lowCaseKey := strings.ToLower(Key) + + // Replace host with host from device params. + if u, err := url.Parse(Value); err == nil { + u.Host = dev.params.Xaddr + Value = u.String() + } + dev.endpoints[lowCaseKey] = Value }