Skip to content

Commit 0cfaded

Browse files
committed
Moved WebSockets example to its own repo
1 parent 11ab055 commit 0cfaded

File tree

11 files changed

+834
-2
lines changed

11 files changed

+834
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@ Carthage/Build
6161

6262
fastlane/report.xml
6363
fastlane/screenshots
64+
65+
Packages/

Package.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Package.swift
3+
// PerfectExamples
4+
//
5+
// Created by Kyle Jessup on 3/22/16.
6+
// Copyright (C) 2016 PerfectlySoft, Inc.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This source file is part of the Perfect.org open source project
11+
//
12+
// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors
13+
// Licensed under Apache License v2.0
14+
//
15+
// See http://perfect.org/licensing.html for license information
16+
//
17+
//===----------------------------------------------------------------------===//
18+
//
19+
20+
import PackageDescription
21+
22+
let package = Package(
23+
name: "WebSocketsServer",
24+
targets: [
25+
26+
],
27+
dependencies: [
28+
.Package(url:"https://github.com/PerfectlySoft/Perfect.git", majorVersion: 0, minor: 17)
29+
]
30+
)

README.md

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,107 @@
1-
# PerfectExample-WebSocketsServer
2-
Perfect Example Module: WebSockets Server
1+
# WebSockets Serving
2+
This example illustrates how to set up a WebSocket server and handle a connection.
3+
4+
To use the example with Swift Package Manager, type ```swift build``` and then run ``` .build/debug/WebSocketsServer```.
5+
6+
To use the example with Xcode, run the **WebSockets Server** target. This will launch the Perfect HTTP Server.
7+
8+
Navigate in your web browser to [http://localhost:8181/](http://localhost:8181/)
9+
10+
## Initiating a WebSocket Session
11+
12+
Add one or more URL routes using the `Routing.Routes` subscript functions. These routes will be the endpoints for the WebSocket session. Set the route handler to `WebSocketHandler` and provide your custom closure which will return your own session handler.
13+
14+
The following code is taken from the example project and shows how to enable the system and add a WebSocket handler.
15+
16+
```
17+
func addWebSocketsHandler() {
18+
19+
// Add a default route which lets us serve the static index.html file
20+
Routing.Routes["*"] = { request, response in StaticFileHandler().handleRequest(request: request, response: response) }
21+
22+
// Add the endpoint for the WebSocket example system
23+
Routing.Routes[.Get, "/echo"] = {
24+
request, response in
25+
26+
// To add a WebSocket service, set the handler to WebSocketHandler.
27+
// Provide your closure which will return your service handler.
28+
WebSocketHandler(handlerProducer: {
29+
(request: WebRequest, protocols: [String]) -> WebSocketSessionHandler? in
30+
31+
// Check to make sure the client is requesting our "echo" service.
32+
guard protocols.contains("echo") else {
33+
return nil
34+
}
35+
36+
// Return our service handler.
37+
return EchoHandler()
38+
}).handleRequest(request: request, response: response)
39+
}
40+
}
41+
```
42+
## Handling WebSocket Sessions
43+
44+
A WebSocket service handler must impliment the `WebSocketSessionHandler` protocol.
45+
This protocol requires the function `handleSession(request: WebRequest, socket: WebSocket)`.
46+
This function will be called once the WebSocket connection has been established,
47+
at which point it is safe to begin reading and writing messages.
48+
49+
The initial `WebRequest` object which instigated the session is provided for reference.
50+
Messages are transmitted through the provided WebSocket object.
51+
Call `WebSocket.sendStringMessage` or `WebSocket.sendBinaryMessage` to send data to the client.
52+
Call `WebSocket.readStringMessage` or `WebSocket.readBinaryMessage` to read data from the client.
53+
By default, reading will block indefinitely until a message arrives or a network error occurs.
54+
A read timeout can be set with `WebSocket.readTimeoutSeconds`.
55+
When the session is over call `WebSocket.close()`.
56+
57+
58+
The example `EchoHandler` consists of the following.
59+
60+
```
61+
class EchoHandler: WebSocketSessionHandler {
62+
63+
// The name of the super-protocol we implement.
64+
// This is optional, but it should match whatever the client-side WebSocket is initialized with.
65+
let socketProtocol: String? = "echo"
66+
67+
// This function is called by the WebSocketHandler once the connection has been established.
68+
func handleSession(request: WebRequest, socket: WebSocket) {
69+
70+
// Read a message from the client as a String.
71+
// Alternatively we could call `WebSocket.readBytesMessage` to get the data as a String.
72+
socket.readStringMessage {
73+
// This callback is provided:
74+
// the received data
75+
// the message's op-code
76+
// a boolean indicating if the message is complete (as opposed to fragmented)
77+
string, op, fin in
78+
79+
// The data parameter might be nil here if either a timeout or a network error, such as the client disconnecting, occurred.
80+
// By default there is no timeout.
81+
guard let string = string else {
82+
// This block will be executed if, for example, the browser window is closed.
83+
socket.close()
84+
return
85+
}
86+
87+
// Print some information to the console for informational purposes.
88+
print("Read msg: \(string) op: \(op) fin: \(fin)")
89+
90+
// Echo the data we received back to the client.
91+
// Pass true for final. This will usually be the case, but WebSockets has the concept of fragmented messages.
92+
// For example, if one were streaming a large file such as a video, one would pass false for final.
93+
// This indicates to the receiver that there is more data to come in subsequent messages but that all the data is part of the same logical message.
94+
// In such a scenario one would pass true for final only on the last bit of the video.
95+
socket.sendStringMessage(string, final: true) {
96+
97+
// This callback is called once the message has been sent.
98+
// Recurse to read and echo new message.
99+
self.handleSession(request, socket: socket)
100+
}
101+
}
102+
}
103+
}
104+
```
105+
106+
## FastCGI Caveat
107+
WebSockets serving is only supported with the stand-alone Perfect HTTP server. At this time, the WebSocket server does not operate with the Perfect FastCGI server.

Sources/WebSocketsHandler.swift

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// PerfectHandlers.swift
3+
// WebSockets Server
4+
//
5+
// Created by Kyle Jessup on 2016-01-06.
6+
// Copyright PerfectlySoft 2016. All rights reserved.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This source file is part of the Perfect.org open source project
11+
//
12+
// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors
13+
// Licensed under Apache License v2.0
14+
//
15+
// See http://perfect.org/licensing.html for license information
16+
//
17+
//===----------------------------------------------------------------------===//
18+
//
19+
20+
import PerfectLib
21+
22+
func addWebSocketsHandler() {
23+
24+
// Add a default route which lets us serve the static index.html file
25+
Routing.Routes["*"] = { request, response in StaticFileHandler().handleRequest(request: request, response: response) }
26+
27+
// Add the endpoint for the WebSocket example system
28+
Routing.Routes[.Get, "/echo"] = {
29+
request, response in
30+
31+
// To add a WebSocket service, set the handler to WebSocketHandler.
32+
// Provide your closure which will return your service handler.
33+
WebSocketHandler(handlerProducer: {
34+
(request: WebRequest, protocols: [String]) -> WebSocketSessionHandler? in
35+
36+
// Check to make sure the client is requesting our "echo" service.
37+
guard protocols.contains("echo") else {
38+
return nil
39+
}
40+
41+
// Return our service handler.
42+
return EchoHandler()
43+
}).handleRequest(request: request, response: response)
44+
}
45+
}
46+
47+
// This is the function which all Perfect Server modules must expose.
48+
// The system will load the module and call this function.
49+
// In here, register any handlers or perform any one-time tasks.
50+
// This is not required when compiling as a stand alone executable, but having it lets us function in a multi-module environment.
51+
public func PerfectServerModuleInit() {
52+
53+
addWebSocketsHandler()
54+
}
55+
56+
// A WebSocket service handler must impliment the `WebSocketSessionHandler` protocol.
57+
// This protocol requires the function `handleSession(request: WebRequest, socket: WebSocket)`.
58+
// This function will be called once the WebSocket connection has been established,
59+
// at which point it is safe to begin reading and writing messages.
60+
//
61+
// The initial `WebRequest` object which instigated the session is provided for reference.
62+
// Messages are transmitted through the provided `WebSocket` object.
63+
// Call `WebSocket.sendStringMessage` or `WebSocket.sendBinaryMessage` to send data to the client.
64+
// Call `WebSocket.readStringMessage` or `WebSocket.readBinaryMessage` to read data from the client.
65+
// By default, reading will block indefinitely until a message arrives or a network error occurs.
66+
// A read timeout can be set with `WebSocket.readTimeoutSeconds`.
67+
// When the session is over call `WebSocket.close()`.
68+
class EchoHandler: WebSocketSessionHandler {
69+
70+
// The name of the super-protocol we implement.
71+
// This is optional, but it should match whatever the client-side WebSocket is initialized with.
72+
let socketProtocol: String? = "echo"
73+
74+
// This function is called by the WebSocketHandler once the connection has been established.
75+
func handleSession(request: WebRequest, socket: WebSocket) {
76+
77+
// Read a message from the client as a String.
78+
// Alternatively we could call `WebSocket.readBytesMessage` to get binary data from the client.
79+
socket.readStringMessage {
80+
// This callback is provided:
81+
// the received data
82+
// the message's op-code
83+
// a boolean indicating if the message is complete (as opposed to fragmented)
84+
string, op, fin in
85+
86+
// The data parameter might be nil here if either a timeout or a network error, such as the client disconnecting, occurred.
87+
// By default there is no timeout.
88+
guard let string = string else {
89+
// This block will be executed if, for example, the browser window is closed.
90+
socket.close()
91+
return
92+
}
93+
94+
// Print some information to the console for informational purposes.
95+
print("Read msg: \(string) op: \(op) fin: \(fin)")
96+
97+
// Echo the data we received back to the client.
98+
// Pass true for final. This will usually be the case, but WebSockets has the concept of fragmented messages.
99+
// For example, if one were streaming a large file such as a video, one would pass false for final.
100+
// This indicates to the receiver that there is more data to come in subsequent messages but that all the data is part of the same logical message.
101+
// In such a scenario one would pass true for final only on the last bit of the video.
102+
socket.sendStringMessage(string: string, final: true) {
103+
104+
// This callback is called once the message has been sent.
105+
// Recurse to read and echo new message.
106+
self.handleSession(request: request, socket: socket)
107+
}
108+
}
109+
}
110+
}
111+
112+

Sources/main.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// UploadHandler.swift
3+
// Upload Enumerator
4+
//
5+
// Created by Kyle Jessup on 2015-11-05.
6+
// Copyright (C) 2015 PerfectlySoft, Inc.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This source file is part of the Perfect.org open source project
11+
//
12+
// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors
13+
// Licensed under Apache License v2.0
14+
//
15+
// See http://perfect.org/licensing.html for license information
16+
//
17+
//===----------------------------------------------------------------------===//
18+
//
19+
20+
import PerfectLib
21+
22+
// Initialize base-level services
23+
PerfectServer.initializeServices()
24+
25+
// Create our webroot
26+
let webRoot = "./webroot"
27+
try Dir(webRoot).create()
28+
29+
// Add our routes and such
30+
addWebSocketsHandler()
31+
32+
do {
33+
34+
// Launch the HTTP server on port 8181
35+
try HTTPServer(documentRoot: webRoot).start(port: 8181)
36+
37+
} catch PerfectError.NetworkError(let err, let msg) {
38+
print("Network error thrown: \(err) \(msg)")
39+
}

Xcode/Info.plist

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>$(EXECUTABLE_NAME)</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>$(PRODUCT_NAME)</string>
15+
<key>CFBundlePackageType</key>
16+
<string>FMWK</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleSignature</key>
20+
<string>????</string>
21+
<key>CFBundleVersion</key>
22+
<string>$(CURRENT_PROJECT_VERSION)</string>
23+
<key>NSHumanReadableCopyright</key>
24+
<string>Copyright © 2016 PerfectlySoft. All rights reserved.</string>
25+
<key>NSPrincipalClass</key>
26+
<string></string>
27+
</dict>
28+
</plist>

Xcode/WebSockets Server.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// WebSockets Server.h
3+
// WebSockets Server
4+
//
5+
// Created by Kyle Jessup on 2016-01-06.
6+
// Copyright © 2016 PerfectlySoft. All rights reserved.
7+
//
8+
9+
#import <Cocoa/Cocoa.h>
10+
11+
//! Project version number for WebSockets Server.
12+
FOUNDATION_EXPORT double WebSockets_ServerVersionNumber;
13+
14+
//! Project version string for WebSockets Server.
15+
FOUNDATION_EXPORT const unsigned char WebSockets_ServerVersionString[];
16+
17+
// In this header, you should import all the public headers of your framework using statements like #import <WebSockets_Server/PublicHeader.h>
18+
19+

0 commit comments

Comments
 (0)