From d4ef12e217d4439a8a5bf30c58506441fa1684c5 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Wed, 3 Jul 2019 12:48:21 +0530 Subject: [PATCH 01/18] Track size of HTTP request --- Sources/KituraNet/HTTP/HTTPRequestHandler.swift | 7 +++++++ Sources/KituraNet/HTTP/RequestsizeHandler.swift | 1 + 2 files changed, 8 insertions(+) create mode 100644 Sources/KituraNet/HTTP/RequestsizeHandler.swift diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index b1317840..7988bae6 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -22,6 +22,8 @@ import Foundation import Dispatch internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandler { + var requestSize: Int = 0 + //var byteCount: Int = 0 /// The HTTPServer instance on which this handler is installed var server: HTTPServer @@ -82,6 +84,9 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle serverRequest = HTTPServerRequest(channel: context.channel, requestHead: header, enableSSL: enableSSLVerification) self.clientRequestedKeepAlive = header.isKeepAlive case .body(var buffer): + requestSize += buffer.readableBytes + print("request size is : \(requestSize)") + guard let serverRequest = serverRequest else { Log.error("No ServerRequest available") return @@ -91,7 +96,9 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle } else { serverRequest.buffer!.byteBuffer.writeBuffer(&buffer) } + case .end: + requestSize = 0 serverResponse = HTTPServerResponse(channel: context.channel, handler: self) //Make sure we use the latest delegate registered with the server DispatchQueue.global().async { diff --git a/Sources/KituraNet/HTTP/RequestsizeHandler.swift b/Sources/KituraNet/HTTP/RequestsizeHandler.swift new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Sources/KituraNet/HTTP/RequestsizeHandler.swift @@ -0,0 +1 @@ + From 2b9edd998b8bafa8272c201a2ff92435ab61df25 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Mon, 22 Jul 2019 14:04:19 +0530 Subject: [PATCH 02/18] Ckeck maximum request size and connection limit --- ConnectionLimitTests.swift | 35 ++++++++++++ HTTPResponseHandler.swift | 22 ++++++++ Sources/KituraNet/HTTP/HTTP.swift | 5 +- .../KituraNet/HTTP/HTTPRequestHandler.swift | 53 +++++++++++++++++-- Sources/KituraNet/HTTP/HTTPServer.swift | 21 ++++++-- .../HTTP/HTTPServerConfiguration.swift | 44 +++++++++++++++ Tests/KituraNetTests/ClientE2ETests.swift | 28 ++++++++++ .../KituraNetTests/HTTPResponseHandler.swift | 21 ++++++++ Tests/KituraNetTests/KituraNIOTest.swift | 29 +++++----- Tests/KituraNetTests/LargePayloadTests.swift | 4 +- 10 files changed, 240 insertions(+), 22 deletions(-) create mode 100644 ConnectionLimitTests.swift create mode 100644 HTTPResponseHandler.swift create mode 100644 Sources/KituraNet/HTTP/HTTPServerConfiguration.swift create mode 100644 Tests/KituraNetTests/HTTPResponseHandler.swift diff --git a/ConnectionLimitTests.swift b/ConnectionLimitTests.swift new file mode 100644 index 00000000..82d3bb2d --- /dev/null +++ b/ConnectionLimitTests.swift @@ -0,0 +1,35 @@ +import Foundation +import Dispatch +import NIO +import XCTest +import KituraNet + +class ConnectionLimitTests: KituraNetTest { + static var allTests: [(String, (ConnectionLimitTests) -> () throws -> Void)] { + return [ + ("testConnectionLimit", testConnectionLimit), + ] + } + + override func setUp() { + doSetUp() + } + + override func tearDown() { + doTearDown() + } + func testConnectionLimit () { + performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 100), nil, useSSL: false, asyncTasks: { expectation in + var channel: Channel + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let bootstrap = ClientBootstrap(group: group) + .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + .channelInitializer { channel in + channel.pipeline.addHTTPClientHandlers().flatMap {_ in + channel.pipeline.addHandler(HTTPResponseHandler()) + } + } + channel = bootstrap.connect(host: "localhost", port: 8080) as! Channel + }) + } +} diff --git a/HTTPResponseHandler.swift b/HTTPResponseHandler.swift new file mode 100644 index 00000000..4eec6fc9 --- /dev/null +++ b/HTTPResponseHandler.swift @@ -0,0 +1,22 @@ +import NIO +import NIOHTTP1 +import NIOWebSocket +import LoggerAPI +import Foundation +import Dispatch + +internal class HTTPResponseHandler: ChannelInboundHandler { + /// The ClientResponse object for the response + internal var statusCode: HTTPResponseStatus = .ok + typealias InboundIn = HTTPServerResponsePart + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { + let response = self.unwrapInboundIn(data) + switch response { + case .head(let header): + statusCode = header.status + default: + break + } + } +} + diff --git a/Sources/KituraNet/HTTP/HTTP.swift b/Sources/KituraNet/HTTP/HTTP.swift index 97c8e5a0..0d3d6b04 100644 --- a/Sources/KituraNet/HTTP/HTTP.swift +++ b/Sources/KituraNet/HTTP/HTTP.swift @@ -87,8 +87,9 @@ public class HTTP { let server = HTTP.createServer() ```` */ - public static func createServer() -> HTTPServer { - return HTTPServer() + public static func createServer(serverConfig: HTTPServerConfiguration = .default) -> HTTPServer { + let serverConfig = serverConfig + return HTTPServer(serverConfig: serverConfig) } /** diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 7988bae6..9f7b21d3 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -22,6 +22,10 @@ import Foundation import Dispatch internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandler { + //default request size limit + + let requestSizeLimit: Int + var requestSize: Int = 0 //var byteCount: Int = 0 @@ -63,12 +67,12 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle public init(for server: HTTPServer) { self.server = server + self.requestSizeLimit = server.serverConfig.requestSizeLimit self.keepAliveState = server.keepAliveState if server.sslConfig != nil { self.enableSSLVerification = true } } - public typealias InboundIn = HTTPServerRequestPart public typealias OutboundOut = HTTPServerResponsePart @@ -78,15 +82,29 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle // If an upgrade to WebSocket fails, both `errorCaught` and `channelRead` are triggered. // We'd want to return the error via `errorCaught`. if errorResponseSent { return } - switch request { case .head(let header): +<<<<<<< HEAD serverRequest = HTTPServerRequest(channel: context.channel, requestHead: header, enableSSL: enableSSLVerification) +======= + if let contentLength = header.headers["Content-Length"].first, + let contentLengthValue = Int(contentLength) { + if contentLengthValue > requestSizeLimit { + sendStatus(context: context) + } + } + let headerSize = getHeaderSize(of: header) + if headerSize > requestSizeLimit { + sendStatus(context: context) + } + serverRequest = HTTPServerRequest(ctx: context, requestHead: header, enableSSL: enableSSLVerification) +>>>>>>> Ckeck maximum request size and connection limit self.clientRequestedKeepAlive = header.isKeepAlive case .body(var buffer): requestSize += buffer.readableBytes - print("request size is : \(requestSize)") - + if requestSize > requestSizeLimit { + sendStatus(context: context) + } guard let serverRequest = serverRequest else { Log.error("No ServerRequest available") return @@ -159,4 +177,31 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle func updateKeepAliveState() { keepAliveState.decrement() } + + func channelInactive(context: ChannelHandlerContext, httpServer: HTTPServer) { + httpServer.connectionCount = httpServer.connectionCount - 1 + } + + func getHeaderSize(of header: HTTPRequestHead) -> Int { + var headerSize = 0 + headerSize += header.uri.cString(using: .utf8)?.count ?? 0 + headerSize += header.version.description.cString(using: .utf8)?.count ?? 0 + headerSize += header.method.rawValue.cString(using: .utf8)?.count ?? 0 + for headers in header.headers { + headerSize += headers.name.cString(using: .utf8)?.count ?? 0 + headerSize += headers.value.cString(using: .utf8)?.count ?? 0 + } + return headerSize + } + + func sendStatus(context: ChannelHandlerContext) { + let statusDescription = HTTP.statusCodes[HTTPStatusCode.requestTooLong.rawValue] ?? "" + do { + serverResponse = HTTPServerResponse(channel: context.channel, handler: self) + errorResponseSent = true + try serverResponse?.end(with: .requestTooLong, message: statusDescription) + } catch { + Log.error("Failed to send error response") + } + } } diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index b919b96a..0007df50 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -127,22 +127,36 @@ public class HTTPServer: Server { var quiescingHelper: ServerQuiescingHelper? + private var ctx: ChannelHandlerContext? + + /// server configuration + public var serverConfig: HTTPServerConfiguration + + //counter for no of connections + public var connectionCount = 0 + + // The data to be written as a part of the response. + private var buffer: ByteBuffer + /** Creates an HTTP server object. ### Usage Example: ### ````swift - let server = HTTPServer() + let config =HTTPServerConfiguration(requestSize: 1000, coonectionLimit: 100) + let server = HTTPServer(serverconfig: config) server.listen(on: 8080) ```` */ - public init() { + public init(serverConfig: HTTPServerConfiguration = .default) { #if os(Linux) let numberOfCores = Int(linux_sched_getaffinity()) self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfCores > 0 ? numberOfCores : System.coreCount) #else self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) #endif + self.serverConfig = serverConfig + self.buffer = ((serverChannel?.allocator.buffer(capacity: 1024))!) } /** @@ -309,7 +323,8 @@ public class HTTPServer: Server { } .childChannelInitializer { channel in let httpHandler = HTTPRequestHandler(for: self) - let config: NIOHTTPServerUpgradeConfiguration = (upgraders: upgraders, completionHandler: { _ in + let config: HTTPUpgradeConfiguration = (upgraders: upgraders, completionHandler: { ctx in + self.ctx = ctx _ = channel.pipeline.removeHandler(httpHandler) }) return channel.pipeline.configureHTTPServerPipeline(withServerUpgrade: config, withErrorHandling: true).flatMap { diff --git a/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift b/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift new file mode 100644 index 00000000..eb1ae995 --- /dev/null +++ b/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift @@ -0,0 +1,44 @@ +/* + * Copyright IBM Corporation 2019 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public struct HTTPServerConfiguration { + /// Defines the maximum size of an incoming request, in bytes. If requests are received that are larger + /// than this limit, they will be rejected and the connection will be closed. + /// + /// A value of `nil` means no limit. + public let requestSizeLimit: Int + + /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting + /// to connect when this limit has been reached will be rejected. + public let connectionLimit: Int + + public static var `default` = HTTPServerConfiguration(requestSizeLimit: 1024 * 1024, connectionLimit: 1024) + + + /// Create an `HTTPServerConfiguration` to determine the behaviour of a `Server`. + /// + /// - parameter requestSizeLimit: The maximum size of an incoming request. Defaults to `IncomingSocketOptions.defaultRequestSizeLimit`. + /// - parameter connectionLimit: The maximum number of concurrent connections. Defaults to `IncomingSocketOptions.defaultConnectionLimit`. + + public init(requestSizeLimit: Int,connectionLimit: Int) + { + self.requestSizeLimit = requestSizeLimit + self.connectionLimit = connectionLimit + } + +} diff --git a/Tests/KituraNetTests/ClientE2ETests.swift b/Tests/KituraNetTests/ClientE2ETests.swift index 87800b6e..677a2153 100644 --- a/Tests/KituraNetTests/ClientE2ETests.swift +++ b/Tests/KituraNetTests/ClientE2ETests.swift @@ -37,6 +37,7 @@ class ClientE2ETests: KituraNetTest { ("testQueryParameters", testQueryParameters), ("testRedirect", testRedirect), ("testPercentEncodedQuery", testPercentEncodedQuery), + ("testRequestSize",testRequestSize), ] } @@ -52,6 +53,33 @@ class ClientE2ETests: KituraNetTest { let delegate = TestServerDelegate() + func testRequestSize() { + performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 100),delegate, useSSL: false, asyncTasks: { expectation in + let payload = "[" + contentTypesString + "," + contentTypesString + contentTypesString + "," + contentTypesString + "]" + self.performRequest("post", path: "/largepost", callback: {response in + XCTAssertEqual(response?.statusCode, HTTPStatusCode.requestTooLong) + do { + let expectedResult = "Request Entity Too Large" + var data = Data() + let count = try response?.readAllData(into: &data) + XCTAssertEqual(count, expectedResult.count, "Result should have been \(expectedResult.count) bytes, was \(String(describing: count)) bytes") + let postValue = String(data: data, encoding: .utf8) + if let postValue = postValue { + print("postvalue:", postValue) + XCTAssertEqual(postValue, expectedResult) + } else { + XCTFail("postValue's value wasn't an UTF8 string") + } + } catch { + XCTFail("Failed reading the body of the response") + } + expectation.fulfill() + }) {request in + request.write(from: payload) + } + }) + } + func testHeadRequests() { performServerTest(delegate) { expectation in self.performRequest("head", path: "/headtest", callback: {response in diff --git a/Tests/KituraNetTests/HTTPResponseHandler.swift b/Tests/KituraNetTests/HTTPResponseHandler.swift new file mode 100644 index 00000000..fe1fe75a --- /dev/null +++ b/Tests/KituraNetTests/HTTPResponseHandler.swift @@ -0,0 +1,21 @@ +import NIO +import NIOHTTP1 +import NIOWebSocket +import LoggerAPI +import Foundation +import Dispatch + +internal class HTTPResponseHandler: ChannelInboundHandler { + /// The ClientResponse object for the response + internal var statusCode: HTTPResponseStatus = .ok + typealias InboundIn = HTTPServerResponsePart + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { + let response = self.unwrapInboundIn(data) + switch response { + case .head(let header): + statusCode = header.status + default: + break + } + } +} diff --git a/Tests/KituraNetTests/KituraNIOTest.swift b/Tests/KituraNetTests/KituraNIOTest.swift index 542935ea..67c523cf 100644 --- a/Tests/KituraNetTests/KituraNIOTest.swift +++ b/Tests/KituraNetTests/KituraNIOTest.swift @@ -85,9 +85,9 @@ class KituraNetTest: XCTestCase { } } - func startServer(_ delegate: ServerDelegate?, unixDomainSocketPath: String? = nil, port: Int = portDefault, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault) throws -> HTTPServer { - - let server = HTTP.createServer() + func startServer(_ delegate: ServerDelegate?, unixDomainSocketPath: String? = nil, port: Int = portDefault, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: HTTPServerConfiguration = .default) throws -> HTTPServer { + let serverConfig = serverConfig + let server = HTTP.createServer(serverConfig: serverConfig) server.delegate = delegate if useSSL { server.sslConfig = KituraNetTest.sslConfig @@ -103,8 +103,9 @@ class KituraNetTest: XCTestCase { /// Convenience function for starting an HTTPServer on an ephemeral port, /// returning the a tuple containing the server and the port it is listening on. - func startEphemeralServer(_ delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault) throws -> (server: HTTPServer, port: Int) { - let server = try startServer(delegate, port: 0, useSSL: useSSL, allowPortReuse: allowPortReuse) + func startEphemeralServer(_ delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: HTTPServerConfiguration = .default) throws -> (server: HTTPServer, port: Int) { + let serverConfig = serverConfig + let server = try startServer(delegate, port: 0, useSSL: useSSL,allowPortReuse: allowPortReuse, serverConfig: serverConfig) guard let serverPort = server.port else { throw KituraNetTestError(message: "Server port was not initialized") } @@ -121,22 +122,25 @@ class KituraNetTest: XCTestCase { case both } - func performServerTest(_ delegate: ServerDelegate?, socketType: SocketType = .both, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: (XCTestExpectation) -> Void...) { + func performServerTest(serverConfig: HTTPServerConfiguration = .default, _ delegate: ServerDelegate?, socketType: SocketType = .both, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: (XCTestExpectation) -> Void...) { + let serverConfig = serverConfig self.socketType = socketType if socketType != .tcp { - performServerTestWithUnixSocket(delegate: delegate, useSSL: useSSL, allowPortReuse: allowPortReuse, line: line, asyncTasks: asyncTasks) + performServerTestWithUnixSocket(serverConfig: serverConfig, delegate: delegate, useSSL: useSSL, allowPortReuse: allowPortReuse, line: line, asyncTasks: asyncTasks) } if socketType != .unixDomainSocket { - performServerTestWithTCPPort(delegate: delegate, useSSL: useSSL, allowPortReuse: allowPortReuse, line: line, asyncTasks: asyncTasks) + performServerTestWithTCPPort(serverConfig: serverConfig ,delegate: delegate, useSSL: useSSL, allowPortReuse: allowPortReuse, line: line, asyncTasks: asyncTasks) } } - func performServerTestWithUnixSocket(delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { + func performServerTestWithUnixSocket(serverConfig: HTTPServerConfiguration = .default, delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { do { + var serverConfig = serverConfig + print("serverconfig: ", serverConfig) var server: HTTPServer self.useSSL = useSSL self.unixDomainSocketPath = self.socketFilePath - server = try startServer(delegate, unixDomainSocketPath: self.unixDomainSocketPath, useSSL: useSSL, allowPortReuse: allowPortReuse) + server = try startServer(delegate, unixDomainSocketPath: self.unixDomainSocketPath, useSSL: useSSL, allowPortReuse: allowPortReuse, serverConfig: serverConfig) defer { server.stop() } @@ -158,12 +162,13 @@ class KituraNetTest: XCTestCase { } } - func performServerTestWithTCPPort(delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { + func performServerTestWithTCPPort(serverConfig: HTTPServerConfiguration = .default, delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { do { + var serverConfig = serverConfig var server: HTTPServer var ephemeralPort: Int = 0 self.useSSL = useSSL - (server, ephemeralPort) = try startEphemeralServer(delegate, useSSL: useSSL, allowPortReuse: allowPortReuse) + (server, ephemeralPort) = try startEphemeralServer(delegate, useSSL: useSSL, allowPortReuse: allowPortReuse,serverConfig: serverConfig) self.port = ephemeralPort self.unixDomainSocketPath = nil defer { diff --git a/Tests/KituraNetTests/LargePayloadTests.swift b/Tests/KituraNetTests/LargePayloadTests.swift index a7e4cef4..11900b8f 100644 --- a/Tests/KituraNetTests/LargePayloadTests.swift +++ b/Tests/KituraNetTests/LargePayloadTests.swift @@ -68,7 +68,7 @@ class LargePayloadTests: KituraNetTest { func testLargeGets() { performServerTest(delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in // This test is NOT using self.performRequest, in order to test an extra signature of HTTP.request - let request = HTTP.request("http://localhost:\(self.port)/largepost") {response in + let request = HTTP.request("http://localhost:\(self.port)/largepost") { response in XCTAssertEqual(response?.statusCode, HTTPStatusCode.OK, "Status code wasn't .Ok was \(String(describing: response?.statusCode))") expectation.fulfill() } @@ -80,6 +80,7 @@ class LargePayloadTests: KituraNetTest { func handle(request: ServerRequest, response: ServerResponse) { if request.method.uppercased() == "GET" { + print("hello world") handleGet(request: request, response: response) } else { handlePost(request: request, response: response) @@ -87,6 +88,7 @@ class LargePayloadTests: KituraNetTest { } func handleGet(request: ServerRequest, response: ServerResponse) { + print("handle Get") var payload = "[" + contentTypesString for _ in 0 ... 320 { payload += "," + contentTypesString From 3256ed7b82698590107a324ebe8bc621c7d68a2d Mon Sep 17 00:00:00 2001 From: Rudrani Date: Mon, 22 Jul 2019 15:25:44 +0530 Subject: [PATCH 03/18] change location of HTTPResponseHandler --- HTTPResponseHandler.swift | 22 ------------------- .../KituraNetTests/ConnectionLimitTests.swift | 0 .../KituraNetTests/HTTPResponseHandler.swift | 1 + 3 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 HTTPResponseHandler.swift rename ConnectionLimitTests.swift => Tests/KituraNetTests/ConnectionLimitTests.swift (100%) diff --git a/HTTPResponseHandler.swift b/HTTPResponseHandler.swift deleted file mode 100644 index 4eec6fc9..00000000 --- a/HTTPResponseHandler.swift +++ /dev/null @@ -1,22 +0,0 @@ -import NIO -import NIOHTTP1 -import NIOWebSocket -import LoggerAPI -import Foundation -import Dispatch - -internal class HTTPResponseHandler: ChannelInboundHandler { - /// The ClientResponse object for the response - internal var statusCode: HTTPResponseStatus = .ok - typealias InboundIn = HTTPServerResponsePart - public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - let response = self.unwrapInboundIn(data) - switch response { - case .head(let header): - statusCode = header.status - default: - break - } - } -} - diff --git a/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift similarity index 100% rename from ConnectionLimitTests.swift rename to Tests/KituraNetTests/ConnectionLimitTests.swift diff --git a/Tests/KituraNetTests/HTTPResponseHandler.swift b/Tests/KituraNetTests/HTTPResponseHandler.swift index fe1fe75a..4eec6fc9 100644 --- a/Tests/KituraNetTests/HTTPResponseHandler.swift +++ b/Tests/KituraNetTests/HTTPResponseHandler.swift @@ -19,3 +19,4 @@ internal class HTTPResponseHandler: ChannelInboundHandler { } } } + From fae41fe6fd289d2c9d9aa19c184d74620a6c50cc Mon Sep 17 00:00:00 2001 From: Rudrani Date: Wed, 24 Jul 2019 13:27:30 +0530 Subject: [PATCH 04/18] Add Test to check connection limit --- Sources/KituraNet/HTTP/HTTPServer.swift | 25 +++++++++-- .../KituraNetTests/ConnectionLimitTests.swift | 44 +++++++++++++++++-- .../KituraNetTests/HTTPResponseHandler.swift | 9 +--- Tests/KituraNetTests/KituraNIOTest.swift | 2 +- 4 files changed, 66 insertions(+), 14 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index 0007df50..1a2cb5e2 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -136,7 +136,7 @@ public class HTTPServer: Server { public var connectionCount = 0 // The data to be written as a part of the response. - private var buffer: ByteBuffer + //private var buffer: ByteBuffer /** Creates an HTTP server object. @@ -156,7 +156,6 @@ public class HTTPServer: Server { self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) #endif self.serverConfig = serverConfig - self.buffer = ((serverChannel?.allocator.buffer(capacity: 1024))!) } /** @@ -322,6 +321,8 @@ public class HTTPServer: Server { return channel.pipeline.addHandler(self.quiescingHelper!.makeServerChannelHandler(channel: channel)) } .childChannelInitializer { channel in + //var buffer: ByteBuffer = ((channel.allocator.buffer(capacity: 1024))) + print("In childChannelInitializer",channel.pipeline) let httpHandler = HTTPRequestHandler(for: self) let config: HTTPUpgradeConfiguration = (upgraders: upgraders, completionHandler: { ctx in self.ctx = ctx @@ -332,7 +333,7 @@ public class HTTPServer: Server { _ = channel.pipeline.addHandler(nioSSLServerHandler, position: .first) } return channel.pipeline.addHandler(httpHandler) - } + }.flatMap { return self.checkConcurrentConnectionCount(channel: channel, requestHandler: httpHandler) } } let listenerDescription: String @@ -383,6 +384,24 @@ public class HTTPServer: Server { ListenerGroup.enqueueAsynchronously(on: DispatchQueue.global(), block: queuedBlock) } + private func checkConcurrentConnectionCount(channel: Channel, requestHandler: HTTPRequestHandler) -> EventLoopFuture { + var buffer: ByteBuffer = channel.allocator.buffer(capacity: 100) + self.connectionCount = self.connectionCount + 1 + let connectionLimit = self.serverConfig.connectionLimit + if self.connectionCount > connectionLimit { + print("In check connection limit") + let statusCode = HTTPStatusCode.serviceUnavailable.rawValue + let statusDescription = HTTP.statusCodes[statusCode] ?? "" + let response = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .serviceUnavailable) + buffer.writeString(statusDescription) + channel.write(requestHandler.wrapOutboundOut(.head(response)), promise: nil) + channel.write(requestHandler.wrapOutboundOut(HTTPServerResponsePart.body(.byteBuffer(buffer))),promise: nil) + channel.writeAndFlush(requestHandler.wrapOutboundOut(.end(nil)), promise: nil) + return channel.close() + } + //connection count lesser than limit + return channel.eventLoop.makeSucceededFuture(()) + } /** Static method to create a new HTTP server and have it listen for connections. diff --git a/Tests/KituraNetTests/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift index 82d3bb2d..a841541e 100644 --- a/Tests/KituraNetTests/ConnectionLimitTests.swift +++ b/Tests/KituraNetTests/ConnectionLimitTests.swift @@ -3,6 +3,7 @@ import Dispatch import NIO import XCTest import KituraNet +import NIOHTTP1 class ConnectionLimitTests: KituraNetTest { static var allTests: [(String, (ConnectionLimitTests) -> () throws -> Void)] { @@ -18,9 +19,17 @@ class ConnectionLimitTests: KituraNetTest { override func tearDown() { doTearDown() } + private func sendRequest(request: HTTPRequestHead, on channel: Channel) { + channel.write(NIOAny(HTTPClientRequestPart.head(request)), promise: nil) + try! channel.writeAndFlush(NIOAny(HTTPClientRequestPart.end(nil))).wait() + } + func testConnectionLimit () { - performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 100), nil, useSSL: false, asyncTasks: { expectation in + let delegate = TestConnectionLimitDelegate() + performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in + //print("port is:",self.port) var channel: Channel + var channel1: Channel let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) let bootstrap = ClientBootstrap(group: group) .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) @@ -28,8 +37,37 @@ class ConnectionLimitTests: KituraNetTest { channel.pipeline.addHTTPClientHandlers().flatMap {_ in channel.pipeline.addHandler(HTTPResponseHandler()) } - } - channel = bootstrap.connect(host: "localhost", port: 8080) as! Channel + } + do { + print("check connecting to \(self.port)") + try channel = bootstrap.connect(host: "localhost", port: self.port).wait() + let request = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/") + self.sendRequest(request: request, on: channel) + } catch (let e) { + print("error: ",e) + XCTFail("Connection is not established.") + } + do { + try channel1 = bootstrap.connect(host: "localhost", port: self.port).wait() + print("connecting to channel1") + } catch { + + } + //expectation.fulfill() }) } } +class TestConnectionLimitDelegate: ServerDelegate { + + func handle(request: ServerRequest, response: ServerResponse) { + do { + let result: String = "Hello, World!" + response.statusCode = .OK + response.headers["Content-Type"] = ["text/plain"] + response.headers["Content-Length"] = ["\(result.count)"] + try response.end(text: result) + } catch { + XCTFail("Error while writing response") + } + } +} diff --git a/Tests/KituraNetTests/HTTPResponseHandler.swift b/Tests/KituraNetTests/HTTPResponseHandler.swift index 4eec6fc9..b61f9f3c 100644 --- a/Tests/KituraNetTests/HTTPResponseHandler.swift +++ b/Tests/KituraNetTests/HTTPResponseHandler.swift @@ -10,13 +10,8 @@ internal class HTTPResponseHandler: ChannelInboundHandler { internal var statusCode: HTTPResponseStatus = .ok typealias InboundIn = HTTPServerResponsePart public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - let response = self.unwrapInboundIn(data) - switch response { - case .head(let header): - statusCode = header.status - default: - break - } + //let response = self.unwrapInboundIn(data) + print(data) } } diff --git a/Tests/KituraNetTests/KituraNIOTest.swift b/Tests/KituraNetTests/KituraNIOTest.swift index 67c523cf..7e90fc9c 100644 --- a/Tests/KituraNetTests/KituraNIOTest.swift +++ b/Tests/KituraNetTests/KituraNIOTest.swift @@ -174,7 +174,7 @@ class KituraNetTest: XCTestCase { defer { server.stop() } - + print("server listening on \(self.port)") let requestQueue = DispatchQueue(label: "Request queue") for (index, asyncTask) in asyncTasks.enumerated() { let expectation = self.expectation(line: line, index: index) From 03f181b516af0e32ebd59c47ac221a27966c650c Mon Sep 17 00:00:00 2001 From: Rudrani Date: Thu, 25 Jul 2019 16:55:28 +0530 Subject: [PATCH 05/18] Implemetation of atomic Int for connection count --- .../KituraNet/HTTP/HTTPRequestHandler.swift | 14 +++- Sources/KituraNet/HTTP/HTTPServer.swift | 45 +++++------ .../KituraNetTests/ConnectionLimitTests.swift | 78 +++++++++++-------- .../KituraNetTests/HTTPResponseHandler.swift | 17 ---- Tests/KituraNetTests/KituraNIOTest.swift | 1 - 5 files changed, 81 insertions(+), 74 deletions(-) delete mode 100644 Tests/KituraNetTests/HTTPResponseHandler.swift diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 9f7b21d3..ec05df1d 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -117,6 +117,18 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle case .end: requestSize = 0 + server.connectionCount.mutate { $0 += 1 } + if server.connectionCount.value > server.serverConfig.connectionLimit { + let statusCode = HTTPStatusCode.serviceUnavailable.rawValue + let statusDescription = HTTP.statusCodes[statusCode] ?? "" + do { + serverResponse = HTTPServerResponse(channel: context.channel, handler: self) + errorResponseSent = true + try serverResponse?.end(with: .serviceUnavailable, message: statusDescription) + } catch { + Log.error("Failed to send error response") + } + } serverResponse = HTTPServerResponse(channel: context.channel, handler: self) //Make sure we use the latest delegate registered with the server DispatchQueue.global().async { @@ -179,7 +191,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle } func channelInactive(context: ChannelHandlerContext, httpServer: HTTPServer) { - httpServer.connectionCount = httpServer.connectionCount - 1 + httpServer.connectionCount.mutate { $0 -= 1 } } func getHeaderSize(of header: HTTPRequestHead) -> Int { diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index 1a2cb5e2..47110c3b 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -23,6 +23,7 @@ import LoggerAPI import NIOWebSocket import CLinuxHelpers import NIOExtras +import Foundation #if os(Linux) import Glibc @@ -133,7 +134,7 @@ public class HTTPServer: Server { public var serverConfig: HTTPServerConfiguration //counter for no of connections - public var connectionCount = 0 + var connectionCount = Atomic(0) // The data to be written as a part of the response. //private var buffer: ByteBuffer @@ -321,8 +322,6 @@ public class HTTPServer: Server { return channel.pipeline.addHandler(self.quiescingHelper!.makeServerChannelHandler(channel: channel)) } .childChannelInitializer { channel in - //var buffer: ByteBuffer = ((channel.allocator.buffer(capacity: 1024))) - print("In childChannelInitializer",channel.pipeline) let httpHandler = HTTPRequestHandler(for: self) let config: HTTPUpgradeConfiguration = (upgraders: upgraders, completionHandler: { ctx in self.ctx = ctx @@ -333,7 +332,7 @@ public class HTTPServer: Server { _ = channel.pipeline.addHandler(nioSSLServerHandler, position: .first) } return channel.pipeline.addHandler(httpHandler) - }.flatMap { return self.checkConcurrentConnectionCount(channel: channel, requestHandler: httpHandler) } + } } let listenerDescription: String @@ -384,24 +383,6 @@ public class HTTPServer: Server { ListenerGroup.enqueueAsynchronously(on: DispatchQueue.global(), block: queuedBlock) } - private func checkConcurrentConnectionCount(channel: Channel, requestHandler: HTTPRequestHandler) -> EventLoopFuture { - var buffer: ByteBuffer = channel.allocator.buffer(capacity: 100) - self.connectionCount = self.connectionCount + 1 - let connectionLimit = self.serverConfig.connectionLimit - if self.connectionCount > connectionLimit { - print("In check connection limit") - let statusCode = HTTPStatusCode.serviceUnavailable.rawValue - let statusDescription = HTTP.statusCodes[statusCode] ?? "" - let response = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .serviceUnavailable) - buffer.writeString(statusDescription) - channel.write(requestHandler.wrapOutboundOut(.head(response)), promise: nil) - channel.write(requestHandler.wrapOutboundOut(HTTPServerResponsePart.body(.byteBuffer(buffer))),promise: nil) - channel.writeAndFlush(requestHandler.wrapOutboundOut(.end(nil)), promise: nil) - return channel.close() - } - //connection count lesser than limit - return channel.eventLoop.makeSucceededFuture(()) - } /** Static method to create a new HTTP server and have it listen for connections. @@ -708,3 +689,23 @@ enum KituraWebSocketUpgradeError: Error { // Unknown upgrade error case unknownUpgradeError } + +class Atomic { + private let queue = DispatchQueue(label: "Atomic serial queue") + private var _value: A + init(_ value: A) { + self._value = value + } + + var value: A { + get { + return queue.sync { self._value } + } + } + + func mutate(_ transform: (inout A) -> ()) { + queue.sync { + transform(&self._value) + } + } +} diff --git a/Tests/KituraNetTests/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift index a841541e..8a20fa56 100644 --- a/Tests/KituraNetTests/ConnectionLimitTests.swift +++ b/Tests/KituraNetTests/ConnectionLimitTests.swift @@ -4,6 +4,8 @@ import NIO import XCTest import KituraNet import NIOHTTP1 +import NIOWebSocket +import LoggerAPI class ConnectionLimitTests: KituraNetTest { static var allTests: [(String, (ConnectionLimitTests) -> () throws -> Void)] { @@ -24,50 +26,60 @@ class ConnectionLimitTests: KituraNetTest { try! channel.writeAndFlush(NIOAny(HTTPClientRequestPart.end(nil))).wait() } + func establishConnection(expectation: XCTestExpectation, responseHandler: HTTPResponseHandler) { + var channel: Channel + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let bootstrap = ClientBootstrap(group: group) + .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + .channelInitializer { channel in + channel.pipeline.addHTTPClientHandlers().flatMap {_ in + channel.pipeline.addHandler(responseHandler) + } + } + do { + try channel = bootstrap.connect(host: "localhost", port: self.port).wait() + let request = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/") + self.sendRequest(request: request, on: channel) + } catch let e { + XCTFail("Connection is not established.") + } + } func testConnectionLimit () { let delegate = TestConnectionLimitDelegate() performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in - //print("port is:",self.port) - var channel: Channel - var channel1: Channel - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let bootstrap = ClientBootstrap(group: group) - .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) - .channelInitializer { channel in - channel.pipeline.addHTTPClientHandlers().flatMap {_ in - channel.pipeline.addHandler(HTTPResponseHandler()) - } - } - do { - print("check connecting to \(self.port)") - try channel = bootstrap.connect(host: "localhost", port: self.port).wait() - let request = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/") - self.sendRequest(request: request, on: channel) - } catch (let e) { - print("error: ",e) - XCTFail("Connection is not established.") - } - do { - try channel1 = bootstrap.connect(host: "localhost", port: self.port).wait() - print("connecting to channel1") - } catch { - - } - //expectation.fulfill() + expectation.expectedFulfillmentCount = 2 + self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.ok, expectation: expectation)) + self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.serviceUnavailable, expectation: expectation)) }) } } class TestConnectionLimitDelegate: ServerDelegate { - func handle(request: ServerRequest, response: ServerResponse) { do { - let result: String = "Hello, World!" - response.statusCode = .OK - response.headers["Content-Type"] = ["text/plain"] - response.headers["Content-Length"] = ["\(result.count)"] - try response.end(text: result) + try response.end() } catch { XCTFail("Error while writing response") } } } + +class HTTPResponseHandler: ChannelInboundHandler { + let expectedStatus: HTTPResponseStatus + let expectation: XCTestExpectation + init(expectedStatus: HTTPResponseStatus, expectation: XCTestExpectation) { + self.expectedStatus = expectedStatus + self.expectation = expectation + } + typealias InboundIn = HTTPClientResponsePart + public func channelRead(context: ChannelHandlerContext, data: NIOAny) { + let response = self.unwrapInboundIn(data) + switch response { + case .head(let header): + let status = header.status + XCTAssertEqual(status, expectedStatus) + expectation.fulfill() + default: do { + } + } + } +} diff --git a/Tests/KituraNetTests/HTTPResponseHandler.swift b/Tests/KituraNetTests/HTTPResponseHandler.swift deleted file mode 100644 index b61f9f3c..00000000 --- a/Tests/KituraNetTests/HTTPResponseHandler.swift +++ /dev/null @@ -1,17 +0,0 @@ -import NIO -import NIOHTTP1 -import NIOWebSocket -import LoggerAPI -import Foundation -import Dispatch - -internal class HTTPResponseHandler: ChannelInboundHandler { - /// The ClientResponse object for the response - internal var statusCode: HTTPResponseStatus = .ok - typealias InboundIn = HTTPServerResponsePart - public func channelRead(context: ChannelHandlerContext, data: NIOAny) { - //let response = self.unwrapInboundIn(data) - print(data) - } -} - diff --git a/Tests/KituraNetTests/KituraNIOTest.swift b/Tests/KituraNetTests/KituraNIOTest.swift index 7e90fc9c..1c433e32 100644 --- a/Tests/KituraNetTests/KituraNIOTest.swift +++ b/Tests/KituraNetTests/KituraNIOTest.swift @@ -174,7 +174,6 @@ class KituraNetTest: XCTestCase { defer { server.stop() } - print("server listening on \(self.port)") let requestQueue = DispatchQueue(label: "Request queue") for (index, asyncTask) in asyncTasks.enumerated() { let expectation = self.expectation(line: line, index: index) From 52434c00105a23af821f57377dd9f13c4583935a Mon Sep 17 00:00:00 2001 From: Rudrani Date: Fri, 23 Aug 2019 18:18:26 +0530 Subject: [PATCH 06/18] Implement NIO's Atomic for connectionCount --- Package.swift | 2 +- .../KituraNet/HTTP/HTTPRequestHandler.swift | 6 ++--- Sources/KituraNet/HTTP/HTTPServer.swift | 23 ++----------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Package.swift b/Package.swift index e39496a2..64e198fe 100644 --- a/Package.swift +++ b/Package.swift @@ -42,7 +42,7 @@ let package = Package( dependencies: []), .target( name: "KituraNet", - dependencies: ["NIO", "NIOFoundationCompat", "NIOHTTP1", "NIOSSL", "SSLService", "LoggerAPI", "NIOWebSocket", "CLinuxHelpers", "NIOExtras"]), + dependencies: ["NIO", "NIOFoundationCompat", "NIOHTTP1", "NIOSSL", "SSLService", "LoggerAPI", "NIOWebSocket", "CLinuxHelpers", "NIOConcurrencyHelpers"]), .testTarget( name: "KituraNetTests", dependencies: ["KituraNet"]) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index ec05df1d..0f9ac32e 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -117,8 +117,8 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle case .end: requestSize = 0 - server.connectionCount.mutate { $0 += 1 } - if server.connectionCount.value > server.serverConfig.connectionLimit { + server.connectionCount.add(1) + if server.connectionCount.load() > server.serverConfig.connectionLimit { let statusCode = HTTPStatusCode.serviceUnavailable.rawValue let statusDescription = HTTP.statusCodes[statusCode] ?? "" do { @@ -191,7 +191,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle } func channelInactive(context: ChannelHandlerContext, httpServer: HTTPServer) { - httpServer.connectionCount.mutate { $0 -= 1 } + httpServer.connectionCount.sub(1) } func getHeaderSize(of header: HTTPRequestHead) -> Int { diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index 47110c3b..acb1720e 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -24,6 +24,7 @@ import NIOWebSocket import CLinuxHelpers import NIOExtras import Foundation +import NIOConcurrencyHelpers #if os(Linux) import Glibc @@ -134,7 +135,7 @@ public class HTTPServer: Server { public var serverConfig: HTTPServerConfiguration //counter for no of connections - var connectionCount = Atomic(0) + var connectionCount = Atomic(value: 0) // The data to be written as a part of the response. //private var buffer: ByteBuffer @@ -689,23 +690,3 @@ enum KituraWebSocketUpgradeError: Error { // Unknown upgrade error case unknownUpgradeError } - -class Atomic { - private let queue = DispatchQueue(label: "Atomic serial queue") - private var _value: A - init(_ value: A) { - self._value = value - } - - var value: A { - get { - return queue.sync { self._value } - } - } - - func mutate(_ transform: (inout A) -> ()) { - queue.sync { - transform(&self._value) - } - } -} From 118c5c5fd93880b6a83ba389b4c98a98ce3f966e Mon Sep 17 00:00:00 2001 From: Rudrani Date: Wed, 4 Sep 2019 14:09:28 +0530 Subject: [PATCH 07/18] Modify testConnectionLimit --- .../KituraNet/HTTP/HTTPRequestHandler.swift | 50 +++++++++---------- .../HTTP/HTTPServerConfiguration.swift | 9 ++-- .../KituraNetTests/ConnectionLimitTests.swift | 24 ++++++--- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 0f9ac32e..4bd277fb 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -22,16 +22,12 @@ import Foundation import Dispatch internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandler { - //default request size limit - - let requestSizeLimit: Int - - var requestSize: Int = 0 - //var byteCount: Int = 0 /// The HTTPServer instance on which this handler is installed var server: HTTPServer + var requestSize: Int = 0 + /// The serverRequest related to this handler instance var serverRequest: HTTPServerRequest? @@ -67,7 +63,6 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle public init(for server: HTTPServer) { self.server = server - self.requestSizeLimit = server.serverConfig.requestSizeLimit self.keepAliveState = server.keepAliveState if server.sslConfig != nil { self.enableSSLVerification = true @@ -84,26 +79,29 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle if errorResponseSent { return } switch request { case .head(let header): -<<<<<<< HEAD serverRequest = HTTPServerRequest(channel: context.channel, requestHead: header, enableSSL: enableSSLVerification) -======= - if let contentLength = header.headers["Content-Length"].first, + if let requestSizeLimit = server.serverConfig.requestSizeLimit, + let contentLength = header.headers["Content-Length"].first, let contentLengthValue = Int(contentLength) { - if contentLengthValue > requestSizeLimit { - sendStatus(context: context) + if contentLengthValue > requestSizeLimit { + sendStatus(context: context) + context.close() } } let headerSize = getHeaderSize(of: header) + if let requestSizeLimit = server.serverConfig.requestSizeLimit { if headerSize > requestSizeLimit { sendStatus(context: context) - } + } + } serverRequest = HTTPServerRequest(ctx: context, requestHead: header, enableSSL: enableSSLVerification) ->>>>>>> Ckeck maximum request size and connection limit self.clientRequestedKeepAlive = header.isKeepAlive case .body(var buffer): requestSize += buffer.readableBytes - if requestSize > requestSizeLimit { - sendStatus(context: context) + if let requestSizeLimit = server.serverConfig.requestSizeLimit { + if requestSize > requestSizeLimit { + sendStatus(context: context) + } } guard let serverRequest = serverRequest else { Log.error("No ServerRequest available") @@ -118,15 +116,17 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle case .end: requestSize = 0 server.connectionCount.add(1) - if server.connectionCount.load() > server.serverConfig.connectionLimit { - let statusCode = HTTPStatusCode.serviceUnavailable.rawValue - let statusDescription = HTTP.statusCodes[statusCode] ?? "" - do { - serverResponse = HTTPServerResponse(channel: context.channel, handler: self) - errorResponseSent = true - try serverResponse?.end(with: .serviceUnavailable, message: statusDescription) - } catch { - Log.error("Failed to send error response") + if let connectionLimit = server.serverConfig.connectionLimit { + if server.connectionCount.load() > connectionLimit { + let statusCode = HTTPStatusCode.serviceUnavailable.rawValue + let statusDescription = HTTP.statusCodes[statusCode] ?? "" + do { + serverResponse = HTTPServerResponse(channel: context.channel, handler: self) + errorResponseSent = true + try serverResponse?.end(with: .serviceUnavailable, message: statusDescription) + } catch { + Log.error("Failed to send error response") + } } } serverResponse = HTTPServerResponse(channel: context.channel, handler: self) diff --git a/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift b/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift index eb1ae995..756bf3d4 100644 --- a/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift +++ b/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift @@ -19,13 +19,12 @@ import Foundation public struct HTTPServerConfiguration { /// Defines the maximum size of an incoming request, in bytes. If requests are received that are larger /// than this limit, they will be rejected and the connection will be closed. - /// - /// A value of `nil` means no limit. - public let requestSizeLimit: Int + + public let requestSizeLimit: Int? /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting /// to connect when this limit has been reached will be rejected. - public let connectionLimit: Int + public let connectionLimit: Int? public static var `default` = HTTPServerConfiguration(requestSizeLimit: 1024 * 1024, connectionLimit: 1024) @@ -35,7 +34,7 @@ public struct HTTPServerConfiguration { /// - parameter requestSizeLimit: The maximum size of an incoming request. Defaults to `IncomingSocketOptions.defaultRequestSizeLimit`. /// - parameter connectionLimit: The maximum number of concurrent connections. Defaults to `IncomingSocketOptions.defaultConnectionLimit`. - public init(requestSizeLimit: Int,connectionLimit: Int) + public init(requestSizeLimit: Int?,connectionLimit: Int?) { self.requestSizeLimit = requestSizeLimit self.connectionLimit = connectionLimit diff --git a/Tests/KituraNetTests/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift index 8a20fa56..914d1101 100644 --- a/Tests/KituraNetTests/ConnectionLimitTests.swift +++ b/Tests/KituraNetTests/ConnectionLimitTests.swift @@ -44,15 +44,23 @@ class ConnectionLimitTests: KituraNetTest { XCTFail("Connection is not established.") } } - func testConnectionLimit () { - let delegate = TestConnectionLimitDelegate() - performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in - expectation.expectedFulfillmentCount = 2 - self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.ok, expectation: expectation)) - self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.serviceUnavailable, expectation: expectation)) - }) - } + +func testConnectionLimit() { + let delegate = TestConnectionLimitDelegate() + performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in + let payload = "Hello, World!" + var payloadBuffer = ByteBufferAllocator().buffer(capacity: 1024) + payloadBuffer.writeString(payload) + _ = self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.ok, expectation: expectation)) + }, { expectation in + let payload = "Hello, World!" + var payloadBuffer = ByteBufferAllocator().buffer(capacity: 1024) + payloadBuffer.writeString(payload) + _ = self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.serviceUnavailable, expectation: expectation)) + }) } +} + class TestConnectionLimitDelegate: ServerDelegate { func handle(request: ServerRequest, response: ServerResponse) { do { From 72a0dc15023696b0f54ce130fa78b32bbe57bf03 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Thu, 5 Sep 2019 10:49:49 +0530 Subject: [PATCH 08/18] Minor fixes --- Package.swift | 2 +- Sources/KituraNet/HTTP/HTTPRequestHandler.swift | 2 +- Sources/KituraNet/HTTP/HTTPServer.swift | 2 +- Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index 64e198fe..c6c8627f 100644 --- a/Package.swift +++ b/Package.swift @@ -42,7 +42,7 @@ let package = Package( dependencies: []), .target( name: "KituraNet", - dependencies: ["NIO", "NIOFoundationCompat", "NIOHTTP1", "NIOSSL", "SSLService", "LoggerAPI", "NIOWebSocket", "CLinuxHelpers", "NIOConcurrencyHelpers"]), + dependencies: ["NIO", "NIOFoundationCompat", "NIOHTTP1", "NIOSSL", "SSLService", "LoggerAPI", "NIOWebSocket", "CLinuxHelpers", "NIOConcurrencyHelpers", "NIOExtras"]), .testTarget( name: "KituraNetTests", dependencies: ["KituraNet"]) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 4bd277fb..c3878313 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -94,7 +94,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle sendStatus(context: context) } } - serverRequest = HTTPServerRequest(ctx: context, requestHead: header, enableSSL: enableSSLVerification) + serverRequest = HTTPServerRequest(channel: context.channel, requestHead: header, enableSSL: enableSSLVerification) self.clientRequestedKeepAlive = header.isKeepAlive case .body(var buffer): requestSize += buffer.readableBytes diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index acb1720e..aea5762c 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -22,8 +22,8 @@ import SSLService import LoggerAPI import NIOWebSocket import CLinuxHelpers -import NIOExtras import Foundation +import NIOExtras import NIOConcurrencyHelpers #if os(Linux) diff --git a/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift b/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift index 882fb657..d2b763e6 100644 --- a/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift +++ b/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift @@ -31,7 +31,7 @@ class KituraNetWebSocketUpgradeTest: KituraNetTest { ] } - var httpHandler: HTTPResponseHandler? + var httpHandler: ResponseHandler? func clientChannelInitializer(channel: Channel) -> EventLoopFuture { var httpRequestEncoder: HTTPRequestEncoder @@ -83,12 +83,12 @@ class KituraNetWebSocketUpgradeTest: KituraNetTest { } func sendUpgradeRequest(toPath: String, usingKey: String, wsVersion: String, semaphore: DispatchSemaphore, errorMessage: String? = nil) { - - self.httpHandler = HTTPResponseHandler(key: usingKey,semaphore: semaphore) + + self.httpHandler = ResponseHandler(key: usingKey,semaphore: semaphore) let clientBootstrap = ClientBootstrap(group: MultiThreadedEventLoopGroup(numberOfThreads: 1)) .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1) .channelInitializer(clientChannelInitializer) - + do { let channel = try clientBootstrap.connect(host: "localhost", port: self.port).wait() var request = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: HTTPMethod(rawValue: "GET"), uri: toPath) @@ -106,7 +106,7 @@ class KituraNetWebSocketUpgradeTest: KituraNetTest { } } -class HTTPResponseHandler: ChannelInboundHandler { +class ResponseHandler: ChannelInboundHandler { public typealias InboundIn = HTTPClientResponsePart public var responseStatus : HTTPResponseStatus From d5ee0c10a4ff6113cb1407fbbc2ff9f01f6afe9a Mon Sep 17 00:00:00 2001 From: Rudrani Date: Thu, 12 Sep 2019 13:52:40 +0530 Subject: [PATCH 09/18] rename HTTPServerConfiguration to ServerOptions --- Sources/KituraNet/HTTP/HTTP.swift | 4 +- .../KituraNet/HTTP/HTTPRequestHandler.swift | 10 +- Sources/KituraNet/HTTP/HTTPServer.swift | 8 +- .../HTTP/HTTPServerConfiguration.swift | 43 ------- Sources/KituraNet/HTTP/ServerOptions.swift | 113 ++++++++++++++++++ Tests/KituraNetTests/ClientE2ETests.swift | 2 +- .../KituraNetTests/ConnectionLimitTests.swift | 6 +- Tests/KituraNetTests/KituraNIOTest.swift | 10 +- 8 files changed, 134 insertions(+), 62 deletions(-) delete mode 100644 Sources/KituraNet/HTTP/HTTPServerConfiguration.swift create mode 100644 Sources/KituraNet/HTTP/ServerOptions.swift diff --git a/Sources/KituraNet/HTTP/HTTP.swift b/Sources/KituraNet/HTTP/HTTP.swift index 0d3d6b04..2a7b4933 100644 --- a/Sources/KituraNet/HTTP/HTTP.swift +++ b/Sources/KituraNet/HTTP/HTTP.swift @@ -87,9 +87,9 @@ public class HTTP { let server = HTTP.createServer() ```` */ - public static func createServer(serverConfig: HTTPServerConfiguration = .default) -> HTTPServer { + public static func createServer(serverConfig: ServerOptions = ServerOptions()) -> HTTPServer { let serverConfig = serverConfig - return HTTPServer(serverConfig: serverConfig) + return HTTPServer(options: serverConfig) } /** diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index c3878313..eaabfa2c 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -80,7 +80,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle switch request { case .head(let header): serverRequest = HTTPServerRequest(channel: context.channel, requestHead: header, enableSSL: enableSSLVerification) - if let requestSizeLimit = server.serverConfig.requestSizeLimit, + if let requestSizeLimit = server.options.requestSizeLimit, let contentLength = header.headers["Content-Length"].first, let contentLengthValue = Int(contentLength) { if contentLengthValue > requestSizeLimit { @@ -89,7 +89,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle } } let headerSize = getHeaderSize(of: header) - if let requestSizeLimit = server.serverConfig.requestSizeLimit { + if let requestSizeLimit = server.options.requestSizeLimit { if headerSize > requestSizeLimit { sendStatus(context: context) } @@ -98,9 +98,11 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle self.clientRequestedKeepAlive = header.isKeepAlive case .body(var buffer): requestSize += buffer.readableBytes - if let requestSizeLimit = server.serverConfig.requestSizeLimit { + if let requestSizeLimit = server.options.requestSizeLimit { + print("request size is", requestSize, requestSizeLimit) if requestSize > requestSizeLimit { sendStatus(context: context) + print("in request size check") } } guard let serverRequest = serverRequest else { @@ -116,7 +118,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle case .end: requestSize = 0 server.connectionCount.add(1) - if let connectionLimit = server.serverConfig.connectionLimit { + if let connectionLimit = server.options.connectionLimit { if server.connectionCount.load() > connectionLimit { let statusCode = HTTPStatusCode.serviceUnavailable.rawValue let statusDescription = HTTP.statusCodes[statusCode] ?? "" diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index aea5762c..a7dc4289 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -132,8 +132,8 @@ public class HTTPServer: Server { private var ctx: ChannelHandlerContext? /// server configuration - public var serverConfig: HTTPServerConfiguration - + public var options: ServerOptions + //counter for no of connections var connectionCount = Atomic(value: 0) @@ -150,14 +150,14 @@ public class HTTPServer: Server { server.listen(on: 8080) ```` */ - public init(serverConfig: HTTPServerConfiguration = .default) { + public init(options: ServerOptions = ServerOptions()) { #if os(Linux) let numberOfCores = Int(linux_sched_getaffinity()) self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfCores > 0 ? numberOfCores : System.coreCount) #else self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) #endif - self.serverConfig = serverConfig + self.options = options } /** diff --git a/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift b/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift deleted file mode 100644 index 756bf3d4..00000000 --- a/Sources/KituraNet/HTTP/HTTPServerConfiguration.swift +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright IBM Corporation 2019 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import Foundation - -public struct HTTPServerConfiguration { - /// Defines the maximum size of an incoming request, in bytes. If requests are received that are larger - /// than this limit, they will be rejected and the connection will be closed. - - public let requestSizeLimit: Int? - - /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting - /// to connect when this limit has been reached will be rejected. - public let connectionLimit: Int? - - public static var `default` = HTTPServerConfiguration(requestSizeLimit: 1024 * 1024, connectionLimit: 1024) - - - /// Create an `HTTPServerConfiguration` to determine the behaviour of a `Server`. - /// - /// - parameter requestSizeLimit: The maximum size of an incoming request. Defaults to `IncomingSocketOptions.defaultRequestSizeLimit`. - /// - parameter connectionLimit: The maximum number of concurrent connections. Defaults to `IncomingSocketOptions.defaultConnectionLimit`. - - public init(requestSizeLimit: Int?,connectionLimit: Int?) - { - self.requestSizeLimit = requestSizeLimit - self.connectionLimit = connectionLimit - } - -} diff --git a/Sources/KituraNet/HTTP/ServerOptions.swift b/Sources/KituraNet/HTTP/ServerOptions.swift new file mode 100644 index 00000000..fb8c69c5 --- /dev/null +++ b/Sources/KituraNet/HTTP/ServerOptions.swift @@ -0,0 +1,113 @@ +/* + * Copyright IBM Corporation 2019 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import LoggerAPI + +/** + ServerOptions allows customization of default server policies, including: + - `requestSizeLimit`: Defines the maximum size of an incoming request, in bytes. If requests are received that are larger than this limit, they will be rejected and the connection will be closed. A value of `nil` means no limit. + - `connectionLimit`: Defines the maximum number of concurrent connections that a server should accept. Clients attempting to connect when this limit has been reached will be rejected. A value of `nil` means no limit. + The server can optionally respond to the client with a message in either of these cases. This message can be customized by defining `requestSizeResponseGenerator` and `connectionResponseGenerator`. + Example usage: + ``` + let server = HTTP.createServer() + server.options = ServerOptions(requestSizeLimit: 1000, connectionLimit: 10) + ``` + */ +public struct ServerOptions { + + /// A default limit of 1mb on the size of requests that a server should accept. + public static let defaultRequestSizeLimit = 1048576 + + /// A default limit of 10,000 on the number of concurrent connections that a server should accept. + public static let defaultConnectionLimit = 10000 + + /// Defines a default response to an over-sized request of HTTP 413: Request Too Long. A message is also + /// logged at debug level. + public static let defaultRequestSizeResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? = { (limit, clientSource) in + Log.debug("Request from \(clientSource) exceeds size limit of \(limit) bytes. Connection will be closed.") + return (.requestTooLong, "") + } + + /// Defines a default response when refusing a new connection of HTTP 503: Service Unavailable. A message is + /// also logged at debug level. + public static let defaultConnectionResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? = { (limit, clientSource) in + Log.debug("Rejected connection from \(clientSource): Maximum connection limit of \(limit) reached.") + return (.serviceUnavailable, "") + } + + /// Defines the maximum size of an incoming request, in bytes. If requests are received that are larger + /// than this limit, they will be rejected and the connection will be closed. + /// + /// A value of `nil` means no limit. + public let requestSizeLimit: Int? + + /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting + /// to connect when this limit has been reached will be rejected. + public let connectionLimit: Int? + + /** + Determines the response message and HTTP status code used to respond to clients whose request exceeds + the `requestSizeLimit`. The current limit and client's address are provided as parameters to enable a + message to be logged, and/or a response to be provided back to the client. + The returned tuple indicates the HTTP status code and response body to send to the client. If `nil` is + returned, then no response will be sent. + Example usage: + ``` + let oversizeResponse: (Int, String) -> (HTTPStatusCode, String)? = { (limit, client) in + Log.debug("Rejecting request from \(client): Exceeds limit of \(limit) bytes") + return (.requestTooLong, "Your request exceeds the limit of \(limit) bytes.\r\n") + } + ``` + */ + public let requestSizeResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? + + /** + Determines the response message and HTTP status code used to respond to clients that attempt to connect + while the server is already servicing the maximum number of connections, as defined by `connectionLimit`. + The current limit and client's address are provided as parameters to enable a message to be logged, + and/or a response to be provided back to the client. + The returned tuple indicates the HTTP status code and response body to send to the client. If `nil` is + returned, then no response will be sent. + Example usage: + ``` + let connectionResponse: (Int, String) -> (HTTPStatusCode, String)? = { (limit, client) in + Log.debug("Rejecting request from \(client): Connection limit \(limit) reached") + return (.serviceUnavailable, "Service busy - please try again later.\r\n") + } + ``` + */ + public let connectionResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? + + /// Create a `ServerOptions` to determine the behaviour of a `Server`. + /// + /// - parameter requestSizeLimit: The maximum size of an incoming request. Defaults to `ServerOptions.defaultRequestSizeLimit`. + /// - parameter connectionLimit: The maximum number of concurrent connections. Defaults to `ServerOptions.defaultConnectionLimit`. + /// - parameter requestSizeResponseGenerator: A closure producing a response to send to a client when an over-sized request is rejected. Defaults to `ServerOptions.defaultRequestSizeResponseGenerator`. + /// - parameter defaultConnectionResponseGenerator: A closure producing a response to send to a client when a the server is busy and new connections are not being accepted. Defaults to `ServerOptions.defaultConnectionResponseGenerator`. + public init(requestSizeLimit: Int? = ServerOptions.defaultRequestSizeLimit, + connectionLimit: Int? = ServerOptions.defaultConnectionLimit, + requestSizeResponseGenerator: @escaping (Int, String) -> (HTTPStatusCode, String)? = ServerOptions.defaultRequestSizeResponseGenerator, + connectionResponseGenerator: @escaping (Int, String) -> (HTTPStatusCode, String)? = ServerOptions.defaultConnectionResponseGenerator) + { + self.requestSizeLimit = requestSizeLimit + self.connectionLimit = connectionLimit + self.requestSizeResponseGenerator = requestSizeResponseGenerator + self.connectionResponseGenerator = connectionResponseGenerator + } + +} diff --git a/Tests/KituraNetTests/ClientE2ETests.swift b/Tests/KituraNetTests/ClientE2ETests.swift index 677a2153..915a410a 100644 --- a/Tests/KituraNetTests/ClientE2ETests.swift +++ b/Tests/KituraNetTests/ClientE2ETests.swift @@ -54,7 +54,7 @@ class ClientE2ETests: KituraNetTest { let delegate = TestServerDelegate() func testRequestSize() { - performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 100),delegate, useSSL: false, asyncTasks: { expectation in + performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 100),delegate, useSSL: false, asyncTasks: { expectation in let payload = "[" + contentTypesString + "," + contentTypesString + contentTypesString + "," + contentTypesString + "]" self.performRequest("post", path: "/largepost", callback: {response in XCTAssertEqual(response?.statusCode, HTTPStatusCode.requestTooLong) diff --git a/Tests/KituraNetTests/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift index 914d1101..ab02f912 100644 --- a/Tests/KituraNetTests/ConnectionLimitTests.swift +++ b/Tests/KituraNetTests/ConnectionLimitTests.swift @@ -47,7 +47,7 @@ class ConnectionLimitTests: KituraNetTest { func testConnectionLimit() { let delegate = TestConnectionLimitDelegate() - performServerTest(serverConfig: HTTPServerConfiguration(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in + performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in let payload = "Hello, World!" var payloadBuffer = ByteBufferAllocator().buffer(capacity: 1024) payloadBuffer.writeString(payload) @@ -57,8 +57,8 @@ func testConnectionLimit() { var payloadBuffer = ByteBufferAllocator().buffer(capacity: 1024) payloadBuffer.writeString(payload) _ = self.establishConnection(expectation: expectation, responseHandler: HTTPResponseHandler(expectedStatus:HTTPResponseStatus.serviceUnavailable, expectation: expectation)) - }) -} + }) + } } class TestConnectionLimitDelegate: ServerDelegate { diff --git a/Tests/KituraNetTests/KituraNIOTest.swift b/Tests/KituraNetTests/KituraNIOTest.swift index 1c433e32..e6f1d24c 100644 --- a/Tests/KituraNetTests/KituraNIOTest.swift +++ b/Tests/KituraNetTests/KituraNIOTest.swift @@ -85,7 +85,7 @@ class KituraNetTest: XCTestCase { } } - func startServer(_ delegate: ServerDelegate?, unixDomainSocketPath: String? = nil, port: Int = portDefault, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: HTTPServerConfiguration = .default) throws -> HTTPServer { + func startServer(_ delegate: ServerDelegate?, unixDomainSocketPath: String? = nil, port: Int = portDefault, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: ServerOptions = ServerOptions()) throws -> HTTPServer { let serverConfig = serverConfig let server = HTTP.createServer(serverConfig: serverConfig) server.delegate = delegate @@ -103,7 +103,7 @@ class KituraNetTest: XCTestCase { /// Convenience function for starting an HTTPServer on an ephemeral port, /// returning the a tuple containing the server and the port it is listening on. - func startEphemeralServer(_ delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: HTTPServerConfiguration = .default) throws -> (server: HTTPServer, port: Int) { + func startEphemeralServer(_ delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: ServerOptions = ServerOptions()) throws -> (server: HTTPServer, port: Int) { let serverConfig = serverConfig let server = try startServer(delegate, port: 0, useSSL: useSSL,allowPortReuse: allowPortReuse, serverConfig: serverConfig) guard let serverPort = server.port else { @@ -122,7 +122,7 @@ class KituraNetTest: XCTestCase { case both } - func performServerTest(serverConfig: HTTPServerConfiguration = .default, _ delegate: ServerDelegate?, socketType: SocketType = .both, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: (XCTestExpectation) -> Void...) { + func performServerTest(serverConfig: ServerOptions = ServerOptions(), _ delegate: ServerDelegate?, socketType: SocketType = .both, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: (XCTestExpectation) -> Void...) { let serverConfig = serverConfig self.socketType = socketType if socketType != .tcp { @@ -133,7 +133,7 @@ class KituraNetTest: XCTestCase { } } - func performServerTestWithUnixSocket(serverConfig: HTTPServerConfiguration = .default, delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { + func performServerTestWithUnixSocket(serverConfig: ServerOptions = ServerOptions(), delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { do { var serverConfig = serverConfig print("serverconfig: ", serverConfig) @@ -162,7 +162,7 @@ class KituraNetTest: XCTestCase { } } - func performServerTestWithTCPPort(serverConfig: HTTPServerConfiguration = .default, delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { + func performServerTestWithTCPPort(serverConfig: ServerOptions = ServerOptions(), delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { do { var serverConfig = serverConfig var server: HTTPServer From 8f3b666dcb5eac82b487902851f3557743c57f60 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Thu, 12 Sep 2019 14:21:38 +0530 Subject: [PATCH 10/18] clean up the code --- Sources/KituraNet/HTTP/HTTPRequestHandler.swift | 2 -- Tests/KituraNetTests/ClientE2ETests.swift | 1 - Tests/KituraNetTests/KituraNIOTest.swift | 1 - 3 files changed, 4 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index eaabfa2c..81973524 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -99,10 +99,8 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle case .body(var buffer): requestSize += buffer.readableBytes if let requestSizeLimit = server.options.requestSizeLimit { - print("request size is", requestSize, requestSizeLimit) if requestSize > requestSizeLimit { sendStatus(context: context) - print("in request size check") } } guard let serverRequest = serverRequest else { diff --git a/Tests/KituraNetTests/ClientE2ETests.swift b/Tests/KituraNetTests/ClientE2ETests.swift index 915a410a..3c398378 100644 --- a/Tests/KituraNetTests/ClientE2ETests.swift +++ b/Tests/KituraNetTests/ClientE2ETests.swift @@ -65,7 +65,6 @@ class ClientE2ETests: KituraNetTest { XCTAssertEqual(count, expectedResult.count, "Result should have been \(expectedResult.count) bytes, was \(String(describing: count)) bytes") let postValue = String(data: data, encoding: .utf8) if let postValue = postValue { - print("postvalue:", postValue) XCTAssertEqual(postValue, expectedResult) } else { XCTFail("postValue's value wasn't an UTF8 string") diff --git a/Tests/KituraNetTests/KituraNIOTest.swift b/Tests/KituraNetTests/KituraNIOTest.swift index e6f1d24c..8ad1d678 100644 --- a/Tests/KituraNetTests/KituraNIOTest.swift +++ b/Tests/KituraNetTests/KituraNIOTest.swift @@ -136,7 +136,6 @@ class KituraNetTest: XCTestCase { func performServerTestWithUnixSocket(serverConfig: ServerOptions = ServerOptions(), delegate: ServerDelegate?, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, line: Int = #line, asyncTasks: [(XCTestExpectation) -> Void]) { do { var serverConfig = serverConfig - print("serverconfig: ", serverConfig) var server: HTTPServer self.useSSL = useSSL self.unixDomainSocketPath = self.socketFilePath From beae27044e560733f315a2c2b2a8ff5f1fa63876 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Thu, 19 Sep 2019 12:17:54 +0530 Subject: [PATCH 11/18] Remove Options from HTTP server initializer. --- Sources/KituraNet/HTTP/HTTP.swift | 5 ++--- Sources/KituraNet/HTTP/HTTPServer.swift | 2 +- Sources/KituraNet/HTTP/ServerOptions.swift | 4 ++-- Tests/KituraNetTests/ClientE2ETests.swift | 2 +- Tests/KituraNetTests/ConnectionLimitTests.swift | 2 +- Tests/KituraNetTests/KituraNIOTest.swift | 3 ++- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTP.swift b/Sources/KituraNet/HTTP/HTTP.swift index 2a7b4933..97c8e5a0 100644 --- a/Sources/KituraNet/HTTP/HTTP.swift +++ b/Sources/KituraNet/HTTP/HTTP.swift @@ -87,9 +87,8 @@ public class HTTP { let server = HTTP.createServer() ```` */ - public static func createServer(serverConfig: ServerOptions = ServerOptions()) -> HTTPServer { - let serverConfig = serverConfig - return HTTPServer(options: serverConfig) + public static func createServer() -> HTTPServer { + return HTTPServer() } /** diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index a7dc4289..777ca4cc 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -132,7 +132,7 @@ public class HTTPServer: Server { private var ctx: ChannelHandlerContext? /// server configuration - public var options: ServerOptions + public var options: ServerOptions = ServerOptions() //counter for no of connections var connectionCount = Atomic(value: 0) diff --git a/Sources/KituraNet/HTTP/ServerOptions.swift b/Sources/KituraNet/HTTP/ServerOptions.swift index fb8c69c5..76d0fea0 100644 --- a/Sources/KituraNet/HTTP/ServerOptions.swift +++ b/Sources/KituraNet/HTTP/ServerOptions.swift @@ -54,11 +54,11 @@ public struct ServerOptions { /// than this limit, they will be rejected and the connection will be closed. /// /// A value of `nil` means no limit. - public let requestSizeLimit: Int? + public var requestSizeLimit: Int? /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting /// to connect when this limit has been reached will be rejected. - public let connectionLimit: Int? + public var connectionLimit: Int? /** Determines the response message and HTTP status code used to respond to clients whose request exceeds diff --git a/Tests/KituraNetTests/ClientE2ETests.swift b/Tests/KituraNetTests/ClientE2ETests.swift index 3c398378..cbb3dee9 100644 --- a/Tests/KituraNetTests/ClientE2ETests.swift +++ b/Tests/KituraNetTests/ClientE2ETests.swift @@ -54,7 +54,7 @@ class ClientE2ETests: KituraNetTest { let delegate = TestServerDelegate() func testRequestSize() { - performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 100),delegate, useSSL: false, asyncTasks: { expectation in + performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 100),delegate,socketType: .tcp, useSSL: false, asyncTasks: { expectation in let payload = "[" + contentTypesString + "," + contentTypesString + contentTypesString + "," + contentTypesString + "]" self.performRequest("post", path: "/largepost", callback: {response in XCTAssertEqual(response?.statusCode, HTTPStatusCode.requestTooLong) diff --git a/Tests/KituraNetTests/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift index ab02f912..d26d7af5 100644 --- a/Tests/KituraNetTests/ConnectionLimitTests.swift +++ b/Tests/KituraNetTests/ConnectionLimitTests.swift @@ -40,7 +40,7 @@ class ConnectionLimitTests: KituraNetTest { try channel = bootstrap.connect(host: "localhost", port: self.port).wait() let request = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/") self.sendRequest(request: request, on: channel) - } catch let e { + } catch _ { XCTFail("Connection is not established.") } } diff --git a/Tests/KituraNetTests/KituraNIOTest.swift b/Tests/KituraNetTests/KituraNIOTest.swift index 8ad1d678..6fbc6666 100644 --- a/Tests/KituraNetTests/KituraNIOTest.swift +++ b/Tests/KituraNetTests/KituraNIOTest.swift @@ -87,7 +87,8 @@ class KituraNetTest: XCTestCase { func startServer(_ delegate: ServerDelegate?, unixDomainSocketPath: String? = nil, port: Int = portDefault, useSSL: Bool = useSSLDefault, allowPortReuse: Bool = portReuseDefault, serverConfig: ServerOptions = ServerOptions()) throws -> HTTPServer { let serverConfig = serverConfig - let server = HTTP.createServer(serverConfig: serverConfig) + let server = HTTP.createServer() + server.options = serverConfig server.delegate = delegate if useSSL { server.sslConfig = KituraNetTest.sslConfig From 80c1aeffda0cb2e415d1956e3546f543b3422efb Mon Sep 17 00:00:00 2001 From: Rudrani Date: Fri, 20 Sep 2019 10:56:05 +0530 Subject: [PATCH 12/18] Change defalt request size limit in ServerOptions --- Sources/KituraNet/HTTP/ServerOptions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/KituraNet/HTTP/ServerOptions.swift b/Sources/KituraNet/HTTP/ServerOptions.swift index 76d0fea0..689a6226 100644 --- a/Sources/KituraNet/HTTP/ServerOptions.swift +++ b/Sources/KituraNet/HTTP/ServerOptions.swift @@ -30,8 +30,8 @@ import LoggerAPI */ public struct ServerOptions { - /// A default limit of 1mb on the size of requests that a server should accept. - public static let defaultRequestSizeLimit = 1048576 + /// A default limit of 10mb on the size of requests that a server should accept. + public static let defaultRequestSizeLimit = 104857600 /// A default limit of 10,000 on the number of concurrent connections that a server should accept. public static let defaultConnectionLimit = 10000 From 5eed49eb74be888895ca80d51b1dc5bc4b32fba3 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Mon, 23 Sep 2019 14:11:30 +0530 Subject: [PATCH 13/18] Stop calculating header size. --- Sources/KituraNet/HTTP/HTTPRequestHandler.swift | 6 ------ Sources/KituraNet/HTTP/ServerOptions.swift | 2 +- Tests/KituraNetTests/ClientE2ETests.swift | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 81973524..45941bfa 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -88,12 +88,6 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle context.close() } } - let headerSize = getHeaderSize(of: header) - if let requestSizeLimit = server.options.requestSizeLimit { - if headerSize > requestSizeLimit { - sendStatus(context: context) - } - } serverRequest = HTTPServerRequest(channel: context.channel, requestHead: header, enableSSL: enableSSLVerification) self.clientRequestedKeepAlive = header.isKeepAlive case .body(var buffer): diff --git a/Sources/KituraNet/HTTP/ServerOptions.swift b/Sources/KituraNet/HTTP/ServerOptions.swift index 689a6226..f915fd78 100644 --- a/Sources/KituraNet/HTTP/ServerOptions.swift +++ b/Sources/KituraNet/HTTP/ServerOptions.swift @@ -30,7 +30,7 @@ import LoggerAPI */ public struct ServerOptions { - /// A default limit of 10mb on the size of requests that a server should accept. + /// A default limit of 100mb on the size of requests that a server should accept. public static let defaultRequestSizeLimit = 104857600 /// A default limit of 10,000 on the number of concurrent connections that a server should accept. diff --git a/Tests/KituraNetTests/ClientE2ETests.swift b/Tests/KituraNetTests/ClientE2ETests.swift index cbb3dee9..3c398378 100644 --- a/Tests/KituraNetTests/ClientE2ETests.swift +++ b/Tests/KituraNetTests/ClientE2ETests.swift @@ -54,7 +54,7 @@ class ClientE2ETests: KituraNetTest { let delegate = TestServerDelegate() func testRequestSize() { - performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 100),delegate,socketType: .tcp, useSSL: false, asyncTasks: { expectation in + performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 100),delegate, useSSL: false, asyncTasks: { expectation in let payload = "[" + contentTypesString + "," + contentTypesString + contentTypesString + "," + contentTypesString + "]" self.performRequest("post", path: "/largepost", callback: {response in XCTAssertEqual(response?.statusCode, HTTPStatusCode.requestTooLong) From cbe7d220636461e14c1729499e77efe71a598c76 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Wed, 25 Sep 2019 11:54:28 +0530 Subject: [PATCH 14/18] Add code to invoke connectionResponseGenerator --- Sources/KituraNet/HTTP/HTTPRequestHandler.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 45941bfa..c144f6dc 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -112,14 +112,14 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle server.connectionCount.add(1) if let connectionLimit = server.options.connectionLimit { if server.connectionCount.load() > connectionLimit { - let statusCode = HTTPStatusCode.serviceUnavailable.rawValue - let statusDescription = HTTP.statusCodes[statusCode] ?? "" do { - serverResponse = HTTPServerResponse(channel: context.channel, handler: self) - errorResponseSent = true - try serverResponse?.end(with: .serviceUnavailable, message: statusDescription) + if let (httpStatus, response) = server.options.connectionResponseGenerator(connectionLimit,"") { + serverResponse = HTTPServerResponse(channel: context.channel, handler: self) + errorResponseSent = true + try serverResponse?.end(with: httpStatus, message: response) + } } catch { - Log.error("Failed to send error response") + Log.error("Failed to send error response") } } } From 904718a3e074caa698217583e9f2bef63179f8c6 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Wed, 25 Sep 2019 12:08:46 +0530 Subject: [PATCH 15/18] Add code to invoke requestSizeResponseGenerator --- .../KituraNet/HTTP/HTTPRequestHandler.swift | 31 +++++++++++-------- Tests/KituraNetTests/ClientE2ETests.swift | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index c144f6dc..30a7bcd9 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -84,7 +84,15 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle let contentLength = header.headers["Content-Length"].first, let contentLengthValue = Int(contentLength) { if contentLengthValue > requestSizeLimit { - sendStatus(context: context) + do { + if let (httpStatus, response) = server.options.requestSizeResponseGenerator(requestSizeLimit, "") { + serverResponse = HTTPServerResponse(channel: context.channel, handler: self) + errorResponseSent = true + try serverResponse?.end(with: httpStatus, message: response) + } + } catch { + Log.error("Failed to send error response") + } context.close() } } @@ -94,7 +102,15 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle requestSize += buffer.readableBytes if let requestSizeLimit = server.options.requestSizeLimit { if requestSize > requestSizeLimit { - sendStatus(context: context) + do { + if let (httpStatus, response) = server.options.requestSizeResponseGenerator(requestSizeLimit, "") { + serverResponse = HTTPServerResponse(channel: context.channel, handler: self) + errorResponseSent = true + try serverResponse?.end(with: httpStatus, message: response) + } + } catch { + Log.error("Failed to send error response") + } } } guard let serverRequest = serverRequest else { @@ -199,15 +215,4 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle } return headerSize } - - func sendStatus(context: ChannelHandlerContext) { - let statusDescription = HTTP.statusCodes[HTTPStatusCode.requestTooLong.rawValue] ?? "" - do { - serverResponse = HTTPServerResponse(channel: context.channel, handler: self) - errorResponseSent = true - try serverResponse?.end(with: .requestTooLong, message: statusDescription) - } catch { - Log.error("Failed to send error response") - } - } } diff --git a/Tests/KituraNetTests/ClientE2ETests.swift b/Tests/KituraNetTests/ClientE2ETests.swift index 3c398378..8c721124 100644 --- a/Tests/KituraNetTests/ClientE2ETests.swift +++ b/Tests/KituraNetTests/ClientE2ETests.swift @@ -59,7 +59,7 @@ class ClientE2ETests: KituraNetTest { self.performRequest("post", path: "/largepost", callback: {response in XCTAssertEqual(response?.statusCode, HTTPStatusCode.requestTooLong) do { - let expectedResult = "Request Entity Too Large" + let expectedResult = "" var data = Data() let count = try response?.readAllData(into: &data) XCTAssertEqual(count, expectedResult.count, "Result should have been \(expectedResult.count) bytes, was \(String(describing: count)) bytes") From 644dae3ec13372577f43818aebc883264b419d90 Mon Sep 17 00:00:00 2001 From: Rudrani Date: Fri, 27 Sep 2019 14:08:06 +0530 Subject: [PATCH 16/18] Fix indentation --- .../KituraNet/HTTP/HTTPRequestHandler.swift | 18 +++--------------- Sources/KituraNet/HTTP/HTTPServer.swift | 5 ++--- .../KituraNet/HTTP/RequestsizeHandler.swift | 1 - .../KituraNetTests/ConnectionLimitTests.swift | 6 +++--- .../KituraNetWebSocketUpgrade.swift | 6 +++--- Tests/KituraNetTests/LargePayloadTests.swift | 1 - 6 files changed, 11 insertions(+), 26 deletions(-) delete mode 100644 Sources/KituraNet/HTTP/RequestsizeHandler.swift diff --git a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift index 30a7bcd9..42b71683 100644 --- a/Sources/KituraNet/HTTP/HTTPRequestHandler.swift +++ b/Sources/KituraNet/HTTP/HTTPRequestHandler.swift @@ -85,7 +85,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle let contentLengthValue = Int(contentLength) { if contentLengthValue > requestSizeLimit { do { - if let (httpStatus, response) = server.options.requestSizeResponseGenerator(requestSizeLimit, "") { + if let (httpStatus, response) = server.options.requestSizeResponseGenerator(requestSizeLimit, serverRequest?.remoteAddress ?? "") { serverResponse = HTTPServerResponse(channel: context.channel, handler: self) errorResponseSent = true try serverResponse?.end(with: httpStatus, message: response) @@ -103,7 +103,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle if let requestSizeLimit = server.options.requestSizeLimit { if requestSize > requestSizeLimit { do { - if let (httpStatus, response) = server.options.requestSizeResponseGenerator(requestSizeLimit, "") { + if let (httpStatus, response) = server.options.requestSizeResponseGenerator(requestSizeLimit,serverRequest?.remoteAddress ?? "") { serverResponse = HTTPServerResponse(channel: context.channel, handler: self) errorResponseSent = true try serverResponse?.end(with: httpStatus, message: response) @@ -129,7 +129,7 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle if let connectionLimit = server.options.connectionLimit { if server.connectionCount.load() > connectionLimit { do { - if let (httpStatus, response) = server.options.connectionResponseGenerator(connectionLimit,"") { + if let (httpStatus, response) = server.options.connectionResponseGenerator(connectionLimit,serverRequest?.remoteAddress ?? "") { serverResponse = HTTPServerResponse(channel: context.channel, handler: self) errorResponseSent = true try serverResponse?.end(with: httpStatus, message: response) @@ -203,16 +203,4 @@ internal class HTTPRequestHandler: ChannelInboundHandler, RemovableChannelHandle func channelInactive(context: ChannelHandlerContext, httpServer: HTTPServer) { httpServer.connectionCount.sub(1) } - - func getHeaderSize(of header: HTTPRequestHead) -> Int { - var headerSize = 0 - headerSize += header.uri.cString(using: .utf8)?.count ?? 0 - headerSize += header.version.description.cString(using: .utf8)?.count ?? 0 - headerSize += header.method.rawValue.cString(using: .utf8)?.count ?? 0 - for headers in header.headers { - headerSize += headers.name.cString(using: .utf8)?.count ?? 0 - headerSize += headers.value.cString(using: .utf8)?.count ?? 0 - } - return headerSize - } } diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index 777ca4cc..e2e0de97 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -324,8 +324,7 @@ public class HTTPServer: Server { } .childChannelInitializer { channel in let httpHandler = HTTPRequestHandler(for: self) - let config: HTTPUpgradeConfiguration = (upgraders: upgraders, completionHandler: { ctx in - self.ctx = ctx + let config: HTTPUpgradeConfiguration = (upgraders: upgraders, completionHandler: {_ in _ = channel.pipeline.removeHandler(httpHandler) }) return channel.pipeline.configureHTTPServerPipeline(withServerUpgrade: config, withErrorHandling: true).flatMap { @@ -333,7 +332,7 @@ public class HTTPServer: Server { _ = channel.pipeline.addHandler(nioSSLServerHandler, position: .first) } return channel.pipeline.addHandler(httpHandler) - } + } } let listenerDescription: String diff --git a/Sources/KituraNet/HTTP/RequestsizeHandler.swift b/Sources/KituraNet/HTTP/RequestsizeHandler.swift deleted file mode 100644 index 8b137891..00000000 --- a/Sources/KituraNet/HTTP/RequestsizeHandler.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Tests/KituraNetTests/ConnectionLimitTests.swift b/Tests/KituraNetTests/ConnectionLimitTests.swift index d26d7af5..5ca68b93 100644 --- a/Tests/KituraNetTests/ConnectionLimitTests.swift +++ b/Tests/KituraNetTests/ConnectionLimitTests.swift @@ -45,9 +45,9 @@ class ConnectionLimitTests: KituraNetTest { } } -func testConnectionLimit() { - let delegate = TestConnectionLimitDelegate() - performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in + func testConnectionLimit() { + let delegate = TestConnectionLimitDelegate() + performServerTest(serverConfig: ServerOptions(requestSizeLimit: 10000, connectionLimit: 1), delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in let payload = "Hello, World!" var payloadBuffer = ByteBufferAllocator().buffer(capacity: 1024) payloadBuffer.writeString(payload) diff --git a/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift b/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift index d2b763e6..4c146e5f 100644 --- a/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift +++ b/Tests/KituraNetTests/KituraNetWebSocketUpgrade.swift @@ -31,7 +31,7 @@ class KituraNetWebSocketUpgradeTest: KituraNetTest { ] } - var httpHandler: ResponseHandler? + var httpHandler: HttpResponseHandler? func clientChannelInitializer(channel: Channel) -> EventLoopFuture { var httpRequestEncoder: HTTPRequestEncoder @@ -84,7 +84,7 @@ class KituraNetWebSocketUpgradeTest: KituraNetTest { func sendUpgradeRequest(toPath: String, usingKey: String, wsVersion: String, semaphore: DispatchSemaphore, errorMessage: String? = nil) { - self.httpHandler = ResponseHandler(key: usingKey,semaphore: semaphore) + self.httpHandler = HttpResponseHandler(key: usingKey,semaphore: semaphore) let clientBootstrap = ClientBootstrap(group: MultiThreadedEventLoopGroup(numberOfThreads: 1)) .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1) .channelInitializer(clientChannelInitializer) @@ -106,7 +106,7 @@ class KituraNetWebSocketUpgradeTest: KituraNetTest { } } -class ResponseHandler: ChannelInboundHandler { +class HttpResponseHandler: ChannelInboundHandler { public typealias InboundIn = HTTPClientResponsePart public var responseStatus : HTTPResponseStatus diff --git a/Tests/KituraNetTests/LargePayloadTests.swift b/Tests/KituraNetTests/LargePayloadTests.swift index 11900b8f..6af60c6e 100644 --- a/Tests/KituraNetTests/LargePayloadTests.swift +++ b/Tests/KituraNetTests/LargePayloadTests.swift @@ -80,7 +80,6 @@ class LargePayloadTests: KituraNetTest { func handle(request: ServerRequest, response: ServerResponse) { if request.method.uppercased() == "GET" { - print("hello world") handleGet(request: request, response: response) } else { handlePost(request: request, response: response) From 18dea620762025e89d149dd222b70e083f4b0fba Mon Sep 17 00:00:00 2001 From: David Jones Date: Mon, 7 Oct 2019 13:46:55 +0100 Subject: [PATCH 17/18] Address review comments --- Sources/KituraNet/HTTP/HTTPServer.swift | 5 ----- Tests/KituraNetTests/LargePayloadTests.swift | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/KituraNet/HTTP/HTTPServer.swift b/Sources/KituraNet/HTTP/HTTPServer.swift index e2e0de97..a50840ef 100644 --- a/Sources/KituraNet/HTTP/HTTPServer.swift +++ b/Sources/KituraNet/HTTP/HTTPServer.swift @@ -129,17 +129,12 @@ public class HTTPServer: Server { var quiescingHelper: ServerQuiescingHelper? - private var ctx: ChannelHandlerContext? - /// server configuration public var options: ServerOptions = ServerOptions() //counter for no of connections var connectionCount = Atomic(value: 0) - // The data to be written as a part of the response. - //private var buffer: ByteBuffer - /** Creates an HTTP server object. diff --git a/Tests/KituraNetTests/LargePayloadTests.swift b/Tests/KituraNetTests/LargePayloadTests.swift index 6af60c6e..a7e4cef4 100644 --- a/Tests/KituraNetTests/LargePayloadTests.swift +++ b/Tests/KituraNetTests/LargePayloadTests.swift @@ -68,7 +68,7 @@ class LargePayloadTests: KituraNetTest { func testLargeGets() { performServerTest(delegate, socketType: .tcp, useSSL: false, asyncTasks: { expectation in // This test is NOT using self.performRequest, in order to test an extra signature of HTTP.request - let request = HTTP.request("http://localhost:\(self.port)/largepost") { response in + let request = HTTP.request("http://localhost:\(self.port)/largepost") {response in XCTAssertEqual(response?.statusCode, HTTPStatusCode.OK, "Status code wasn't .Ok was \(String(describing: response?.statusCode))") expectation.fulfill() } @@ -87,7 +87,6 @@ class LargePayloadTests: KituraNetTest { } func handleGet(request: ServerRequest, response: ServerResponse) { - print("handle Get") var payload = "[" + contentTypesString for _ in 0 ... 320 { payload += "," + contentTypesString From 19e0c6bb70c80cf47ce6c77f33bb1f654b3f5d19 Mon Sep 17 00:00:00 2001 From: David Jones Date: Mon, 7 Oct 2019 13:51:08 +0100 Subject: [PATCH 18/18] Synchronise ServerOptions with Kitura-net --- Sources/KituraNet/HTTP/ServerOptions.swift | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Sources/KituraNet/HTTP/ServerOptions.swift b/Sources/KituraNet/HTTP/ServerOptions.swift index f915fd78..33a645d2 100644 --- a/Sources/KituraNet/HTTP/ServerOptions.swift +++ b/Sources/KituraNet/HTTP/ServerOptions.swift @@ -19,9 +19,12 @@ import LoggerAPI /** ServerOptions allows customization of default server policies, including: - - `requestSizeLimit`: Defines the maximum size of an incoming request, in bytes. If requests are received that are larger than this limit, they will be rejected and the connection will be closed. A value of `nil` means no limit. + + - `requestSizeLimit`: Defines the maximum size for the body of an incoming request, in bytes. If a request body is larger than this limit, it will be rejected and the connection will be closed. A value of `nil` means no limit. - `connectionLimit`: Defines the maximum number of concurrent connections that a server should accept. Clients attempting to connect when this limit has been reached will be rejected. A value of `nil` means no limit. + The server can optionally respond to the client with a message in either of these cases. This message can be customized by defining `requestSizeResponseGenerator` and `connectionResponseGenerator`. + Example usage: ``` let server = HTTP.createServer() @@ -29,8 +32,8 @@ import LoggerAPI ``` */ public struct ServerOptions { - - /// A default limit of 100mb on the size of requests that a server should accept. + + /// A default limit of 100mb on the size of the request body that a server should accept. public static let defaultRequestSizeLimit = 104857600 /// A default limit of 10,000 on the number of concurrent connections that a server should accept. @@ -50,52 +53,56 @@ public struct ServerOptions { return (.serviceUnavailable, "") } - /// Defines the maximum size of an incoming request, in bytes. If requests are received that are larger - /// than this limit, they will be rejected and the connection will be closed. + /// Defines the maximum size for the body of an incoming request, in bytes. If a request body is larger + /// than this limit, it will be rejected and the connection will be closed. /// /// A value of `nil` means no limit. - public var requestSizeLimit: Int? + public let requestSizeLimit: Int? /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting /// to connect when this limit has been reached will be rejected. - public var connectionLimit: Int? + public let connectionLimit: Int? /** Determines the response message and HTTP status code used to respond to clients whose request exceeds the `requestSizeLimit`. The current limit and client's address are provided as parameters to enable a message to be logged, and/or a response to be provided back to the client. + The returned tuple indicates the HTTP status code and response body to send to the client. If `nil` is returned, then no response will be sent. + Example usage: ``` let oversizeResponse: (Int, String) -> (HTTPStatusCode, String)? = { (limit, client) in - Log.debug("Rejecting request from \(client): Exceeds limit of \(limit) bytes") - return (.requestTooLong, "Your request exceeds the limit of \(limit) bytes.\r\n") + Log.debug("Rejecting request from \(client): Exceeds limit of \(limit) bytes") + return (.requestTooLong, "Your request exceeds the limit of \(limit) bytes.\r\n") } ``` */ public let requestSizeResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? - + /** Determines the response message and HTTP status code used to respond to clients that attempt to connect while the server is already servicing the maximum number of connections, as defined by `connectionLimit`. The current limit and client's address are provided as parameters to enable a message to be logged, and/or a response to be provided back to the client. + The returned tuple indicates the HTTP status code and response body to send to the client. If `nil` is returned, then no response will be sent. + Example usage: ``` let connectionResponse: (Int, String) -> (HTTPStatusCode, String)? = { (limit, client) in - Log.debug("Rejecting request from \(client): Connection limit \(limit) reached") - return (.serviceUnavailable, "Service busy - please try again later.\r\n") + Log.debug("Rejecting request from \(client): Connection limit \(limit) reached") + return (.serviceUnavailable, "Service busy - please try again later.\r\n") } ``` */ public let connectionResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? - + /// Create a `ServerOptions` to determine the behaviour of a `Server`. /// - /// - parameter requestSizeLimit: The maximum size of an incoming request. Defaults to `ServerOptions.defaultRequestSizeLimit`. + /// - parameter requestSizeLimit: The maximum size of an incoming request body. Defaults to `ServerOptions.defaultRequestSizeLimit`. /// - parameter connectionLimit: The maximum number of concurrent connections. Defaults to `ServerOptions.defaultConnectionLimit`. /// - parameter requestSizeResponseGenerator: A closure producing a response to send to a client when an over-sized request is rejected. Defaults to `ServerOptions.defaultRequestSizeResponseGenerator`. /// - parameter defaultConnectionResponseGenerator: A closure producing a response to send to a client when a the server is busy and new connections are not being accepted. Defaults to `ServerOptions.defaultConnectionResponseGenerator`.