Building a Web Server in Swift — Part1
I’ve been wanting to teach myself Swift “The new shinny programming language from apple” for the past few months now. But I can’t get myself to sit down and work on an iOS or a Mac project. Plus I wanted something within Vim & the Terminal ecosystem.
Coming from backend experince and ruby development i thought it would be extermly cool to build a Web Server in Swift. The challenge is not use XCode during this project. All the tools needs to be a command line tools. So, Lets get started.
We will divide this series into 3 parts:
- Compiling Swift Code from Terminal
- Build Slayer “a minimal interface between webserver and Swift code”
- Build Slayer.Server()
Compiling Swift Code from Terminal
Swift REPL:
Swift ships with the REPL (Read-Evaluate-Print Loop) which lets you execute blocks of code in an interactive way, within the context of a single session. This concept, already available in a number of other languages, is a terrific experimental playground, as well as a great addition to the debugging experience. you can start REPL by typing
$ swift
Welcome to Swift! Type :help for assistance.
1>
Lets try it by creating a simple welcome function:
func welcome(name) -> String {
return "Hello "+ name
}
welcome("Moski")
That’s all good, but how can we compile a swift script into an executable?
The Swift Compiler
The Swift Compiler is what compiles your Swift code to binary, and its called swiftc. lets create a simple swift file “spacex.swift” using vim(I highly recommend this awesome vim plugin for swift) or your favorite editor.
struct Dragon{
fun fly(){
print("Dragon9 is ready for launch")
}
}Dragon().fly()
Now compile spacex.swift and run it
$ swiftc spacex.swift -o spacex
$ ./spacex
Dragon9 is ready for launch
That’s awsome, so now we can run our scripts from the shell. But as soon as we start building our project, the spacex.swift will get bigger and bigger. It will be much harder to maintain the code. For example imagine that i have multiple rockets and i would like to divide them into multiple files such as dragon.swift & falcon.swift.
Sadly to this point we can’t import a swift file inside another swift file. Luckly we have away around this issue. We can create a Swift Module that contains all these files and we can import it inside our spacex.swift file.
The Swift External Module
Lets start by creating three swift files dragon.swift, falcon.swift & main.swift.
A side note, when creating any swift Module that has more than one file, the compiler expects main.swift to be the entrace point of the app. We will use it to define our Flyable Protocol.
//main.swift
import Foundationprotocol Flyable{
func fly() -> Void
}//dragon.swift
// Dragon implements the Flayble protocol defined in main.swift//@NOTE the public keyword.
public struct Dragon: Flyable{
public func fly(){
print("Dragon status is ready to go, lanuching in 3,2,1 ...")
}
}//falcon.swift
// Falcon implements the Flayble protocol defined in main.swiftpublic struct Falcon: Flyable{
public init(){}
public func fly(){
print("Falcon status is ready to go, lanuching in 3,2,1 ...")
}
}
Important: For code in our script to access a symbol in the Module the symbol must be exported using the public keyword. This includes classes, methods, functions, variables, and protocols.
Now, lets compile all these 3 files and generate a module that we can use inside out spacex.swift script
$ mkdir Modules
$ swiftc main.swift dragon.swift falcon.swift \
-emit-executable \
-emit-module \
-module-name Spacex \
-emit-module-path Modules/Spacex.swiftmodule \
-sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk \
-o Modules/Spacex
The most important options are “-emit-module & -emit-executable” . You can always run swiftc — help to see all the available options.
The previous command will produce 3 files:
$ ls Modules
Spacex Spacex.swiftdoc Spacex.swiftmodule
Let’s create out spacex.swift and try to import our new module
//spacex.swift
#!/usr/bin/env xcrun swift -I Modulesimport Spacex
Spacex.Dragon().fly()
- - I <value> Add directory to the import search path
Lets try and and take it for a spin.
$ chmod +x spacex.swift
$ ./spacex.swift$ LLVM ERROR: Program used external function '__TFV6Spacex6Dragon3flyfS0_FT_T_' which could not be resolved!
We are not getting an error in the Import statment. But trying to to call any Class inside the module will give us an error.
swiftmodule
Well, as it turns out a swiftmodule describes the Swift module’s interface but it does not contain the module’s implementation. A library or set of object files is still required to link your application against. So the previous error is telling that i can see that application is calling an external application but i can’t reslove it.
dylib
dylib is a “DYnamic linked LIBrary” that contains generic, unmodifiable code intended to be reused by many applications. Just like windows DLL files. Luckly we can generate libSpacex.dylib using swiftc command.
$ swiftc main.swift dragon.swift falcon.swift \
-emit-library -olibSpacex.dylib \
-module-name Spacex \
-sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk$ mv libSpacex.dylib Modules
This will generate a dylib file that we can include inside out swift script. So, let’s revisit our spacex.swift
//spacex.swift
#!/usr/bin/env xcrun swift -I Modules -L Modules -lSpaceximport SpacexSpacex.Dragon().fly()
Falcon.fly()Dragon status is ready to go, lanuching in 3,2,1 ...
Falcon status is ready to go, lanuching in 3,2,1 ...
:) :) it works, so now we can divide our project into smaller libs.
One last thing, i tool the librety to divide all these big compile files into a Makefile insipred by http://owensd.io/2015/01/15/swift-makefiles-take-2.html
# A simple build script for building projects.
#
# usage: make [CONFIG=debug|release] PROJECT=SpacexPROJECT ?= project
MODULE_NAME ?= $(PROJECT)
SDK = macosx
ARCH = x86_64
MACOSX_MIN_VERSION = 10.10CONFIG ?= debugROOT_DIR = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
OUTPUT_DIR = $(ROOT_DIR)/bin
TARGET_DIR = $(OUTPUT_DIR)/$(SDK)/$(CONFIG)
SRC_DIR = $(ROOT_DIR)/srcFINAL_BUILD = $(ROOT_DIR)/Rome/$(MODULE_NAME)
FINAL_LIB = $(FINAL_BUILD)/libifeq ($(CONFIG), debug)
CFLAGS=-Onone -g
else
CFLAGS=-O3
endSWIFTC = $(shell xcrun -f swiftc)
CLANG = $(shell xcrun -f clang)
SDK_PATH = $(shell xcrun --show-sdk-path --sdk $(SDK))
SWIFT_FILES = $(wildcard $(SRC_DIR)/*.swift)all: build setupbuild:
mkdir -p $(TARGET_DIR) $(SWIFTC) $(SWIFT_FILES) -emit-executable -sdk $(SDK_PATH) -module-name $(MODULE_NAME) -emit-module -emit-module-path $(TARGET_DIR)/$(MODULE_NAME).swiftmodule -F Rome -o $(TARGET_DIR)/$(MODULE_NAME) $(SWIFTC) -emit-library -olib$(MODULE_NAME).dylib $(SWIFT_FILES) -module-name $(MODULE_NAME) -sdk $(SDK_PATH) -F Romesetup:
mkdir -p $(FINAL_LIB)
mv $(TARGET_DIR)/* $(FINAL_BUILD)
mv lib$(MODULE_NAME).dylib $(FINAL_LIB)clean:
rm -rf $(TARGET_DIR)
rm -rf $(FINAL_BUILD)nuke:
rm -rf $(OUTPUT_DIR)
So, lets try it out. The makefile assumes that you have the swift module files in the a src folder.
$ ls .
Makefile Rome bin spacex.swift src$ ls src
dragon.swift falcon.swift main.swift
Let’s leave Rome, into another post. For now, it’s an empty folder. Bin is auto generated. I’ve also changed spacex.swift to use the new folder path.
//spacex.swift
#!/usr/bin/env xcrun swift -I Rome/Spacex -L Rome/Spacex/lib -lSpaceximport SpacexSpacex.Dragon().fly()
Falcon.fly()
You can run make by entering the following command:
$ make CONFIG=debug PROJECT=Spacex all
When all is done, a Spacex folder will be generated inside the Rome folder. It will contain spacex.swiftmodule and it will also contain a lib folder that has libSpacex.dylib
$ ls Rome/Spacex
$ Spacex Spacex.swiftmodule Spacex.swiftdoc lib
Next Post, Build SLayer “a minimal interface between webserver and Swift code” coming soon.