Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/marciok/katan
Swift playground with a micro web server that replies "Hello world!" to every request
https://github.com/marciok/katan
Last synced: about 1 month ago
JSON representation
Swift playground with a micro web server that replies "Hello world!" to every request
- Host: GitHub
- URL: https://github.com/marciok/katan
- Owner: marciok
- Created: 2017-03-29T22:20:16.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2017-04-19T02:45:19.000Z (over 7 years ago)
- Last Synced: 2024-04-22T12:31:08.886Z (8 months ago)
- Language: Swift
- Homepage:
- Size: 2.92 MB
- Stars: 25
- Watchers: 5
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- Awesome-Swift-Playgrounds - Katan - A micro web server that replies "Hello world!" to every request, an example of how to use sockets in Swift. (Learning Swift: Advanced Topics)
README
# Katan
A micro web server that replies *"Hello world!"* to every request
The idea is to show the basics steps to create a web server in Swift.
*A web server overview:*
![alt text](https://s3.amazonaws.com/marcioklepacz/cloud%2Bflow_final.png)
## 1. Create Socket 🐣```swift
func startWebServer(){
let socketDescriptor = Darwin.socket(AF_INET, SOCK_STREAM, 0)
````socket` -- creates an endpoint for communication and returns a descriptor.
**domain**: Communication domain, selects the protocol family, in our case ipv4 (AF_INET). AF_INET6 if we wanted to use ipv6.
**type**: Specifies semantics of communication. A SOCK_STREAM type provides sequenced, reliable, two-way connection based byte streams.
**protocol**: The protocol specifies a particular protocol to be used with the socket.
Normally only a single protocol exists to support a particular socket type within a given protocol family
Returns -1 if there's an error otherwise the descriptor (a reference).## 2. Set options 🎛
```swift
var noSigPipe: Int32 = 1
setsockopt(socketDescriptor, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, socklen_t(MemoryLayout.size))
```
`setsockopt` -- get and set options on sockets
**socket**: The socket descriptor
**level**: To manipulate options at the socket level
**option_name**: The name of our the option, in our case we do not generate SIGPIPE, instead return EPIPE
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).
**socklen_t**: the option length
## 3. Create adress and bind 🚪➕🔌
![alt text](https://s3.amazonaws.com/marcioklepacz/overview.png)
```swift
let port: in_port_t = 9292
var address = sockaddr_in(
sin_len: UInt8(MemoryLayout.stride),
sin_family: UInt8(AF_INET),
sin_port: port.bigEndian,
sin_addr: in_addr(s_addr: in_addr_t(0)),
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
)var bindResult: Int32 = -1
bindResult = withUnsafePointer(to: &address) {
bind(socketDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size))
}
```
`bind` -- assigns a name to an unnamed socket.
When a socket is created with socket() it exists in a name space (address family) but has no name
assigned. bind() requests that address be assigned to the socket.
```swift
if bindResult == -1 {
fatalError(String(cString: UnsafePointer(strerror(errno))))
}
```## 4. Listen 📡
```swift
listen(socketDescriptor, SOMAXCONN)
```
`listen` -- for connections on a socket
The backlog parameter defines the maximum length for the queue of pending
connections. If a connection request arrives with the queue full, the
client may receive an error with an indication of ECONNREFUSED.
## 5. Accept connection on socket ✅
```swift
print("Starting HTTP server on port \(port)")
repeat {
var address = sockaddr()
var length: socklen_t = 0
let clientSocket = accept(socketDescriptor, &address, &length)
if clientSocket == -1 {
fatalError(String(cString: UnsafePointer(strerror(errno))))
}
```
`accept` -- extracts the first connection request on the queue of pending connections,
Creates a new socket with the same properties of
socket, and allocates a new file descriptor for the socket.
The argument address is a result parameter that is filled in with the
address of the connecting entity, as known to the communications layer.
The address_len is a value-result
parameter; it should initially contain the amount of space pointed to by
address; on return it will contain the actual length (in bytes) of the
address returned.
```swiftvar characters = ""
var received: UInt8 = 0
repeat {
var buffer = [UInt8](repeatElement(0, count: 1))
```
## 6. Read socket 📖`recv` -- receive a message from a socket
```swift
let resp = recv(clientSocket, &buffer, Int(buffer.count), 0)
if resp <= 0 {
fatalError(String(cString: UnsafePointer(strerror(errno))))
}
received = buffer.first!
if received > 13 /* Carriage Return on ASCII table */ {
characters.append(Character(UnicodeScalar(received)))
}
} while received != 10 /* New Line on ASCII table */
print("Received -> \(characters)")
```
## 7. Write response 📝`write` -- write output
```swift
let message = "HTTP/1.1 200 OK\r\n\r\n Hello World!"
print("Response -> \(message)")
let messageData = ArraySlice(message.utf8)
_ = messageData.withUnsafeBytes {write(clientSocket, $0.baseAddress, messageData.count)
}
```
## 8. Close socket ⚰️`close` -- delete a descriptor
```swiftclose(clientSocket)
} while true
}
startWebServer()
```
# References:
* https://ruslanspivak.com/lsbaws-part1/
* https://github.com/httpswift/swifter