Skip to content

Commit

Permalink
Kitura/Kitura#33 Initial push. Basic connect support
Browse files Browse the repository at this point in the history
  • Loading branch information
shmuelk committed Oct 27, 2016
1 parent 33fdde5 commit c776bbd
Show file tree
Hide file tree
Showing 11 changed files with 429 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.build
build
.vagrant
Packages
*.pkg
Kitura-WebSocket.xcodeproj
*.DS_Store
25 changes: 25 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import PackageDescription

let package = Package(
name: "Kitura-WebSocket",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 0),
.Package(url: "https://github.com/IBM-Swift/BlueCryptor.git", majorVersion: 0, minor: 7),
]
)
67 changes: 67 additions & 0 deletions Sources/KituraWebSocket/WSConnectionUpgradeFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright IBM Corporation 2015
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

import Cryptor
import KituraNet

public class WSConnectionUpgradeFactory: ConnectionUpgradeFactory {
private var registry = Dictionary<String, WebSocketService>()

private let wsGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

/// The name of the protocol supported by this `ConnectionUpgradeFactory`. A case insensitive compare is made with this name.
public let name = "websocket"

init() {
ConnectionUpgrader.register(factory: self)
}

/// "Upgrade" a connection to the protocol supported by this `ConnectionUpgradeFactory`.
///
/// - Parameter handler: The `IncomingSocketHandler` that is handling the connection being upgraded.
/// - Parameter request: The `ServerRequest` object of the incoming "upgrade" request.
/// - Parameter response: The `ServerResponse` object that will be used to send the response of the "upgrade" request.
///
/// - Returns: A tuple of the created `IncomingSocketProcessor` and a message to send as the body of the response to
/// the upgrade request. The `IncomingSocketProcessor` should be nil if the upgrade request wasn't successful.
/// If the message is nil, the response will not contain a body.
///
/// - Note: The `ConnectionUpgradeFactory` instance doesn't need to work with the `ServerResponse` unless it
/// needs to add special headers to the response.
public func upgrade(handler: IncomingSocketHandler, request: ServerRequest, response: ServerResponse) -> (IncomingSocketProcessor?, String?) {

var processor: IncomingSocketProcessor?

if let securityKey = request.headers["Sec-WebSocket-Key"] {
let sha1 = Digest(using: .sha1)
let sha1Bytes = sha1.update(string: securityKey[0] + wsGUID)!.final()
let sha1Data = NSData(bytes: sha1Bytes, length: sha1Bytes.count)
response.headers["Sec-WebSocket-Accept"] =
[sha1Data.base64EncodedString(options: .lineLength64Characters)]
response.headers["Sec-WebSocket-Protocol"] = request.headers["Sec-WebSocket-Protocol"]

processor = WSSocketProcessor()
}

return (processor, nil)
}

func register(service: WebSocketService, onPath: String) {

}
}
61 changes: 61 additions & 0 deletions Sources/KituraWebSocket/WSSocketProcessor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright IBM Corporation 2015
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

import KituraNet

class WSSocketProcessor: IncomingSocketProcessor {
/// A back reference to the `IncomingSocketHandler` processing the socket that
/// this `IncomingDataProcessor` is processing.
public weak var handler: IncomingSocketHandler?

/// The socket if idle will be kept alive until...
public var keepAliveUntil: TimeInterval = 500.0

/// A flag to indicate that the socket has a request in progress
public var inProgress = true

/// Process data read from the socket.
///
/// - Parameter buffer: An NSData object containing the data that was read in
/// and needs to be processed.
///
/// - Returns: true if the data was processed, false if it needs to be processed later.
public func process(_ buffer: NSData) -> Bool {
return true
}

/// Write data to the socket
///
/// - Parameter from: An NSData object containing the bytes to be written to the socket.
public func write(from data: NSData) {
handler?.write(from: data)
}

/// Write a sequence of bytes in an array to the socket
///
/// - Parameter from: An UnsafeRawPointer to the sequence of bytes to be written to the socket.
/// - Parameter length: The number of bytes to write to the socket.
public func write(from bytes: UnsafeRawPointer, length: Int) {
handler?.write(from: bytes, length: length)
}

/// Close the socket and mark this handler as no longer in progress.
public func close() {
handler?.prepareToClose()
}
}
25 changes: 25 additions & 0 deletions Sources/KituraWebSocket/WebSocket.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright IBM Corporation 2015
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import KituraNet

public class WebSocket {
private static let factory = WSConnectionUpgradeFactory()

public static func register(service: WebSocketService, onPath path: String) {
factory.register(service: service, onPath: path.lowercased())
}
}
21 changes: 21 additions & 0 deletions Sources/KituraWebSocket/WebSocketClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright IBM Corporation 2015
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

public class WebSocketClient {

}
22 changes: 22 additions & 0 deletions Sources/KituraWebSocket/WebSocketService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright IBM Corporation 2015
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

public protocol WebSocketService {
func connected(client: WebSocketClient)
func disconnected(client: WebSocketClient)
}
70 changes: 70 additions & 0 deletions Tests/KituraWebSocketTests/BasicTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// BasicTests.swift
// Kitura-WebSocket
//
// Created by Samuel Kallner on 26/10/2016.
//
//

import Foundation/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import XCTest
import Foundation

@testable import Kitura
@testable import KituraNet
@testable import KituraWebSocket

class BasicTests: XCTestCase {

static var allTests: [(String, (BasicTests) -> () throws -> Void)] {
return [
("testPing", testPing)
]
}

override func setUp() {
doSetUp()
}

override func tearDown() {
doTearDown()
}

func testPing() {
WebSocket.register(service: TestWebSocketService(), onPath: "/wstester")

performServerTest(TestServerDelegate()) { (expectation: XCTestExpectation) in
}
}

class TestWebSocketService: WebSocketService {
public func connected(client: WebSocketClient) {
print("Connected")
}

public func disconnected(client: WebSocketClient){
print("disconnected")
}
}

class TestServerDelegate : ServerDelegate {
func handle(request: ServerRequest, response: ServerResponse) {
XCTFail("Server delegate invoked in an Upgrade scenario")
}
}
}
72 changes: 72 additions & 0 deletions Tests/KituraWebSocketTests/KituraTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright IBM Corporation 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

import XCTest
import Kitura

@testable import KituraNet

import Foundation
import Dispatch

protocol KituraTest {
func expectation(_ index: Int) -> XCTestExpectation
func waitExpectation(timeout t: TimeInterval, handler: XCWaitCompletionHandler?)
}

extension KituraTest {

func doSetUp() {
PrintLogger.use()
}

func doTearDown() {
// sleep(10)
}

func performServerTest(_ router: ServerDelegate,
asyncTasks: @escaping (XCTestExpectation) -> Void...) {
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.start()
sleep(1)
let requestQueue = DispatchQueue(label: "Request queue")

for (index, asyncTask) in asyncTasks.enumerated() {
let expectation = self.expectation(index)
requestQueue.async() {
asyncTask(expectation)
}
}

waitExpectation(timeout: 100) { error in
// blocks test until request completes
Kitura.stop()
XCTAssertNil(error)
}
}
}

extension XCTestCase: KituraTest {
func expectation(_ index: Int) -> XCTestExpectation {
let expectationDescription = "\(type(of: self))-\(index)"
return self.expectation(description: expectationDescription)
}

func waitExpectation(timeout t: TimeInterval, handler: XCWaitCompletionHandler?) {
self.waitForExpectations(timeout: t, handler: handler)
}
}

0 comments on commit c776bbd

Please sign in to comment.