I'm using the libchan
library by docker. Their example goes like this:
// client.go
package main
import (
"log"
"io"
"net"
"os"
"github.com/docker/libchan"
"github.com/docker/libchan/spdy"
)
type RemoteCommand struct {
Cmd string
Args []string
Stdin io.Writer
Stdout io.Reader
Stderr io.Reader
StatusChan libchan.Sender
}
type CommandResponse struct {
Status int
}
func main() {
var client net.Conn
client, err := net.Dial("tcp", "127.0.0.1:9323")
if err != nil {
log.Fatal(err)
}
p, err := spdy.NewSpdyStreamProvider(client, false)
transport := spdy.NewTransport(p)
sender, err := transport.NewSendChannel()
if err != nil {
log.Fatal(err)
}
receiver, remoteSender := libchan.Pipe()
command := &RemoteCommand{
Cmd: os.Args[1],
Args: os.Args[2:],
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
StatusChan: remoteSender,
}
err = sender.Send(command)
if err != nil {
log.Fatal(err)
}
response := &CommandResponse{}
err = receiver.Receive(response)
if err != nil {
log.Fatal(err)
}
os.Exit(response.Status)
}
And this is the server:
// server.go
package main
import (
"log"
"net"
"io"
"os/exec"
"syscall"
"github.com/docker/libchan"
"github.com/docker/libchan/spdy"
)
type RemoteReceivedCommand struct {
Cmd string
Args []string
Stdin io.Reader
Stdout io.WriteCloser
Stderr io.WriteCloser
StatusChan libchan.Sender
}
type CommandResponse struct {
Status int
}
func main() {
var listener net.Listener
var err error
listener, err = net.Listen("tcp", "localhost:9323")
if err != nil {
log.Fatal(err)
}
for {
c, err := listener.Accept()
if err != nil {
log.Print("listener accept error")
log.Print(err)
break
}
p, err := spdy.NewSpdyStreamProvider(c, true)
if err != nil {
log.Print("spdy stream error")
log.Print(err)
break
}
t := spdy.NewTransport(p)
go func() {
for {
receiver, err := t.WaitReceiveChannel()
if err != nil {
log.Print("receiver error")
log.Print(err)
break
}
log.Print("about to spawn receive proc")
go func() {
for {
command := &RemoteReceivedCommand{}
err := receiver.Receive(command)
log.Print("received command")
log.Print(command)
if err != nil {
log.Print("command error")
log.Print(err)
break
}
cmd := exec.Command(command.Cmd, command.Args...)
cmd.Stdout = command.Stdout
cmd.Stderr = command.Stderr
stdin, err := cmd.StdinPipe()
if err != nil {
log.Print("stdin error")
log.Print(err)
break
}
go func() {
io.Copy(stdin, command.Stdin)
stdin.Close()
}()
log.Print("about to run the command")
res := cmd.Run()
command.Stdout.Close()
command.Stderr.Close()
returnResult := &CommandResponse{}
if res != nil {
if exiterr, ok := res.(*exec.ExitError); ok {
returnResult.Status = exiterr.Sys().(syscall.WaitStatus).ExitStatus()
} else {
log.Print("res")
log.Print(res)
returnResult.Status = 10
}
}
err = command.StatusChan.Send(returnResult)
if err != nil {
log.Print(err)
}
}
}()
}
}()
}
}
When I run the server and send a message with the client:
$ ./client /bin/echo "hello"
I see this output in the server logs:
2018/06/18 23:13:56 about to spawn receive proc
2018/06/18 23:13:56 received command
2018/06/18 23:13:56 &{/bin/echo [hello] 0xc4201201b0 0xc42023c030 0xc42023c090 0xc420186080}
2018/06/18 23:13:56 about to run the command
2018/06/18 23:13:56 received command
2018/06/18 23:13:56 &{ [] <nil> <nil> <nil> <nil>}
2018/06/18 23:13:56 command error
2018/06/18 23:13:56 EOF
My server receives the message with the echo
command and executes it successfully. However, it also receives an empty command and then throws an EOF:
2018/06/18 23:13:56 &{ [] <nil> <nil> <nil> <nil>}
2018/06/18 23:13:56 command error
2018/06/18 23:13:56 EOF
Why is the command an empty string?
My suspicion is that the client exits and then sends an exit
signal. But if that was the case, why would the command be blank? Please help me understand what's going on.