├── Katan.playground ├── Contents.swift ├── Resources │ ├── cloud+flow_final.png │ └── overview.png ├── contents.xcplayground └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── MarcioK.xcuserdatad │ └── UserInterfaceState.xcuserstate └── README.md /Katan.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // KatanSandbox 4 | // 5 | // Created by Marcio Klepacz on 3/27/17. 6 | // Copyright © 2017 Marcio Klepacz. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | /*: 11 | # Katan 12 | A micro web server that replies *"Hello world!"* to every request 13 | 14 | The idea is to show the basics steps to create a web server in Swift. 15 | 16 | *A web server overview:* 17 | 18 | ![alt text](cloud+flow_final.png) 19 | 20 | 21 | ## 1. Create Socket 🐣 22 | */ 23 | 24 | func startWebServer(){ 25 | 26 | let socketDescriptor = Darwin.socket(AF_INET, SOCK_STREAM, 0) 27 | 28 | /*: 29 | `socket` -- creates an endpoint for communication and returns a descriptor. 30 | 31 | **domain**: Communication domain, selects the protocol family, in our case ipv4 (AF_INET). AF_INET6 if we wanted to use ipv6. 32 | 33 | **type**: Specifies semantics of communication. A SOCK_STREAM type provides sequenced, reliable, two-way connection based byte streams. 34 | 35 | **protocol**: The protocol specifies a particular protocol to be used with the socket. 36 | Normally only a single protocol exists to support a particular socket type within a given protocol family 37 | 38 | Returns -1 if there's an error otherwise the descriptor (a reference). 39 | */ 40 | 41 | /*: 42 | ## 2. Set options 🎛 43 | */ 44 | 45 | var noSigPipe: Int32 = 1 46 | setsockopt(socketDescriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, socklen_t(MemoryLayout.size)) 47 | /*: 48 | `setsockopt` -- get and set options on sockets 49 | 50 | **socket**: The socket descriptor 51 | 52 | **level**: To manipulate options at the socket level 53 | 54 | **option_name**: The name of our the option, in our case we do not generate SIGPIPE, instead return EPIPE 55 | A SIGPIPE is sent to a process if it tried to write to a socket that had been shutdown for writing or isn't connected (anymore). 56 | 57 | **socklen_t**: the option length 58 | 59 | ## 3. Create adress and bind 🚪➕🔌 60 | 61 | ![alt text](overview.png) 62 | */ 63 | let port: in_port_t = 9292 64 | 65 | var address = sockaddr_in( 66 | sin_len: UInt8(MemoryLayout.stride), 67 | sin_family: UInt8(AF_INET), 68 | sin_port: port.bigEndian, 69 | sin_addr: in_addr(s_addr: in_addr_t(0)), 70 | sin_zero:(0, 0, 0, 0, 0, 0, 0, 0) // Add some padding, more info at: http://stackoverflow.com/questions/15608707/why-is-zero-padding-needed-in-sockaddr-in#15609050 71 | ) 72 | 73 | var bindResult: Int32 = -1 74 | bindResult = withUnsafePointer(to: &address) { 75 | bind(socketDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size)) 76 | } 77 | /*: 78 | 79 | `bind` -- assigns a name to an unnamed socket. 80 | 81 | When a socket is created with socket() it exists in a name space (address family) but has no name 82 | assigned. bind() requests that address be assigned to the socket. 83 | 84 | */ 85 | 86 | if bindResult == -1 { 87 | fatalError(String(cString: UnsafePointer(strerror(errno)))) 88 | } 89 | 90 | /*: 91 | ## 4. Listen 📡 92 | */ 93 | listen(socketDescriptor, SOMAXCONN) 94 | /*: 95 | `listen` -- for connections on a socket 96 | 97 | The backlog parameter defines the maximum length for the queue of pending 98 | connections. If a connection request arrives with the queue full, the 99 | client may receive an error with an indication of ECONNREFUSED. 100 | 101 | */ 102 | 103 | /*: 104 | ## 5. Accept connection on socket ✅ 105 | */ 106 | 107 | print("Starting HTTP server on port \(port)") 108 | repeat { 109 | var address = sockaddr() 110 | var length: socklen_t = 0 111 | 112 | let clientSocket = accept(socketDescriptor, &address, &length) 113 | if clientSocket == -1 { 114 | fatalError(String(cString: UnsafePointer(strerror(errno)))) 115 | } 116 | /*: 117 | `accept` -- extracts the first connection request on the queue of pending connections, 118 | 119 | Creates a new socket with the same properties of 120 | socket, and allocates a new file descriptor for the socket. 121 | 122 | The argument address is a result parameter that is filled in with the 123 | address of the connecting entity, as known to the communications layer. 124 | 125 | The address_len is a value-result 126 | parameter; it should initially contain the amount of space pointed to by 127 | address; on return it will contain the actual length (in bytes) of the 128 | address returned. 129 | */ 130 | 131 | var characters = "" 132 | var received: UInt8 = 0 133 | repeat { 134 | var buffer = [UInt8](repeatElement(0, count: 1)) 135 | 136 | 137 | /*: 138 | ## 6. Read socket 📖 139 | 140 | `recv` -- receive a message from a socket 141 | 142 | */ 143 | let resp = recv(clientSocket, &buffer, Int(buffer.count), 0) 144 | if resp <= 0 { 145 | fatalError(String(cString: UnsafePointer(strerror(errno)))) 146 | } 147 | 148 | received = buffer.first! 149 | if received > 13 /* Carriage Return on ASCII table */ { 150 | characters.append(Character(UnicodeScalar(received))) 151 | } 152 | } while received != 10 /* New Line on ASCII table */ 153 | 154 | print("Received -> \(characters)") 155 | /*: 156 | ## 7. Write response 📝 157 | 158 | `write` -- write output 159 | 160 | */ 161 | let message = "HTTP/1.1 200 OK\r\n\r\n Hello World!" 162 | print("Response -> \(message)") 163 | let messageData = ArraySlice(message.utf8) 164 | 165 | _ = messageData.withUnsafeBytes { 166 | 167 | write(clientSocket, $0.baseAddress, messageData.count) 168 | } 169 | 170 | /*: 171 | ## 8. Close socket ⚰️ 172 | 173 | `close` -- delete a descriptor 174 | 175 | */ 176 | close(clientSocket) 177 | 178 | } while true 179 | 180 | } 181 | 182 | -------------------------------------------------------------------------------- /Katan.playground/Resources/cloud+flow_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marciok/katan/e42d450389fc2aece6edaa8b65752fb204b5942d/Katan.playground/Resources/cloud+flow_final.png -------------------------------------------------------------------------------- /Katan.playground/Resources/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marciok/katan/e42d450389fc2aece6edaa8b65752fb204b5942d/Katan.playground/Resources/overview.png -------------------------------------------------------------------------------- /Katan.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Katan.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Katan.playground/playground.xcworkspace/xcuserdata/MarcioK.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marciok/katan/e42d450389fc2aece6edaa8b65752fb204b5942d/Katan.playground/playground.xcworkspace/xcuserdata/MarcioK.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Katan 2 | A micro web server that replies *"Hello world!"* to every request 3 | 4 | The idea is to show the basics steps to create a web server in Swift. 5 | 6 | *A web server overview:* 7 | 8 | ![alt text](https://s3.amazonaws.com/marcioklepacz/cloud%2Bflow_final.png) 9 | 10 | 11 | ## 1. Create Socket 🐣 12 | 13 | ```swift 14 | func startWebServer(){ 15 | 16 | let socketDescriptor = Darwin.socket(AF_INET, SOCK_STREAM, 0) 17 | ``` 18 | 19 | `socket` -- creates an endpoint for communication and returns a descriptor. 20 | 21 | **domain**: Communication domain, selects the protocol family, in our case ipv4 (AF_INET). AF_INET6 if we wanted to use ipv6. 22 | 23 | **type**: Specifies semantics of communication. A SOCK_STREAM type provides sequenced, reliable, two-way connection based byte streams. 24 | 25 | **protocol**: The protocol specifies a particular protocol to be used with the socket. 26 | Normally only a single protocol exists to support a particular socket type within a given protocol family 27 | 28 | Returns -1 if there's an error otherwise the descriptor (a reference). 29 | 30 | 31 | ## 2. Set options 🎛 32 | 33 | ```swift 34 | var noSigPipe: Int32 = 1 35 | setsockopt(socketDescriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, socklen_t(MemoryLayout.size)) 36 | ``` 37 | `setsockopt` -- get and set options on sockets 38 | 39 | **socket**: The socket descriptor 40 | 41 | **level**: To manipulate options at the socket level 42 | 43 | **option_name**: The name of our the option, in our case we do not generate SIGPIPE, instead return EPIPE 44 | A SIGPIPE is sent to a process if it tried to write to a socket that had been shutdown for writing or isn't connected (anymore). 45 | 46 | **socklen_t**: the option length 47 | 48 | ## 3. Create adress and bind 🚪➕🔌 49 | 50 | ![alt text](https://s3.amazonaws.com/marcioklepacz/overview.png) 51 | ```swift 52 | let port: in_port_t = 9292 53 | 54 | var address = sockaddr_in( 55 | sin_len: UInt8(MemoryLayout.stride), 56 | sin_family: UInt8(AF_INET), 57 | sin_port: port.bigEndian, 58 | sin_addr: in_addr(s_addr: in_addr_t(0)), 59 | sin_zero:(0, 0, 0, 0, 0, 0, 0, 0) // Add some padding, more info at: http://stackoverflow.com/questions/15608707/why-is-zero-padding-needed-in-sockaddr-in#15609050 60 | ) 61 | 62 | var bindResult: Int32 = -1 63 | bindResult = withUnsafePointer(to: &address) { 64 | bind(socketDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size)) 65 | } 66 | ``` 67 | 68 | `bind` -- assigns a name to an unnamed socket. 69 | 70 | When a socket is created with socket() it exists in a name space (address family) but has no name 71 | assigned. bind() requests that address be assigned to the socket. 72 | 73 | ```swift 74 | if bindResult == -1 { 75 | fatalError(String(cString: UnsafePointer(strerror(errno)))) 76 | } 77 | 78 | ``` 79 | 80 | ## 4. Listen 📡 81 | ```swift 82 | listen(socketDescriptor, SOMAXCONN) 83 | ``` 84 | `listen` -- for connections on a socket 85 | 86 | The backlog parameter defines the maximum length for the queue of pending 87 | connections. If a connection request arrives with the queue full, the 88 | client may receive an error with an indication of ECONNREFUSED. 89 | 90 | 91 | 92 | 93 | ## 5. Accept connection on socket ✅ 94 | 95 | ```swift 96 | print("Starting HTTP server on port \(port)") 97 | repeat { 98 | var address = sockaddr() 99 | var length: socklen_t = 0 100 | 101 | let clientSocket = accept(socketDescriptor, &address, &length) 102 | if clientSocket == -1 { 103 | fatalError(String(cString: UnsafePointer(strerror(errno)))) 104 | } 105 | ``` 106 | `accept` -- extracts the first connection request on the queue of pending connections, 107 | 108 | Creates a new socket with the same properties of 109 | socket, and allocates a new file descriptor for the socket. 110 | 111 | The argument address is a result parameter that is filled in with the 112 | address of the connecting entity, as known to the communications layer. 113 | 114 | The address_len is a value-result 115 | parameter; it should initially contain the amount of space pointed to by 116 | address; on return it will contain the actual length (in bytes) of the 117 | address returned. 118 | ```swift 119 | 120 | var characters = "" 121 | var received: UInt8 = 0 122 | repeat { 123 | var buffer = [UInt8](repeatElement(0, count: 1)) 124 | 125 | 126 | ``` 127 | ## 6. Read socket 📖 128 | 129 | `recv` -- receive a message from a socket 130 | 131 | ```swift 132 | let resp = recv(clientSocket, &buffer, Int(buffer.count), 0) 133 | if resp <= 0 { 134 | fatalError(String(cString: UnsafePointer(strerror(errno)))) 135 | } 136 | 137 | received = buffer.first! 138 | if received > 13 /* Carriage Return on ASCII table */ { 139 | characters.append(Character(UnicodeScalar(received))) 140 | } 141 | } while received != 10 /* New Line on ASCII table */ 142 | 143 | print("Received -> \(characters)") 144 | ``` 145 | ## 7. Write response 📝 146 | 147 | `write` -- write output 148 | ```swift 149 | let message = "HTTP/1.1 200 OK\r\n\r\n Hello World!" 150 | print("Response -> \(message)") 151 | let messageData = ArraySlice(message.utf8) 152 | 153 | _ = messageData.withUnsafeBytes { 154 | 155 | write(clientSocket, $0.baseAddress, messageData.count) 156 | } 157 | 158 | ``` 159 | ## 8. Close socket ⚰️ 160 | 161 | `close` -- delete a descriptor 162 | ```swift 163 | 164 | close(clientSocket) 165 | 166 | } while true 167 | 168 | } 169 | startWebServer() 170 | ``` 171 | # References: 172 | * https://ruslanspivak.com/lsbaws-part1/ 173 | * https://github.com/httpswift/swifter 174 | --------------------------------------------------------------------------------