Skip to main content

Socket Programming Overview

Sproogy's socket module provides a high-level abstraction for building secure, scalable TCP/SSL socket applications.

Architecture

Sproogy's socket architecture consists of three main layers:

Key Components

1. Request/Response Model

All socket communication uses structured JSON messages:

Request:

{
"id": "req-12345",
"endpoint": "/users/42",
"headers": {
"Authorization": "Bearer token",
"X-Client-Version": "1.0"
},
"data": {
"includeDetails": true
}
}

Response:

{
"id": "req-12345",
"status": 200,
"headers": {
"X-Server-Version": "1.0"
},
"data": {
"id": 42,
"username": "alice",
"email": "alice@example.com"
}
}

2. SSL/TLS Security

Sproogy enforces SSL/TLS by default:

  • Server side: Implement SSLFactoryLoader to provide certificates
  • Client side: Implement SSLSecuritySocketLoader to trust server certificates
  • Protocols: Supports TLSv1.2, TLSv1.3
  • Cipher suites: Configurable via SSLContext

3. Routing System

Controllers use annotations for endpoint mapping:

@Controller
public class UserController {

@AppMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
// Automatic routing based on endpoint pattern
}

@AppMapping("/users")
public ResponseEntity<List<User>> listUsers() {
// Different endpoint, different method
}
}

4. Filter Chain

Cross-cutting concerns (auth, logging, encryption) are handled via filters:

@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
// Pre-processing: Check authentication
if (!isAuthenticated(request)) {
response.setStatus(401);
return; // Block request
}

chain.doFilter(request, response); // Continue to next filter/controller

// Post-processing: Add security headers
response.getHeaders().put("X-Frame-Options", "DENY");
}
}

5. Transformers

Transformers allow custom encoding/decoding:

  • InputTransformer: Transform raw input before deserialization (e.g., decrypt)
  • OutputTransformer: Transform output before sending (e.g., encrypt, compress)
@Component
public class EncryptionTransformer implements InputTransformer, OutputTransformer {

@Override
public String transform(String input) {
return decrypt(input); // Decrypt incoming data
}

@Override
public String transformOutput(String output) {
return encrypt(output); // Encrypt outgoing data
}
}

Complete Request Flow

Let's trace a complete request from client to server and back, showing every step:

Steps:

  1. Client Request: Client sends request as String
  2. Socket Connection: Request received by Server Socket
  3. Thread Creation: Server spawns new Thread to handle connection
  4. Request Handler: Thread delegates to Request Handler
  5. Input Transformation: Optional custom transformation (decrypt, decompress)
  6. Deserialization: JSON string → Request object
  7. Filter Chain Forward: Filters process request in order (PRE-processing)
    • Filter 1 (e.g., Authentication)
    • Filter 2 (e.g., Logging)
  8. Controller Routing: ControllerHandler uses Route Matcher to find endpoint
  9. Controller Execution: Matched controller method executes business logic
  10. Filter Chain Unstack: Filters process response in reverse order (POST-processing)
    • Filter 2 (e.g., Log response)
    • Filter 1 (e.g., Add security headers)
  11. Serialization: Response object → JSON string
  12. Output Transformation: Optional custom transformation (encrypt, compress)
  13. Send Response: Response String sent back to Client via Socket

Server vs Client

Server Responsibilities

  • Listen on configured port
  • Accept incoming SSL connections
  • Process requests through filter chain
  • Route to appropriate controllers
  • Return responses

Modules needed:

  • core (DI container)
  • socket-common (Request/Response models)
  • socket-server (Server, filters, routing)

Client Responsibilities

  • Connect to remote SSL server
  • Send requests with proper format
  • Receive and deserialize responses
  • Handle connection errors

Modules needed:

  • socket-common (Request/Response models)
  • socket-client (SocketTemplate, SSL loaders)

Communication Patterns

1. Request-Response (Synchronous)

Client sends request, waits for response:

// Client
ResponseEntity<User> response = socketTemplate.sendRequest("/users/1", User.class);
User user = response.getData();

2. Fire-and-Forget (Asynchronous)

Client sends request without waiting for response:

// Use ResponseListener for async handling
socketTemplate.addResponseListener(response -> {
System.out.println("Async response: " + response.getData());
});

socketTemplate.sendRequestAsync("/notify", notificationData);

3. Long-Lived Connections

Clients can maintain persistent connections for real-time communication:

// Server pushes updates to connected clients
@Component
public class NotificationService {
private final List<Socket> connectedClients = new CopyOnWriteArrayList<>();

public void broadcastUpdate(String message) {
for (Socket client : connectedClients) {
sendToClient(client, message);
}
}
}

Performance Considerations

Thread Pool

Sproogy uses a thread pool for handling concurrent clients:

// DefaultServer uses ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2
);

Tuning:

  • Small thread pool = fewer concurrent clients
  • Large thread pool = more memory usage
  • Consider virtual threads (Java 21+) for massive concurrency

Connection Limits

OS limits (file descriptors) affect max connections:

# Check limits (Linux)
ulimit -n

# Increase for production
ulimit -n 65536

Message Size

Large messages impact performance:

  • Keep messages small (< 1MB recommended)
  • Use compression for large payloads
  • Stream large files separately (don't send via socket protocol)

Security Best Practices

  1. Always use TLS 1.2+: Disable older protocols

    sslContext.init(keyManagers, trustManagers, null);
    SSLParameters params = new SSLParameters();
    params.setProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
  2. Validate client certificates (mutual TLS):

    sslSocket.setNeedClientAuth(true);
  3. Implement authentication filters:

    @Component
    public class AuthFilter implements Filter {
    public void doFilter(Request req, Response res, FilterChain chain) {
    String token = req.getHeaders().get("Authorization");
    if (!tokenService.validate(token)) {
    res.setStatus(401);
    return;
    }
    chain.doFilter(req, res);
    }
    }
  4. Rate limiting:

    @Component
    public class RateLimitFilter implements Filter {
    private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();

    public void doFilter(Request req, Response res, FilterChain chain) {
    String clientId = extractClientId(req);
    if (!limiters.computeIfAbsent(clientId, k -> createLimiter()).tryAcquire()) {
    res.setStatus(429); // Too Many Requests
    return;
    }
    chain.doFilter(req, res);
    }
    }

Next Steps

Dive deeper into specific topics:

👉 Server Setup - Configure and customize your server

👉 Client Setup - Build robust socket clients

👉 Routing - Advanced endpoint mapping

👉 Filters - Build powerful interceptors