【问题标题】:Creating a web server in iOS application only for one page仅在 iOS 应用程序中为一页创建 Web 服务器
【发布时间】:2022-06-16 15:46:47
【问题描述】:

我必须在 Swift 或 Obj-C 中为 iOS 应用程序创建一个简单的 Web 服务器。

事实上,它必须是最简单的网络服务器,因为它只需要创建一个套接字或其他任何东西来监听网页请求。

然后它必须在请求到来时提供 HTML 字符串。

仅此而已,不需要来自真实网络服务器的其他功能。

它必须只响应单一类型的请求

localhost:port/page_number.html

或使用不同的别名,例如

alias/page_number.html

有可能吗?

我阅读了 Mac OS 的示例,源代码非常短,但我也发现了 iOS 的示例,源代码文件很多,而且它们一点都不简单。

Mac OS 示例利用了 Darwin 库。也许它非常强大,只需几条指令就可以构建一个简单的 Web 服务器。

来自Tiny http server engine written in Swift programming language

代码如下:

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

但我知道 iOS 也很先进,所以也许有一个非常紧凑的代码可以在 iOS 上创建 Web 服务器的最小功能。

【问题讨论】:

  • 这个问题的范围是什么(你打算提交到商店的iOS应用程序,或者企业应用程序,或者测试,或者一些应用程序间的通信)?这个“服务单页”服务器的最终目标是什么?例如,如果你只想显示一个页面,你可以简单地使用 WKWebView 或 Safari。
  • @thiscommunityistoxic 目的是在 Safari 中打开该页面,而不是 WKWebView。我知道这很奇怪。我会在苹果开发者论坛中询问是否允许,但首先我想知道它在技术上是否可行。 WKWebView 存在差异,需要对某些方面进行复杂的处理。
  • 在这种情况下可能只是导入 GCDWebServer (github.com/swisspol/GCDWebServer),或者将其用作原型,至少它是为 iOS 编写的,而不是为 Mac 编写的

标签: html ios swift sockets webserver


【解决方案1】:

这是一个非常基本且简化的 Swift 中的 http 服务器实现,如果您不想使用外部库,可能就足够了:

class PicoHttpServer {
    private static var serverSocket: Int32?
    private static let queue = DispatchQueue(label: "com.pico.http.server.queue")
    private static let semaphore = DispatchSemaphore(value: 1)
    static func httpOkResponse(html: String) -> String {
        return "HTTP/1.1 200 OK\r\nServer: PicoHttpServer\r\nContent-Length: \(html.count)\r\n\r\n\(html)"
    }
    static func start(port: UInt16 = 7000, address: UInt32 = INADDR_LOOPBACK, requestHandler: @escaping ((String) -> String)) {
        semaphore.wait()
        let started = serverSocket != nil
        semaphore.signal()
        if started {
            return
        }
        queue.async {
            realStart(port: port, address: address, requestHandler: requestHandler)
        }
    }
    static func stop() {
        semaphore.wait()
        if let serverSocker = serverSocket {
            close(serverSocker)
        }
        serverSocket = nil
        semaphore.signal()
    }
    private static func realStart(port: UInt16, address: UInt32, requestHandler: ((String) -> String)) {
        let tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
        if tcpSocket == -1 {
            return
        }
        serverSocket = tcpSocket
        var reuseOn = Int32(1)
        setsockopt(tcpSocket, SOL_SOCKET, SO_REUSEADDR, &reuseOn, socklen_t(MemoryLayout.size(ofValue: reuseOn)))
        var socketAddress = sockaddr_in()
        socketAddress.sin_family = sa_family_t(AF_INET)
        socketAddress.sin_port = port.bigEndian
        socketAddress.sin_addr = in_addr(s_addr: address.bigEndian)
        socketAddress.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)
        let socklen = UInt8(socklen_t(MemoryLayout<sockaddr_in>.size))
        let bindResult = withUnsafePointer(to: &socketAddress) { sockaddrInPtr -> Int32 in
            let sockaddrPtr = UnsafeRawPointer(sockaddrInPtr).assumingMemoryBound(to: sockaddr.self)
            return bind(tcpSocket, sockaddrPtr, socklen_t(socklen))
        }
        if bindResult == -1 {
            return
        }
        let listenResult = listen(tcpSocket, 5)
        if listenResult == -1 {
            return
        }
        print("Server started")
        while(true) {
            semaphore.wait()
            let stopped = serverSocket == nil
            semaphore.signal()
            if stopped {
                break
            }
            let mtu = 65536
            let client = accept(tcpSocket, nil, nil)
            if client == -1 {
                continue
            }
            var buffer = Data(repeating: 0, count: mtu)
            let readResult = buffer.withUnsafeMutableBytes { pointer in
                return read(client, pointer.baseAddress, mtu)
            }
            if readResult == -1 {
                continue
            }
            let clientData = buffer.subdata(in: 0..<readResult)
            let clientRequest = String(data: clientData, encoding: .utf8) ?? ""
            let response = requestHandler(clientRequest)
            response.withCString { bytes in
              write(client, bytes, Int(strlen(bytes)))
              close(client)
            }
        }
        print("Server stopped")
    }
}

然后你可以如下使用它来响应每个页码的不同html:

PicoHttpServer.start { request in
    if request.hasPrefix("GET /1.html") {
        return PicoHttpServer.httpOkResponse(html: "<html><body>Page 1</body></html>")
    } else if request.hasPrefix("GET /2.html") {
        return PicoHttpServer.httpOkResponse(html: "<html><body>Page 2</body></html>")
    } else {
        return PicoHttpServer.httpOkResponse(html: "<html><body>Other page</body></html>")
    }
}

请注意,如果您想在 Safari 中的应用程序中打开它,正如您在问题的评论中所写,那么您可能还需要使用 UIApplication.shared.beginBackgroundTask 创建一个后台任务,以便您的应用程序可以运行此服务器至少在你的 iPhone 应用程序进入后台的那一刻。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-08
    相关资源
    最近更新 更多