@@ -92,11 +92,87 @@ internal final class RecordingHandler<Input, Output>: ChannelDuplexHandler {
92
92
}
93
93
}
94
94
95
+ enum TemporaryFileHelpers {
96
+ private static var temporaryDirectory : String {
97
+ #if targetEnvironment(simulator)
98
+ // Simulator temp directories are so long (and contain the user name) that they're not usable
99
+ // for UNIX Domain Socket paths (which are limited to 103 bytes).
100
+ return " /tmp "
101
+ #else
102
+ #if os(Android)
103
+ return " /data/local/tmp "
104
+ #elseif os(Linux)
105
+ return " /tmp "
106
+ #else
107
+ if #available( macOS 10 . 12 , iOS 10 , tvOS 10 , watchOS 3 , * ) {
108
+ return FileManager . default. temporaryDirectory. path
109
+ } else {
110
+ return " /tmp "
111
+ }
112
+ #endif // os
113
+ #endif // targetEnvironment
114
+ }
115
+
116
+ private static func openTemporaryFile( ) -> ( CInt , String ) {
117
+ let template = " \( temporaryDirectory) /ahc_XXXXXX "
118
+ var templateBytes = template. utf8 + [ 0 ]
119
+ let templateBytesCount = templateBytes. count
120
+ let fd = templateBytes. withUnsafeMutableBufferPointer { ptr in
121
+ ptr. baseAddress!. withMemoryRebound ( to: Int8 . self, capacity: templateBytesCount) { ptr in
122
+ mkstemp ( ptr)
123
+ }
124
+ }
125
+ templateBytes. removeLast ( )
126
+ return ( fd, String ( decoding: templateBytes, as: Unicode . UTF8. self) )
127
+ }
128
+
129
+ /// This function creates a filename that can be used for a temporary UNIX domain socket path.
130
+ ///
131
+ /// If the temporary directory is too long to store a UNIX domain socket path, it will `chdir` into the temporary
132
+ /// directory and return a short-enough path. The iOS simulator is known to have too long paths.
133
+ internal static func withTemporaryUnixDomainSocketPathName< T> ( directory: String = temporaryDirectory,
134
+ _ body: ( String ) throws -> T ) throws -> T {
135
+ // this is racy but we're trying to create the shortest possible path so we can't add a directory...
136
+ let ( fd, path) = self . openTemporaryFile ( )
137
+ close ( fd)
138
+ try ! FileManager . default. removeItem ( atPath: path)
139
+
140
+ let saveCurrentDirectory = FileManager . default. currentDirectoryPath
141
+ let restoreSavedCWD : Bool
142
+ let shortEnoughPath : String
143
+ do {
144
+ _ = try SocketAddress ( unixDomainSocketPath: path)
145
+ // this seems to be short enough for a UDS
146
+ shortEnoughPath = path
147
+ restoreSavedCWD = false
148
+ } catch SocketAddressError . unixDomainSocketPathTooLong {
149
+ FileManager . default. changeCurrentDirectoryPath ( URL ( fileURLWithPath: path) . deletingLastPathComponent ( ) . absoluteString)
150
+ shortEnoughPath = URL ( fileURLWithPath: path) . lastPathComponent
151
+ restoreSavedCWD = true
152
+ print ( " WARNING: Path ' \( path) ' could not be used as UNIX domain socket path, using chdir & ' \( shortEnoughPath) ' " )
153
+ }
154
+ defer {
155
+ if FileManager . default. fileExists ( atPath: path) {
156
+ try ? FileManager . default. removeItem ( atPath: path)
157
+ }
158
+ if restoreSavedCWD {
159
+ FileManager . default. changeCurrentDirectoryPath ( saveCurrentDirectory)
160
+ }
161
+ }
162
+ return try body ( shortEnoughPath)
163
+ }
164
+ }
165
+
95
166
internal final class HTTPBin {
96
167
let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
97
168
let serverChannel : Channel
98
169
let isShutdown : NIOAtomic < Bool > = . makeAtomic( value: false )
99
170
171
+ enum BindTarget {
172
+ case unixDomainSocket( String )
173
+ case localhostIPv4RandomPort
174
+ }
175
+
100
176
var port : Int {
101
177
return Int ( self . serverChannel. localAddress!. port!)
102
178
}
@@ -112,7 +188,18 @@ internal final class HTTPBin {
112
188
return channel. pipeline. addHandler ( try ! NIOSSLServerHandler ( context: context) , position: . first)
113
189
}
114
190
115
- init ( ssl: Bool = false , compress: Bool = false , simulateProxy: HTTPProxySimulator . Option ? = nil , channelPromise: EventLoopPromise < Channel > ? = nil ) {
191
+ init ( ssl: Bool = false ,
192
+ compress: Bool = false ,
193
+ bindTarget: BindTarget = . localhostIPv4RandomPort,
194
+ simulateProxy: HTTPProxySimulator . Option ? = nil ,
195
+ channelPromise: EventLoopPromise < Channel > ? = nil ) {
196
+ let socketAddress : SocketAddress
197
+ switch bindTarget {
198
+ case . localhostIPv4RandomPort:
199
+ socketAddress = try ! SocketAddress ( ipAddress: " 127.0.0.1 " , port: 0 )
200
+ case . unixDomainSocket( let path) :
201
+ socketAddress = try ! SocketAddress ( unixDomainSocketPath: path)
202
+ }
116
203
self . serverChannel = try ! ServerBootstrap ( group: self . group)
117
204
. serverChannelOption ( ChannelOptions . socket ( SocketOptionLevel ( SOL_SOCKET) , SO_REUSEADDR) , value: 1 )
118
205
. childChannelOption ( ChannelOptions . socket ( IPPROTO_TCP, TCP_NODELAY) , value: 1 )
@@ -145,7 +232,7 @@ internal final class HTTPBin {
145
232
}
146
233
}
147
234
}
148
- . bind ( host : " 127.0.0.1 " , port : 0 ) . wait ( )
235
+ . bind ( to : socketAddress ) . wait ( )
149
236
}
150
237
151
238
func shutdown( ) throws {
@@ -250,6 +337,16 @@ internal final class HttpBinHandler: ChannelInboundHandler {
250
337
case . head( let req) :
251
338
let url = URL ( string: req. uri) !
252
339
switch url. path {
340
+ case " / " :
341
+ var headers = HTTPHeaders ( )
342
+ headers. add ( name: " X-Is-This-Slash " , value: " Yes " )
343
+ self . resps. append ( HTTPResponseBuilder ( status: . ok, headers: headers) )
344
+ return
345
+ case " /echo-uri " :
346
+ var headers = HTTPHeaders ( )
347
+ headers. add ( name: " X-Calling-URI " , value: req. uri)
348
+ self . resps. append ( HTTPResponseBuilder ( status: . ok, headers: headers) )
349
+ return
253
350
case " /ok " :
254
351
self . resps. append ( HTTPResponseBuilder ( status: . ok) )
255
352
return
0 commit comments