Skip to content

Commit 3d5b0ce

Browse files
committed
Test HTTPServer.swift fixes:
- Set SO_NOSIGPIPE on the client socket (Darwin) or use send() with flag MSG_NOSIGNAL (Linux). - listen() just after bind()ing. - Close the client socket after writing out the response. - When sending a response with a start delay dont use asyncAfter() with a semaphore, just use Thread.sleep(). - Fix parsing the HTTP request so that all of the header lines are read correctly instead of reading the last header line as the body. - When waiting for the server to become ready, wait for the semaphore with a timeout rather than an indefinite wait(). - Add setUp() to be run before each test class. - For test cases that check the respnse body, check the data is not nil before reading it. - Use 1 server for whole class clifetime. - Use shutdown() to interrupt accept() when shutting down the server.
1 parent 0215082 commit 3d5b0ce

File tree

1 file changed

+108
-58
lines changed

1 file changed

+108
-58
lines changed

TestFoundation/HTTPServer.swift

Lines changed: 108 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ extension UInt16 {
4848
}
4949

5050
class _TCPSocket {
51-
51+
52+
private let sendFlags: CInt
5253
private var listenSocket: Int32!
5354
private var socketAddress = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1)
5455
private var connectionSocket: Int32!
@@ -63,27 +64,37 @@ class _TCPSocket {
6364

6465
private func attempt(_ name: String, file: String = #file, line: UInt = #line, valid: (CInt) -> Bool, _ b: @autoclosure () -> CInt) throws -> CInt {
6566
let r = b()
66-
guard valid(r) else { throw ServerError(operation: name, errno: r, file: file, line: line) }
67+
guard valid(r) else {
68+
throw ServerError(operation: name, errno: errno, file: file, line: line)
69+
}
6770
return r
6871
}
6972

7073
public private(set) var port: UInt16
7174

7275
init(port: UInt16?) throws {
73-
#if os(Linux) && !os(Android)
76+
#if canImport(Darwin)
77+
sendFlags = 0
78+
#else
79+
sendFlags = CInt(MSG_NOSIGNAL)
80+
#endif
81+
82+
#if os(Linux) && !os(Android)
7483
let SOCKSTREAM = Int32(SOCK_STREAM.rawValue)
75-
#else
84+
#else
7685
let SOCKSTREAM = SOCK_STREAM
77-
#endif
86+
#endif
7887
self.port = port ?? 0
7988
listenSocket = try attempt("socket", valid: isNotNegative, socket(AF_INET, SOCKSTREAM, Int32(IPPROTO_TCP)))
80-
var on: Int = 1
81-
_ = try attempt("setsockopt", valid: isZero, setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<Int>.size)))
89+
var on: CInt = 1
90+
_ = try attempt("setsockopt", valid: isZero, setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<CInt>.size)))
91+
8292
let sa = createSockaddr(port)
8393
socketAddress.initialize(to: sa)
8494
try socketAddress.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size, {
8595
let addr = UnsafePointer<sockaddr>($0)
8696
_ = try attempt("bind", valid: isZero, bind(listenSocket, addr, socklen_t(MemoryLayout<sockaddr>.size)))
97+
_ = try attempt("listen", valid: isZero, listen(listenSocket, SOMAXCONN))
8798
})
8899

89100
var actualSA = sockaddr_in()
@@ -112,18 +123,21 @@ class _TCPSocket {
112123
}
113124

114125
func acceptConnection(notify: ServerSemaphore) throws {
115-
_ = try attempt("listen", valid: isZero, listen(listenSocket, SOMAXCONN))
116126
try socketAddress.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size, {
117127
let addr = UnsafeMutablePointer<sockaddr>($0)
118128
var sockLen = socklen_t(MemoryLayout<sockaddr>.size)
119-
notify.signal()
120129
connectionSocket = try attempt("accept", valid: isNotNegative, accept(listenSocket, addr, &sockLen))
130+
#if canImport(Dawin)
131+
// Disable SIGPIPEs when writing to closed sockets
132+
var on: CInt = 1
133+
_ = try attempt("setsockopt", valid: isZero, setsockopt(connectionSocket, SOL_SOCKET, SO_NOSIGPIPE, &on, socklen_t(MemoryLayout<CInt>.size)))
134+
#endif
121135
})
122136
}
123137

124138
func readData() throws -> String {
125139
var buffer = [UInt8](repeating: 0, count: 4096)
126-
_ = try attempt("read", valid: isNotNegative, CInt(read(connectionSocket, &buffer, 4096)))
140+
_ = try attempt("read", valid: isNotNegative, CInt(read(connectionSocket, &buffer, buffer.count)))
127141
return String(cString: &buffer)
128142
}
129143

@@ -137,33 +151,39 @@ class _TCPSocket {
137151

138152
func writeRawData(_ data: Data) throws {
139153
_ = try data.withUnsafeBytes { ptr in
140-
try attempt("write", valid: isNotNegative, CInt(write(connectionSocket, ptr, data.count)))
154+
try attempt("send", valid: isNotNegative, CInt(send(connectionSocket, ptr, data.count, sendFlags)))
141155
}
142156
}
143157

144158
func writeData(header: String, body: String, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
145-
var header = Array(header.utf8)
146-
_ = try attempt("write", valid: isNotNegative, CInt(write(connectionSocket, &header, header.count)))
147-
159+
var _header = Array(header.utf8)
160+
_ = try attempt("send", valid: isNotNegative, CInt(send(connectionSocket, &_header, _header.count, sendFlags)))
161+
148162
if let sendDelay = sendDelay, let bodyChunks = bodyChunks {
149163
let count = max(1, Int(Double(body.utf8.count) / Double(bodyChunks)))
150164
let texts = split(body, count)
151165

152166
for item in texts {
153167
sleep(UInt32(sendDelay))
154168
var bytes = Array(item.utf8)
155-
_ = try attempt("write", valid: isNotNegative, CInt(write(connectionSocket, &bytes, bytes.count)))
169+
_ = try attempt("send", valid: isNotNegative, CInt(send(connectionSocket, &bytes, bytes.count, sendFlags)))
156170
}
157171
} else {
158172
var bytes = Array(body.utf8)
159-
_ = try attempt("write", valid: isNotNegative, CInt(write(connectionSocket, &bytes, bytes.count)))
173+
_ = try attempt("send", valid: isNotNegative, CInt(send(connectionSocket, &bytes, bytes.count, sendFlags)))
160174
}
161175
}
162176

163-
func shutdown() {
177+
func closeClient() {
164178
if let connectionSocket = self.connectionSocket {
165179
close(connectionSocket)
180+
self.connectionSocket = nil
166181
}
182+
}
183+
184+
func shutdownListener() {
185+
closeClient()
186+
shutdown(listenSocket, CInt(SHUT_RDWR))
167187
close(listenSocket)
168188
}
169189
}
@@ -190,32 +210,23 @@ class _HTTPServer {
190210
}
191211

192212
public func stop() {
193-
socket.shutdown()
213+
socket.closeClient()
214+
socket.shutdownListener()
194215
}
195216

196217
public func request() throws -> _HTTPRequest {
197218
return try _HTTPRequest(request: socket.readData())
198219
}
199220

200221
public func respond(with response: _HTTPResponse, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
201-
let semaphore = DispatchSemaphore(value: 0)
202-
let deadlineTime: DispatchTime
203-
204-
if let startDelay = startDelay {
205-
deadlineTime = .now() + .seconds(Int(startDelay))
206-
} else {
207-
deadlineTime = .now()
222+
if let delay = startDelay {
223+
Thread.sleep(forTimeInterval: delay)
208224
}
209-
210-
DispatchQueue.global().asyncAfter(deadline: deadlineTime) {
211-
do {
212-
try self.socket.writeData(header: response.header, body: response.body, sendDelay: sendDelay, bodyChunks: bodyChunks)
213-
semaphore.signal()
214-
} catch { }
225+
do {
226+
try self.socket.writeData(header: response.header, body: response.body, sendDelay: sendDelay, bodyChunks: bodyChunks)
227+
} catch {
215228
}
216-
semaphore.wait()
217-
218-
}
229+
}
219230

220231
func respondWithBrokenResponses(uri: String) throws {
221232
let responseData: Data
@@ -293,11 +304,13 @@ struct _HTTPRequest {
293304
let headers: [String]
294305

295306
public init(request: String) {
296-
let lines = request.components(separatedBy: _HTTPUtils.CRLF2)[0].components(separatedBy: _HTTPUtils.CRLF)
297-
headers = Array(lines[0...lines.count-2])
298-
method = Method(rawValue: headers[0].components(separatedBy: " ")[0])!
299-
uri = headers[0].components(separatedBy: " ")[1]
300-
body = lines.last!
307+
let headerEnd = (request as NSString).range(of: _HTTPUtils.CRLF2)
308+
let header = (request as NSString).substring(to: headerEnd.location)
309+
headers = header.components(separatedBy: _HTTPUtils.CRLF)
310+
let action = headers[0]
311+
method = Method(rawValue: action.components(separatedBy: " ")[0])!
312+
uri = action.components(separatedBy: " ")[1]
313+
body = (request as NSString).substring(from: headerEnd.location + headerEnd.length)
301314
}
302315

303316
public func getCommaSeparatedHeaders() -> String {
@@ -357,11 +370,7 @@ public class TestURLSessionServer {
357370
self.sendDelay = sendDelay
358371
self.bodyChunks = bodyChunks
359372
}
360-
public func start(started: ServerSemaphore) throws {
361-
started.signal()
362-
try httpServer.listen(notify: started)
363-
}
364-
373+
365374
public func readAndRespond() throws {
366375
let req = try httpServer.request()
367376
if req.uri.hasPrefix("/LandOfTheLostCities/") {
@@ -408,6 +417,16 @@ public class TestURLSessionServer {
408417
return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)", body: text)
409418
}
410419

420+
if uri == "/requestCookies" {
421+
let text = request.getCommaSeparatedHeaders()
422+
return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)\r\nSet-Cookie: fr=anjd&232; Max-Age=7776000; path=/\r\nSet-Cookie: nm=sddf&232; Max-Age=7776000; path=/; domain=.swift.org; secure; httponly\r\n", body: text)
423+
}
424+
425+
if uri == "/setCookies" {
426+
let text = request.getCommaSeparatedHeaders()
427+
return _HTTPResponse(response: .OK, headers: "Content-Length: \(text.data(using: .utf8)!.count)", body: text)
428+
}
429+
411430
if uri == "/UnitedStates" {
412431
let value = capitals[String(uri.dropFirst())]!
413432
let text = request.getCommaSeparatedHeaders()
@@ -480,8 +499,8 @@ extension ServerError : CustomStringConvertible {
480499
public class ServerSemaphore {
481500
let dispatchSemaphore = DispatchSemaphore(value: 0)
482501

483-
public func wait() {
484-
dispatchSemaphore.wait()
502+
public func wait(timeout: DispatchTime) -> DispatchTimeoutResult {
503+
return dispatchSemaphore.wait(timeout: timeout)
485504
}
486505

487506
public func signal() {
@@ -493,6 +512,11 @@ class LoopbackServerTest : XCTestCase {
493512
private static let staticSyncQ = DispatchQueue(label: "org.swift.TestFoundation.HTTPServer.StaticSyncQ")
494513

495514
private static var _serverPort: Int = -1
515+
private static let serverReady = ServerSemaphore()
516+
private static var _serverActive = false
517+
private static var testServer: TestURLSessionServer? = nil
518+
519+
496520
static var serverPort: Int {
497521
get {
498522
return staticSyncQ.sync { _serverPort }
@@ -502,30 +526,56 @@ class LoopbackServerTest : XCTestCase {
502526
}
503527
}
504528

529+
static var serverActive: Bool {
530+
get { return staticSyncQ.sync { _serverActive } }
531+
set { staticSyncQ.sync { _serverActive = newValue }}
532+
}
533+
534+
static func terminateServer() {
535+
serverActive = false
536+
testServer?.stop()
537+
testServer = nil
538+
}
539+
505540
override class func setUp() {
506541
super.setUp()
507542
func runServer(with condition: ServerSemaphore, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
508-
while true {
509-
let test = try TestURLSessionServer(port: nil, startDelay: startDelay, sendDelay: sendDelay, bodyChunks: bodyChunks)
510-
serverPort = Int(test.port)
511-
try test.start(started: condition)
512-
try test.readAndRespond()
513-
serverPort = -2
514-
test.stop()
543+
let server = try TestURLSessionServer(port: nil, startDelay: startDelay, sendDelay: sendDelay, bodyChunks: bodyChunks)
544+
testServer = server
545+
serverPort = Int(server.port)
546+
serverReady.signal()
547+
serverActive = true
548+
549+
while serverActive {
550+
do {
551+
try server.httpServer.listen(notify: condition)
552+
try server.readAndRespond()
553+
server.httpServer.socket.closeClient()
554+
} catch {
555+
}
515556
}
557+
serverPort = -2
558+
516559
}
517560

518-
let serverReady = ServerSemaphore()
519561
globalDispatchQueue.async {
520562
do {
521563
try runServer(with: serverReady)
522-
523564
} catch {
524-
XCTAssertTrue(true)
525-
return
526565
}
527566
}
528567

529-
serverReady.wait()
568+
let timeout = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + 2_000_000_000)
569+
570+
while serverPort == -2 {
571+
guard serverReady.wait(timeout: timeout) == .success else {
572+
fatalError("Timedout waiting for server to be ready")
573+
}
574+
}
575+
}
576+
577+
override class func tearDown() {
578+
super.tearDown()
579+
terminateServer()
530580
}
531581
}

0 commit comments

Comments
 (0)