Parcourir la source

Merge pull request #222 from stansidel/feature/set_ip_on_start

Added an option to set the listening address
Damian Kołakowski il y a 9 ans
Parent
commit
0a56fec1db
2 fichiers modifiés avec 59 ajouts et 31 suppressions
  1. 34 23
      Sources/HttpServerIO.swift
  2. 25 8
      Sources/Socket+Server.swift

+ 34 - 23
Sources/HttpServerIO.swift

@@ -15,7 +15,7 @@ public protocol HttpServerIODelegate: class {
 public class HttpServerIO {
 
     public weak var delegate : HttpServerIODelegate?
-    
+
     private var socket = Socket(socketFileDescriptor: -1)
     private var sockets = Set<Socket>()
 
@@ -25,9 +25,9 @@ public class HttpServerIO {
         case stopping
         case stopped
     }
-    
+
     private var stateValue: Int32 = HttpServerIOState.stopped.rawValue
-    
+
     public private(set) var state: HttpServerIOState {
         get {
             return HttpServerIOState(rawValue: stateValue)!
@@ -41,29 +41,40 @@ public class HttpServerIO {
             #endif
         }
     }
-    
+
     public var operating: Bool { get { return self.state == .running } }
-    
+
+    /// String representation of the IPv4 address to receive requests from.
+    /// It's only used when the server is started with `forceIPv4` option set to true.
+    /// Otherwise, `listenAddressIPv6` will be used.
+    public var listenAddressIPv4: String?
+
+    /// String representation of the IPv6 address to receive requests from.
+    /// It's only used when the server is started with `forceIPv4` option set to false.
+    /// Otherwise, `listenAddressIPv4` will be used.
+    public var listenAddressIPv6: String?
+
     private let queue = DispatchQueue(label: "swifter.httpserverio.clientsockets")
-    
+
     public func port() throws -> Int {
         return Int(try socket.port())
     }
-    
+
     public func isIPv4() throws -> Bool {
         return try socket.isIPv4()
     }
-    
+
     deinit {
         stop()
     }
-    
+
     @available(macOS 10.10, *)
     public func start(_ port: in_port_t = 8080, forceIPv4: Bool = false, priority: DispatchQoS.QoSClass = DispatchQoS.QoSClass.background) throws {
         guard !self.operating else { return }
         stop()
         self.state = .starting
-        self.socket = try Socket.tcpSocketForListen(port, forceIPv4)
+        let address = forceIPv4 ? listenAddressIPv4 : listenAddressIPv6
+        self.socket = try Socket.tcpSocketForListen(port, forceIPv4, SOMAXCONN, address)
         DispatchQueue.global(qos: priority).async { [weak self] in
             guard let `self` = self else { return }
             guard self.operating else { return }
@@ -84,7 +95,7 @@ public class HttpServerIO {
         }
         self.state = .running
     }
-    
+
     public func stop() {
         guard self.operating else { return }
         self.state = .stopping
@@ -98,11 +109,11 @@ public class HttpServerIO {
         socket.close()
         self.state = .stopped
     }
-    
+
     public func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) {
         return ([:], { _ in HttpResponse.notFound })
     }
-    
+
     private func handleConnection(_ socket: Socket) {
         let parser = HttpParser()
         while self.operating, let request = try? parser.readHttpRequest(socket) {
@@ -129,7 +140,7 @@ public class HttpServerIO {
         }
         socket.close()
     }
-    
+
     private struct InnerWriteContext: HttpResponseBodyWriter {
         
         let socket: Socket
@@ -149,38 +160,38 @@ public class HttpServerIO {
         func write(_ data: NSData) throws {
             try socket.writeData(data)
         }
-        
+
         func write(_ data: Data) throws {
             try socket.writeData(data)
         }
     }
-    
+
     private func respond(_ socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool {
         guard self.operating else { return false }
 
         try socket.writeUTF8("HTTP/1.1 \(response.statusCode()) \(response.reasonPhrase())\r\n")
-        
+
         let content = response.content()
-        
+
         if content.length >= 0 {
             try socket.writeUTF8("Content-Length: \(content.length)\r\n")
         }
-        
+
         if keepAlive && content.length != -1 {
             try socket.writeUTF8("Connection: keep-alive\r\n")
         }
-        
+
         for (name, value) in response.headers() {
             try socket.writeUTF8("\(name): \(value)\r\n")
         }
-        
+
         try socket.writeUTF8("\r\n")
-    
+
         if let writeClosure = content.write {
             let context = InnerWriteContext(socket: socket)
             try writeClosure(context)
         }
-        
+
         return keepAlive && content.length != -1;
     }
 }

+ 25 - 8
Sources/Socket+Server.swift

@@ -8,19 +8,23 @@
 import Foundation
 
 extension Socket {
-    
-    public class func tcpSocketForListen(_ port: in_port_t, _ forceIPv4: Bool = false, _ maxPendingConnection: Int32 = SOMAXCONN) throws -> Socket {
-        
+
+    /// - Parameters:
+    ///   - listenAddress: String representation of the address the socket should accept
+    ///       connections from. It should be in IPv4 format if forceIPv4 == true,
+    ///       otherwise - in IPv6.
+    public class func tcpSocketForListen(_ port: in_port_t, _ forceIPv4: Bool = false, _ maxPendingConnection: Int32 = SOMAXCONN, _ listenAddress: String? = nil) throws -> Socket {
+
         #if os(Linux)
             let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0)
         #else
             let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0)
         #endif
-        
+
         if socketFileDescriptor == -1 {
             throw SocketError.socketCreationFailed(Errno.description())
         }
-        
+
         var value: Int32 = 1
         if setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size)) == -1 {
             let details = Errno.description()
@@ -28,7 +32,6 @@ extension Socket {
             throw SocketError.socketSettingReUseAddrFailed(details)
         }
         Socket.setNoSigPipe(socketFileDescriptor)
-        
 
         var bindResult: Int32 = -1
         if forceIPv4 {
@@ -46,6 +49,13 @@ extension Socket {
                 sin_addr: in_addr(s_addr: in_addr_t(0)),
                 sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
             #endif
+            if let address = listenAddress {
+              if address.withCString({ cstring in inet_pton(AF_INET, cstring, &addr.sin_addr) }) == 1 {
+                // print("\(address) is converted to \(addr.sin_addr).")
+              } else {
+                // print("\(address) is not converted.")
+              }
+            }
             bindResult = withUnsafePointer(to: &addr) {
                 bind(socketFileDescriptor, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
             }
@@ -66,17 +76,24 @@ extension Socket {
                 sin6_addr: in6addr_any,
                 sin6_scope_id: 0)
             #endif
+            if let address = listenAddress {
+              if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &addr.sin6_addr) }) == 1 {
+                //print("\(address) is converted to \(addr.sin6_addr).")
+              } else {
+                //print("\(address) is not converted.")
+              }
+            }
             bindResult = withUnsafePointer(to: &addr) {
                 bind(socketFileDescriptor, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in6>.size))
             }
         }
-        
+
         if bindResult == -1 {
             let details = Errno.description()
             Socket.close(socketFileDescriptor)
             throw SocketError.bindFailed(details)
         }
-        
+
         if listen(socketFileDescriptor, maxPendingConnection) == -1 {
             let details = Errno.description()
             Socket.close(socketFileDescriptor)