Skip to content

Commit 9f715d6

Browse files
committed
Group all the clients and only provide the actually supported services.
1 parent a11c8f7 commit 9f715d6

File tree

8 files changed

+213
-71
lines changed

8 files changed

+213
-71
lines changed

README.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,33 @@ gonvif completion bash
2828
## Client Usage
2929

3030
```golang
31-
import "github.com/hooklift/gowsdl/soap"
32-
33-
...
34-
35-
client := soap.NewClient("http://IP[:PORT]/onvif/Media2")
36-
client.SetHeaders(soap.NewSecurity("USERNAME", "PASSWORD"))
37-
media := wsdl.NewMedia2(client)
38-
resp, err := media.GetProfiles(&wsdl.GetProfiles{
39-
Type: []string{"All"},
40-
})
31+
import (
32+
"log"
33+
34+
"github.com/eyetowers/gonvif/pkg/client"
35+
)
36+
37+
func main() {
38+
// Connect to the Onvif device.
39+
onvif, err := client.New("http://IP[:PORT]", "USERNAME", "PASSWORD")
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
// Get the Media2 service client.
44+
media, err := onvif.Media2()
45+
if err != nil {
46+
log.Fatal(err)
47+
}
48+
// Make a request.
49+
resp, err := media.GetProfiles(&wsdl.GetProfiles{
50+
Type: []string{"All"},
51+
})
52+
if err != nil {
53+
log.Fatal(err)
54+
}
55+
// Process the response.
56+
log.Printf("Got profiles: %v", resp)
57+
}
4158
```
4259

4360
## License

cmd/gonvif/device/cmd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/spf13/cobra"
55

66
"github.com/eyetowers/gonvif/cmd/gonvif/root"
7+
"github.com/eyetowers/gonvif/pkg/client"
78
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver10/device/wsdl"
89
)
910

@@ -21,9 +22,9 @@ func init() {
2122
}
2223

2324
func ServiceClient(url, username, password string, verbose bool) (wsdl.Device, error) {
24-
serviceURL, err := root.ServiceURL(url, "onvif/device_service")
25+
onvif, err := client.New(url, username, password, verbose)
2526
if err != nil {
2627
return nil, err
2728
}
28-
return wsdl.NewDevice(root.AuthorizedSOAPClient(serviceURL, username, password, verbose)), nil
29+
return onvif.Device()
2930
}

cmd/gonvif/imaging/cmd.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/spf13/cobra"
55

66
"github.com/eyetowers/gonvif/cmd/gonvif/root"
7+
"github.com/eyetowers/gonvif/pkg/client"
78
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/imaging/wsdl"
89
)
910

@@ -26,10 +27,10 @@ func init() {
2627
)
2728
}
2829

29-
func ServiceClient(url, username, password string, vebose bool) (wsdl.ImagingPort, error) {
30-
serviceURL, err := root.ServiceURL(url, "onvif/Imaging")
30+
func ServiceClient(url, username, password string, verbose bool) (wsdl.ImagingPort, error) {
31+
onvif, err := client.New(url, username, password, verbose)
3132
if err != nil {
3233
return nil, err
3334
}
34-
return wsdl.NewImagingPort(root.AuthorizedSOAPClient(serviceURL, username, password, vebose)), nil
35+
return onvif.Imaging()
3536
}

cmd/gonvif/media/cmd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/spf13/cobra"
55

66
"github.com/eyetowers/gonvif/cmd/gonvif/root"
7+
"github.com/eyetowers/gonvif/pkg/client"
78
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/media/wsdl"
89
)
910

@@ -23,9 +24,9 @@ func init() {
2324
}
2425

2526
func ServiceClient(url, username, password string, verbose bool) (wsdl.Media2, error) {
26-
serviceURL, err := root.ServiceURL(url, "onvif/Media2")
27+
onvif, err := client.New(url, username, password, verbose)
2728
if err != nil {
2829
return nil, err
2930
}
30-
return wsdl.NewMedia2(root.AuthorizedSOAPClient(serviceURL, username, password, verbose)), nil
31+
return onvif.Media2()
3132
}

cmd/gonvif/ptz/cmd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/spf13/cobra"
55

66
"github.com/eyetowers/gonvif/cmd/gonvif/root"
7+
"github.com/eyetowers/gonvif/pkg/client"
78
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/ptz/wsdl"
89
)
910

@@ -22,9 +23,9 @@ func init() {
2223
}
2324

2425
func ServiceClient(url, username, password string, verbose bool) (wsdl.PTZ, error) {
25-
u, err := root.ServiceURL(url, "onvif/PTZ")
26+
onvif, err := client.New(url, username, password, verbose)
2627
if err != nil {
2728
return nil, err
2829
}
29-
return wsdl.NewPTZ(root.AuthorizedSOAPClient(u, username, password, verbose)), nil
30+
return onvif.PTZ()
3031
}

cmd/gonvif/root/cmd.go

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
package root
22

33
import (
4-
"bytes"
54
"encoding/json"
6-
"fmt"
7-
"io"
8-
"log"
9-
"net/http"
10-
"net/url"
115
"os"
126

13-
"github.com/hooklift/gowsdl/soap"
14-
"github.com/motemen/go-loghttp"
157
"github.com/spf13/cobra"
168
)
179

@@ -41,52 +33,9 @@ func RequireAuthFlags(cmd *cobra.Command) {
4133
cmd.MarkPersistentFlagRequired("password")
4234
}
4335

44-
func ServiceURL(baseURL, suffix string) (string, error) {
45-
base, err := url.Parse(baseURL)
46-
if err != nil {
47-
return "", fmt.Errorf("malformed base URL: %w", err)
48-
}
49-
u, err := url.Parse(suffix)
50-
if err != nil {
51-
return "", fmt.Errorf("malformed service suffix URL: %w", err)
52-
}
53-
return base.ResolveReference(u).String(), nil
54-
}
55-
56-
func AuthorizedSOAPClient(serviceURL, username, password string, verbose bool) *soap.Client {
57-
httpClient := http.DefaultClient
58-
if verbose {
59-
httpClient = &http.Client{
60-
Transport: &loghttp.Transport{
61-
LogResponse: logResponse,
62-
LogRequest: logRequest,
63-
},
64-
}
65-
}
66-
client := soap.NewClient(serviceURL, soap.WithHTTPClient(httpClient))
67-
client.SetHeaders(soap.NewSecurity(username, password))
68-
return client
69-
}
70-
7136
func OutputJSON(payload interface{}) error {
7237
encoder := json.NewEncoder(os.Stdout)
7338
encoder.SetIndent("", " ")
7439

7540
return encoder.Encode(payload)
7641
}
77-
78-
func logResponse(resp *http.Response) {
79-
log.Printf("<-- %d %s", resp.StatusCode, resp.Request.URL)
80-
defer resp.Body.Close()
81-
body, _ := io.ReadAll(resp.Body)
82-
log.Printf("BODY:\n%s", string(body))
83-
resp.Body = io.NopCloser(bytes.NewReader(body))
84-
}
85-
86-
func logRequest(req *http.Request) {
87-
log.Printf("--> %s %s", req.Method, req.URL)
88-
defer req.Body.Close()
89-
body, _ := io.ReadAll(req.Body)
90-
log.Printf("BODY:\n%s", string(body))
91-
req.Body = io.NopCloser(bytes.NewReader(body))
92-
}

pkg/client/client.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package client
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"log"
9+
"net/http"
10+
"net/url"
11+
12+
device "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver10/device/wsdl"
13+
analytics "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/analytics/wsdl"
14+
imaging "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/imaging/wsdl"
15+
media2 "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/media/wsdl"
16+
ptz "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/ptz/wsdl"
17+
"github.com/hooklift/gowsdl/soap"
18+
"github.com/motemen/go-loghttp"
19+
)
20+
21+
var (
22+
ErrServiceNotSupported = errors.New("onvif service not supported")
23+
24+
verboseHTTPClient = &http.Client{
25+
Transport: &loghttp.Transport{
26+
LogResponse: logResponse,
27+
LogRequest: logRequest,
28+
},
29+
}
30+
)
31+
32+
type Client interface {
33+
Analytics() (analytics.AnalyticsEnginePort, error)
34+
Device() (device.Device, error)
35+
Imaging() (imaging.ImagingPort, error)
36+
Media2() (media2.Media2, error)
37+
PTZ() (ptz.PTZ, error)
38+
}
39+
40+
type impl struct {
41+
analytics analytics.AnalyticsEnginePort
42+
device device.Device
43+
imaging imaging.ImagingPort
44+
media2 media2.Media2
45+
ptz ptz.PTZ
46+
}
47+
48+
func New(baseURL, username, password string, verbose bool) (Client, error) {
49+
soapClient, err := serviceSOAPClient(baseURL, "onvif/device_service", username, password, verbose)
50+
if err != nil {
51+
return nil, err
52+
}
53+
d := device.NewDevice(soapClient)
54+
resp, err := d.GetServices(&device.GetServices{})
55+
if err != nil {
56+
return nil, fmt.Errorf("listing available Onvif services: %w", err)
57+
}
58+
59+
var result impl
60+
for _, svc := range resp.Service {
61+
svcClient, err := serviceSOAPClient(baseURL, svc.XAddr, username, password, verbose)
62+
if err != nil {
63+
return nil, err
64+
}
65+
if svc.Namespace == "http://www.onvif.org/ver20/analytics/wsdl" {
66+
result.analytics = analytics.NewAnalyticsEnginePort(svcClient)
67+
}
68+
if svc.Namespace == "http://www.onvif.org/ver10/device/wsdl" {
69+
result.device = device.NewDevice(svcClient)
70+
}
71+
if svc.Namespace == "http://www.onvif.org/ver20/imaging/wsdl" {
72+
result.imaging = imaging.NewImagingPort(svcClient)
73+
}
74+
if svc.Namespace == "http://www.onvif.org/ver20/media/wsdl" {
75+
result.media2 = media2.NewMedia2(svcClient)
76+
}
77+
if svc.Namespace == "http://www.onvif.org/ver20/ptz/wsdl" {
78+
result.ptz = ptz.NewPTZ(svcClient)
79+
}
80+
}
81+
82+
return &result, nil
83+
}
84+
85+
func (c *impl) Analytics() (analytics.AnalyticsEnginePort, error) {
86+
if c.analytics == nil {
87+
return nil, ErrServiceNotSupported
88+
}
89+
return c.analytics, nil
90+
}
91+
92+
func (c *impl) Device() (device.Device, error) {
93+
if c.device == nil {
94+
return nil, ErrServiceNotSupported
95+
}
96+
return c.device, nil
97+
}
98+
99+
func (c *impl) Imaging() (imaging.ImagingPort, error) {
100+
if c.imaging == nil {
101+
return nil, ErrServiceNotSupported
102+
}
103+
return c.imaging, nil
104+
}
105+
106+
func (c *impl) Media2() (media2.Media2, error) {
107+
if c.media2 == nil {
108+
return nil, ErrServiceNotSupported
109+
}
110+
return c.media2, nil
111+
}
112+
113+
func (c *impl) PTZ() (ptz.PTZ, error) {
114+
if c.ptz == nil {
115+
return nil, ErrServiceNotSupported
116+
}
117+
return c.ptz, nil
118+
}
119+
120+
func serviceURL(baseURL, suffix string) (string, error) {
121+
base, err := url.Parse(baseURL)
122+
if err != nil {
123+
return "", fmt.Errorf("malformed base URL: %w", err)
124+
}
125+
u, err := url.Parse(suffix)
126+
if err != nil {
127+
return "", fmt.Errorf("malformed service suffix URL: %w", err)
128+
}
129+
return base.ResolveReference(u).String(), nil
130+
}
131+
132+
func sanitizeServiceURL(baseURL, advertisedURL string) (string, error) {
133+
u, err := url.Parse(advertisedURL)
134+
if err != nil {
135+
return "", fmt.Errorf("malformed service advertised URL: %w", err)
136+
}
137+
return serviceURL(baseURL, u.Path)
138+
}
139+
140+
func serviceSOAPClient(baseURL, advertisedURL, username, password string, verbose bool) (*soap.Client, error) {
141+
u, err := sanitizeServiceURL(baseURL, advertisedURL)
142+
if err != nil {
143+
return nil, err
144+
}
145+
return AuthorizedSOAPClient(u, username, password, verbose), nil
146+
}
147+
148+
func AuthorizedSOAPClient(serviceURL, username, password string, verbose bool) *soap.Client {
149+
httpClient := http.DefaultClient
150+
if verbose {
151+
httpClient = verboseHTTPClient
152+
}
153+
client := soap.NewClient(serviceURL, soap.WithHTTPClient(httpClient))
154+
client.SetHeaders(soap.NewSecurity(username, password))
155+
return client
156+
}
157+
158+
func logResponse(resp *http.Response) {
159+
log.Printf("<-- %d %s", resp.StatusCode, resp.Request.URL)
160+
defer resp.Body.Close()
161+
body, _ := io.ReadAll(resp.Body)
162+
log.Printf("BODY:\n%s", string(body))
163+
resp.Body = io.NopCloser(bytes.NewReader(body))
164+
}
165+
166+
func logRequest(req *http.Request) {
167+
log.Printf("--> %s %s", req.Method, req.URL)
168+
defer req.Body.Close()
169+
body, _ := io.ReadAll(req.Body)
170+
log.Printf("BODY:\n%s", string(body))
171+
req.Body = io.NopCloser(bytes.NewReader(body))
172+
}

pkg/generated/onvif/www_onvif_org/ver10/device/wsdl/0.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)