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