Skip to content

Commit

Permalink
Simple http server (#39)
Browse files Browse the repository at this point in the history
* Checkpointing initial static filesystem

* Add some simple config to specify hosting

* Panic on bad config, don't default

* Check config first

* Whitespace

* Use gonsen for pages

* Move execution code into its own package so we can directly access it from http server in next commit

* Basic working http endpoint with actual configured endpoints
  • Loading branch information
Evertras authored Jul 16, 2023
1 parent 9ec0441 commit a9a1286
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 58 deletions.
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ build: bin/cyn
build-all: bin/cyn bin/cyn-linux bin/cyn-mac bin/cyn-windows

# Build for local
bin/cyn: ./cmd/cyn/*.go ./pkg/listener/*.go ./pkg/sender/*.go ./cmd/cyn/cmds/*.go
bin/cyn: \
./cmd/cyn/*.go \
./cmd/cyn/cmds/*.go \
./pkg/cyn/*.go \
./pkg/httpserver/*.go \
./pkg/httpserver/site/*.html \
./pkg/httpserver/site/pages/*.html \
./pkg/listener/*.go \
./pkg/sender/*.go
CGO_ENABLED=0 go build -o bin/cyn ./cmd/cyn/*.go

# Build for other OSes
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,21 @@ Customizable interval to send data.
Use in a Docker container for Docker-related networking, or just use the raw
binary for native level testing.

Optional HTTP server that provides a simple landing page to show current
status and configuration of the given `cyn` instance.

### Future

These will be converted to issues in the future, but some other ideas...

Test that connectivity is NOT made between different machines that should not
talk to each other, for firewall/security reasons.

Customizable data to send.

Allow metric collection (Prometheus, etc).

Optional self-hosted web page that shows realtime statistics.
Realtime streaming updates for the HTML view.

## How to install it

Expand Down Expand Up @@ -124,6 +129,8 @@ send-udp:
send-tcp:
- 192.168.58.3:1235
send-interval: 30s
http:
address: 127.0.0.1:8080
```
The configuration file must be supplied with the `--config/-c` command line
Expand Down Expand Up @@ -154,6 +161,7 @@ Available Commands:
Flags:
-c, --config-file string A file path to load as additional configuration.
-h, --help help for this command
--http.address string An address:port to host an HTTP server on for realtime data, such as '127.0.0.1:8080'
-t, --listen-tcp strings An IP:port address to listen on for TCP. Can be specified multiple times.
-u, --listen-udp strings An IP:port address to listen on for UDP. Can be specified multiple times.
-i, --send-interval duration How long to wait between attempting to send data (default 1s)
Expand Down
73 changes: 22 additions & 51 deletions cmd/cyn/cmds/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"

"github.com/evertras/cynomys/pkg/cyn"
"github.com/evertras/cynomys/pkg/listener"
"github.com/evertras/cynomys/pkg/sender"
)
Expand All @@ -21,6 +21,9 @@ var config struct {
SendUDP []string `mapstructure:"send-udp"`
SendTCP []string `mapstructure:"send-tcp"`
SendInterval time.Duration `mapstructure:"send-interval"`
HTTPServer struct {
Address string `mapstructure:"address"`
} `mapstructure:"http"`
}

var (
Expand All @@ -40,8 +43,13 @@ func init() {
flags.StringSliceP("send-udp", "U", nil, "An IP:port address to send to (UDP). Can be specified multiple times.")
flags.StringSliceP("send-tcp", "T", nil, "An IP:port address to send to (TCP). Can be specified multiple times.")
flags.DurationP("send-interval", "i", time.Second, "How long to wait between attempting to send data")
flags.String("http.address", "", "An address:port to host an HTTP server on for realtime data, such as '127.0.0.1:8080'")

viper.BindPFlags(flags)
err := viper.BindPFlags(flags)

if err != nil {
panic(err)
}
}

func initConfig() {
Expand All @@ -64,94 +72,57 @@ func initConfig() {

var rootCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
eg := errgroup.Group{}
count := 0
instance := cyn.New()

if config.HTTPServer.Address != "" {
log.Printf("Hosting on http://%s", config.HTTPServer.Address)

instance.AddHTTPServer(config.HTTPServer.Address)
}

for _, listenOnUDP := range config.ListenUDP {
count++
addr, err := net.ResolveUDPAddr("udp", listenOnUDP)

if err != nil {
return fmt.Errorf("net.ResolveUDPAddr for %q: %w", listenOnUDP, err)
}

eg.Go(func() error {
l := listener.NewUDP(*addr)

return l.Listen()
})
instance.AddUDPListener(listener.NewUDP(*addr))
}

for _, listenOnTCP := range config.ListenTCP {
count++
addr, err := net.ResolveTCPAddr("tcp", listenOnTCP)

if err != nil {
return fmt.Errorf("net.ResolveTCPAddr for %q: %w", listenOnTCP, err)
}

eg.Go(func() error {
l := listener.NewTCP(*addr)

return l.Listen()
})
instance.AddTCPListener(listener.NewTCP(*addr))
}

for _, sendUDPTo := range config.SendUDP {
count++
addr, err := net.ResolveUDPAddr("udp", sendUDPTo)

if err != nil {
return fmt.Errorf("net.ResolveUDPAddr for %q: %w", sendUDPTo, err)
}

// Shadow capture for use within func below
sendUDPTo := sendUDPTo

eg.Go(func() error {
c := sender.NewUDPSender(*addr)

for {
err := c.Send([]byte("hi"))
if err != nil {
log.Printf("Failed to send to %q: %v", sendUDPTo, err)
}
time.Sleep(config.SendInterval)
}
})
instance.AddUDPSender(sender.NewUDPSender(*addr, config.SendInterval))
}

// We could probably generalize this a bit better, but it's short enough
// not to care for now.
for _, sendTCPTo := range config.SendTCP {
count++
addr, err := net.ResolveTCPAddr("tcp", sendTCPTo)

if err != nil {
return fmt.Errorf("net.ResolveTCPAddr for %q: %w", sendTCPTo, err)
}

// Shadow capture for use within func below
sendTCPTo := sendTCPTo

eg.Go(func() error {
c := sender.NewTCPSender(*addr)

for {
err := c.Send([]byte("hi"))
if err != nil {
log.Printf("Failed to send to %q: %v", sendTCPTo, err)
}
time.Sleep(config.SendInterval)
}
})
}

if count == 0 {
return fmt.Errorf("no listeners or senders specified")
instance.AddTCPSender(sender.NewTCPSender(*addr, config.SendInterval))
}

return eg.Wait()
return instance.Run()
},
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.19

require (
github.com/cucumber/godog v0.12.5
github.com/evertras/gonsen v0.3.3
github.com/spf13/cobra v1.1.1
github.com/spf13/viper v1.7.0
golang.org/x/sync v0.0.0-20190423024810-112230192c58
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/evertras/gonsen v0.3.3 h1:QIXhicDsdDDC2FDy7X4yKE4WbCjUqL2fA7aq6v2goVA=
github.com/evertras/gonsen v0.3.3/go.mod h1:bXfItIATDHsi7JwQSUBKyCs/90lmPHPrZksToENpntk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down
76 changes: 76 additions & 0 deletions pkg/cyn/cyn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package cyn

import (
"sync"

"github.com/evertras/cynomys/pkg/httpserver"
"github.com/evertras/cynomys/pkg/listener"
"github.com/evertras/cynomys/pkg/sender"
)

type Cyn struct {
mu sync.RWMutex

tcpListeners []*listener.TCPListener
udpListeners []*listener.UDPListener
tcpSenders []*sender.TCPSender
udpSenders []*sender.UDPSender

httpServer *httpserver.Server
}

func New() *Cyn {
return &Cyn{}
}

func (c *Cyn) AddTCPListener(tcpListener *listener.TCPListener) {
c.mu.Lock()
c.tcpListeners = append(c.tcpListeners, tcpListener)
c.mu.Unlock()
}

func (c *Cyn) TCPListeners() []*listener.TCPListener {
c.mu.RLock()
defer c.mu.RUnlock()

listeners := make([]*listener.TCPListener, len(c.tcpListeners))
copy(listeners, c.tcpListeners)

return listeners
}

func (c *Cyn) AddUDPListener(udpListener *listener.UDPListener) {
c.mu.Lock()
c.udpListeners = append(c.udpListeners, udpListener)
c.mu.Unlock()
}

func (c *Cyn) UDPListeners() []*listener.UDPListener {
c.mu.RLock()
defer c.mu.RUnlock()

listeners := make([]*listener.UDPListener, len(c.udpListeners))
copy(listeners, c.udpListeners)

return listeners
}

func (c *Cyn) AddTCPSender(tcpSender *sender.TCPSender) {
c.mu.Lock()
c.tcpSenders = append(c.tcpSenders, tcpSender)
c.mu.Unlock()
}

func (c *Cyn) AddUDPSender(udpSender *sender.UDPSender) {
c.mu.Lock()
c.udpSenders = append(c.udpSenders, udpSender)
c.mu.Unlock()
}

func (c *Cyn) AddHTTPServer(addr string) {
server := httpserver.NewServer(addr, c)

c.mu.Lock()
c.httpServer = server
c.mu.Unlock()
}
51 changes: 51 additions & 0 deletions pkg/cyn/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cyn

import (
"fmt"

"golang.org/x/sync/errgroup"
)

func (c *Cyn) Run() error {
eg := errgroup.Group{}

listenOrSendCount := 0

c.mu.RLock()

if c.httpServer != nil {
eg.Go(c.httpServer.ServeAndListen)
}

for _, tcpListener := range c.tcpListeners {
listenOrSendCount++
listen := tcpListener.Listen
eg.Go(listen)
}

for _, udpListener := range c.udpListeners {
listenOrSendCount++
listen := udpListener.Listen
eg.Go(listen)
}

for _, tcpSender := range c.tcpSenders {
listenOrSendCount++
run := tcpSender.Run
eg.Go(run)
}

for _, udpSender := range c.udpSenders {
listenOrSendCount++
run := udpSender.Run
eg.Go(run)
}

c.mu.RUnlock()

if listenOrSendCount == 0 {
return fmt.Errorf("no listeners or senders specified")
}

return eg.Wait()
}
32 changes: 32 additions & 0 deletions pkg/httpserver/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package httpserver

type ListenerInfo struct {
Address string
}

type OverallStatus struct {
TCPListeners []ListenerInfo
UDPListeners []ListenerInfo
}

type IndexData struct {
OverallStatus
}

func overallStatusFromGetter(getter OverallStatusGetter) OverallStatus {
s := OverallStatus{}

for _, tcpListener := range getter.TCPListeners() {
s.TCPListeners = append(s.TCPListeners, ListenerInfo{
Address: tcpListener.Addr(),
})
}

for _, udpListener := range getter.UDPListeners() {
s.UDPListeners = append(s.UDPListeners, ListenerInfo{
Address: udpListener.Addr(),
})
}

return s
}
Loading

0 comments on commit a9a1286

Please sign in to comment.