Skip to content

Commit

Permalink
Group all the clients and only provide the actually supported services.
Browse files Browse the repository at this point in the history
  • Loading branch information
osery committed Sep 26, 2022
1 parent a11c8f7 commit 9f715d6
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 71 deletions.
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,33 @@ gonvif completion bash
## Client Usage

```golang
import "github.com/hooklift/gowsdl/soap"

...

client := soap.NewClient("http://IP[:PORT]/onvif/Media2")
client.SetHeaders(soap.NewSecurity("USERNAME", "PASSWORD"))
media := wsdl.NewMedia2(client)
resp, err := media.GetProfiles(&wsdl.GetProfiles{
Type: []string{"All"},
})
import (
"log"

"github.com/eyetowers/gonvif/pkg/client"
)

func main() {
// Connect to the Onvif device.
onvif, err := client.New("http://IP[:PORT]", "USERNAME", "PASSWORD")
if err != nil {
log.Fatal(err)
}
// Get the Media2 service client.
media, err := onvif.Media2()
if err != nil {
log.Fatal(err)
}
// Make a request.
resp, err := media.GetProfiles(&wsdl.GetProfiles{
Type: []string{"All"},
})
if err != nil {
log.Fatal(err)
}
// Process the response.
log.Printf("Got profiles: %v", resp)
}
```

## License
Expand Down
5 changes: 3 additions & 2 deletions cmd/gonvif/device/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"

"github.com/eyetowers/gonvif/cmd/gonvif/root"
"github.com/eyetowers/gonvif/pkg/client"
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver10/device/wsdl"
)

Expand All @@ -21,9 +22,9 @@ func init() {
}

func ServiceClient(url, username, password string, verbose bool) (wsdl.Device, error) {
serviceURL, err := root.ServiceURL(url, "onvif/device_service")
onvif, err := client.New(url, username, password, verbose)
if err != nil {
return nil, err
}
return wsdl.NewDevice(root.AuthorizedSOAPClient(serviceURL, username, password, verbose)), nil
return onvif.Device()
}
7 changes: 4 additions & 3 deletions cmd/gonvif/imaging/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"

"github.com/eyetowers/gonvif/cmd/gonvif/root"
"github.com/eyetowers/gonvif/pkg/client"
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/imaging/wsdl"
)

Expand All @@ -26,10 +27,10 @@ func init() {
)
}

func ServiceClient(url, username, password string, vebose bool) (wsdl.ImagingPort, error) {
serviceURL, err := root.ServiceURL(url, "onvif/Imaging")
func ServiceClient(url, username, password string, verbose bool) (wsdl.ImagingPort, error) {
onvif, err := client.New(url, username, password, verbose)
if err != nil {
return nil, err
}
return wsdl.NewImagingPort(root.AuthorizedSOAPClient(serviceURL, username, password, vebose)), nil
return onvif.Imaging()
}
5 changes: 3 additions & 2 deletions cmd/gonvif/media/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"

"github.com/eyetowers/gonvif/cmd/gonvif/root"
"github.com/eyetowers/gonvif/pkg/client"
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/media/wsdl"
)

Expand All @@ -23,9 +24,9 @@ func init() {
}

func ServiceClient(url, username, password string, verbose bool) (wsdl.Media2, error) {
serviceURL, err := root.ServiceURL(url, "onvif/Media2")
onvif, err := client.New(url, username, password, verbose)
if err != nil {
return nil, err
}
return wsdl.NewMedia2(root.AuthorizedSOAPClient(serviceURL, username, password, verbose)), nil
return onvif.Media2()
}
5 changes: 3 additions & 2 deletions cmd/gonvif/ptz/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"

"github.com/eyetowers/gonvif/cmd/gonvif/root"
"github.com/eyetowers/gonvif/pkg/client"
"github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/ptz/wsdl"
)

Expand All @@ -22,9 +23,9 @@ func init() {
}

func ServiceClient(url, username, password string, verbose bool) (wsdl.PTZ, error) {
u, err := root.ServiceURL(url, "onvif/PTZ")
onvif, err := client.New(url, username, password, verbose)
if err != nil {
return nil, err
}
return wsdl.NewPTZ(root.AuthorizedSOAPClient(u, username, password, verbose)), nil
return onvif.PTZ()
}
51 changes: 0 additions & 51 deletions cmd/gonvif/root/cmd.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
package root

import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"

"github.com/hooklift/gowsdl/soap"
"github.com/motemen/go-loghttp"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -41,52 +33,9 @@ func RequireAuthFlags(cmd *cobra.Command) {
cmd.MarkPersistentFlagRequired("password")
}

func ServiceURL(baseURL, suffix string) (string, error) {
base, err := url.Parse(baseURL)
if err != nil {
return "", fmt.Errorf("malformed base URL: %w", err)
}
u, err := url.Parse(suffix)
if err != nil {
return "", fmt.Errorf("malformed service suffix URL: %w", err)
}
return base.ResolveReference(u).String(), nil
}

func AuthorizedSOAPClient(serviceURL, username, password string, verbose bool) *soap.Client {
httpClient := http.DefaultClient
if verbose {
httpClient = &http.Client{
Transport: &loghttp.Transport{
LogResponse: logResponse,
LogRequest: logRequest,
},
}
}
client := soap.NewClient(serviceURL, soap.WithHTTPClient(httpClient))
client.SetHeaders(soap.NewSecurity(username, password))
return client
}

func OutputJSON(payload interface{}) error {
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")

return encoder.Encode(payload)
}

func logResponse(resp *http.Response) {
log.Printf("<-- %d %s", resp.StatusCode, resp.Request.URL)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
log.Printf("BODY:\n%s", string(body))
resp.Body = io.NopCloser(bytes.NewReader(body))
}

func logRequest(req *http.Request) {
log.Printf("--> %s %s", req.Method, req.URL)
defer req.Body.Close()
body, _ := io.ReadAll(req.Body)
log.Printf("BODY:\n%s", string(body))
req.Body = io.NopCloser(bytes.NewReader(body))
}
172 changes: 172 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package client

import (
"bytes"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"

device "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver10/device/wsdl"
analytics "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/analytics/wsdl"
imaging "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/imaging/wsdl"
media2 "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/media/wsdl"
ptz "github.com/eyetowers/gonvif/pkg/generated/onvif/www_onvif_org/ver20/ptz/wsdl"
"github.com/hooklift/gowsdl/soap"
"github.com/motemen/go-loghttp"
)

var (
ErrServiceNotSupported = errors.New("onvif service not supported")

verboseHTTPClient = &http.Client{
Transport: &loghttp.Transport{
LogResponse: logResponse,
LogRequest: logRequest,
},
}
)

type Client interface {
Analytics() (analytics.AnalyticsEnginePort, error)
Device() (device.Device, error)
Imaging() (imaging.ImagingPort, error)
Media2() (media2.Media2, error)
PTZ() (ptz.PTZ, error)
}

type impl struct {
analytics analytics.AnalyticsEnginePort
device device.Device
imaging imaging.ImagingPort
media2 media2.Media2
ptz ptz.PTZ
}

func New(baseURL, username, password string, verbose bool) (Client, error) {
soapClient, err := serviceSOAPClient(baseURL, "onvif/device_service", username, password, verbose)
if err != nil {
return nil, err
}
d := device.NewDevice(soapClient)
resp, err := d.GetServices(&device.GetServices{})
if err != nil {
return nil, fmt.Errorf("listing available Onvif services: %w", err)
}

var result impl
for _, svc := range resp.Service {
svcClient, err := serviceSOAPClient(baseURL, svc.XAddr, username, password, verbose)
if err != nil {
return nil, err
}
if svc.Namespace == "http://www.onvif.org/ver20/analytics/wsdl" {
result.analytics = analytics.NewAnalyticsEnginePort(svcClient)
}
if svc.Namespace == "http://www.onvif.org/ver10/device/wsdl" {
result.device = device.NewDevice(svcClient)
}
if svc.Namespace == "http://www.onvif.org/ver20/imaging/wsdl" {
result.imaging = imaging.NewImagingPort(svcClient)
}
if svc.Namespace == "http://www.onvif.org/ver20/media/wsdl" {
result.media2 = media2.NewMedia2(svcClient)
}
if svc.Namespace == "http://www.onvif.org/ver20/ptz/wsdl" {
result.ptz = ptz.NewPTZ(svcClient)
}
}

return &result, nil
}

func (c *impl) Analytics() (analytics.AnalyticsEnginePort, error) {
if c.analytics == nil {
return nil, ErrServiceNotSupported
}
return c.analytics, nil
}

func (c *impl) Device() (device.Device, error) {
if c.device == nil {
return nil, ErrServiceNotSupported
}
return c.device, nil
}

func (c *impl) Imaging() (imaging.ImagingPort, error) {
if c.imaging == nil {
return nil, ErrServiceNotSupported
}
return c.imaging, nil
}

func (c *impl) Media2() (media2.Media2, error) {
if c.media2 == nil {
return nil, ErrServiceNotSupported
}
return c.media2, nil
}

func (c *impl) PTZ() (ptz.PTZ, error) {
if c.ptz == nil {
return nil, ErrServiceNotSupported
}
return c.ptz, nil
}

func serviceURL(baseURL, suffix string) (string, error) {
base, err := url.Parse(baseURL)
if err != nil {
return "", fmt.Errorf("malformed base URL: %w", err)
}
u, err := url.Parse(suffix)
if err != nil {
return "", fmt.Errorf("malformed service suffix URL: %w", err)
}
return base.ResolveReference(u).String(), nil
}

func sanitizeServiceURL(baseURL, advertisedURL string) (string, error) {
u, err := url.Parse(advertisedURL)
if err != nil {
return "", fmt.Errorf("malformed service advertised URL: %w", err)
}
return serviceURL(baseURL, u.Path)
}

func serviceSOAPClient(baseURL, advertisedURL, username, password string, verbose bool) (*soap.Client, error) {
u, err := sanitizeServiceURL(baseURL, advertisedURL)
if err != nil {
return nil, err
}
return AuthorizedSOAPClient(u, username, password, verbose), nil
}

func AuthorizedSOAPClient(serviceURL, username, password string, verbose bool) *soap.Client {
httpClient := http.DefaultClient
if verbose {
httpClient = verboseHTTPClient
}
client := soap.NewClient(serviceURL, soap.WithHTTPClient(httpClient))
client.SetHeaders(soap.NewSecurity(username, password))
return client
}

func logResponse(resp *http.Response) {
log.Printf("<-- %d %s", resp.StatusCode, resp.Request.URL)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
log.Printf("BODY:\n%s", string(body))
resp.Body = io.NopCloser(bytes.NewReader(body))
}

func logRequest(req *http.Request) {
log.Printf("--> %s %s", req.Method, req.URL)
defer req.Body.Close()
body, _ := io.ReadAll(req.Body)
log.Printf("BODY:\n%s", string(body))
req.Body = io.NopCloser(bytes.NewReader(body))
}
2 changes: 1 addition & 1 deletion pkg/generated/onvif/www_onvif_org/ver10/device/wsdl/0.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9f715d6

Please sign in to comment.