在 iOS 应用程序中仅为一个页面创建一个 Web 服务器

发布于 2025-01-13 22:04:45 字数 2079 浏览 4 评论 0原文

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

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

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

仅此而已,不需要真正的 Web 服务器的其他功能。

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

localhost:port/page_number.html

或使用不同的别名,例如

alias/page_number.html

是否可能?

我读了一个Mac OS的例子,源代码很短,但我也找到了iOS的例子,源代码文件很多,而且一点也不简单。

Mac OS 示例利用 Darwin 库。也许它是如此强大,以至于只需几条指令就可以创建一个简单的 Web 服务器。

它来自 用 Swift 编程编写的小型 http 服务器引擎语言

这是代码:

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 服务器最小功能。

I have to create a simple web server in Swift or Obj-C for an iOS application.

In fact it has to be the simplest web server ever because it just has to create a socket or whatever to listen to a web page request.

Then it has to provide the HTML string when the request comes.

That's all, no other feature from a real web server needed.

It has to respond only to a single type of requests

localhost:port/page_number.html

or with different alias, like

alias/page_number.html

Is it possible?

I read an example for Mac OS, with very short source code, but I also found examples for iOS that have many source code files and they are not simple at all.

The Mac OS example leverages Darwin library. Maybe it is so powerful that a simple web server is possible with a few instructions.

It is from Tiny http server engine written in Swift programming language

Here is the code:

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

But I know that iOS is advanced too, so maybe there is a very compact code to create that web server minimal capability on iOS.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

秋千易 2025-01-20 22:04:45

这是一个非常基本且简化的 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 应用程序进入后台。

Here is a very basic and simplified http server implementation in Swift that might be enough for your case if you do not want to use external libraries:

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")
    }
}

Then you can use it as below to response with different html for each page number:

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>")
    }
}

Note that if you want to open this from your app in Safari as you wrote in the comment to the question then you will probably also need to create a background task with UIApplication.shared.beginBackgroundTask so that your app can run this server at least for a moment when your iPhone app goes to background.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文