@@ -48,7 +48,8 @@ extension UInt16 {
48
48
}
49
49
50
50
class _TCPSocket {
51
-
51
+
52
+ private let sendFlags : CInt
52
53
private var listenSocket : Int32 !
53
54
private var socketAddress = UnsafeMutablePointer< sockaddr_in> . allocate( capacity: 1 )
54
55
private var connectionSocket : Int32 !
@@ -63,27 +64,37 @@ class _TCPSocket {
63
64
64
65
private func attempt( _ name: String , file: String = #file, line: UInt = #line, valid: ( CInt ) -> Bool , _ b: @autoclosure ( ) -> CInt ) throws -> CInt {
65
66
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
+ }
67
70
return r
68
71
}
69
72
70
73
public private( set) var port : UInt16
71
74
72
75
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)
74
83
let SOCKSTREAM = Int32 ( SOCK_STREAM . rawValue)
75
- #else
84
+ #else
76
85
let SOCKSTREAM = SOCK_STREAM
77
- #endif
86
+ #endif
78
87
self . port = port ?? 0
79
88
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
+
82
92
let sa = createSockaddr ( port)
83
93
socketAddress. initialize ( to: sa)
84
94
try socketAddress. withMemoryRebound ( to: sockaddr. self, capacity: MemoryLayout< sockaddr> . size, {
85
95
let addr = UnsafePointer < sockaddr > ( $0)
86
96
_ = try attempt ( " bind " , valid: isZero, bind ( listenSocket, addr, socklen_t ( MemoryLayout< sockaddr> . size) ) )
97
+ _ = try attempt ( " listen " , valid: isZero, listen ( listenSocket, SOMAXCONN) )
87
98
} )
88
99
89
100
var actualSA = sockaddr_in ( )
@@ -112,18 +123,21 @@ class _TCPSocket {
112
123
}
113
124
114
125
func acceptConnection( notify: ServerSemaphore ) throws {
115
- _ = try attempt ( " listen " , valid: isZero, listen ( listenSocket, SOMAXCONN) )
116
126
try socketAddress. withMemoryRebound ( to: sockaddr. self, capacity: MemoryLayout< sockaddr> . size, {
117
127
let addr = UnsafeMutablePointer < sockaddr > ( $0)
118
128
var sockLen = socklen_t ( MemoryLayout< sockaddr> . size)
119
- notify. signal ( )
120
129
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
121
135
} )
122
136
}
123
137
124
138
func readData( ) throws -> String {
125
139
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 ) ) )
127
141
return String ( cString: & buffer)
128
142
}
129
143
@@ -137,33 +151,39 @@ class _TCPSocket {
137
151
138
152
func writeRawData( _ data: Data ) throws {
139
153
_ = 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 ) ) )
141
155
}
142
156
}
143
157
144
158
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
+
148
162
if let sendDelay = sendDelay, let bodyChunks = bodyChunks {
149
163
let count = max ( 1 , Int ( Double ( body. utf8. count) / Double( bodyChunks) ) )
150
164
let texts = split ( body, count)
151
165
152
166
for item in texts {
153
167
sleep ( UInt32 ( sendDelay) )
154
168
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 ) ) )
156
170
}
157
171
} else {
158
172
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 ) ) )
160
174
}
161
175
}
162
176
163
- func shutdown ( ) {
177
+ func closeClient ( ) {
164
178
if let connectionSocket = self . connectionSocket {
165
179
close ( connectionSocket)
180
+ self . connectionSocket = nil
166
181
}
182
+ }
183
+
184
+ func shutdownListener( ) {
185
+ closeClient ( )
186
+ shutdown ( listenSocket, CInt ( SHUT_RDWR) )
167
187
close ( listenSocket)
168
188
}
169
189
}
@@ -190,32 +210,23 @@ class _HTTPServer {
190
210
}
191
211
192
212
public func stop( ) {
193
- socket. shutdown ( )
213
+ socket. closeClient ( )
214
+ socket. shutdownListener ( )
194
215
}
195
216
196
217
public func request( ) throws -> _HTTPRequest {
197
218
return try _HTTPRequest ( request: socket. readData ( ) )
198
219
}
199
220
200
221
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)
208
224
}
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 {
215
228
}
216
- semaphore. wait ( )
217
-
218
- }
229
+ }
219
230
220
231
func respondWithBrokenResponses( uri: String ) throws {
221
232
let responseData : Data
@@ -293,11 +304,13 @@ struct _HTTPRequest {
293
304
let headers : [ String ]
294
305
295
306
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)
301
314
}
302
315
303
316
public func getCommaSeparatedHeaders( ) -> String {
@@ -357,11 +370,7 @@ public class TestURLSessionServer {
357
370
self . sendDelay = sendDelay
358
371
self . bodyChunks = bodyChunks
359
372
}
360
- public func start( started: ServerSemaphore ) throws {
361
- started. signal ( )
362
- try httpServer. listen ( notify: started)
363
- }
364
-
373
+
365
374
public func readAndRespond( ) throws {
366
375
let req = try httpServer. request ( )
367
376
if req. uri. hasPrefix ( " /LandOfTheLostCities/ " ) {
@@ -408,6 +417,16 @@ public class TestURLSessionServer {
408
417
return _HTTPResponse ( response: . OK, headers: " Content-Length: \( text. data ( using: . utf8) !. count) " , body: text)
409
418
}
410
419
420
+ if uri == " /requestCookies " {
421
+ let text = request. getCommaSeparatedHeaders ( )
422
+ return _HTTPResponse ( response: . OK, headers: " Content-Length: \( text. data ( using: . utf8) !. count) \r \n Set-Cookie: fr=anjd&232; Max-Age=7776000; path=/ \r \n Set-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
+
411
430
if uri == " /UnitedStates " {
412
431
let value = capitals [ String ( uri. dropFirst ( ) ) ] !
413
432
let text = request. getCommaSeparatedHeaders ( )
@@ -480,8 +499,8 @@ extension ServerError : CustomStringConvertible {
480
499
public class ServerSemaphore {
481
500
let dispatchSemaphore = DispatchSemaphore ( value: 0 )
482
501
483
- public func wait( ) {
484
- dispatchSemaphore. wait ( )
502
+ public func wait( timeout : DispatchTime ) -> DispatchTimeoutResult {
503
+ return dispatchSemaphore. wait ( timeout : timeout )
485
504
}
486
505
487
506
public func signal( ) {
@@ -493,6 +512,11 @@ class LoopbackServerTest : XCTestCase {
493
512
private static let staticSyncQ = DispatchQueue ( label: " org.swift.TestFoundation.HTTPServer.StaticSyncQ " )
494
513
495
514
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
+
496
520
static var serverPort : Int {
497
521
get {
498
522
return staticSyncQ. sync { _serverPort }
@@ -502,30 +526,56 @@ class LoopbackServerTest : XCTestCase {
502
526
}
503
527
}
504
528
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
+
505
540
override class func setUp( ) {
506
541
super. setUp ( )
507
542
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
+ }
515
556
}
557
+ serverPort = - 2
558
+
516
559
}
517
560
518
- let serverReady = ServerSemaphore ( )
519
561
globalDispatchQueue. async {
520
562
do {
521
563
try runServer ( with: serverReady)
522
-
523
564
} catch {
524
- XCTAssertTrue ( true )
525
- return
526
565
}
527
566
}
528
567
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 ( )
530
580
}
531
581
}
0 commit comments