Book Image

Hands-On Full-Stack Development with Swift

By : Ankur Patel
Book Image

Hands-On Full-Stack Development with Swift

By: Ankur Patel

Overview of this book

Making Swift an open-source language enabled it to share code between a native app and a server. Building a scalable and secure server backend opens up new possibilities, such as building an entire application written in one language—Swift. This book gives you a detailed walk-through of tasks such as developing a native shopping list app with Swift and creating a full-stack backend using Vapor (which serves as an API server for the mobile app). You'll also discover how to build a web server to support dynamic web pages in browsers, thereby creating a rich application experience. You’ll begin by planning and then building a native iOS app using Swift. Then, you'll get to grips with building web pages and creating web views of your native app using Vapor. To put things into perspective, you'll learn how to build an entire full-stack web application and an API server for your native mobile app, followed by learning how to deploy the app to the cloud, and add registration and authentication to it. Once you get acquainted with creating applications, you'll build a tvOS version of the shopping list app and explore how easy is it to create an app for a different platform with maximum code shareability. Towards the end, you’ll also learn how to create an entire app for different platforms in Swift, thus enhancing your productivity.
Table of Contents (19 chapters)
Title Page
Copyright and Credits
Dedication
Packt Upsell
Contributors
Preface
Index

Building a web server in Swift


Now that we have a little background on HTTP and formats for its request and response payloads, let's try to build an HTTP server from scratch using Swift and some C libraries that we can access via Swift. The point of this exercise is to learn how to build a very basic HTTP web server so we have a better understanding of how all of these web servers are built using sockets. Getting a full stack view is helpful in case you need to dive into low-level code to debug an issue or fix a bug in a package or library you might be using to build our web server. You might also be curious on how to code your own simple web servers, it's actually fairly easy. To create a web server, let's try the following steps:

  1. Import the C libraries in Swift:
import Darwin.C
  1. Create a socket using the socket system call. Sockets are a way for other hosts on the network to connect to this process:
let sock = socket(AF_INET, SOCK_STREAM, 0)
  1. Create a socket address structure and initialize it with host and port information. Then call the bind system call with the socket address structure and bind the server to the localhost on the port specified:
bind(sock, sockaddrPtr, socklen_t(socklen))
  1. Listen for incoming requests by calling listen and specifying the max number of requests to be added to the queue to be served by our process:
listen(sock, 5)
  1. Now, we can accept incoming connections by calling accept with the socket file descriptor for our socket that clients connect to. It will remove requests from the listen queue and return a new client socket connection:
let client = accept(sock, nil, nil)
  1. We can read from the new client socket connection and send data to it using HTTP Protocol:
  let html = "<!DOCTYPE html><html><body><h1>Hello from Swift Web Server.</h1></body></html>"
  let httpResponse: String = """
    HTTP/1.1 200 OK
    server: simple-swift-server
    content-length: \(html.count)

    \(html)
    """
  httpResponse.withCString { bytes in
    send(client, bytes, Int(strlen(bytes)), 0)
  }
  1. Close the connection using the close system call:
close(client)

That was a quick overview of how network-based programs work and how our web server will work as well. Now, let's look at the code as a whole:

import Darwin.C
let zero = Int8(0)
let transportLayerType = SOCK_STREAM // TCP
let internetLayerProtocol = AF_INET // IPv4
let sock = socket(internetLayerProtocol, Int32(transportLayerType), 0)
let portNumber = UInt16(4000)
let socklen = UInt8(socklen_t(MemoryLayout<sockaddr_in>.size))
var serveraddr = sockaddr_in()
serveraddr.sin_family = sa_family_t(AF_INET)
serveraddr.sin_port = in_port_t((portNumber << 8) + (portNumber >> 8))
serveraddr.sin_addr = in_addr(s_addr: in_addr_t(0))
serveraddr.sin_zero = (zero, zero, zero, zero, zero, zero, zero, zero)
withUnsafePointer(to: &serveraddr) { sockaddrInPtr in
  let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
  bind(sock, sockaddrPtr, socklen_t(socklen))
}
listen(sock, 5)
print("Server listening on port \(portNumber)")
repeat {
  let client = accept(sock, nil, nil)
  let html = "<!DOCTYPE html><html><body style='text-align:center;'><h1>Hello from <a href='https://swift.org'>Swift</a> Web Server.</h1></body></html>"
  let httpResponse: String = """
    HTTP/1.1 200 OK
    server: simple-swift-server
    content-length: \(html.count)

    \(html)
    """
  httpResponse.withCString { bytes in
    send(client, bytes, Int(strlen(bytes)), 0)
    close(client)
  }
} while sock > -1

To run our server, let's take look at the following steps:

  1. Create a Swift file and call it simple-server.swift.
  2. Copy the preceding code into the file.
  3. Run the code using the Swift command and pass the file name as the first argument, as follows:
$ swift simple-server.swift
  1. The Swift compiler will try to compile the contents of simple-server.swift and run it in one command. You should see the following printed in the Terminal when the server has started:
Server listening on port 4000
  1. Open the browser and go to http://localhost:4000. You will see the response from our Swift simple HTTP server replying back with HTML content:

The example code works only on macOS but you can easily make it work in Linux by using import Glibc instead of import Darwin.C and changing some of the datatypes that are passed in creation of a socket. Swift supports some of the C directives, such as #if,#elsif,#else, and #endifto help include or skip code blocks before compiling on certain platforms, such as Linux or macOS, where a certain feature or API usage may be different. In our case, since we depend on C-based libraries, we'd need to import Glibc when OS is Linux or import Darwin.C and also set different types for two variables we use in our code, as follows:

#if os(Linux)

import Glibc
let zero = UInt8(0)
let transportLayerType = SOCK_STREAM.rawValue // TCP

#else

import Darwin.C
let zero = Int8(0)
let transportLayerType = SOCK_STREAM // TCP

#endif

Note

Directives are a way to let the compiler know how to process the source code before compiling. There are specific language constructs that let the compiler preprocess the source code and this comes in handy when we want to build a server-side Swift web applications where we need to ignore certain code blocks in Linux that are specific to macOS and vice versa so that the compiler does not fail to compile the code because certain standard libraries are missing or do not exist on certain platforms.