1
0
mirror of https://github.com/schollz/croc.git synced 2024-11-23 15:44:16 +03:00

Refactor tcp (#749)

* Refactor TCP server initialization

This refactor uses the functional options pattern to extract away the
optional TCP server configuration parameters. This:

- improves overall readability, by moving away from global variables
- makes it easier to configure the tcp server for tests

* Use ticker instead of for loop for room deletion

Go offers a ticker abstraction designed for performing tasks at
a regular interval, and this change uses the ticker for tcp room
deletion. It also cleans up the deletion goroutine gracefully.

* Add local relay interaction diagram

The diagram sketches out the interaction between clients and a local
relay.

* Add debug logs for room cleanup

These would be useful for future development (e.g.
adding a stopping mechanism for the TCP listener).
This commit is contained in:
Alex Bledea 2024-07-08 03:49:29 +03:00 committed by GitHub
parent b5da962bd1
commit da51eb8da3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 126 additions and 27 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

9
src/tcp/defaults.go Normal file
View File

@ -0,0 +1,9 @@
package tcp
import "time"
const (
DEFAULT_LOG_LEVEL = "debug"
DEFAULT_ROOM_CLEANUP_INTERVAL = 10 * time.Minute
DEFAULT_ROOM_TTL = 3 * time.Hour
)

53
src/tcp/options.go Normal file
View File

@ -0,0 +1,53 @@
package tcp
import (
"fmt"
"time"
)
// TODO: maybe export from logger library?
var availableLogLevels = []string{"info", "error", "warn", "debug", "trace"}
type serverOptsFunc func(s *server) error
func WithBanner(banner ...string) serverOptsFunc {
return func(s *server) error {
if len(banner) > 0 {
s.banner = banner[0]
}
return nil
}
}
func WithLogLevel(level string) serverOptsFunc {
return func(s *server) error {
if !containsSlice(availableLogLevels, level) {
return fmt.Errorf("invalid log level specified: %s", level)
}
s.debugLevel = level
return nil
}
}
func WithRoomCleanupInterval(interval time.Duration) serverOptsFunc {
return func(s *server) error {
s.roomCleanupInterval = interval
return nil
}
}
func WithRoomTTL(ttl time.Duration) serverOptsFunc {
return func(s *server) error {
s.roomTTL = ttl
return nil
}
}
func containsSlice(s []string, e string) bool {
for _, ss := range s {
if e == ss {
return true
}
}
return false
}

View File

@ -23,6 +23,11 @@ type server struct {
banner string
password string
rooms roomMap
roomCleanupInterval time.Duration
roomTTL time.Duration
stopRoomCleanup chan struct{}
}
type roomInfo struct {
@ -39,21 +44,36 @@ type roomMap struct {
const pingRoom = "pinglkasjdlfjsaldjf"
var timeToRoomDeletion = 10 * time.Minute
// Run starts a tcp listener, run async
func Run(debugLevel, host, port, password string, banner ...string) (err error) {
// newDefaultServer initializes a new server, with some default configuration options
func newDefaultServer() *server {
s := new(server)
s.roomCleanupInterval = DEFAULT_ROOM_CLEANUP_INTERVAL
s.roomTTL = DEFAULT_ROOM_TTL
s.debugLevel = DEFAULT_LOG_LEVEL
s.stopRoomCleanup = make(chan struct{})
return s
}
// RunWithOptionsAsync asynchronously starts a TCP listener.
func RunWithOptionsAsync(host, port, password string, opts ...serverOptsFunc) error {
s := newDefaultServer()
s.host = host
s.port = port
s.password = password
s.debugLevel = debugLevel
if len(banner) > 0 {
s.banner = banner[0]
for _, opt := range opts {
err := opt(s)
if err != nil {
return fmt.Errorf("could not apply optional configurations: %w", err)
}
}
return s.start()
}
// Run starts a tcp listener, run async
func Run(debugLevel, host, port, password string, banner ...string) (err error) {
return RunWithOptionsAsync(host, port, password, WithBanner(banner...), WithLogLevel(debugLevel))
}
func (s *server) start() (err error) {
log.SetLevel(s.debugLevel)
log.Debugf("starting with password '%s'", s.password)
@ -61,24 +81,8 @@ func (s *server) start() (err error) {
s.rooms.rooms = make(map[string]roomInfo)
s.rooms.Unlock()
// delete old rooms
go func() {
for {
time.Sleep(timeToRoomDeletion)
var roomsToDelete []string
s.rooms.Lock()
for room := range s.rooms.rooms {
if time.Since(s.rooms.rooms[room].opened) > 3*time.Hour {
roomsToDelete = append(roomsToDelete, room)
}
}
s.rooms.Unlock()
for _, room := range roomsToDelete {
s.deleteRoom(room)
}
}
}()
go s.deleteOldRooms()
defer s.stopRoomDeletion()
err = s.run()
if err != nil {
@ -173,6 +177,39 @@ func (s *server) run() (err error) {
}
}
// deleteOldRooms checks for rooms at a regular interval and removes those that
// have exceeded their allocated TTL.
func (s *server) deleteOldRooms() {
ticker := time.NewTicker(s.roomCleanupInterval)
for {
select {
case <-ticker.C:
var roomsToDelete []string
s.rooms.Lock()
for room := range s.rooms.rooms {
if time.Since(s.rooms.rooms[room].opened) > s.roomTTL {
roomsToDelete = append(roomsToDelete, room)
}
}
s.rooms.Unlock()
for _, room := range roomsToDelete {
s.deleteRoom(room)
log.Debugf("room cleaned up: %s", room)
}
case <-s.stopRoomCleanup:
ticker.Stop()
log.Debug("room cleanup stopped")
return
}
}
}
func (s *server) stopRoomDeletion() {
log.Debug("stop room cleanup fired")
s.stopRoomCleanup <- struct{}{}
}
var weakKey = []byte{1, 2, 3}
func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) {

View File

@ -23,8 +23,8 @@ func BenchmarkConnection(b *testing.B) {
func TestTCP(t *testing.T) {
log.SetLevel("error")
timeToRoomDeletion = 100 * time.Millisecond
go Run("debug", "127.0.0.1", "8381", "pass123", "8382")
timeToRoomDeletion := 100 * time.Millisecond
go RunWithOptionsAsync("127.0.0.1", "8381", "pass123", WithBanner("8382"), WithLogLevel("debug"), WithRoomTTL(timeToRoomDeletion))
time.Sleep(timeToRoomDeletion)
err := PingServer("127.0.0.1:8381")
assert.Nil(t, err)