Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go example and docs #55

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion doc/source/swupdate-ipc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,10 @@ Install API
POST /upload

This initiates an update: the initiator sends the request and start to stream the SWU in the same
way as described in :ref:`install_api`.
way as described in :ref:`install_api`. The SWU file must be sent with a ``Content-Type`` of
``multipart/form-data`` and **must not** be sent with chunked Transfer-Encoding.
Complete examples are available in both Go and python in the examples directory included
with the source code.

Restart API
-----------
Expand Down
21 changes: 21 additions & 0 deletions examples/goclient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# goclient
## About
An example swupdate client written in go. This client does not connect to the websocket for status but does illustrate how to POST the SWU file to `/upload`. It also has the nice feature that it is a streaming client, which is to say that it does not load the whole SWU file into RAM.

## Building
```
go build
```

## Running
Just like the python example client:
```
./goclient /some/path/to/some/swupade.swu hostname
```

It does include command line help:
```
Usage of ./goclient [-port PORT] <path to image> <hostname>:
-port int
The port to connect to. (default 8080)
```
3 changes: 3 additions & 0 deletions examples/goclient/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/sbabic/swupdate/goclient

go 1.13
112 changes: 112 additions & 0 deletions examples/goclient/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"
)

type commandLineOptions struct {
hostname string
path string
port int
}

func parseCommandLine() *commandLineOptions {
options := new(commandLineOptions)

flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [-port PORT] <path to image> <hostname>:\n", os.Args[0])
flag.PrintDefaults()
}

flag.IntVar(&options.port, "port", 8080,
"The port to connect to.")
flag.Parse()
if options.port < 1 || options.port > 65535 {
fmt.Fprintf(os.Stderr, "A valid port is required.\n")
flag.PrintDefaults()
os.Exit(1)
}

args := flag.Args()
if len(args) != 2 {
fmt.Fprintf(os.Stderr, "<path to image> <hostname> are required.\n")
flag.PrintDefaults()
os.Exit(1)
}
options.path = args[0]
options.hostname = args[1]

return options
}

func main() {
options := parseCommandLine()

file, err := os.Open(options.path)
defer file.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}

contentType, contentLength, body, err := bodyMultiReader(file)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}

url := fmt.Sprintf("http://%s:%d/upload", options.hostname, options.port)
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
req.Header.Set("Content-type", contentType)

req.ContentLength = contentLength

fmt.Printf("attempting to swupdate %s\n", options.hostname)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}

if resp.StatusCode == http.StatusOK {
fmt.Printf("%s swupdate POST succeeded!\n", options.hostname)
} else {
fmt.Fprintf(os.Stderr, "%s swupdate POST failed with %d\n", url, resp.StatusCode)
os.Exit(1)
}
}

// returns contentType, contentLength, body
//
// Further reading:
// https://medium.com/akatsuki-taiwan-technology/uploading-large-files-in-golang-without-using-buffer-or-pipe-9b5aafacfc16
func bodyMultiReader(file *os.File) (string, int64, io.Reader, error) {
fileInfo, err := file.Stat()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return "", int64(0), nil, err
}

boundary := "superCalifragilisticExpialiBoundary"
fileHeader := "Content-type: application/octet-stream"
// swupdate doesn't seem to care about name or filename but we include them to be polite
fileFormat := "--%s\r\nContent-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n%s\r\n\r\n"
filePart := fmt.Sprintf(fileFormat, boundary, fileInfo.Name(), fileHeader)
bodyBottom := fmt.Sprintf("\r\n--%s--\r\n", boundary)
body := io.MultiReader(strings.NewReader(filePart), file, strings.NewReader(bodyBottom))
contentType := fmt.Sprintf("multipart/form-data; boundary=%s", boundary)

contentLength := int64(len(filePart)) + fileInfo.Size() + int64(len(bodyBottom))

return contentType, contentLength, body, nil
}