first commit
This commit is contained in:
123
listeners/http_stats.go
Normal file
123
listeners/http_stats.go
Normal file
@ -0,0 +1,123 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2023 mysubarumq
|
||||
// SPDX-FileContributor: alex-savin
|
||||
|
||||
package listeners
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.savin.nyc/alex/mysubaru-mq/system"
|
||||
)
|
||||
|
||||
// HTTPStats is a listener for presenting the server $SYS stats on a JSON http endpoint.
|
||||
type HTTPStats struct {
|
||||
sync.RWMutex
|
||||
id string // the internal id of the listener
|
||||
address string // the network address to bind to
|
||||
config *Config // configuration values for the listener
|
||||
listen *http.Server // the http server
|
||||
sysInfo *system.Info // pointers to the server data
|
||||
log *slog.Logger // server logger
|
||||
end uint32 // ensure the close methods are only called once
|
||||
}
|
||||
|
||||
// NewHTTPStats initialises and returns a new HTTP listener, listening on an address.
|
||||
func NewHTTPStats(id, address string, config *Config, sysInfo *system.Info) *HTTPStats {
|
||||
if config == nil {
|
||||
config = new(Config)
|
||||
}
|
||||
return &HTTPStats{
|
||||
id: id,
|
||||
address: address,
|
||||
sysInfo: sysInfo,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// ID returns the id of the listener.
|
||||
func (l *HTTPStats) ID() string {
|
||||
return l.id
|
||||
}
|
||||
|
||||
// Address returns the address of the listener.
|
||||
func (l *HTTPStats) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
// Protocol returns the address of the listener.
|
||||
func (l *HTTPStats) Protocol() string {
|
||||
if l.listen != nil && l.listen.TLSConfig != nil {
|
||||
return "https"
|
||||
}
|
||||
|
||||
return "http"
|
||||
}
|
||||
|
||||
// Init initializes the listener.
|
||||
func (l *HTTPStats) Init(log *slog.Logger) error {
|
||||
l.log = log
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", l.jsonHandler)
|
||||
l.listen = &http.Server{
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
Addr: l.address,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
if l.config.TLSConfig != nil {
|
||||
l.listen.TLSConfig = l.config.TLSConfig
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve starts listening for new connections and serving responses.
|
||||
func (l *HTTPStats) Serve() {
|
||||
|
||||
var err error
|
||||
if l.listen.TLSConfig != nil {
|
||||
err = l.listen.ListenAndServeTLS("", "")
|
||||
} else {
|
||||
err = l.listen.ListenAndServe()
|
||||
}
|
||||
|
||||
// After the listener has been shutdown, no need to print the http.ErrServerClosed error.
|
||||
if err != nil && atomic.LoadUint32(&l.end) == 0 {
|
||||
l.log.Error("failed to serve.", "error", err, "listener", l.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the listener and any client connections.
|
||||
func (l *HTTPStats) Close() {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if atomic.CompareAndSwapUint32(&l.end, 0, 1) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = l.listen.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// closeClients(l.id)
|
||||
}
|
||||
|
||||
// jsonHandler is an HTTP handler which outputs the $SYS stats as JSON.
|
||||
func (l *HTTPStats) jsonHandler(w http.ResponseWriter, req *http.Request) {
|
||||
info := *l.sysInfo.Clone()
|
||||
|
||||
out, err := json.MarshalIndent(info, "", "\t")
|
||||
if err != nil {
|
||||
_, _ = io.WriteString(w, err.Error())
|
||||
}
|
||||
|
||||
_, _ = w.Write(out)
|
||||
}
|
126
listeners/listeners.go
Normal file
126
listeners/listeners.go
Normal file
@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2023 mysubarumq
|
||||
// SPDX-FileContributor: alex-savin
|
||||
|
||||
package listeners
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"sync"
|
||||
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Config contains configuration values for a listener.
|
||||
type Config struct {
|
||||
// TLSConfig is a tls.Config configuration to be used with the listener.
|
||||
// See examples folder for basic and mutual-tls use.
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
// Listener is an interface for network listeners. A network listener listens
|
||||
// for incoming client connections and adds them to the server.
|
||||
type Listener interface {
|
||||
Init(*slog.Logger) error // open the network address
|
||||
Serve() // starting actively listening for new connections
|
||||
ID() string // return the id of the listener
|
||||
Address() string // the address of the listener
|
||||
Protocol() string // the protocol in use by the listener
|
||||
Close() // stop and close the listener
|
||||
}
|
||||
|
||||
// Listeners contains the network listeners for the broker.
|
||||
type Listeners struct {
|
||||
ClientsWg sync.WaitGroup // a waitgroup that waits for all clients in all listeners to finish.
|
||||
internal map[string]Listener // a map of active listeners.
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// New returns a new instance of Listeners.
|
||||
func New() *Listeners {
|
||||
return &Listeners{
|
||||
internal: map[string]Listener{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a new listener to the listeners map, keyed on id.
|
||||
func (l *Listeners) Add(val Listener) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.internal[val.ID()] = val
|
||||
}
|
||||
|
||||
// Get returns the value of a listener if it exists.
|
||||
func (l *Listeners) Get(id string) (Listener, bool) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
val, ok := l.internal[id]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Len returns the length of the listeners map.
|
||||
func (l *Listeners) Len() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return len(l.internal)
|
||||
}
|
||||
|
||||
// Delete removes a listener from the internal map.
|
||||
func (l *Listeners) Delete(id string) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
delete(l.internal, id)
|
||||
}
|
||||
|
||||
// Serve starts a listener serving from the internal map.
|
||||
func (l *Listeners) Serve(id string) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
listener := l.internal[id]
|
||||
|
||||
go func() {
|
||||
listener.Serve()
|
||||
}()
|
||||
}
|
||||
|
||||
// ServeAll starts all listeners serving from the internal map.
|
||||
func (l *Listeners) ServeAll() {
|
||||
l.RLock()
|
||||
i := 0
|
||||
ids := make([]string, len(l.internal))
|
||||
for id := range l.internal {
|
||||
ids[i] = id
|
||||
i++
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
for _, id := range ids {
|
||||
l.Serve(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops a listener from the internal map.
|
||||
func (l *Listeners) Close(id string) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
if listener, ok := l.internal[id]; ok {
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// CloseAll iterates and closes all registered listeners.
|
||||
func (l *Listeners) CloseAll() {
|
||||
l.RLock()
|
||||
i := 0
|
||||
ids := make([]string, len(l.internal))
|
||||
for id := range l.internal {
|
||||
ids[i] = id
|
||||
i++
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
for _, id := range ids {
|
||||
l.Close(id)
|
||||
}
|
||||
l.ClientsWg.Wait()
|
||||
}
|
102
listeners/mock.go
Normal file
102
listeners/mock.go
Normal file
@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// SPDX-FileCopyrightText: 2023 mysubarumq
|
||||
// SPDX-FileContributor: alex-savin
|
||||
|
||||
package listeners
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// MockEstablisher is a function signature which can be used in testing.
|
||||
func MockEstablisher(id string, c net.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockCloser is a function signature which can be used in testing.
|
||||
func MockCloser(id string) {}
|
||||
|
||||
// MockListener is a mock listener for establishing client connections.
|
||||
type MockListener struct {
|
||||
sync.RWMutex
|
||||
id string // the id of the listener
|
||||
address string // the network address the listener binds to
|
||||
Config *Config // configuration for the listener
|
||||
done chan bool // indicate the listener is done
|
||||
Serving bool // indicate the listener is serving
|
||||
Listening bool // indiciate the listener is listening
|
||||
ErrListen bool // throw an error on listen
|
||||
}
|
||||
|
||||
// NewMockListener returns a new instance of MockListener.
|
||||
func NewMockListener(id, address string) *MockListener {
|
||||
return &MockListener{
|
||||
id: id,
|
||||
address: address,
|
||||
done: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves the mock listener.
|
||||
func (l *MockListener) Serve() {
|
||||
l.Lock()
|
||||
l.Serving = true
|
||||
l.Unlock()
|
||||
|
||||
for range l.done {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the listener.
|
||||
func (l *MockListener) Init(log *slog.Logger) error {
|
||||
if l.ErrListen {
|
||||
return fmt.Errorf("listen failure")
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.Listening = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns the id of the mock listener.
|
||||
func (l *MockListener) ID() string {
|
||||
return l.id
|
||||
}
|
||||
|
||||
// Address returns the address of the listener.
|
||||
func (l *MockListener) Address() string {
|
||||
return l.address
|
||||
}
|
||||
|
||||
// Protocol returns the address of the listener.
|
||||
func (l *MockListener) Protocol() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
// Close closes the mock listener.
|
||||
func (l *MockListener) Close() {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.Serving = false
|
||||
close(l.done)
|
||||
}
|
||||
|
||||
// IsServing indicates whether the mock listener is serving.
|
||||
func (l *MockListener) IsServing() bool {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.Serving
|
||||
}
|
||||
|
||||
// IsListening indicates whether the mock listener is listening.
|
||||
func (l *MockListener) IsListening() bool {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
return l.Listening
|
||||
}
|
Reference in New Issue
Block a user