-
Notifications
You must be signed in to change notification settings - Fork 40
Proposal: Some suggestions for better, more readble API #83
Description
I don't know where I can submit proposals for api changes so I opened this issue.
HTTPServer port
server port is determined in start(port:, handler:) method, while I think a server can start once with a one specified port. I could find a port property in HTTPServer class while with this design it have to be ports array. I propose relocating port argument from start() to class init() method.
HTTPResponse write methods
Personally I prefer something like response.header.write() instead of response.writeHeader(), response.body.write() instead of response.writeBody() and response.trailer.write() instead of response.writeTrailer(). Also we can keep room to extend header, body and trailer properties later.
HTTP version static variables
HTTP versions are limited, thus having HTTPVersion.v1_1 and HTTPVersion.v2_0 static vars might be fair.
A more complicated HTTPHeaders
HTTP headers are simply defined as a pair of strings. But for many of headers there have a set of defined values or a structured value. This implementation is prone to typo and human errors. So I propose to have some setters for frequent ones.
Implementation detail
// To set 'Range'
let range = 100... // half open or closed
headers.set(range: range) // result: 'Range: bytes 100-'
// To set 'Accept-Encoding'
headers.set(acceptEncodings: [.deflate, .gzip, .brotli, .identity])
// or
headers.set(acceptEncoding: .deflate, quality: 1.0)
headers.add(acceptEncoding: .gzip, quality: 0.8) // Note add instead of set
headers.add(acceptEncoding: .identity, quality: 0.5)
// values are defined in a enum/struct
// To set 'Cookie'
let cookie = HTTPCookie(properties: [.name : "name", .expires: Date(timeIntervalSinceNow: 3600)])
headers.set(cookie: cookie)
...
headers.add(cookie: cookie2)
// To set 'Accept-Language'
let locale = Locale.current
headers.set(acceptLanguages: [locale])
// also 'add(acceptLanguage:)' method to add progressively like Accept-Encoding
// To set 'Accept-Charset'
headers.set(acceptCharset: String.Encoding.utf8)For Content-Type, we would define this struct in HTTPHeaders (should be extended with more types):
extension HTTPHeaders {
struct MIMEType: RawRepresentable {
public var rawValue: String
public typealias RawValue = String
public init(rawValue: String) {
self.rawValue = rawValue
}
static let javascript = MIMEType(rawValue: "application/javascript")
static let json = MIMEType(rawValue: "application/json")
static let pdf = MIMEType(rawValue: "application/pdf")
static let stream = MIMEType(rawValue: "application/octet-stream")
static let zip = MIMEType(rawValue: "application/zip")
// Texts
static let css = MIMEType(rawValue: "text/css")
static let html = MIMEType(rawValue: "text/html")
static let plainText = MIMEType(rawValue: "text/plain")
static let xml = MIMEType(rawValue: "text/xml")
// Images
static let gif = MIMEType(rawValue: "image/gif")
static let jpeg = MIMEType(rawValue: "image/jpeg")
static let png = MIMEType(rawValue: "image/png")
}
}And then we can set MIMEs this way:
headers.set(accept: .plainText)
headers.set(contentType: .json, encoding: .utf8)
// method converts 'NSStringEncoding' to IANA by 'CFStringConvertEncodingToIANACharSetName()' method
// Also similar approach for 'Pragma', 'TE' , etc...
headers.set(pragma: .noCache)
headers.set(transferEncodings: [.trailers, .deflate])For some headers that accept date or integer or url, we implement methods accordingly:
// to set 'Content-Length'
headers.set(contentLength: 100)
// to set 'Date', 'If-Modified-Since', etc...
let fileModifiedDate = file.modifiedDate // or Date(timeIntervalSinceReferenceDate: 1000000)
headers.set(ifModifiedSince: fileModifiedDate)
// to set 'Referer' or 'Origin'
headers.set(referer: URL(string: "https://www.apple.com")!)About Authorization header, we can have something like that, though I think it should be more refined and must be evaluated either we need them or not:
headers.set(authorizationType: .basic, user: "example", password: "pass")Obviously, we must have some methods to fetch values. For example headers.contentLength will return a Int64? value, if it's defined by user, or headers.cookies will return a [HTTPCookie] array, headers.contentType will return a HTTPHeaders.MIMEType? optional, and so on and so forth.
I hope I can participate in implementing some parts if proposals are accepted
Best wish