1Thrift Swift Library 2========================= 3 4License 5------- 6Licensed to the Apache Software Foundation (ASF) under one 7or more contributor license agreements. See the NOTICE file 8distributed with this work for additional information 9regarding copyright ownership. The ASF licenses this file 10to you under the Apache License, Version 2.0 (the 11"License"); you may not use this file except in compliance 12with the License. You may obtain a copy of the License at 13 14 http://www.apache.org/licenses/LICENSE-2.0 15 16Unless required by applicable law or agreed to in writing, 17software distributed under the License is distributed on an 18"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19KIND, either express or implied. See the License for the 20specific language governing permissions and limitations 21under the License. 22 23 24## Build 25 swift build 26 27## Test 28 swift test 29 30## Install Library 31##### Cocoapods 32Add the following to your podfile 33```ruby 34 pod 'Thrift-swift3', :git => 'git@github.com:apache/thrift.git', :branch => 'master' 35``` 36 37##### SPM 38Unfortunately due to some limitations in SPM, the Package manifest and Sources directory must be at the root of the project. 39To get around that for the time being, you can use this mirrored repo. 40Add the following to your Package.swift 41```swift 42dependencies: [ 43 .Package(url: "https://github.com/apocolipse/Thrift-Swift.git", majorVersion: 1) 44] 45``` 46 47## Thrift Compiler 48 49You can compile IDL sources for Swift 3 with the following command: 50 51 thrift --gen swift thrift_file 52 53## Client Example 54```swift 55let transport = TSocketTransport(hostname: "localhost", port: 9090)! 56 57// var proto = TCompactProtocol(transport: transport) 58let proto = TBinaryProtocol(on: transport) 59// var client = HermesClient(inoutProtocol: proto) 60let client = ThriftTestClient(inoutProtocol: proto) 61do { 62 try client.testVoid() 63} catch let error { 64 print("\(error)") 65} 66``` 67 68## Library Notes 69- Eliminated Protocol Factories, They were only used in async clients and server implementations, where Generics provide a better alternative. 70- Swifty Errors, All `TError` types have a nested `ErrorCode` Enum as well as some extra flavor where needed. 71- Value typed everything. `TTransport` operates on value typed `Data` rather than reference typed `NSData` or `UnsafeBufferPointer`s 72- Swift 3 Named protocols. Swift 3 naming conventions suggest the elimination of redundant words that can be inferred from variable/function signatures. This renaming is applied throughout the Swift 3 library converting most naming conventions used in the Swift2/Cocoa library to Swift 3-esque naming conventions. eg. 73```swift 74func readString() throws -> String 75func writeString(_ val: String) throws 76``` 77have been renamed to eliminate redundant words: 78```swift 79func read() throws -> String 80func write(_ val: String) throws 81``` 82 83- Eliminated `THTTPTransport` that uses `NSURLConnection` due to it being deprecated and not available at all in Swift 3 for Linux. `THTTPSessionTransport` from the Swift2/Cocoa library that uses `NSURLSession` has been renamed to `THTTPTransport` for this library and leverages `URLSession`, providing both synchronous (with semaphores) and asynchronous behavior. 84- Probably some More things I've missed here. 85 86## Generator Notes 87#### Generator Flags 88| Flag | Description | 89| ------------- |:-------------:| 90| async_clients | Generate clients which invoke asynchronously via block syntax. Asynchronous classes are appended with `_Async` | 91| no_strict* | Generates non-strict structs | 92| debug_descriptions | Allow use of debugDescription so the app can add description via a cateogory/extension | 93| log_unexpected | Log every time an unexpected field ID or type is encountered. | 94| safe_enums | Generate enum types with an unknown case to handle unspecified values rather than throw a serialization error | 95 96 97 98*Most thrift libraries allow empty initialization of Structs, initializing `required` fields with nil/null/None (Python and Node generators). Swift on the other hand requires initializers to initialize all non-Optional fields, and thus the Swift 3 generator does not provide default values (unlike the Swift 2/Cocoa generator). In other languages, this allows the sending of NULL values in fields that are marked `required`, and thus will throw an error in Swift clients attempting to validate fields. The `no_strict` option here will ignore the validation check, as well as behave similar to the Swift2/Cocoa generator and initialize required fields with empty initializers (where possible). 99 100 101## Whats implemented 102#### Library 103##### Transports 104- [x] TSocketTransport - CFSocket and PosixSocket variants available. CFSocket variant only currently available for Darwin platforms 105- [x] THTTPTransport - Currently only available for Darwin platforms, Swift Foundation URLSession implementation needs completion on linux. 106- [x] TSocketServer - Uses CFSockets only for binding, should be working on linux 107- [x] TFramedTransport 108- [x] TMemoryBufferTransport 109- [x] TFileTransport - A few variants using File handles and file descriptors. 110- [x] TStreamTransport - Fully functional in Darwin, Foundation backing not yet completed in Linux (This limits TCFSocketTransport to Darwin) 111- [ ] HTTPServer - Currently there is no lightweight HTTPServer implementation the Swift Standard Library, so other 3rd party alternatives are required and out of scope for the Thrift library. Examples using Perfect will be provided. 112- [ ] Other (gz, etc) 113 114##### Protocols 115- [x] TBinaryProtocol 116- [x] TCompactProtocol 117- [ ] TJSONProtocol - This will need to be implemented 118 119##### Generator 120- [x] Code Complete Generator 121- [x] Async clients 122- [x] Documentation Generation - Generator will transplant IDL docs to Swift code for easy lookup in Xcode 123- [ ] Default Values - TODO 124- [ ] no_strict mode - TODO 125- [ ] Namespacing - Still haven't nailed down a good paradigm for namespacing. It will likely involve creating subdirectories for different namespaces and expecting the developer to import each subdirectory as separate modules. It could extend to creating SPM Package manifests with sub-modules within the generated module 126 127 128 129## Example HTTP Server with Perfect 130```swift 131import PerfectLib 132import PerfectHTTP 133import PerfectHTTPServer 134import Dispatch 135 136let logQueue = DispatchQueue(label: "log", qos: .background, attributes: .concurrent) 137let pQueue = DispatchQueue(label: "log", qos: .userInitiated, attributes: .concurrent) 138 139 140class TPerfectServer<InProtocol: TProtocol, OutProtocol: TProtocol> { 141 142 private var server = HTTPServer() 143 private var processor: TProcessor 144 145 init(address: String? = nil, 146 path: String? = nil, 147 port: Int, 148 processor: TProcessor, 149 inProtocol: InProtocol.Type, 150 outProtocol: OutProtocol.Type) throws { 151 152 self.processor = processor 153 154 if let address = address { 155 server.serverAddress = address 156 } 157 server.serverPort = UInt16(port) 158 159 var routes = Routes() 160 var uri = "/" 161 if let path = path { 162 uri += path 163 } 164 routes.add(method: .post, uri: uri) { request, response in 165 pQueue.async { 166 response.setHeader(.contentType, value: "application/x-thrift") 167 168 let itrans = TMemoryBufferTransport() 169 if let bytes = request.postBodyBytes { 170 let data = Data(bytes: bytes) 171 itrans.reset(readBuffer: data) 172 } 173 174 let otrans = TMemoryBufferTransport(flushHandler: { trans, buff in 175 let array = buff.withUnsafeBytes { 176 Array<UInt8>(UnsafeBufferPointer(start: $0, count: buff.count)) 177 } 178 response.status = .ok 179 response.setBody(bytes: array) 180 response.completed() 181 }) 182 183 let inproto = InProtocol(on: itrans) 184 let outproto = OutProtocol(on: otrans) 185 186 do { 187 try processor.process(on: inproto, outProtocol: outproto) 188 try otrans.flush() 189 } catch { 190 response.status = .badRequest 191 response.completed() 192 } 193 } 194 } 195 server.addRoutes(routes) 196 } 197 198 func serve() throws { 199 try server.start() 200 } 201} 202``` 203 204#### Example Usage 205```swift 206class ServiceHandler : Service { 207 ... 208} 209let server = try? TPerfectServer(port: 9090, 210 processor: ServiceProcessor(service: ServiceHandler()), 211 inProtocol: TBinaryProtocol.self, 212 outProtocol: TBinaryProtocol.self) 213 214try? server?.serve() 215``` 216