Skip to content

Commit

Permalink
Add ObjectAdapter use to install middleware in Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardnormier committed Jun 7, 2024
1 parent 5d5b650 commit 43757b2
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 3 deletions.
1 change: 1 addition & 0 deletions swift/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ $tests = [
"Ice/inheritance",
"Ice/invoke",
"Ice/location",
"Ice/middleware",
"Ice/objects",
"Ice/operations",
"Ice/optional",
Expand Down
1 change: 1 addition & 0 deletions swift/src/Ice/Exception.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) ZeroC, Inc.

/// Base protocol for Ice exceptions.
public protocol Exception: Error {
/// Returns the type id of this exception.
Expand Down
13 changes: 13 additions & 0 deletions swift/src/Ice/ObjectAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import Foundation
/// object adapter is responsible for receiving requests from endpoints, and for mapping between servants, identities,
/// and proxies.
public protocol ObjectAdapter: AnyObject {

/// Get the dispatch pipeline of this object adapter.
var dispatchPipeline: Dispatcher { get }

/// Get the name of this object adapter.
///
/// - returns: `String` - This object adapter's name.
Expand Down Expand Up @@ -56,6 +60,15 @@ public protocol ObjectAdapter: AnyObject {
/// the same name.
func destroy()

/// Install a middleware in this object adapter.
/// - Parameter middleware: The middleware to install.
/// - Returns: This object adapter.
/// - Throws: `Ice.InitializationException` if the object adapter's dispatch pipeline has already been created. This
/// creation typically occurs the first time the object adapter dispatches an incoming request.
// TODO: should the middleware function throw?
@discardableResult
func use(_: @escaping (Dispatcher) -> Dispatcher) throws -> Self

/// Add a servant to this object adapter's Active Servant Map. Note that one servant can implement several Ice
/// objects by registering the servant with multiple identities. Adding a servant with an identity that is in the
/// map already throws AlreadyRegisteredException.
Expand Down
31 changes: 28 additions & 3 deletions swift/src/Ice/ObjectAdapterI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@
import IceImpl

class ObjectAdapterI: LocalObject<ICEObjectAdapter>, ObjectAdapter, ICEDispatchAdapter, Hashable {
private let communicator: Communicator
let servantManager: ServantManager

lazy var dispatchPipeline: Dispatcher = {
dispatchPipelineInitialized = true

// Since this computation is idempotent, we don't need to make it thread-safe. And we don't worry about the
// thread-safety of the erroneous situation where the application installs a middleware during the very first
// use of dispatchPipeline.
var dispatcher: Dispatcher = servantManager
for middleware in middlewareList.reversed() {
dispatcher = middleware(dispatcher)
}
return dispatcher
}()

private let communicator: Communicator
private var middlewareList: [(Dispatcher) -> Dispatcher] = []
private var dispatchPipelineInitialized: Bool = false

init(handle: ICEObjectAdapter, communicator: Communicator) {
self.communicator = communicator
servantManager = ServantManager(adapterName: handle.getName(), communicator: communicator)
super.init(handle: handle)

super.init(handle: handle)
handle.registerDispatchAdapter(self)
}

Expand Down Expand Up @@ -60,6 +76,15 @@ class ObjectAdapterI: LocalObject<ICEObjectAdapter>, ObjectAdapter, ICEDispatchA
return handle.destroy()
}

@discardableResult
func use(_ middleware: @escaping (Dispatcher) -> Dispatcher) throws -> Self {
if dispatchPipelineInitialized {
throw InitializationException(reason: "All middleware must be installed before the first dispatch.")
}
middlewareList.append(middleware)
return self
}

func add(servant: Dispatcher, id: Identity) throws -> ObjectPrx {
return try addFacet(servant: servant, id: id, facet: "")
}
Expand Down Expand Up @@ -237,7 +262,7 @@ class ObjectAdapterI: LocalObject<ICEObjectAdapter>, ObjectAdapter, ICEDispatchA

let request = IncomingRequest(current: current, inputStream: istr)

servantManager.dispatch(request).map { response in
dispatchPipeline.dispatch(request).map { response in
response.outputStream.finished().withUnsafeBytes {
completionHandler(
response.replyStatus.rawValue,
Expand Down
85 changes: 85 additions & 0 deletions swift/test/Ice/middleware/AllTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) ZeroC, Inc.

import Ice
import PromiseKit
import TestCommon

func allTests(_ helper: TestHelper) throws {
func test(_ value: Bool, file: String = #file, line: Int = #line) throws {
try helper.test(value, file: file, line: line)
}

let communicator = helper.communicator()
let output = helper.getWriter()

try testMiddlewareExecutionOrder(communicator, output)

// Verifies the middleware execute in installation order.
func testMiddlewareExecutionOrder(_ communicator: Communicator, _ output: TextWriter) throws {
output.write("testing middleware execution order... ")

// Arrange
let oa = try communicator.createObjectAdapterWithEndpoints(name: "MyOA", endpoints: "tcp -h 127.0.0.1 -p 0")
let log = MiddlewareLog()

let objPrx = try oa.add(servant: MyObjectDisp(MyObjectI()), id: Ice.stringToIdentity("test"))
try oa.use { next in
Middleware(next, "A", log)
}.use { next in
Middleware(next, "B", log)
}.use { next in
Middleware(next, "C", log)
}

// We're actually using colloc so no need to activate oa.

let p = uncheckedCast(prx: objPrx, type: MyObjectPrx.self)

// Act
try p.ice_ping()

// Assert
try test(log.inLog == ["A", "B", "C"])
try test(log.outLog == ["C", "B", "A"])

output.writeLine("ok")
oa.deactivate()
}

final class Middleware: Dispatcher {
private let next: Dispatcher
private let name: String
private var log: MiddlewareLog

func dispatch(_ request: IncomingRequest) -> Promise<OutgoingResponse> {
log.inLog.append(name)

return next.dispatch(request).map { response in
self.log.outLog.append(self.name)
return response
}
}

init(_ next: Dispatcher, _ name: String, _ log: MiddlewareLog) {
self.next = next
self.name = name
self.log = log
}
}

final class MiddlewareLog {
var inLog: [String]
var outLog: [String]

init() {
inLog = []
outLog = []
}
}

final class MyObjectI: MyObject {
func getName(current: Ice.Current) throws -> String {
"Foo"
}
}
}
14 changes: 14 additions & 0 deletions swift/test/Ice/middleware/Client.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) ZeroC, Inc.

import Ice
import TestCommon

class Client: TestHelperI {
override public func run(args: [String]) throws {
let communicator = try initialize(args)
defer {
communicator.destroy()
}
try allTests(self)
}
}
10 changes: 10 additions & 0 deletions swift/test/Ice/middleware/Test.ice
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) ZeroC, Inc.
#pragma once

module Test
{
interface MyObject
{
string getName();
}
}

0 comments on commit 43757b2

Please sign in to comment.