Skip to content

Instantly share code, notes, and snippets.

@bohrasd
Created August 27, 2021 16:27
Show Gist options
  • Save bohrasd/488f7fb55a5d8bc7c7c4b2ffb980731d to your computer and use it in GitHub Desktop.
Save bohrasd/488f7fb55a5d8bc7c7c4b2ffb980731d to your computer and use it in GitHub Desktop.
a naive POC of bidirectional connection with HTTP/2+FETCH+SSE
#!/bin/bash
a=0
while true
do
a=$(($a + 1))
echo "waiting for command $a"
read -r abc
echo "executing $abc"
$abc
done
//main.go
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"time"
)
type Client struct {
cmd *exec.Cmd
events chan string
stdout *io.ReadCloser
stdin *io.WriteCloser
}
var Clients map[string]*Client = make(map[string]*Client)
func NewCmd(addr string) *Client {
if Clients[addr] == nil {
Clients[addr] = &Client{cmd: exec.Command("./huh.sh"), events: make(chan string, 10)}
cl := Clients[addr]
cl.cmd.Stderr = os.Stderr
stdin, err := cl.cmd.StdinPipe()
if nil != err {
log.Fatalf("Error obtaining stdin: %s", err.Error())
}
stdout, err := cl.cmd.StdoutPipe()
if nil != err {
log.Fatalf("Error obtaining stdout: %s", err.Error())
}
cl.stdout = &stdout
cl.stdin = &stdin
if err := cl.cmd.Start(); nil != err {
log.Fatalf("Error starting program: %s, %s", cl.cmd.Path, err.Error())
}
}
return Clients[addr]
}
func main() {
http.HandleFunc("/sse", dashboardHandler)
log.Fatal(http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil))
}
func handler(f http.HandlerFunc) http.Handler {
return http.HandlerFunc(f)
}
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
client := NewCmd(r.RemoteAddr)
if r.Method == "POST" {
bufwt := bufio.NewWriter(*client.stdin)
content, _ := ioutil.ReadAll(r.Body)
bufwt.Write(content)
bufwt.Write([]byte("\n"))
bufwt.Flush()
w.WriteHeader(http.StatusOK)
} else {
go terminalOutput(client)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
for {
timeout := time.After(300 * time.Millisecond)
select {
case ev := <-client.events:
fmt.Fprintf(w, "data: %v\n\n", ev)
fmt.Printf("data: %v\n", ev)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
case <-timeout:
fmt.Printf("nothing to sent\n\n")
}
}
}
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
func terminalOutput(client *Client) {
reader := bufio.NewReader(*client.stdout)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
client.events <- scanner.Text()
}
client.cmd.Wait()
}
#start SSE
const source = new EventSource("https://localhost:8080/sse")
source.onmessage = (event) => {
console.log(event.data)
}
#send terminal command
fetch("/sse", {
method: "POST",
body: "uname"
}).then(res => {
console.log("Request complete! response:", res);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment