Back in the early days of the World Wide Web (the browser-based Internet you know today), the idea of a web server was transparent to most developers. You would install a program to host your HTML files (or open a free account on a service such as GeoCities) and spend most of your time thinking about what size to make your text or where you could find the best GIFs to put. To most people, a web server was just a place where you would upload your files.
As web development technologies continued to progress, more people found themselves needing the ability to control their web hosting themselves, leading to greater awareness of web server technologies. Instead of relying on your hosting provider’s guarantees of security or uptime, you could begin to take matters into your own hands by configuring the server yourself.
Additionally, more advanced applications such as e-commerce and electronic medical records (EMR) systems began taking off, with their own requirements for user personalization, database integration, and security. To meet these needs, scripting languages, such as PHP and Ruby began to rise in popularity, allowing programmers to create pages that could perform all of the logic of processing data and generating a static page immediately after a user requested the page from his/her browser. Instead of static web pages, people were beginning to develop web applications.
In the world of the Internet of Things (IoT), you can take advantage of web application technology to help you expose data from your IoT devices over HTTP, the Hypertext Transfer Protocol. Whenever you use a web browser or an app that connects to a server, it is using HTTP to transfer that data. As you may remember from earlier chapters, protocols such as Bluetooth or HomeKit are optimized for specific use cases and sometimes require a massive amount of domain-specific knowledge to be deployed successfully. Web technologies, on the other hand, are a well-known platform with a strong development community and low hardware requirements (you just need something that can power a web server).
In this chapter, you will revisit the Raspberry Pi and Node.js development environment from earlier chapters and expand both to advertise the same data over HTTP and HTTPS, using the Express module for Node.js. Due to its small binary size, potential for scaling, and huge developer base, Node has been taking over as a first-class web application scripting language.
Learning Objectives
In this chapter, you will learn how to set up a web server on the Raspberry Pi you have been using throughout this book, expose its sensor data over HTTPS end points, and connect to the server from the IOTHome app for iOS. You will use Node.js to create the web application and Swift for the iOS app. While some projects to get Swift running on Raspberry Pi are starting to gather steam, in today’s environment, Python and Node.js are the most well-documented and -supported options for building web-based applications on a Raspberry Pi.
For this chapter, rather than depending on a full web server application, such as Apache, to listen for HTTPS connections and route them to Node.js, you will learn how to use the Express module for Node.js, to accomplish the same task. For projects for which you will be running multiple web applications on the same server, you may want to look at Apache or NGINX, to better manage your connections. However, the project in this chapter aims to replicate the use case for a single-purpose IoT device or a single-purpose web microservice, such as one you would host on Heroku or an Amazon Web Services Lambda.
Web application development core concepts
Setting up Express to expose web services through Node.js
Reading data from a temperature sensor in a Node.js application
Reading data from Bluetooth sensors within a Node.js application
Providing security with HTTPS
The project in this chapter is heavily based on the Express module for Node.js. If you would like to learn more about the module or find more support on it, please visit its official home page at www.expressjs.com .
Creating a Web Server to Share Data over HTTPS
In this section, I will focus on the process of setting up a web server using Node.js and Express, as well as how to use Node modules to expose the data generated by the IOTHome sensors (temperature, humidity, status of a switch) over HTTPS. The projects in this section assume that Node.js and the temperature sensor from Chapter 7 have been set up correctly. If you still feel uncomfortable with either topic, or are coming into this chapter directly, I highly recommend going back to the setup sections of Chapter 7 before progressing with this chapter.
In this section, you will start by implementing a web server that transfers data over HTTP, then you will learn how to apply an SSL certificate to the server, to make it an HTTPS server—the new, secure standard for web applications. After the web server is set up successfully, you will use it to provide the data for the Home screen on the IOTHome app.
Using Express to Expose Web Services
Creating a Folder for the Express Project
Creating a New Express-Based Node Application
A Quick Introduction to how Express Works
Express works by listening for HTTP requests on a port, then responding to them, based on the end point that is specified. End points are defined as functions that are available to consumers of your API, made up of a route, the path component appended to the server’s address and the HTTP method that is used to make the request (for example, GET, POST). Throughout web application development, you will see these two terms used interchangeably. To reduce confusion, I think the clearest explanation is that end point is primarily used as a term to describe external (client-facing) interactions with your server, and route is used primarily to describe the internal logic of your Express application.
As you can tell, the method called on the app object changed, as well as the string for the route.
Finally, two of the other most frequently used HTTP methods are PUT and DELETE, which are used to update and delete records. You will not use them in this chapter, but they may be helpful to you in your own projects in the future.
Creating a New Express-Based Node Application
In this example, I instructed Node to send the text, Hello World, upon receiving a GET request for the document root. The res object is built into Node and specifies that you want to pipe output as an HTTP response. In this example, I asked Express to listen on port 3000 instead of the standard HTTP port 80, because Node considers port 80 a privileged port. Unless you are logged in to a root or system user account, you are not able to run applications from this port. Later in this section, you will switch to port 80, but for initial testing, it is best to use a non-privileged port.
To verify that the Express application is working, you must tell Node to start executing the new script, then try to make the request from a web browser.
Congratulations on creating your first Node application! It will keep executing until you kill the Node process on your Raspberry Pi.
Reading Values from the DHT Temperature Sensor
Defining a New Route for the Temperature Sensor
At this point, I recommend making two changes: removing the end point for the earlier example and changing the port to 80. Many web servers are easily hacked when debugging end points are left active. Vigilant code maintenance is an easy way to reduce this risk in your applications. By using port 80, you will also be able to make your server behave closer to the HTTP specification, which states that HTTP traffic is transmitted on TCP/IP port 80.
Reading DHT22 Data from a Node Application
In this example, you will notice that I used the json() method on the res object, to send back the temperature data. While plain text was sufficient for the Hello World example, using JSON (JavaScript Object Notation) is a widely adopted practice to represent dictionaries and hierarchical data in web application development. Additionally, most web and mobile frameworks these days provide built-in JSON validation and encoding/decoding, making it much easier to work with than custom data types. Following this line of reasoning, you will notice that I also used the status() method to return the error as a standard HTTP 500 server error. This allows you to use built-in HTTP error handling.
Tip
If you would like your Node application to automatically restart whenever you change its source code, I recommend looking into the nodemon tool, available via npm and GitHub at https://github.com/remy/nodemon . While this tool can be convenient during the development phase, I recommend disabling it in production.
Caution
It takes approximately two seconds for the DHT22 temperature and humidity sensor to get an accurate reading. Keep this in mind if you are getting stale data or time-outs when pinging the temperature route.
Reading Information from Bluetooth Devices
In the last section, you exposed the data from the temperature sensor over HTTP by reading its value directly from a Node application and then echoing it via Express. In this section, you will expose the Bluetooth door sensor’s data over HTTP, by making the Node application act like a Bluetooth central manager, using the Noble module for Node ( https://github.com/noble/noble ). You may remember from previous chapters that one of the riskiest and time-consuming parts of Bluetooth communication is discovering the device and establishing a connection to receive data. To help with this operation, in this section, you will add end points for managing the connection state and transmit data based on the last update (rather than making a new connection each time data is requested).
Adding Noble and Bluetooth UUIDs to the Node Application
Remember: These UUIDs were defined in Chapter 6 as unique, random hexadecimal values that identify the device. Just as with Chapter 6’s Bluetooth app and Chapter 7’s HomeBridge configuration, you need these values to find and identify the device. Because the values will not change while the application is executing, you can define them as constants, using the const keyword.
Just as with connecting to a hardware protocol like Bluetooth or I2C, it is common for flow-based web server operations, such as creating a new user account, to require the client developer (for example, mobile app developer, front-end web developer) to follow a specified flow of API calls to complete the operation. For the IOTHome Node application, the client developer must POST the /door/connect/ end point before attempting to request data from the device. Similarly, after they have finished their session, they must POST to the /door/disconnect/ end point, to close the connection and allow other applications to use the hardware.
Discovering a Bluetooth Peripheral Using Noble
The arrangement of the connection may seem a bit odd at first. In the Arduino code for the door sensor, you had to implement completion handlers to progress through the connection flow for the Bluetooth server. In the iOS app, you had to implement delegate methods. To receive messages with Noble, you must respond to discover events, which are triggered by initiating a scan for devices. To implement an efficient Bluetooth LE connection process, you should scan only for the devices advertising the services you need. However, at the time of writing, I noticed that the results of the scan API that specifies service UUIDs were hard to predict, so, instead, I decided to filter discovered devices by the name specified in their advertisement data.
Connecting to a Bluetooth Peripheral Using Noble
Observing and Responding to Characteristic Updates with Noble
The subscription process is initiated through the subscribe method, but the data must be observed through the on method. Because it is impractical to make the user wait until the first update has been delivered, I save the values to global variables that can be queried later.
Caution
While researching this chapter, I noticed that the Bluetooth utility on the Raspberry Pi became unable to maintain a connection after several connection debugging sessions. If you are having issues with the door sensor not reporting a successful connection via its blue status LED, try restarting the Pi and then trying again.
Reading Data from a Bluetooth Peripheral, Using Noble
Disconnecting from a Bluetooth Peripheral, Using Noble
Using HTTPS to Provide Secure HTTP Connections
In a move to increase the privacy of users’ data and reduce phishing (false identity) attacks on the Internet, starting in 2016, Apple, Google, and other major technology companies announced that their platforms would be moving toward primarily supporting servers that implement HTTPS, an extension of HTTP that requires all data to be encrypted with Transport Layer Security (TLS). TLS is implemented by adding a Secure Sockets Layer (SSL) certificate to your server, which has been issued by a provider that is trusted by the major browsers and your platform (for example, iOS).
In Google Chrome, some of the most obvious implications of not using HTTPS are that your site will show up lower in Google’s search ranking. Additionally, sites with untrusted TLS certificates will be marked as Not Secure and present users a warning page when they are loaded in the browser. In iOS, Apple enforces HTTPS by making all HTTP requests fail inside of an app, unless the developer manually re-enables them. Additionally, all untrusted HTTPS requests will fail.
To work around these limitations and improve the security of the IOTHome device, you should extend the Node application to support HTTPS. As with the other functionality in this project, you can take advantage of Node modules and tools that have been developed for web apps, to easily add HTTPS to the IOTHome project.
- 1.
If you are developing for a production environment, you must request an SSL certification from a service that is trusted by the Internet Engineering Task Force (IETF), the organization that maintains the HTTPS standard. I recommend using Comodo, Verisign, or a certificate from Amazon Web Services (AWS). Certificates from these providers have the greatest compatibility, clear instructions, and support.
- 2.
If you would like to develop a production-level prototype and already have a domain, you can use the Let’s Encrypt trust authority ( www.letsencrypt.org ) and its accompanying tool, certbot-auto ( https://certbot.eff.org/docs/install.html ), to generate a free, trusted SSL certificate for testing.
- 3.
For pure prototype purposes, you can generate your own SSL certificate, using OpenSSL on your Raspberry Pi.
For the purposes of this book, I have chosen option #3. If you would like to use options #1 or #2, I suggest creating those certificates on the server attached to your domain name, then copying it over to your Raspberry Pi (granted your SSL provider allows this capability).
Generating an OpenSSL Self-Signed SSL Certificate
With OpenSSL, you act as your own trust provider and generate an SSL certificate that meets the basic encryption requirements of HTTPS. This is referred to as a self-signed certificate . Because it is not generated by one of the trusted vendors I mentioned above, most browsers and iOS will initially reject it, until you perform some steps to trust it on the device, which I will explain after you are done generating the certificate.
If you have ever created an Apple Developer Program iOS development certificate or Push Notification certificate, you are already familiar with the process of generating SSL certificates (although the delivery method is different). In Apple’s model, you create a private key (a unique hex value that is used as the base for the encryption/decryption of communications), create a Certificate Signing Request (CSR) file using the Keychain Access tool to serve as a receipt for your application for a new certificate, and then submit the CSR file to Apple’s web site, which will refresh with a new SSL certificate you can download once your request has been processed.
The preceding command specifies that you want to create a private key based on RSA 2048-bit encryption and a certificate based on that key, which is good for 365 days. As with your iPhone developer certificate, make sure that you save the private key and do not share it with others. Losing the key will result in being unable to use the certificate. Sharing the key will allow others to break your encryption.
Adding the https and fs Modules to the Node Project
Earlier in the chapter, app.listen(80) was used to instruct Express to listen for HTTP traffic on port 80. To use HTTPS in place of HTTP, you must disable this line and, instead, instruct an https object to listen for traffic. To initialize the https object, you must provide it with the paths to the SSL certificate and its private key on the Raspberry Pi. If you used Let’s Encrypt to generate these, they will be under the folder that was output by the certbot-auto tool. If you generated the SSL certificates with your own provider, you will have to save the files to your Raspberry Pi, either by downloading them via the Chromium browser on the Pi itself or configuring another tool, such as avahi-daemon, to help make the Raspberry Pi discoverable by your Mac over Bonjour.
Configuring the Node Project to Use HTTPS Instead of HTTP
After verifying that you want to load the page, you should now be able to see the temperature JSON data in the browser, just like you did when it was exposed through normal HTTP.
At this point, it is safe to change your application to listen on port 443. Just remember that you will have to run Node as a superuser, and that you will have to trust the :443 end point in Chrome. For HTTPS requests to port 443, you do not have to append the port number in iOS or your web browser.
Configuring the Server to Start Up with the Raspberry Pi
As the final step in setting up the web server, you should make the Node application start with the Raspberry Pi on boot. This will prevent you from having to manually start and run the Node application every time you want to access its data through HTTPS. If you implemented this step for the HomeBridge project from the last chapter, this setup process should be extremely familiar to you, as you will create a service using the systemd tool , to manage this operation. Unlike HomeBridge, however, the IOTHome web server is much easier to set up as a service.
The name of the service
The working directory for the script that will be run as a service
The location of the script
The user permissions for the script
The failure behavior for the script
Service Definition for the IOTHome Node Application
From here on out, if you have to modify your service definition or would like to modify the script itself, stop the service before performing your changes, then restart it once you are done.
Connecting to Your Server from an iOS App
At this point, you are able to access all of the data from the IOTHome system, using the web server on the Raspberry Pi. You also learned many different tools to debug the connection, including Google Chrome, the command line, and Postman. However, this is an iOS book, so it is only natural to learn how to apply these skills to iOS apps.
In this section, you will expand the IOTHome app from previous chapters to add a screen that allows users to access the sensors in the system via HTTP instead of Bluetooth. While the UI code for Apple platforms are mostly single-use, the networking code can be reused among all platforms. In Chapter 9, you will reuse the networking code from this chapter to power an Apple TV–based dashboard for the IOTHome system.
Setting Up the User Interface
For this project, the user interface plays a supporting role to the networking code. As such, I do not want to focus too much on creating a new user interface for the Home Manager screen (the one intended to show data for the entire system). As opposed to the Door Manager, it should show information from the temperature system and connect to the web server instead of Bluetooth to retrieve data. To accomplish this, you will subclass the DoorViewController class (the backbone for the Door Manager screen), add the new properties for displaying temperature, and override the connect() method to initiate a call to the HTTPS web server, instead of a Bluetooth device.
To start implementing the project, make a clone of the IOTHome app from Chapter 6. You can copy your project files or a fresh copy from the GitHub project for this book ( https://github.com/Apress/program-internet-of-things-w-swift-for-ios ).
Styling for Home View Controller User Interface Elements
Element Name | Text Style | Height | Top Margin | Bottom Margin | Left Margin | Right Margin |
---|---|---|---|---|---|---|
Navigation bar | Prefers large text | — | — | — | — | — |
“Temperature” title label | Title 2 | 24 | 40 | — | 30 | 20 |
“Temperature” value label | Title 2 | 24 | 40 | — | 20 | ≥30 |
“Humidity” title label | Title 2 | 24 | 8 | — | 30 | 20 |
“Humidity” value label | Title 2 | 24 | 8 | — | 20 | ≥30 |
“Door” title label | Title 2 | 24 | 8 | — | 30 | 20 |
“Door” value label | Title 2 | 24 | 8 | — | 20 | ≥30 |
“Battery Level” title label | Title 2 | 24 | 8 | — | 30 | 20 |
“Battery Level” value label | Title 2 | 24 | 8 | — | 20 | ≥30 |
“Last Updated” label | Body | 25 | 8 | — | 20 | 20 |
“Press to Connect” label | Body | 25 | — | 20 | 20 | 20 |
“Connect” button | Title 1 | 60 | 20 | 30 | 20 | 20 |
Updated HomeViewController Class , Including User Interface Scaffolding
Making and Responding to HTTPS Requests
Initial implementation of Network Manager as a Singleton
Singletons are a contentious subject in the Apple developer community, owing to their nature of being shared globally, but for this chapter, a singleton is a convenient choice, because there are no side effects, and I want to re-create Apple’s approach to accessing hardware APIs (using a single object throughout iOS to manage one resource, for example, GPS, camera). If you are interested in alternatives to singletons, I recommend researching dependency injection. Dependency injection is not a first-class design pattern from Apple, so you should exercise some caution in picking a library or implementation that suits your application.
Singletons are implemented in Swift by adding a static property to an object, which returns an initialized instance of that class. If the object was initialized before, the existing object will be returned; otherwise, a new object will be initialized. This is referred to as lazy-loading. For my initializer, all I needed to do was initialize the class with the base URL for the network requests. For the base URL, use the Bonjour name of the device, as I did in my example. As of this writing, Raspbian ships with Bonjour enabled by default. Bonjour allows Apple devices to find devices on a network by their domain name, instead of an IP address.
Network Manager Method for Making HTTP Requests
The basic flow of the method is to create a URL request object using the end point and HTTP method string, then create a completion handler for the data task, and execute the task using the resume() method. You may notice a significant amount of error handling in this method. Although the JSONSerializer and URLSession classes abstract a lot of logic for you, they are prone to failure from incorrect configuration. Adding detailed error handling will make it easier for you to find which step failed later. Because the method returns its result through a completion handler, you can pass along the error object, instead of the result from the server.
Using the Network Manager to Get the Temperature from the Server
Caution
When you must access a class’s properties from within a completion handler, always perform the operation through a weak reference. Accessing self directly creates what is referred to as a retain cycle: a memory leak resulting from strong references to a class never being completely released.
Info.plist Entry for Enabling Self-Signed SSL
Enabling Self-Signed SSL Certificates Through URLSessionDelegate
Getting the Status of the Door Sensor Through the Network Manager
Automatically Disconnecting from the Door Sensor
Summary
In this chapter, you were able to build a classic IoT device by expanding the Raspberry Pi from earlier chapters to act as a web server and expose its data via HTTPS end points. During this process, you also learned how HTTP requests work, how Express and Noble can offload the hard work of implementing the HTTP and Bluetooth stacks for you, and how to connect to the end points using an iOS app. In a similar manner to setting up HomeKit, many of these tasks were not as much iOS- or Raspberry Pi–specific as they were implementations of established Linux and web application development practices.
Before single-board computers such as the Raspberry Pi achieved commercial success, many proprietary system-on-a-chip solutions were providing this same core functionality, GPIO and a web server, with a much higher price tag and learning curve. Thanks to the streamlining of technologies such as these, the IoT continues to expand, but as you will learn later in the book, you should also remember to add HTTPS or other security measures, to help make it a safe IoT.