Stomper is a Swift network socket communication library that uses the STOMP protocol.
- Commands and headers can be safely defined at compile time.
- Converts STOMP frames into Swift objects.
- Transforms bidirectional socket communication into a pub/sub model, making it convenient to use completion handlers.
- Provides specialized protocols for easy management of specifications and endpoints.
- Allows implementation of interceptors and retry logic.
For more details on STOMP, refer to the STOMP Specification.
To install the package using SPM, add the dependency to your Package.swift
file.
.package(url: "https://github.com/dodo849/Stomper", from: "0.6.0")
- Open your Xcode project.
- Select the project file in the project navigator.
- Click on the "Swift Packages" tab.
- Click the "Add Package Dependency" button.
- Enter the GitHub URL or another repository URL, proceed to the next steps, and select the desired version.
- Xcode will automatically download and integrate the dependency into your project.
After installing the package, you can use the import statement to use the library in your project.
import Stomper
You can manage communication specifications by inheriting from EntryType
. First, enumerate the communication names in an enum and receive the necessary parameters using associated values. Then, inherit EntryType
and define the required baseURL
, topic
, command
, body
, and additionalHeaders
.
The server address, starting with
ws
orwss
.
The path corresponding to
destination
in STOMP communication. Usenil
if not needed.
Define which STOMP command to use. Each command can pass the required headers specified by the STOMP specification as associated values.
- For detailed command types and headers, refer to
EntryCommand
.
If there is information to be included in the body, pass the parameters along with the decode specification.
.none
: No body.withPlain
: Simple String.withData
: Data type information.withJSON
: JSON information. Receives anEncodable
object..withCustomJSONE
: Used when a custom encoder other than the defaultJSONEncoder
is needed. Receives anEncodable
object and anEncoder
..withParameters
: Used when passing key-value type parameters. Requires a special encoder to convert to Data type for the body.
If special headers are needed beyond the STOMP specification, use this. If it overlaps with the STOMP specification,
additionalHeaders
takes precedence.
enum ChatEntry {
case connect
case subscribeChat
case sendMessage(message: ChatRequestDTO)
case disconnect
}
extension ChatEntry: EntryType {
static var baseURL: URL {
URL(string: "wss://localhost:8080")!
}
var topic: String? {
switch self {
case .subscribeChat:
return "/sub/chat"
case .sendMessage:
return "/pub/chat"
default:
return nil
}
}
var command: EntryCommand {
switch self {
case .connect:
return .connect(host: "wss://localhost:8080")
case .subscribeChat:
return .subscribe()
case .sendMessage:
return .send()
case .disconnect:
return .disconnect()
}
}
var body: EntryRequestBodyType {
switch self {
case .sendMessage(let message):
return .withJSON(message)
default:
return .none
}
}
var additionalHeaders: [String : String] {
return [:]
}
}
Declare a StompProvider
that takes the enum implementing EntryType
as a generic parameter.
let provider = StompProvider<ChatEntry>()
You can send requests using the request
method. By defining the response type with the of
parameter, the provider decodes and delivers it through the success
parameter. For more details, refer to STOMPProviderProtocol
.
provider.request(
of: String.self,
entry: .connect
) { [weak self] result in
switch result {
case .failure(let error):
// Handle failure
case .success(_):
// Handle success
}
}
Stomper
provides the Interceptor
feature to add logic before and after communication.
Declare a structure that adopts the Interceptor
protocol to implement common logic before and after sending messages.
- execute: Method executed before sending a specific message.
- retry: Method to set retry logic and preprocessing when receiving an error frame from the server.
retry(count:delay:)
: Set the number of retries and the delay in seconds before retrying.doNotRetry
: Do not retry on failure.doNotRetryWithError
: Do not retry and deliver an error.
receipt-id
, the failed frame will not be resent.
struct StompTokenInterceptor: Interceptor {
func execute(
message: StompRequestMessage,
completion: @escaping (StompRequestMessage) -> Void
) {
let accessToken = tokenRepository.getAccessToken()
message.headers.addHeader(key: "Authorization", value: "Bearer \(accessToken)")
completion(message)
}
func retry(
message: StompRequestMessage,
errorMessage: StompReceiveMessage,
completion: @escaping (StompRequestMessage, InterceptorRetryType) -> Void
) {
Task {
do {
let accessToken = try await tokenRepository.fetchNewAccessToken()
var updatedMessage = message
updatedMessage.headers.addHeader(key: "Authorization", value: "Bearer \(accessToken)")
completion(updatedMessage, .retry(count: 3, delay: 2.0))
} catch {
// Fail to get new access token
let tokenError = TokenError.failedFetchNewAccessToken(
accessToken: tokenRepository.getAccessToken(),
refreshToken: tokenRepository.getRefreshToken()
)
completion(message, .doNotRetryWithError(tokenError))
}
}
}
}
You can set the interceptor by chaining it to the provider.
private let provider = StompProvider<ChatEntry>()
.intercepted(StompTokenInterceptor())
If you want to set multiple interceptors, refer to MultiInterceptor
.
Enable logging to view meaningful debugging information at the socket level in the console. It uses OSLog.
private let provider = StompProvider<ChatEntry>()
.enableLogging()
With this guide, you are now ready to set up and use STOMP communication effectively. You can add appropriate interceptors and logging features to enhance the reliability and debugging efficiency of your communication.