Skip to content

Commit

Permalink
Update Readme and Changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
yvbeek committed Oct 29, 2018
1 parent fc2997e commit 0063772
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 29 deletions.
34 changes: 33 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
# Change Log
# Changelog

All notable changes to this project will be documented in this file.
Telegraph adheres to [Semantic Versioning](http://semver.org/).

## [0.18](https://github.com/Building42/Telegraph/releases/tag/0.18)

### Breaking

- Convert to Swift 4.2
- `HTTPHeader` is now called `HTTPHeaderName`
- `HTTPMethod` is now a struct and uses uppercase notation
- `HTTPResponse` initializer uses `HTTPStatus` instead of `HTTPStatusCode`
- `HTTPVersion` initializer uses `major:` and `minor:` labels
- `Server` now has a `delegate` and a `webSocketDelegate`
- `Server` start method has different labels and is easier to overload

### HTTP changes

- Fix keep-alive connections, gracefully handle both HTTP/1.0 and HTTP/1.1
- Any protocol data already read by the `HTTPParser` is now correctly passed on connection upgrade
- Add worker queue to control the concurrency of requests (see `Server.concurrency`)
- Add symbolic link support to the `HTTPFileHandler` (thanks [lj-dickey](https://github.com/lj-dickey))
- Performance improvements in the `HTTPParser` and smarter HTTP header serialization

### WebSockets changes

- `WebSocketClient` is now more reliable, with better error and upgrade handling
- WebSocket connections perform a proper close handshake according to spec
- WebSocket close codes are now handled by the parser

### Other changes

- Date formatting is now centralized in the `RFC1123` helper
- `SHA1` has been replaced with Apple's CommonCrypto
- Update demo based on the recent changes and improvements

## [0.17](https://github.com/Building42/Telegraph/releases/tag/0.17)

- **Fix big performance issue in parsing the WebSocket payload**
Expand Down
89 changes: 61 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Telegraph is a Secure Web Server for iOS, tvOS and macOS written in Swift.

## Versions

- Swift 4.1: [master branch](https://github.com/Building42/Telegraph/tree/master)
- Swift 4.2: [master branch](https://github.com/Building42/Telegraph/tree/master)
- Swift 4.0: [swift-4 branch](https://github.com/Building42/Telegraph/tree/swift-4)
- Swift 3.0: [swift-3 branch](https://github.com/Building42/Telegraph/tree/swift-3)

Expand Down Expand Up @@ -77,27 +77,33 @@ $ brew install carthage
To integrate Telegraph into your Xcode project using Carthage, specify it in your `Cartfile`:

```ogdl
github "Building42/Telegraph" "master"
github "Building42/Telegraph"
```

Run `carthage update` to build the framework and drag the `CocoaAsyncSocket`, `HTTPParserC` and `Telegraph` frameworks into your Xcode project.

## Usage

### Configure App Transport Security

With iOS 9 Apple introduced APS (App Transport Security) in an effort to improve user security and privacy by requiring apps to use secure network connections over HTTPS. It means that without additional configuration unsecure HTTP requests in apps that target iOS 9 or higher will fail. In iOS 9, APS is unfortunately also activated for LAN connections. Apple fixed this in iOS 10, by adding `NSAllowsLocalNetworking`.

Even though we are using HTTPS, we have to consider the following:

1. when we are communicating between iPads, it is likely that we'll connect on IP addresses, or at least that the hostname of the device won't match the *common name* of the certificate.
2. our server will use a certificate signed by our own Certificate Authority instead of a well recognized root Certificate Authority.

You can disable APS by adding the key `App Transport Security Settings` to your Info.plist, with a sub-key where `Allow Arbitrary Loads` is set to `Yes`. For more information see [ATS Configuration Basics](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35).

### Prepare the certificates

For a secure web server you'll need two things:

1. one or more Certificate Authority certificates in DER format.
2. a PKCS12 bundle containing a private key and certificate (signed by the CA)

Telegraph contains classes to make it easier to load the certificates:

```swift
let caCertificateURL = Bundle.main.url(forResource: "ca", withExtension: "der")!
let caCertificate = Certificate(derURL: caCertificateURL)!
Expand All @@ -109,36 +115,44 @@ let identity = CertificateIdentity(p12URL: identityURL, passphrase: "test")!
> Note: macOS doesn't accept P12 files without passphrase.
### HTTP: Server

You most likely want to create a secure server by passing in the certificates:

```swift
serverHTTPs = Server(identity: identity, caCertificates: [caCertificate])
try! server.start(onPort: 9000)
try! server.start(port: 9000)
```

Or for a quick test, create an unsecure server:

```swift
serverHTTP = Server()
try! server.start(onPort: 9000)
try! server.start(port: 9000)
```

You can limit the server to localhost connections by specifying an interface when you start it:

```swift
try! server.start(onInterface: "localhost", port: 9000)
try! server.start(port: 9000, interface: "localhost")
```

### HTTP: Routes
Routes consist of three parts: HTTP method, path and handler:

Routes consist of three parts: HTTP method, path and a handler:

```swift
server.route(.post, "test", handleTest)
server.route(.get, "hello/:name", handleGreeting)
server.route(.get, "secret/*") { .forbidden }
server.route(.get, "status") { (.ok, "Server is running") }
server.route(.POST, "test", handleTest)
server.route(.GET, "hello/:name", handleGreeting)
server.route(.GET, "secret/*") { .forbidden }
server.route(.GET, "status") { (.ok, "Server is running") }

server.serveBundle(.main, "/")

// You can also serve custom urls, for example the Demo folder in your bundle
let demoBundleURL = Bundle.main.url(forResource: "Demo", withExtension: nil)!
server.serveDirectory(demoBundleURL, "/demo")
```

Slashes at the start of the path are optional. Routes are case insensitive. You can specify custom regular expressions for more advanced route matching. When none of the routes are matched, the server will return a 404 not found.

The first route in the example above has a route parameter (name). When the server matches the incoming request to that route it will place the parameter in the `params` array of the request:
Expand All @@ -149,7 +163,9 @@ func handleGreeting(request: HTTPRequest) -> HTTPResponse {
return HTTPResponse(content: "Hello \(name.capitalized)")
}
```

### HTTP: Middleware

When a HTTP request is handled by the Server it is passed through a chain of message handlers. If you don't change the default configuration, requests will first be passed to the `HTTPWebSocketHandler` and then to the `HTTPRouteHandler`.

Below is an example of a message handler:
Expand All @@ -158,7 +174,7 @@ Below is an example of a message handler:
public class HTTPGETOnlyHandler: HTTPRequestHandler {
public func respond(to request: HTTPRequest, nextHandler: HTTPRequest.Handler) throws -> HTTPResponse? {
// If this is a GET request, pass it to the next handler
if request.method == .get {
if request.method == .GET {
return try nextHandler(request)
}

Expand All @@ -181,7 +197,7 @@ You can also modify the request in handlers. This handler copies the QueryString
```swift
public class HTTPRequestParamsHandler: HTTPRequestHandler {
public func respond(to request: HTTPRequest, nextHandler: HTTPRequest.Handler) throws -> HTTPResponse? {
// Extract the QueryString items and put them in the HTTPRequest params
// Extract the query string items and put them in the HTTPRequest params
request.uri.queryItems?.forEach { item in
request.params[item.name] = item.value
}
Expand All @@ -206,9 +222,12 @@ public class HTTPAppDetailsHandler: HTTPRequestHandler {
}
}
```

### HTTP: Client

For client connections we'll use Apple's [URLSession](https://developer.apple.com/reference/foundation/urlsession) class. Ray Wenderlich has an [excellent tutorial](https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started) on it.
We're going to have to manually verify the TLS handshake (App Transport Security needs to be disabled for this):

```swift
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let tlsPolicy = TLSPolicy(commonName: "localhost", certificates: [caCertificate])
Expand All @@ -222,16 +241,21 @@ extension YourClass: URLSessionDelegate {
}
}
```

The common name in the `TLSPolicy` should match the common name of the certificate of the server (that was provided in the P12 archive). Although I don't recommend it, you can disable the common name check by providing an empty string (if you provide `nil`, the common name will be compared to the device's hostname).

For the common name you aren't limited to the hostname or IP address of the device. Your backend could, for example, generate a certificate with a common name that matches the UUID of the device. If the client knows the UUID of device it is connecting to, you could make it part of the `TLSPolicy` check.

### WebSockets: Server-side

Your Server will automatically recognize WebSocket requests, thanks to the `HTTPWebSocketHandler` that is by default in the list of HTTP request handlers. Set the WebSocket delegate to handle incoming messages:

```swift
server.webSocketDelegate = self
```

Next step is to implement the `ServerWebSocketDelegate` methods:

```swift
func server(_ server: Server, webSocketDidConnect webSocket: WebSocket, handshake: HTTPRequest) {
// A web socket connected, you can extract additional information from the handshake request
Expand Down Expand Up @@ -273,15 +297,19 @@ public class AwesomeWebSocketHandler: WebSocketMessageHandler {
```

### WebSockets: Client-side

Now that we have a secure WebSocket server, we can use a secure WebSocket client as well by passing the CA certificate. Note that you only have to specify the CA certificate if the certificate isn't a root CA trusted by Apple or if you want to benefit from [certificate pinning](https://security.stackexchange.com/questions/29988/what-is-certificate-pinning).

```swift
client = try! WebSocketClient("wss://localhost:9000", certificates: [caCertificate])
client.delegate = self

// You can specify headers too
client.headers.authorization = "Bearer secret-token"
```

The delegate methods look like this:

```swift
func webSocketClient(_ client: WebSocketClient, didConnectToHost host: String) {
// The connection starts off as a HTTP request and then is upgraded to a
Expand All @@ -301,65 +329,70 @@ func webSocketClient(_ client: WebSocketClient, didReceiveText text: String) {
client.send(text: "Message received")
}
```

## FAQ

### Why Telegraph?

There are only a few web servers available for iOS and many of them don't have SSL support. Our main goal with Telegraph is to offer secure HTTP and Web Socket traffic between iPads. The name is a tribute to the electrical telegraph, the first form of electrical telecommunications.

### How do I create the certificates?

Basically what you want is a Certificate Authority and a Device certificate signed by that authority.
You can find a nice tutorial at: https://jamielinux.com/docs/openssl-certificate-authority/

### My Server isn't working properly
### My server isn't working properly

Please check the following:

1. is Application Transport Security disabled?
2. are your certificates valid?
3. are your routes valid?
4. have you customized any handlers? Is the route handler still included?

Have a look at the example project in this repository for a working starting point.

### Chrome doesn't display images from my bundle
### My browser doesn't display images from my bundle

During the build of your project Apple tries to optimize your images to reduce the size of your bundle. This optimization process sometimes causes the images to become unreadable by Chrome. Test your image url in Safari to double check if this is the case.

To solve this you can go to the property inspector in Xcode and change the type of the resource from `Default - PNG image` to `Data`. After that the build process won't optimize your file. I've also done this with `logo.png` in the example projects.

If you want to reduce the size of your images, I highly recommend [ImageOptim](https://imageoptim.com).

### Why can't I use port 80 and 443?

The first 1024 port numbers are restricted to root access only and your app doesn't have root access on the device. If you try to open a Server on those ports you will get a permission denied error when you start the server. For more information, read [why are the first 1024 ports restricted to the root user only](https://unix.stackexchange.com/questions/16564/why-are-the-first-1024-ports-restricted-to-the-root-user-only).

### What if the device goes on standby?

If your app is send to the background or if the device goes on standby you typically have about 3 minutes to handle requests and close connections. You can create a background task with `UIApplication.beginBackgroundTask` to let iOS know that you need extra time to complete your operations. The property `UIApplication.shared.backgroundTimeRemaining` tells you the time that is left until a possible forced-kill of your app.

### What about HTTP/2 support?

Ever wondered how the remote server knows that your browser is HTTP/2 compatible? During TLS negotiation, the application-layer protocol negotation (ALPN) extension field contains "h2" to signal that HTTP/2 is going be used. Apple doesn't offer any (public) methods in Secure Transport or CFNetwork to configure ALPN extensions. A secure HTTP/2 iOS implementation is therefor not possible at the moment.

### Can I use this in my Objective-C project?
This library was written in Swift and for performance reasons I haven't decorated any classes with `NSObject` unless absolutely necessary (no dynamic dispatch). If you are willing to add Swift code to your project, you can integrate the server by adding a Swift wrapper class that inherits from `NSObject` and has a Telegraph `Server` variable.

## TODOs
This library was written in Swift and for performance reasons I haven't decorated classes with `NSObject` unless absolutely necessary (no dynamic dispatch). If you are willing to add Swift code to your project, you can integrate the server by adding a Swift wrapper class that inherits from `NSObject` and has a Telegraph `Server` variable.

- [ ] Handle WebSocket error messages
- [ ] Optimize route handling
- [ ] Add more documentation / wiki
- [ ] Add more tests
## Authors

Your pull requests are most welcome and appreciated!
This library was created by:

## Authors
- [Yvo van Beek](https://github.com/Zyphrax)
- [Bernd de Graaf](https://github.com/Vernadsky)

[Building42](https://github.com/Building42)<br />
[Yvo van Beek](https://github.com/Zyphrax)<br />
[Bernd de Graaf](https://github.com/Vernadsky)
Code and design was inspired by:

This library was inspired by the following libraries. A big thank you to their creators!
- [CocoaHTTPServer](https://github.com/robbiehanson/CocoaHTTPServer) - a Web Server written in Objective-C
- [Vapor](https://github.com/vapor/vapor) - a Swift Web framework

Thank you to our contributors, your pull requests are most welcome and appreciated!

## License

Telegraph is available under the Mozilla Public License, version 2.0.<br />
You are welcome to use Telegraph in your commercial or open-source apps.<br />
Telegraph is available under the Mozilla Public License, version 2.0.
You are welcome to use Telegraph in your commercial or open-source apps.

See the LICENSE file for more info.

0 comments on commit 0063772

Please sign in to comment.