Executing terminal command in go and populating textarea html tag in Webpage

Hi,

I am writing a webserver in go that will render a webpage with a button and a textarea. On the click of the button go will execute a terminal command and display its output per line in a

tag I defined in my html. I am using gorilla websocket to do this. The command I run outputs an xml file, which I parse in go, and I want to output this data in the textarea on my webpage after the command execution has finished, but nothing is being rendered to the textarea.

Here is a snippet of my code:

  func wsHandler(w http.ResponseWriter, r *http.Request) {

        conn, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            fmt.Println(err)
        }

        nmap_chain := "nmap -F -sS 172.16.2.0/24 -oX output.xml --stats-every 2s | grep --line-buffered 'Ping Scan Timing' | mawk -W interactive '{print $5}'"
        cmd := exec.Command("bash","-c",nmap_chain)
        stdout, err := cmd.StdoutPipe()
        if err != nil {
            log.Fatal(err)
        }

        if err := cmd.Start(); err != nil {
            log.Fatal(err)
        }

        in := bufio.NewScanner(stdout)

        for in.Scan() {
            c := []byte(in.Text())
            err = conn.WriteMessage(websocket.TextMessage, c)
            if err != nil {
                fmt.Println(err)
            }
            time.Sleep(time.Second)

        }

        if err := cmd.Wait(); err == nil {
            p := Test{}
            p.SetName("hello")    
        } else {
            log.Fatal(err)
        }
        
        conn.Close()



    }

    func handler(w http.ResponseWriter, r *http.Request) {
        
        t, _ := template.ParseFiles("index.html")
        t.Execute(w,p)
    }

    func main() {
        
        http.HandleFunc("/", handler)
        http.Handle("/layout/", http.StripPrefix("/layout/", http.FileServer(http.Dir("layout"))))
        http.HandleFunc("/exec", execCommandHandler)
        http.HandleFunc("/websocket", wsHandler)
        http.ListenAndServe(":8080", nil)

    }

And here is my html file.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>NMAP Demo</title>
  <link rel="stylesheet" type="text/css" href="layout/mystyle.css">
  <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
  <script type="text/javascript">
    function myWebsocketStart() {
      var ws = new WebSocket("ws://localhost:8080/websocket");
      ws.onmessage = function(evt) {
        var myTextArea = document.getElementById("textarea1");
        <!-- myTextArea.value = myTextArea.value + "\n" + evt.data --> document.getElementById("textarea1").innerHTML = evt.data $('#exec').prop('disabled', true);
      };
      ws.onclose = function() {
        $('#exec').prop('disabled', false)
      };
    }
  </script>
</head>

<body>
  <div id="headerBar"></div>
  <div id="buttonformat">
    <button onclick="javascript:myWebsocketStart()" id="exec">Execute</button>
  </div>
  <div id="results">
    <textarea readonly id="response" rows="50" cols="50" style="resize">{{.Value}}</textarea>
  </div>
  <div>
    <p id="textarea1"></p>
  </div>
</body>

</html>

As stated above, my issue right now is that when I render my webpage, and press the button, it starts streaming the output process of the terminal command, but after the execution is finished, my textarea is not populated with anything. Right now I am just trying to print a simple hello string to my textarea as shown above in the code snippet after my command execution has finished but nothing gets outputted to it.

Hey again @Chris_S,

I wrote you up a small example, streaming from executing a command to displaying it on the browser using websockets since that seems to be what you’re trying to do (my example is not at all perfect), as a little sample that you can work with, but I do think you should probably take the time to invest in learning some more javascript just to make this type of stuff a little easier to understand :slight_smile:

Edit: It actually looks like I didn’t have to change hardly anything to make a working example from your code, so maybe the issue is with the command that you are running? Does it output properly in the terminal regularly? Either way, let me know if my example is helpful at all or not.

script.sh:

( 
for ((i=0;i<3;++i)) do
	echo "Hello, World!";
	sleep 1;
done
)

main.go:

package main

import (
	"bufio"
	"html/template"
	"log"
	"net/http"
	"os/exec"

	"github.com/gorilla/websocket"
)

var (
	upgrader = websocket.Upgrader{}
	tmpl     = template.Must(template.New("tmpl").Parse(tmplSrc))
)

func indexHandler(w http.ResponseWriter, r *http.Request) {
	if err := tmpl.Execute(w, nil); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	go func(conn *websocket.Conn) {
		for {
			mType, _, err := conn.ReadMessage()
			if err != nil || mType == websocket.CloseMessage {
				conn.Close()
				return
			}
		}
	}(conn)

	go func(conn *websocket.Conn) {
		cmd := exec.Command("bash", "script.sh")
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if err := cmd.Start(); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		scanner := bufio.NewScanner(stdout)
		for scanner.Scan() {
			err := conn.WriteMessage(websocket.TextMessage, []byte(scanner.Text()))
			if err != nil {
				conn.Close()
				return
			}
		}
		if err := scanner.Err(); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if err := cmd.Wait(); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}(conn)
}

func main() {
	http.HandleFunc("/exec", wsHandler)
	http.HandleFunc("/", indexHandler)
	log.Fatal(http.ListenAndServe(":9000", nil))
}

const tmplSrc = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Example</title>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
$(document).ready(function() {
	$("#btn").on('click', function() {
		var ws = new WebSocket("ws://localhost:9000/exec");
		ws.onmessage = function(e) {
			$("#results").append(e.data + "<br/>");
		};
	});
});
</script>
</head>
<body>
	<button id="btn">Execute</button>
	<div id="results"></div>
</body>
</html>`

Ah, I think I might know what the issue is.

In your Javascript you are using something like results.innerHTML = evt.data; to update your stream and are sending line by line the results from your buffered scanner. I believe the issue you might be having is that your command is sending a blank line at the end of the command, therefore erasing the last result.

To test if this is the problem, just use the javascript I used which appends to the current result instead of replacing the current text every time any new text is received from the stream.

One solution if this is the problem, would be to replace your code here:

for in.Scan() {
	c := []byte(in.Text())
	err = conn.WriteMessage(websocket.TextMessage, c)
	if err != nil {
		fmt.Println(err)
	}
}

With this:

for in.Scan() {
	c := strings.TrimSpace(in.Text())
	if c == "" {
		continue
	}
	err = conn.WriteMessage(websocket.TextMessage, []byte(c))
	if err != nil {
		fmt.Println(err)
	}
}
1 Like

Hi Ben,

Thanks for the reply, so I’ve gotten the stream to run fine on my webpage, but the terminal command I am executing in my code generates an xml file. After the command has finished executing I wrote a function that parses the xml and store the needed data into a struct (not included in the snippet). What I want to be able to do is populate the textarea tag I have in my html file with this information after the websocket stream has finished, that is the part I am having issues with. I have been trying to play around with just writing a simple “hello” string to the textarea tag after the websocket stream has ended but I haven’t been able to do this yet.

If you take a look at this section in my code

if err := cmd.Wait(); err == nil {
    p := Test{}
    p.SetName("hello")    
} else {
    log.Fatal(err)
}
conn.Close()
}

func handler(w http.ResponseWriter, r *http.Request) {

    t, _ := template.ParseFiles("index.html")
    t.Execute(w,p)
}

I’ve defined a struct called Test{ Value string } that I did not include in this snippet, as well as a global variable p = Test{}. I try to wait until the command has finished executing by checking the if statement cmd.wait, and then I set a string value for my struct parameter. My logic was that this would get updated in my handler function as well since I am also passing the parameter p in it. But When I run my webserver, and I click the button in my webpage to start the execution of the terminal command nothing happens, not even the websocket streaming.

PS Made a mistake in my code, I set p := Test{} under the if statement for cmd.Wait(), but after changing this to p = Test{} I can see the websocket stream happening when I click the button on my webpage, but my textarea does not get populated with a hello string after the execution of the command.

Hey @Chris_S,

I’m not sure if you are missing code from what you’ve pasted, but if you are trying to display the p object in your browser, in the code you just posted above, you aren’t writing it to the websocket. Am I missing something?

Hey Ben,

The websocket writes to the paragraph tag i have defined in my html. My choice of words in my previous response was poor. What I want to do is after in.Scan() finishes, I want to post a “hello” string to my textarea tag i have defined in the html file. Currently I am trying to use the p object I defined to do this, but I cannot see anything written to the textarea when rendering it in a browser, after the execution of the terminal command.

Oh ok, I think I understand what you mean, however you haven’t actually posted any of the code that shows how you are trying to accomplish this, so it’s hard to help without that information.

Hey Ben,

I ended up solving this by passing a json object when I write to my websocket, and then extracting the info I need in javascript. Thanks for the help above.

Hey @Chris_S,

Glad to hear ya got it working :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.