MacOS HTTP Listener for a Swift App

I have a wireless stateless-switch within my private network (LAN) which transmits HTTP posts to a configured URL (e.g. an always-on MacMini) about the switch's state. Upon receipt of a post, my MacOS app will then take appropriate action. No response to the sender is required. The frequency of posts will be minimal ( a few per day), but require immediate attention when received. The LAN is protected from external misuse by a secure gateway.

I'd appreciate any suggestions for a lightweight solution (i.e. not a full-blown web-server), either as an overview of methods or sample source (e.g. on GitHub).

Regards, Michaela

This is a difficult question to answer. On one hand, there are too many answers. On the other hand, you haven't provided enough information. Most of the available solutions assume that your listener always runs in the background and doesn't need a logged-in GUI session.

If your listener can run in the background and doesn't need to be constantly logged in, then any solution you can find on the internet will work. That's just a basic network listener. Your only complication is that you will have to support HTTP.

Your Mac already has a full-blown web-server built in. The easiest solution would be to just enable it and use it. From there, it is merely a question of how much extra cleverness and complexity you want to add.

If you don't want to use the built-in web server, you will have to add your own code to respond to HTTP requests. Apple's APIs do support that.

You will need to use launchd to start your server automatically. You can just start your server at boot up or you can use launchd cleverness to automatically launch your app in response to activity on the listening port.

If you require a logged in user GUI session, then you will need a launchd agent to launch your app. I don't know if the other launchd cleverness will work in this case.

Annoyingly, macOS does not have a simple HTTP server API. There are a bunch of HTTP server libraries out there. If I were in your situation I’d do this using SwiftNIO. You can run it on top of Network framework, so it’s a first class network product on Apple platforms.

The SwiftNIO folks are very active on Swift Forums > Related Projects > SwiftNIO.

I'm already using the Web Server built into MacOS for publicly facing web-sites, through the DMZ of my secure gateway to a different Mac.

So does that result in a port conflict, with both programs listener on port 80? Or can you configure one or the other to use a different port?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

OMG, the solution is very easy - at least in a prototype state. Now let's see how it performs in reality, with code for handling state changes.

import Foundation
import Network

class HTTPServer {
    static let shared = HTTPServer()
    var listener : NWListener
    var queue : DispatchQueue
    var connected : Bool = false
    let nwParms = NWParameters.tcp
    
    init() {
        queue = DispatchQueue(label: "HTTP Server Queue")
        listener = try! NWListener(using: nwParms, on: 80)
        listener.newConnectionHandler = { [weak self] (newConnection ) in
            print("**** New Connection added")
            if let strongSelf = self {
                newConnection.start(queue: strongSelf.queue)
                strongSelf.receive(on: newConnection)
            }
        }

        listener.stateUpdateHandler = { (newState) in
            print("**** Listener changed state to \(newState)")
        }

        listener.start(queue: queue)
    }

    func receive(on connection: NWConnection) {
        connection.receive(minimumIncompleteLength: 0, maximumLength: 4096) { (content, context, isComplete, error) in
            guard let receivedData = content else {
                print("**** content is nil")
                return
            }
            let dataString = String(decoding: receivedData, as: UTF8.self)
            print("**** received data = \(dataString)")
            connection.cancel()
        }
    }
}

This solution is a modification of a Bonjour Listener demo app in a WWDC presentation.

As I said in my original question, this is for listening for infrequent HTTP packets on an internal, secure network. I wouldn't dare use it as a fully open web-server. For my purposes, the information in the incoming HTTP command is all I need: it contains the new state of the WiFi switch that is on the local network.

Regards, Michaela

MacOS HTTP Listener for a Swift App
 
 
Q