我必须用Swift或Obj-C为iOS应用程序创建一个简单的web服务器。
事实上,它必须是有史以来最简单的web服务器,因为它只需要创建一个套接字或其他什么来侦听网页请求。
然后,当请求到来时,它必须提供HTML字符串。
仅此而已,不需要真正的网络服务器的其他功能。
它只需响应单一类型的请求
localhost:port/page_number.html
或者使用不同的别名,如
alias/page_number.html
有可能吗?
我读过一个Mac OS的例子,源代码很短,但我也发现iOS的例子有很多源代码文件,它们一点也不简单。
Mac操作系统的例子利用了Darwin库。也许它功能强大,只需几条指令就可以创建一个简单的web服务器。
它来自用Swift编程语言编写的Tinyhttp服务器引擎
这是代码:
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服务器的最低功能。
如果您不想使用外部库,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 OKrnServer: PicoHttpServerrnContent-Length: (html.count)rnrn(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应用程序进入后台时至少运行一段时间。