|
| 1 | +/* |
| 2 | + * Copyright IBM Corporation 2019 |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +import Foundation |
| 18 | +import LoggerAPI |
| 19 | + |
| 20 | +/** |
| 21 | + ServerOptions allows customization of default server policies, including: |
| 22 | + |
| 23 | + - `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. |
| 24 | + - `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. |
| 25 | + |
| 26 | + 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`. |
| 27 | + |
| 28 | + Example usage: |
| 29 | + ``` |
| 30 | + let server = HTTP.createServer() |
| 31 | + server.options = ServerOptions(requestSizeLimit: 1000, connectionLimit: 10) |
| 32 | + ``` |
| 33 | + */ |
| 34 | +public struct ServerOptions { |
| 35 | + |
| 36 | + /// A default limit of 100mb on the size of the request body that a server should accept. |
| 37 | + public static let defaultRequestSizeLimit = 104857600 |
| 38 | + |
| 39 | + /// A default limit of 10,000 on the number of concurrent connections that a server should accept. |
| 40 | + public static let defaultConnectionLimit = 10000 |
| 41 | + |
| 42 | + /// Defines a default response to an over-sized request of HTTP 413: Request Too Long. A message is also |
| 43 | + /// logged at debug level. |
| 44 | + public static let defaultRequestSizeResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? = { (limit, clientSource) in |
| 45 | + Log.debug("Request from \(clientSource) exceeds size limit of \(limit) bytes. Connection will be closed.") |
| 46 | + return (.requestTooLong, "") |
| 47 | + } |
| 48 | + |
| 49 | + /// Defines a default response when refusing a new connection of HTTP 503: Service Unavailable. A message is |
| 50 | + /// also logged at debug level. |
| 51 | + public static let defaultConnectionResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? = { (limit, clientSource) in |
| 52 | + Log.debug("Rejected connection from \(clientSource): Maximum connection limit of \(limit) reached.") |
| 53 | + return (.serviceUnavailable, "") |
| 54 | + } |
| 55 | + |
| 56 | + /// Defines the maximum size for the body of an incoming request, in bytes. If a request body is larger |
| 57 | + /// than this limit, it will be rejected and the connection will be closed. |
| 58 | + /// |
| 59 | + /// A value of `nil` means no limit. |
| 60 | + public let requestSizeLimit: Int? |
| 61 | + |
| 62 | + /// Defines the maximum number of concurrent connections that a server should accept. Clients attempting |
| 63 | + /// to connect when this limit has been reached will be rejected. |
| 64 | + public let connectionLimit: Int? |
| 65 | + |
| 66 | + /** |
| 67 | + Determines the response message and HTTP status code used to respond to clients whose request exceeds |
| 68 | + the `requestSizeLimit`. The current limit and client's address are provided as parameters to enable a |
| 69 | + message to be logged, and/or a response to be provided back to the client. |
| 70 | + |
| 71 | + The returned tuple indicates the HTTP status code and response body to send to the client. If `nil` is |
| 72 | + returned, then no response will be sent. |
| 73 | + |
| 74 | + Example usage: |
| 75 | + ``` |
| 76 | + let oversizeResponse: (Int, String) -> (HTTPStatusCode, String)? = { (limit, client) in |
| 77 | + Log.debug("Rejecting request from \(client): Exceeds limit of \(limit) bytes") |
| 78 | + return (.requestTooLong, "Your request exceeds the limit of \(limit) bytes.\r\n") |
| 79 | + } |
| 80 | + ``` |
| 81 | + */ |
| 82 | + public let requestSizeResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? |
| 83 | + |
| 84 | + /** |
| 85 | + Determines the response message and HTTP status code used to respond to clients that attempt to connect |
| 86 | + while the server is already servicing the maximum number of connections, as defined by `connectionLimit`. |
| 87 | + The current limit and client's address are provided as parameters to enable a message to be logged, |
| 88 | + and/or a response to be provided back to the client. |
| 89 | + |
| 90 | + The returned tuple indicates the HTTP status code and response body to send to the client. If `nil` is |
| 91 | + returned, then no response will be sent. |
| 92 | + |
| 93 | + Example usage: |
| 94 | + ``` |
| 95 | + let connectionResponse: (Int, String) -> (HTTPStatusCode, String)? = { (limit, client) in |
| 96 | + Log.debug("Rejecting request from \(client): Connection limit \(limit) reached") |
| 97 | + return (.serviceUnavailable, "Service busy - please try again later.\r\n") |
| 98 | + } |
| 99 | + ``` |
| 100 | + */ |
| 101 | + public let connectionResponseGenerator: (Int, String) -> (HTTPStatusCode, String)? |
| 102 | + |
| 103 | + /// Create a `ServerOptions` to determine the behaviour of a `Server`. |
| 104 | + /// |
| 105 | + /// - parameter requestSizeLimit: The maximum size of an incoming request body. Defaults to `ServerOptions.defaultRequestSizeLimit`. |
| 106 | + /// - parameter connectionLimit: The maximum number of concurrent connections. Defaults to `ServerOptions.defaultConnectionLimit`. |
| 107 | + /// - parameter requestSizeResponseGenerator: A closure producing a response to send to a client when an over-sized request is rejected. Defaults to `ServerOptions.defaultRequestSizeResponseGenerator`. |
| 108 | + /// - 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`. |
| 109 | + public init(requestSizeLimit: Int? = ServerOptions.defaultRequestSizeLimit, |
| 110 | + connectionLimit: Int? = ServerOptions.defaultConnectionLimit, |
| 111 | + requestSizeResponseGenerator: @escaping (Int, String) -> (HTTPStatusCode, String)? = ServerOptions.defaultRequestSizeResponseGenerator, |
| 112 | + connectionResponseGenerator: @escaping (Int, String) -> (HTTPStatusCode, String)? = ServerOptions.defaultConnectionResponseGenerator) |
| 113 | + { |
| 114 | + self.requestSizeLimit = requestSizeLimit |
| 115 | + self.connectionLimit = connectionLimit |
| 116 | + self.requestSizeResponseGenerator = requestSizeResponseGenerator |
| 117 | + self.connectionResponseGenerator = connectionResponseGenerator |
| 118 | + } |
| 119 | + |
| 120 | +} |
0 commit comments