Files
golib/socket/example_test.go
nabbar 3837f0b2bb Improvements, test & documentatons (2025-12 #1)
[file/bandwidth]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[file/perm]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases
- ADD function to parse form "rwx-wxr-x" or "-rwx-w-r-x"
- ADD function to ParseFileMode to convert os.FileMode to file.Perm

[file/progress]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[ioutils/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases

[logger/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- UPDATE config: remove FileBufferSize from OptionFile (rework hookfile)
- UPDATE fields: expose Store function in interface
- REWORK hookfile: rework package, use aggregator to allow multi write and
  single file
- FIX hookstderr: fix bug with NonColorable
- FIX hookstdout: fix bug with NonColorable
- FIX hookwriter: fix bug with NonColorable

[network/protocol]
- ADD function IsTCP, IsUDP, IsUnixLike to check type of protocol

[runner]
- FIX typo

[socket]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- REWORK server: use context compatible io.reader, io.writer, io.closer
  instead of reader / writer
- REWORK server: simplify, optimize server
- REMOVE reader, writer type
- ADD context: add new interface in root socket interface to expose
  context interface that extend context, io reader/writer/closer,
dediacted function to server (IsConnected, ...)
2025-12-02 02:56:20 +01:00

483 lines
13 KiB
Go

/*
* MIT License
*
* Copyright (c) 2022 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package socket_test
import (
"context"
"fmt"
"io"
"log"
"net"
"time"
libsck "github.com/nabbar/golib/socket"
)
// ExampleConnState demonstrates the string representation of connection states.
// This is useful for logging and monitoring connection lifecycle events.
func ExampleConnState() {
// Display all connection states
states := []libsck.ConnState{
libsck.ConnectionDial,
libsck.ConnectionNew,
libsck.ConnectionRead,
libsck.ConnectionHandler,
libsck.ConnectionWrite,
libsck.ConnectionClose,
}
for _, state := range states {
fmt.Println(state.String())
}
// Output:
// Dial Connection
// New Connection
// Read Incoming Stream
// Run HandlerFunc
// Write Outgoing Steam
// Close Connection
}
// ExampleErrorFilter demonstrates filtering expected network errors.
// This is useful during connection cleanup and shutdown sequences.
func ExampleErrorFilter() {
// Simulate various errors
normalErr := fmt.Errorf("connection timeout")
closedErr := fmt.Errorf("use of closed network connection")
// Filter errors
if err := libsck.ErrorFilter(normalErr); err != nil {
fmt.Println("Error occurred:", err.Error())
}
// This error is filtered out (returns nil)
if err := libsck.ErrorFilter(closedErr); err != nil {
fmt.Println("Error occurred:", err.Error())
} else {
fmt.Println("Closed connection error filtered")
}
// Nil errors are handled safely
if err := libsck.ErrorFilter(nil); err != nil {
fmt.Println("Error occurred:", err.Error())
} else {
fmt.Println("No error")
}
// Output:
// Error occurred: connection timeout
// Closed connection error filtered
// No error
}
// ExampleFuncError demonstrates registering an error callback for socket operations.
// This pattern is used in both client and server implementations.
func ExampleFuncError() {
// Define error handler
errHandler := func(errs ...error) {
for _, err := range errs {
// Filter expected errors
if err := libsck.ErrorFilter(err); err != nil {
log.Printf("Socket error: %v", err)
}
}
}
// Simulate error handling
errHandler(fmt.Errorf("connection refused"))
errHandler(fmt.Errorf("use of closed network connection")) // Filtered
errHandler(fmt.Errorf("timeout"), fmt.Errorf("network unreachable"))
fmt.Println("Error handler demonstrated")
// Output:
// Error handler demonstrated
}
// ExampleFuncInfo demonstrates tracking connection state changes.
// This is useful for monitoring, logging, and debugging.
func ExampleFuncInfo() {
// Define connection info handler
infoHandler := func(local, remote net.Addr, state libsck.ConnState) {
fmt.Printf("[%s] %s -> %s\n", state.String(), remote.String(), local.String())
}
// Simulate connection lifecycle
localAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080}
remoteAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 54321}
infoHandler(localAddr, remoteAddr, libsck.ConnectionNew)
infoHandler(localAddr, remoteAddr, libsck.ConnectionRead)
infoHandler(localAddr, remoteAddr, libsck.ConnectionWrite)
infoHandler(localAddr, remoteAddr, libsck.ConnectionClose)
// Output:
// [New Connection] 127.0.0.1:54321 -> 127.0.0.1:8080
// [Read Incoming Stream] 127.0.0.1:54321 -> 127.0.0.1:8080
// [Write Outgoing Steam] 127.0.0.1:54321 -> 127.0.0.1:8080
// [Close Connection] 127.0.0.1:54321 -> 127.0.0.1:8080
}
// ExampleHandlerFunc demonstrates a basic request handler for socket servers.
// This shows the typical pattern for handling connections.
func ExampleHandlerFunc() {
// Define handler function
handler := func(ctx libsck.Context) {
// Check if connection is active
if !ctx.IsConnected() {
return
}
// Log connection info
fmt.Printf("Handling connection from %s\n", ctx.RemoteHost())
// Read request
buf := make([]byte, 1024)
n, err := ctx.Read(buf)
if err != nil {
if err != io.EOF {
fmt.Printf("Read error: %v\n", err)
}
return
}
// Process request
request := string(buf[:n])
fmt.Printf("Received: %s\n", request)
// Send response
response := []byte("Response: " + request)
if _, err := ctx.Write(response); err != nil {
fmt.Printf("Write error: %v\n", err)
return
}
fmt.Println("Request handled successfully")
}
// Handler is now ready to be used in server configuration
_ = handler
fmt.Println("Handler function created")
// Output:
// Handler function created
}
// ExampleContext demonstrates using the Context interface in a handler.
// This shows how to access connection state and perform I/O operations.
func ExampleContext() {
// This example demonstrates Context usage patterns
// In real usage, Context is provided by the server to HandlerFunc
// Example handler showing Context usage
handler := func(ctx libsck.Context) {
// Check connection state
if !ctx.IsConnected() {
fmt.Println("Connection closed")
return
}
// Get connection information
fmt.Printf("Local: %s, Remote: %s\n", ctx.LocalHost(), ctx.RemoteHost())
// Check for cancellation
select {
case <-ctx.Done():
fmt.Println("Context canceled:", ctx.Err())
return
default:
}
// Perform I/O operations
buf := make([]byte, 1024)
if n, err := ctx.Read(buf); err == nil {
fmt.Printf("Read %d bytes\n", n)
ctx.Write([]byte("ACK"))
}
}
_ = handler
fmt.Println("Context usage demonstrated")
// Output:
// Context usage demonstrated
}
// ExampleServer demonstrates the Server interface usage pattern.
// This shows how servers are typically configured and started.
func ExampleServer() {
// This demonstrates the Server interface pattern
// Actual server creation is done via protocol-specific packages
var srv libsck.Server // Created via server.New() or protocol-specific constructors
if srv != nil {
// Register error handler
srv.RegisterFuncError(func(errs ...error) {
for _, err := range errs {
log.Printf("Error: %v", err)
}
})
// Register connection state tracker
srv.RegisterFuncInfo(func(local, remote net.Addr, state libsck.ConnState) {
log.Printf("[%s] %s -> %s", state, remote, local)
})
// Start server with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// This would block until context is canceled or error occurs
_ = srv.Listen(ctx)
// Graceful shutdown
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
srv.Shutdown(shutdownCtx)
}
fmt.Println("Server interface demonstrated")
// Output:
// Server interface demonstrated
}
// ExampleClient demonstrates the Client interface usage pattern.
// This shows how clients are typically configured and used.
func ExampleClient() {
// This demonstrates the Client interface pattern
// Actual client creation is done via protocol-specific packages
var cli libsck.Client // Created via client.New() or protocol-specific constructors
if cli != nil {
// Register error handler
cli.RegisterFuncError(func(errs ...error) {
for _, err := range errs {
log.Printf("Error: %v", err)
}
})
// Connect to server
ctx := context.Background()
if err := cli.Connect(ctx); err != nil {
log.Fatal(err)
}
defer cli.Close()
// Write request
if _, err := cli.Write([]byte("Hello, server!\n")); err != nil {
log.Fatal(err)
}
// Read response
buf := make([]byte, 1024)
if n, err := cli.Read(buf); err == nil {
fmt.Printf("Response: %s", buf[:n])
}
}
fmt.Println("Client interface demonstrated")
// Output:
// Client interface demonstrated
}
// ExampleHandler demonstrates using a stateful handler type instead of HandlerFunc.
// This is useful for handlers that need to maintain state or access dependencies.
func ExampleHandler() {
// Define a stateful handler type
type EchoHandler struct {
prefix string
}
// Implement the Handler interface
handleFunc := func(h *EchoHandler, ctx libsck.Context) {
buf := make([]byte, 1024)
n, err := ctx.Read(buf)
if err != nil {
return
}
// Use handler state
response := []byte(h.prefix + string(buf[:n]))
ctx.Write(response)
}
// Create handler instance
handler := &EchoHandler{prefix: "Echo: "}
// Use in server (would be: cfg.Handler = handler)
_ = handleFunc
_ = handler
fmt.Println("Stateful handler demonstrated")
// Output:
// Stateful handler demonstrated
}
// ExampleUpdateConn demonstrates customizing connection settings.
// This callback is used to configure socket options before use.
func ExampleUpdateConn() {
// Define connection customization function
updateConn := func(conn net.Conn) {
// Set connection deadlines
if tcpConn, ok := conn.(*net.TCPConn); ok {
// Disable Nagle's algorithm for low latency
_ = tcpConn.SetNoDelay(true)
// Set keepalive
_ = tcpConn.SetKeepAlive(true)
_ = tcpConn.SetKeepAlivePeriod(30 * time.Second)
// Set buffer sizes
_ = tcpConn.SetReadBuffer(64 * 1024)
_ = tcpConn.SetWriteBuffer(64 * 1024)
}
// Set overall deadlines
_ = conn.SetDeadline(time.Now().Add(5 * time.Minute))
}
_ = updateConn
fmt.Println("Connection customization demonstrated")
// Output:
// Connection customization demonstrated
}
// ExampleResponse demonstrates processing server responses on the client side.
// This callback is used with Client.Once() for request/response patterns.
func ExampleResponse() {
// Define response handler
responseHandler := func(r io.Reader) {
buf := make([]byte, 1024)
n, err := r.Read(buf)
if err != nil && err != io.EOF {
log.Printf("Response error: %v", err)
return
}
response := string(buf[:n])
fmt.Printf("Server response: %s\n", response)
// Process response data
// Parse, validate, etc.
}
_ = responseHandler
fmt.Println("Response handler demonstrated")
// Output:
// Response handler demonstrated
}
// Example_errorHandling demonstrates comprehensive error handling patterns.
// This shows best practices for handling errors in socket operations.
func Example_errorHandling() {
// Define comprehensive error handler
handleErrors := func(operation string, errs ...error) {
for _, err := range errs {
// Filter expected errors
if err := libsck.ErrorFilter(err); err != nil {
// Log with context
fmt.Printf("[%s] Error: %v\n", operation, err)
// Handle specific error types
if netErr, ok := err.(net.Error); ok {
if netErr.Timeout() {
fmt.Println("Network timeout")
}
if netErr.Temporary() {
fmt.Println("Temporary error, can retry")
}
}
}
}
}
// Simulate various error scenarios
handleErrors("connect", fmt.Errorf("connection refused"))
handleErrors("read", fmt.Errorf("use of closed network connection")) // Filtered
handleErrors("write", fmt.Errorf("broken pipe"))
// Output:
// [connect] Error: connection refused
// [write] Error: broken pipe
}
// Example_connectionLifecycle demonstrates a complete connection lifecycle
// with proper state tracking and resource management.
func Example_connectionLifecycle() {
// Track connection state
states := make([]libsck.ConnState, 0)
// Info callback to track states
trackState := func(local, remote net.Addr, state libsck.ConnState) {
states = append(states, state)
}
// Simulate lifecycle
localAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8080}
remoteAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 54321}
// Client dialing
trackState(localAddr, remoteAddr, libsck.ConnectionDial)
// Connection established
trackState(localAddr, remoteAddr, libsck.ConnectionNew)
// Request processing
trackState(localAddr, remoteAddr, libsck.ConnectionRead)
trackState(localAddr, remoteAddr, libsck.ConnectionHandler)
trackState(localAddr, remoteAddr, libsck.ConnectionWrite)
// Connection teardown
trackState(localAddr, remoteAddr, libsck.ConnectionCloseRead)
trackState(localAddr, remoteAddr, libsck.ConnectionCloseWrite)
trackState(localAddr, remoteAddr, libsck.ConnectionClose)
// Display lifecycle
fmt.Println("Connection lifecycle:")
for i, state := range states {
fmt.Printf("%d. %s\n", i+1, state.String())
}
// Output:
// Connection lifecycle:
// 1. Dial Connection
// 2. New Connection
// 3. Read Incoming Stream
// 4. Run HandlerFunc
// 5. Write Outgoing Steam
// 6. Close Incoming Stream
// 7. Close Outgoing Stream
// 8. Close Connection
}