exec command every one minute and serve the output via http - go

I want to run a command in the Bash shell every one minute, and serve the output via http, on http://localhost:8080/feed
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := `<a piped command>`
out, err := exec.Command("bash", "-c", cmd).Output()
if err != nil {
fmt.Sprintf("Failed to execute command: %s", cmd)
}
fmt.Println(string(out))
}
UPDATE :
package main
import (
"fmt"
"log"
"net/http"
"os/exec"
)
func handler(w http.ResponseWriter, r *http.Request) {
cmd := `<a piped command>`
out, err := exec.Command("bash", "-c", cmd).Output()
if err != nil {
fmt.Sprintf("Failed to execute command: %s", cmd)
}
fmt.Fprintf(w, string(out))
}
func main() {
http.HandleFunc("/feed", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
With the above code, the command is run every time http://localhost:8080/feed is accessed. How do I make it cache(?) the output of the command for one minute, and then run the command again only after the cache time is expired?

If output is not too big, you can keep it in memory variable.
I took approach to wait for result in case that script is executed (using mutex):
package main
import (
"fmt"
"net/http"
"os/exec"
"sync"
"time"
)
var LoopDelay = 60*time.Second
type Output struct {
sync.Mutex
content string
}
func main() {
var output *Output = new(Output)
go updateResult(output)
http.HandleFunc("/feed", initHandle(output))
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("ERROR", err)
}
}
func initHandle(output *Output) func(http.ResponseWriter, *http.Request) {
return func(respw http.ResponseWriter, req *http.Request) {
output.Lock()
defer output.Unlock()
_, err := respw.Write([]byte(output.content))
if err != nil {
fmt.Println("ERROR: Unable to write response: ", err)
}
}
}
func updateResult(output *Output) {
var execFn = func() { /* Extracted so that 'defer' executes at the end of loop iteration */
output.Lock()
defer output.Unlock()
command := exec.Command("bash", "-c", "date | nl ")
output1, err := command.CombinedOutput()
if err != nil {
output.content = err.Error()
} else {
output.content = string(output1)
}
}
for {
execFn()
time.Sleep(LoopDelay)
}
}
Executing
date; curl http://localhost:8080/feed
gives output (on multiple calls):
1 dimanche 13 octobre 2019, 09:41:40 (UTC+0200)
http-server-cmd-output> date; curl http://localhost:8080/feed
dimanche 13 octobre 2019, 09:42:05 (UTC+0200)
1 dimanche 13 octobre 2019, 09:41:40 (UTC+0200)
Few things to consider:
- Used 'date | nl' as command with pipe example
- If output too big, write to file
- Very likely it is good idea to keep mutex only for content update (no need to wait during script execution) - you can try to improve it
- Go routine may have variable (channel) to exit on signal (ex: when program ends)
EDIT: variable moved to main function

How do I make it cache(?) the output of the command for one minute,
and then run the command again only after the cache time is expired?
In below solution two goroutines are declared.
First goroutine loop until the context is done to execute the command at regular interval and send a copy to the second go routine.
The second routine, tries to get from the first goroutine, or distribute its value to other goroutines.
The http handler, third goroutine, only queries the value from the getter and does something with it.
The reason to use three routines instead of two in this example is to prevent blocking the http routines if the command is being executed. With that additional routines, http requests only wait for the synchronization to occur.
type state is a dummy struct to transport the values within channels.
We prevent race conditions because of two facts, the state is passed by value, cmd.Output() allocates a new buffer each time it runs.
To retrieve original command in the http handler, OP should build a custom error type and attach those information to the recorded error, within the http handler, OP should type assert the error to its custom type and get the specific details from there.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os/exec"
"time"
)
type state struct {
out []byte
err error
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
set := make(chan state, 1)
go func() {
ticker := time.NewTicker(2 * time.Second)
cmd := []string{"date"}
s := state{}
s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
set <- s
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.out, s.err = exec.Command(cmd[0], cmd[1:]...).Output()
set <- s
}
}
}()
get := make(chan state)
go func() {
s := state{}
for {
select {
case <-ctx.Done():
return
case s = <-set: // get the fresh value, if any
case get <- s: // distribute your copy
}
}
}()
http.HandleFunc("/feed", func(w http.ResponseWriter, r *http.Request) {
state := <-get
if state.err != nil {
fmt.Printf("Failed to execute command: %v", state.err)
}
fmt.Fprintf(w, string(state.out))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}

You could:
write the last command instance (the "string(out)") to a log file: see "Go/Golang write log to file"
serve that log file (and only the file) in a Go http server: see "How to serve a file to a specific URL path with the FileServer function in go/golang"
That way, any time you go to your server, you will get the last command output.
How do I make it cache(?) the output of the command for one minute, and then run the command again only after the cache time is expired?
By keeping the execution of the command separate from the command output being served.
That is why you must kept the two asynchronous, typically through a goroutine, a a time.NewTicker.
See "Is there a way to do repetitive tasks at intervals?"

Related

More accurate ticker than time.NewTicker in go on macOS

I am writing code to control external LEDs over UDP, and need to keep a frame rate (e.g. 60Hz) that is as constant as possible. Too much jitter will look bad. I've written a simple test using time.NewTicker and the results are not ideal. I'm wondering if there is a different way to execute code on a more accurate interval. This test was run on macOS, but needs to run on Windows and Linux too. For what it's worth, it needs to sync to audio, so maybe there is an audio ticker API on each OS it could potentially sync with?
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"strconv"
"time"
)
var f *os.File
var testTimeSeconds = 30
func appendToCsv(t time.Time) {
w := csv.NewWriter(f)
defer w.Flush()
records := []string{strconv.FormatInt(t.UnixMicro(), 10)}
w.Write(records)
}
func init() {
var err error
f, err = os.Create("newTicker.csv")
if err != nil {
log.Fatal(err)
}
w := csv.NewWriter(f)
defer w.Flush()
records := []string{"timestamps"}
w.Write(records)
}
func main() {
usPerFrame := 16666
ticker := time.NewTicker(time.Duration(usPerFrame) * time.Microsecond)
defer ticker.Stop()
done := make(chan bool)
go func() {
time.Sleep(time.Duration(testTimeSeconds) * time.Second)
done <- true
}()
for {
select {
case <-done:
fmt.Println("Done!")
return
case t := <-ticker.C:
appendToCsv(t)
}
}
}
UPDATE:
I ran another test comparing the first method with the method in #jochen's answer, still not very accurate.
One idea would be to just use time.Sleep() instead of a ticker. This takes the channel send/receive out of the loop and may lead to more accurate timing. To do this, you could run a function like the following in a separate goroutine:
func ticker(step time.Duration, done <-chan struct{}) {
next := time.Now().Add(step)
for {
time.Sleep(time.Until(next))
appendToCsv(time.Now())
select { // check whether `done` was closed
case <-done:
return
default:
// pass
}
next = next.Add(step)
}
}

How to wait for the command to finish/ till some seconds in golang?

I am running command exec.Command("cf api https://something.com/") and the response takes sometimes. But when executing this command, there is no wait happens but executed and goes further immediately. I need to wait for some seconds or until output has been received. How to achieve this?
func TestCMDExex(t *testing.T) {
expectedText := "Success"
cmd := exec.Command("cf api https://something.com/")
cmd.Dir = "/root//"
out, err := cmd.Output()
if err != nil {
t.Fail()
}
assert.Contains(t, string(out), expectedText)
}
First: the correct way to create the cmd is:
cmd := exec.Command("cf", "api", "https://something.com/")
Every argument to the child program must be a separate string. This way you can also pass arguments that contain spaces in them. For instance, executing the program with:
cmd := exec.Command("cf", "api https://something.com/")
will pass one command line argument to cf, which is "api https://something.com/", whereas passing two strings will pass two arguments "api" and "https://something.com/".
In your original code, you are trying to execute a program whose name is "cf api https://something.com/".
Then you can run it and get the output:
out, err:=cmd.Output()
This can be solved with a goroutine, a channel and the select statement. The sample code below also does error handling:
func main() {
type output struct {
out []byte
err error
}
ch := make(chan output)
go func() {
// cmd := exec.Command("sleep", "1")
// cmd := exec.Command("sleep", "5")
cmd := exec.Command("false")
out, err := cmd.CombinedOutput()
ch <- output{out, err}
}()
select {
case <-time.After(2 * time.Second):
fmt.Println("timed out")
case x := <-ch:
fmt.Printf("program done; out: %q\n", string(x.out))
if x.err != nil {
fmt.Printf("program errored: %s\n", x.err)
}
}
}
By choosing one of the 3 options in exec.Command(), you can see the code behaving in the 3 possible ways: timed out, normal subprocess termination, errored subprocess termination.
As usual when using goroutines, care must be taken to ensure they terminate, to avoid resource leaks.
Note also that if the executed subprocess is interactive or if it prints its progression to stdout and it is important to see the output while it is happening, then it is better to use cmd.Run(), remove the struct and report only the error in the channel.
You can use a go waitGroup. This is the function we’ll run in every goroutine. Note that a WaitGroup must be passed to functions by pointer. On return, notify the WaitGroup that we’re done. Sleep to simulate an expensive task. (remove it in your case) This WaitGroup is used to wait for all the goroutines launched here to finish. Block until the WaitGroup counter goes back to 0; all the workers notified they’re done.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
}

Can`t figure out how to use buffers with binary web socket

everyone!
I'm trying to get my go code work with openstack serial console. It`s exposed via web socket. And i have problems with it.
I found gorrilla websocket lib (which is great) and took this example as a reference
With a few tweaks, now i have a code like this:
package main
import (
"log"
"net/url"
"os"
"os/signal"
"time"
"net/http"
"github.com/gorilla/websocket"
)
func main() {
DialSettings := &websocket.Dialer {
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
Subprotocols: []string{"binary",},
ReadBufferSize: 4096,
WriteBufferSize: 4096,
}
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u, _ := url.Parse("ws://172.17.0.64:6083/?token=d1763f2b-3466-424c-aece-6aeea2a733d5") //websocket url as it outputs from 'nova get-serial-console test' cmd
log.Printf("connecting to %s", u.String())
c, _, err := DialSettings.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("%s", message)
}
}()
c.WriteMessage(websocket.TextMessage, []byte("\n")) //just to force output to console
for {
select {
case <-done:
return
case <-interrupt:
log.Println("interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
And i get output like this:
connecting to ws://172.17.0.64:6083/?token=d1763f2b-3466-424c-aece-6aeea2a733d5
CentOS Linux 7
(C
ore)
K
erne
l
3.10.0-862.el7.x86_64
o
n an
x
86_64
centos
-test login:
Total mess...
I think it's because i recieve just a chunks of bytes with no way to delimit them. I need some buffer to store them and when do something like bufio.ReadLine. But i'm not most experienced go programmer, and i run out of ideas how to do this. At the end i just need strings to work with.
The log package writes each log message on a separate line. If the log message does not end with a newline, then the log package will add one.
These extra newlines are garbling the output. To fix the output, replace the call to log.Printf("%s", message) with a function that does not add newlines to the output. Here are some options:
Write the message to stderr (same destination as default log package config):
os.Stderr.Write(message)
Write the message to stdout (a more conventional location to write program output):
os.Stdout.Write(message)

How to read a key in Go but continue application if no key pressed within x seconds?

This is an easy way to read a key from the console
reader := bufio.NewReader(os.Stdin)
// ...
func readKey() rune {
char, _, err := reader.ReadRune()
if err != nil {
fmt.Println("Error reading key: ", err)
}
return char
}
// ...
fmt.Println("Checking keyboard input...")
loop:
for {
keyb := readKey()
switch keyb {
case 'x':
fmt.Println("x key pressed, exiting loop")
break loop
}
}
However the issue is the application always waits for a key to be read. What if you want to wait only 5 seconds for a key to be read, and if no key is read, continue the application?
I'm thinking that I must pull in a dependency maybe, such as ncurses or a unit (module) that turbopascal had which was called crt and had a readkey function. But is a dependency really necessary or is there an easy way to do it without? Possibly even some defer() tricks, I don't know.
You don't need external dependencies to achieve this.
You can use a channel and set a timeout on it.
Here's documentation info about that: https://gobyexample.com/timeouts
The key part is making the input go through the channel in a separate goroutine, so that the main thread does not block waiting. You can then decide how long to wait to receive the input through the channel by setting a timeout in the select clause.
And here's a working sample using your post as a base:
package main
import (
"bufio"
"os"
"log"
"fmt"
"time"
)
var reader = bufio.NewReader(os.Stdin)
func readKey(input chan rune) {
char, _, err := reader.ReadRune()
if err != nil {
log.Fatal(err)
}
input <- char
}
func main() {
input := make(chan rune, 1)
fmt.Println("Checking keyboard input...")
go readKey(input)
select {
case i := <-input:
fmt.Printf("Input : %v\n", i)
case <-time.After(5000 * time.Millisecond):
fmt.Println("Time out!")
}
}
Probably the most "go-isch" way to do this is using a goroutine and channels. You start two goroutines, one which waits for input, and one which sleeps until after the timeout period. You then use a select statement in your main goroutine to check what event happened first (the input, or the timeout). Example code:
package main
import (
"fmt"
"time"
)
func waitForInput(didInput chan<- bool) {
// Wait for a valid input here
didInput <- true
}
func main() {
didInput := make(chan bool, 1)
timeout := make(chan bool, 1)
go func() {
time.Sleep(5 * time.Second)
timeout <- true
}()
go waitForInput(didInput)
select {
case <-didInput:
fmt.Println("")
// Continue your application here
case <-timeout:
// Input timed out, quit your application here
}
}

Golang: go command inside script?

I got a script written in Golang that I do not quite understand. I want to know why he wrote go server.Start() inside the script? Why not simply write server.Start?
package main
import (
"github.com/miekg/dns"
"testing"
"time"
)
const TEST_ADDR = "127.0.0.1:9953"
func TestDNSResponse(t *testing.T) {
server := NewDNSServer(&Config{
dnsAddr: TEST_ADDR,
})
go server.Start()
// Allow some time for server to start
time.Sleep(150 * time.Millisecond)
m := new(dns.Msg)
m.Id = dns.Id()
m.Question = []dns.Question{
dns.Question{"docker.", dns.TypeA, dns.ClassINET},
}
c := new(dns.Client)
_, _, err := c.Exchange(m, TEST_ADDR)
if err != nil {
t.Error("Error response from the server", err)
}
server.Stop()
c = new(dns.Client)
_, _, err = c.Exchange(m, TEST_ADDR)
if err == nil {
t.Error("Server still running but should be shut down.")
}
}
If you invoke a function prefixed with the go keyword it will call the function in as goroutine. A goroutine is a function that is capable of running concurrently with other functions.
Normally when we invoke a function it will execute all the function statements in normal order, then return to the next line following the invocation. With a goroutine we return immediately to the next line and don't wait for the function to complete.

Resources