aboutsummaryrefslogtreecommitdiff
path: root/Sources/swiftGopherClient
diff options
context:
space:
mode:
Diffstat (limited to 'Sources/swiftGopherClient')
-rw-r--r--Sources/swiftGopherClient/gopherClient.swift161
-rw-r--r--Sources/swiftGopherClient/gopherRequestResponseHandler.swift113
2 files changed, 0 insertions, 274 deletions
diff --git a/Sources/swiftGopherClient/gopherClient.swift b/Sources/swiftGopherClient/gopherClient.swift
deleted file mode 100644
index 3fd61ec..0000000
--- a/Sources/swiftGopherClient/gopherClient.swift
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-// gopherClient.swift
-//
-//
-// Created by Navan Chauhan on 12/12/23.
-//
-
-import Foundation
-import GopherHelpers
-import NIO
-import NIOTransportServices
-
-/// `GopherClient` is a class for handling network connections and requests to Gopher servers.
-///
-/// This client utilizes Swift NIO for efficient, non-blocking network operations. It automatically
-/// chooses the appropriate `EventLoopGroup` based on the running platform:
-/// - On iOS/macOS 10.14+, it uses `NIOTSEventLoopGroup` for optimal performance.
-/// - On Linux or older Apple platforms, it falls back to `MultiThreadedEventLoopGroup`.
-///
-/// The client supports both synchronous (completion handler-based) and asynchronous (Swift concurrency) APIs
-/// for sending requests to Gopher servers.
-public class GopherClient {
- /// The event loop group used for managing network operations.
- private let group: EventLoopGroup
-
- /// Initializes a new instance of `GopherClient`.
- ///
- /// This initializer automatically selects the appropriate `EventLoopGroup` based on the running platform.
- public init() {
- #if os(Linux)
- self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
- #else
- if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, visionOS 1.0, *) {
- self.group = NIOTSEventLoopGroup()
- } else {
- self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
- }
- #endif
- }
-
- /// Cleans up resources when the instance is deinitialized.
- deinit {
- self.shutdownEventLoopGroup()
- }
-
- /// Sends a request to a Gopher server using a completion handler.
- ///
- /// This method asynchronously establishes a connection, sends the request, and calls the completion
- /// handler with the result.
- ///
- /// - Parameters:
- /// - host: The host address of the Gopher server.
- /// - port: The port of the Gopher server. Defaults to 70.
- /// - message: The message to be sent to the server.
- /// - completion: A closure that handles the result of the request. It takes a `Result` type
- /// which either contains an array of `gopherItem` on success or an `Error` on failure.
- public func sendRequest(
- to host: String,
- port: Int = 70,
- message: String,
- completion: @escaping (Result<[gopherItem], Error>) -> Void
- ) {
- let bootstrap = self.createBootstrap(message: message, completion: completion)
- bootstrap.connect(host: host, port: port).whenComplete { result in
- switch result {
- case .success(let channel):
- channel.closeFuture.whenComplete { _ in
- print("Connection closed")
- }
- case .failure(let error):
- completion(.failure(error))
- }
- }
- }
-
- /// Sends a request to a Gopher server using Swift concurrency.
- ///
- /// This method asynchronously establishes a connection and sends the request,
- /// returning the result as an array of `gopherItem`.
- ///
- /// - Parameters:
- /// - host: The host address of the Gopher server.
- /// - port: The port of the Gopher server. Defaults to 70.
- /// - message: The message to be sent to the server.
- ///
- /// - Returns: An array of `gopherItem` representing the server's response.
- ///
- /// - Throws: An error if the connection fails or the server returns an invalid response.
- @available(iOS 13.0, *)
- @available(macOS 10.15, *)
- public func sendRequest(to host: String, port: Int = 70, message: String) async throws
- -> [gopherItem]
- {
- return try await withCheckedThrowingContinuation { continuation in
- let bootstrap = self.createBootstrap(message: message) { result in
- continuation.resume(with: result)
- }
-
- bootstrap.connect(host: host, port: port).whenComplete { result in
- switch result {
- case .success(let channel):
- channel.closeFuture.whenComplete { _ in
- print("Connection Closed")
- }
- case .failure(let error):
- continuation.resume(throwing: error)
- }
- }
- }
- }
-
- /// Creates a bootstrap for connecting to a Gopher server.
- ///
- /// This method sets up the appropriate bootstrap based on the platform and configures
- /// the channel with a `GopherRequestResponseHandler`.
- ///
- /// - Parameters:
- /// - message: The message to be sent to the server.
- /// - completion: A closure that handles the result of the request.
- ///
- /// - Returns: A `NIOClientTCPBootstrapProtocol` configured for Gopher communication.
- private func createBootstrap(
- message: String,
- completion: @escaping (Result<[gopherItem], Error>) -> Void
- ) -> NIOClientTCPBootstrapProtocol {
- let handler = GopherRequestResponseHandler(message: message, completion: completion)
-
- #if os(Linux)
- return ClientBootstrap(group: group)
- .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
- .channelInitializer { channel in
- channel.pipeline.addHandler(handler)
- }
- #else
- if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, visionOS 1.0, *) {
- return NIOTSConnectionBootstrap(group: group)
- .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
- .channelInitializer { channel in
- channel.pipeline.addHandler(handler)
- }
- } else {
- return ClientBootstrap(group: group)
- .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
- .channelInitializer { channel in
- channel.pipeline.addHandler(handler)
- }
- }
- #endif
- }
-
- /// Shuts down the event loop group, releasing any resources.
- ///
- /// This method is called during deinitialization to ensure clean shutdown of network resources.
- private func shutdownEventLoopGroup() {
- do {
- try group.syncShutdownGracefully()
- } catch {
- print("Error shutting down event loop group: \(error)")
- }
- }
-}
diff --git a/Sources/swiftGopherClient/gopherRequestResponseHandler.swift b/Sources/swiftGopherClient/gopherRequestResponseHandler.swift
deleted file mode 100644
index 10da67e..0000000
--- a/Sources/swiftGopherClient/gopherRequestResponseHandler.swift
+++ /dev/null
@@ -1,113 +0,0 @@
-//
-// gopherRequestResponseHandler.swift
-//
-//
-// Created by Navan Chauhan on 12/12/23.
-//
-
-import Foundation
-import GopherHelpers
-import NIO
-
-final class GopherRequestResponseHandler: ChannelInboundHandler {
- typealias InboundIn = ByteBuffer
- typealias OutboundOut = ByteBuffer
-
- private var accumulatedData: ByteBuffer
- private let message: String
- private let completion: (Result<[gopherItem], Error>) -> Void
-
- init(message: String, completion: @escaping (Result<[gopherItem], Error>) -> Void) {
- self.message = message
- self.completion = completion
- self.accumulatedData = ByteBuffer()
- }
-
- func channelActive(context: ChannelHandlerContext) {
- var buffer = context.channel.allocator.buffer(capacity: message.utf8.count)
- buffer.writeString(message)
- context.writeAndFlush(self.wrapOutboundOut(buffer), promise: nil)
- }
-
- func channelRead(context: ChannelHandlerContext, data: NIOAny) {
- var buffer = unwrapInboundIn(data)
- accumulatedData.writeBuffer(&buffer)
- }
-
- func channelInactive(context: ChannelHandlerContext) {
- if let dataCopy = accumulatedData.getSlice(at: 0, length: accumulatedData.readableBytes) {
- parseGopherServerResponse(
- response: accumulatedData.readString(length: accumulatedData.readableBytes) ?? "",
- originalBytes: dataCopy)
- }
- }
-
- func errorCaught(context: ChannelHandlerContext, error: Error) {
- print("Error: ", error)
- context.close(promise: nil)
- }
-
- func createGopherItem(rawLine: String, itemType: gopherItemType = .info, rawData: ByteBuffer)
- -> gopherItem
- {
- var item = gopherItem(rawLine: rawLine)
- item.parsedItemType = itemType
- item.rawData = rawData
-
- if rawLine.isEmpty {
- item.valid = false
- } else {
- let components = rawLine.components(separatedBy: "\t")
-
- // Handle cases where rawLine does not have any itemType in the first character
- item.message = String(components[0].dropFirst())
-
- if components.indices.contains(1) {
- item.selector = String(components[1])
- }
-
- if components.indices.contains(2) {
- item.host = String(components[2])
- }
-
- if components.indices.contains(3) {
- item.port = Int(String(components[3])) ?? 70
- }
- }
-
- return item
- }
-
- func parseGopherServerResponse(response: String, originalBytes: ByteBuffer) {
- var gopherServerResponse: [gopherItem] = []
-
- print("parsing")
- let carriageReturnCount = response.filter({ $0 == "\r" }).count
- let newlineCarriageReturnCount = response.filter({ $0 == "\r\n" }).count
- print(
- "Carriage Returns: \(carriageReturnCount), Newline + Carriage Returns: \(newlineCarriageReturnCount)"
- )
-
- if newlineCarriageReturnCount == 0 {
- for line in response.split(separator: "\n") {
- let lineItemType = getGopherFileType(item: "\(line.first ?? " ")")
- let item = createGopherItem(
- rawLine: String(line), itemType: lineItemType, rawData: originalBytes)
- gopherServerResponse.append(item)
-
- }
- } else {
- for line in response.split(separator: "\r\n") {
- let lineItemType = getGopherFileType(item: "\(line.first ?? " ")")
- let item = createGopherItem(
- rawLine: String(line), itemType: lineItemType, rawData: originalBytes)
- gopherServerResponse.append(item)
-
- }
- }
-
- print("done parsing")
-
- completion(.success(gopherServerResponse))
- }
-}