Welcome to the networking library, a powerful Kotlin/JVM component designed for the Bevel suite of developer tools! This library equips your applications with versatile client-side networking capabilities. It offers elegant abstractions for reactive HTTP/REST communication and provides a straightforward mechanism for local socket-based inter-process communication (IPC).
A key design principle of networking is its seamless integration with other Bevel components, particularly by implementing communication interfaces defined in the file-system-domain library. This ensures a decoupled and modular architecture within the Bevel ecosystem.
- 🚀 Reactive HTTP Clients: Build non-blocking, high-performance HTTP clients with
ReactorWebClientandReactorMonoWebClientinterfaces. - 🔩 Multiple HTTP Backends:
JavaNetReactorMonoWebClient: Leverages Java's modernjava.net.http.HttpClient(Java 11+), automatically supporting system proxy settings (e.g.,HTTPS_PROXY).ApacheReactorMonoWebClient: Utilizes the robust Apache HttpClient 5 for versatile HTTP communication, including specific parsing for OpenAI-like GET responses.
- 💬 Simplified REST Communication: The
RestCommunicationInterfacemakes interacting with local RESTful APIs a breeze, abstracting away the complexities of HTTP calls. - 🔗 Local Socket IPC:
LocalSocketoffers a simple, framed TCP/IP socket solution for direct messaging between processes on the same machine. - ✨ Project Reactor Integration: Fully embraces Project Reactor (
Mono,Flux) for asynchronous and reactive programming paradigms, exposed viaReactorMonoResponseandReactorFluxResponse. - 🔄 Effortless JSON Handling: Uses Jackson for smooth serialization and deserialization of objects, perfect for message payloads.
- 🧩 Modular Design: Integrates cleanly with interfaces from
software.bevel:file-system-domain, promoting loose coupling. - 🌍 Maven Central Availability: Easily incorporate into any JVM project using Gradle or Maven.
The networking library is not just a standalone utility; it's a crucial implementer of contracts defined in the Bevel file-system-domain library. This relationship is fundamental to its design:
networkingprovides concrete implementations for interfaces likesoftware.bevel.file_system_domain.web.LocalCommunicationInterface,software.bevel.file_system_domain.web.WebClient, andsoftware.bevel.file_system_domain.web.CommunicationInterfaceCreator.- This allows other Bevel tools (and your applications) to depend on the stable interfaces from
file-system-domainwhilenetworkingserves as a powerful, interchangeable runtime implementation for actual communication.
You'll need to include file-system-domain as a peer dependency when using networking.
Let's dive into the heart of the networking library:
LocalCommunicationInterface(fromfile-system-domain): This is the contract for local communication.networkingprovides two main implementations:RestCommunicationInterface:- Ideal for interacting with local REST APIs (e.g., a sidecar service).
- Uses a
WebClient(see below) to send HTTP requests to standard endpoints like/api/commandand/api/isAlive. - Simplifies sending data and checking service availability.
LocalSocket:- Provides direct TCP/IP socket communication on
localhost. - Messages are framed using
_!START_and_!END_tokens to ensure message integrity. - Suitable for simple, low-overhead IPC.
- The
messages: Flux<String>property is available for observing incoming messages. Note: For typical request-response, usesend(). This Flux is for potential future enhancements or passive listening.
- Provides direct TCP/IP socket communication on
These interfaces and classes form the foundation for HTTP communication:
ReactorWebClient<T>(Interface): A generic interface for web clients that return aReactorWebResponse.ReactorMonoWebClient(Interface): A specialization ofReactorWebClientfor responses that areReactorMonoResponse<String>, meaning they wrap aMono<String>.- Implementations:
JavaNetReactorMonoWebClient:- Uses Java's built-in
java.net.http.HttpClient(available since Java 11). - Feature: Automatically respects system-wide proxy configurations (e.g.,
HTTPS_PROXYenvironment variable). - A good default choice for modern Java environments.
- Uses Java's built-in
ApacheReactorMonoWebClient:- Built on the mature Apache HttpClient 5 library.
- Feature: Includes specific parsing logic for GET responses structured similarly to OpenAI API outputs (extracting
contentfromresponse.choices[0].message.content). - Offers robust and configurable HTTP communication.
How you handle responses matters in a reactive world:
ReactorWebResponse<T, PUBLISHER_TYPE>(Sealed Class): The base for responses backed by Project Reactor'sPublisher.ReactorMonoResponse<T>:- Represents a response expected to yield a single item (or none), wrapped in a
Mono<T>. - Implements
Blockable<T>:block()to synchronously get the result. - Implements
Subscribable<T>:subscribe(...)for asynchronous processing.
- Represents a response expected to yield a single item (or none), wrapped in a
ReactorFluxResponse<T>:- For responses that stream multiple items, wrapped in a
Flux<T>. - Implements
Blockable<List<T>>:block()to synchronously get all items as a list. - Implements
Subscribable<T>:subscribe(...)to process each item as it arrives.
- For responses that stream multiple items, wrapped in a
RestCommunicationInterfaceCreator:- A factory implementing
CommunicationInterfaceCreator(fromfile-system-domain). - Useful for scenarios where you need to defer the creation of
RestCommunicationInterfaceinstances or manage them through a standardized creator pattern.
- A factory implementing
Get up and running with the networking library in your JVM project.
- Java Development Kit (JDK) 17 or higher.
- A build tool like Gradle or Maven.
The library is available on Maven Central. Remember to also include file-system-domain as it provides the core interfaces.
Gradle (Kotlin DSL - build.gradle.kts):
dependencies {
implementation("software.bevel:networking:1.1.0")
implementation("software.bevel:file-system-domain:1.1.0") // Required peer dependency
// Transitive dependencies like Jackson, Reactor, SLF4J, Apache HttpClient are included.
}Gradle (Groovy DSL - build.gradle):
dependencies {
implementation 'software.bevel:networking:1.1.0'
implementation 'software.bevel:file-system-domain:1.1.0' // Required peer dependency
}Maven (pom.xml):
<dependencies>
<dependency>
<groupId>software.bevel</groupId>
<artifactId>networking</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>software.bevel</groupId>
<artifactId>file-system-domain</artifactId>
<version>1.1.0</version> <!-- Required peer dependency -->
</dependency>
</dependencies>(Always check Maven Central for the latest versions of networking and file-system-domain and update accordingly.)
Let's see the networking library in action!
Use RestCommunicationInterface to talk to a service running on localhost.
import software.bevel.networking.RestCommunicationInterface
import software.bevel.networking.web_client.JavaNetReactorMonoWebClient // Or ApacheReactorMonoWebClient
fun main() {
// Choose your WebClient implementation
val webClient = JavaNetReactorMonoWebClient()
val servicePort = "8080" // The port your local REST service listens on
val restApi = RestCommunicationInterface(webClient, servicePort)
if (restApi.isConnected()) {
println("Successfully connected to service on port $servicePort.")
// Example: Send a structured command (serialized to JSON)
val command = mapOf("action" to "processFile", "filePath" to "/path/to/file.txt")
try {
val responseJson = restApi.send(command) // POSTs to /api/command
println("Service response: $responseJson")
// You'd typically deserialize responseJson here
} catch (e: Exception) {
System.err.println("Error sending command: ${e.message}")
}
// Example: Send a simple string message and don't wait for detailed content
restApi.sendWithoutResponse("{\"event\":\"heartbeat\"}")
println("Sent heartbeat event.")
} else {
println("Could not connect to service on port $servicePort. Is it running?")
}
restApi.close() // For RestCommunicationInterface, this is currently a no-op but good practice.
}For direct, framed messaging between local processes.
import software.bevel.networking.LocalSocket
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper // For object serialization
fun main() {
val targetPort = 12345 // Port your local server process is listening on
// Attempt to connect
val socketComm = LocalSocket(targetPort)
if (socketComm.isConnected()) {
println("Connected to local server via socket on port $targetPort.")
// Send a plain string message
val response1 = socketComm.send("PING")
println("Server replied to PING: '$response1'")
// Send an object (will be JSON serialized)
val dataObject = mapOf("id" to 101, "payload" to "important data")
val response2 = socketComm.send(dataObject)
println("Server replied to object: '$response2'")
// You might deserialize response2 if it's JSON
// Send a fire-and-forget message
socketComm.sendWithoutResponse("System update: Process A started.")
println("Sent a notification message.")
// Important: Close the connection when done
socketComm.close()
println("Socket connection closed.")
} else {
println("Failed to establish socket connection on port $targetPort.")
}
}Directly use a WebClient like JavaNetReactorMonoWebClient for fine-grained control.
import software.bevel.networking.web_client.JavaNetReactorMonoWebClient
import software.bevel.networking.ReactorMonoResponse // To work with the response type
fun main() {
val webClient = JavaNetReactorMonoWebClient()
val apiUrl = "http://localhost:8080/api/resource" // Your target endpoint
// Asynchronous POST request
val postPayload = "{\"name\":\"gizmo\",\"value\":42}"
val postResponse: ReactorMonoResponse<String> = webClient.sendPostRequest(
url = apiUrl,
body = postPayload,
headers = listOf("X-Custom-Header" to "MyValue")
)
println("POST request sent. Subscribing for response...")
postResponse.subscribe(
{ responseBody -> println("Async POST Response Body: $responseBody") }, // onNext
{ error -> System.err.println("Async POST Error: ${error.message}") }, // onError
{ println("Async POST Completed.") } // onComplete (Mono only has onNext or onError for value)
)
// Asynchronous GET request
val getResponse: ReactorMonoResponse<String> = webClient.sendGetRequest(
url = "$apiUrl/123", // Example with a path parameter
parameters = listOf("filter" to "active")
)
println("GET request sent. Subscribing for response...")
getResponse.subscribe(
{ data -> println("Async GET Response Data: $data") },
{ err -> System.err.println("Async GET Error: ${err.message}") }
)
// In a real app, you wouldn't use Thread.sleep.
// This is just to keep the main thread alive for demo purposes.
println("Waiting for async operations to complete...")
Thread.sleep(5000)
println("Demo finished.")
}Useful for dependency injection or when setup needs to be delayed.
import software.bevel.networking.RestCommunicationInterfaceCreator
import software.bevel.networking.web_client.ApacheReactorMonoWebClient
fun main() {
val webClient = ApacheReactorMonoWebClient()
val servicePort = "9090"
// Create the factory
val interfaceCreator = RestCommunicationInterfaceCreator(webClient, servicePort)
// Create the instance when needed
val restComms = interfaceCreator.create()
println("RestCommunicationInterface created. Is connected: ${restComms.isConnected()}")
// Now use 'restComms' as shown in Example 1
// ...
restComms.close()
}Want to build the library yourself or contribute? Here's how:
-
Clone the Repository:
git clone https://github.com/Bevel-Software/networking.git cd networking -
Build with Gradle: The Gradle wrapper (
gradlew) is included../gradlew build
This command compiles the code, runs tests, and creates the JAR file (usually in
build/libs/). -
Run Tests:
./gradlew test
We welcome contributions! If you're interested in helping improve the networking library:
- Found a Bug or Have an Idea? Open an issue on the GitHub repository. Clear descriptions are greatly appreciated!
- Ready to Code?
- Fork the repository.
- Create a new branch for your feature or bug fix (e.g.,
feature/new-clientorfix/socket-timeout). - Make your changes. Please adhere to Kotlin coding conventions and ensure your code is well-formatted.
- Write Tests! New functionality should be accompanied by unit tests. Ensure all tests pass (
./gradlew test). - Commit your changes with clear, concise messages.
- Push your branch to your fork.
- Open a Pull Request against the main repository, detailing your changes.
We value respectful and constructive collaboration.
This project is open source and distributed under the Mozilla Public License Version 2.0. You can find the full license text in the LICENSE file.