mirror of
https://github.com/nabbar/golib.git
synced 2025-12-24 11:51:02 +08:00
[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, ...)
483 lines
13 KiB
Go
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
|
|
}
|