-
Notifications
You must be signed in to change notification settings - Fork 42
Proposal: Some suggestions for better, more readble API #83
Comments
About the content type, I have this code from 2+ years ago (Swift 1.x or 2) for having it more statically defined. I don't suggest using this directly, as performance is probably not great and names could be more fitting, but I remember having a look at the specs to make this so at least the structure should be good 👍 private let permittedMIMECharacters: NSCharacterSet = .alphanumericCharacterSet()
private let trimmedMIMECharacters: NSCharacterSet = .whitespaceAndNewlineCharacterSet()
public struct MIMEContentType {
public enum MIMEType: String {
case Application = "application"
case Audio = "audio"
case Example = "example"
case Image = "image"
case Message = "message"
case Model = "model"
case MultiPart = "multipart"
case Text = "text"
case Video = "video"
}
public enum RegistrationTree: String {
case Standards = ""
case Vendor = "vnd."
case Personal = "prs."
case Unregistered = "x."
static func splitStringWithRegistrationTree(string: String) -> (RegistrationTree, String) {
if let dotIndex = string.characters.indexOf(".") {
let treeString = string[string.startIndex ... dotIndex]
if let tree = RegistrationTree(rawValue: treeString) {
return (tree, string[dotIndex.successor() ..< string.endIndex])
}
}
return (.Standards, string)
}
}
public enum Suffix: String {
case XML = "xml"
case JSON = "json"
case BER = "ber"
case DER = "der"
case FastInfoSet = "fastinfoset"
case WBXML = "wbxml"
case Zip = "zip"
case CBOR = "cbor"
}
public let type: MIMEType
public let tree: RegistrationTree
public let subtype: String
public let suffix: Suffix?
public let parameters: [String: String]
public init(type: MIMEType, tree: RegistrationTree = .Standards, subtype: String, suffix: Suffix? = nil, parameters: [String: String] = [:]) {
self.type = type
self.tree = tree
self.subtype = subtype
self.suffix = suffix
self.parameters = parameters
}
public init?(contentType: String) {
let trimmedContentType = contentType.stringByTrimmingCharactersInSet(trimmedMIMECharacters)
let colonSeparatedComponents = trimmedContentType.componentsSeparatedByString(";").map({ $0.stringByTrimmingCharactersInSet(trimmedMIMECharacters) })
let (fullTypeString, parametersStrings) = (colonSeparatedComponents.first!, colonSeparatedComponents.dropFirst())
let slashSeparatedComponents = fullTypeString.componentsSeparatedByString("/")
if slashSeparatedComponents.count != 2 { return nil }
let (typeString, fullSubtypeString) = (slashSeparatedComponents.first!, slashSeparatedComponents.last!)
if let type = MIMEType(rawValue: typeString) {
self.type = type
} else { return nil }
let (tree, afterTreeSubtypeString) = RegistrationTree.splitStringWithRegistrationTree(fullSubtypeString)
self.tree = tree
let suffixSeparatedComponents = afterTreeSubtypeString.componentsSeparatedByString("+")
if suffixSeparatedComponents.count > 2 { return nil }
let (subtypeString, suffixString) = (suffixSeparatedComponents.first!, suffixSeparatedComponents.count > 1 ? suffixSeparatedComponents.last : nil)
self.subtype = subtypeString
self.suffix = suffixString.flatMap({ Suffix(rawValue: $0)})
let parametersStringsPairs = Array(parametersStrings).flatMap({ (parameterString: String) -> (String, String)? in
let parameterComponents = parameterString.componentsSeparatedByString("=")
if parameterComponents.count != 2 { return nil }
return (parameterComponents.first!, parameterComponents.last!)
})
self.parameters = [String: String](parametersStringsPairs)
}
public var string: String {
return "\(type.rawValue)/\(tree.rawValue)\(subtype)" + ((suffix?.rawValue).map({ "+\($0)" }) ?? "") + parameters.map({ "; \($0)=\($1)" }).joinWithSeparator("")
}
} |
First I don't know how a server can use parts of MIME. "Accept" and
"Content-Type" should be "exactly" same I'm afraid. Thus comparing
parts will not help.
Second, Parsing from/to string is a time-taking procedure for this
struct, which makes it unsuitable for a server with hundreds of
request per second
…On Sat, Nov 18, 2017 at 7:49 PM, Davide De Franceschi ***@***.***> wrote:
About the content type, I have this code from 2+ years ago (Swift 1.x or 2)
for having it more statically defined. I don't suggest using this directly,
as performance is probably not great and names could be more fitting, but I
remember having a look at the specs to make this so at least the structure
should be good
private let permittedMIMECharacters: NSCharacterSet =
.alphanumericCharacterSet()
private let trimmedMIMECharacters: NSCharacterSet =
.whitespaceAndNewlineCharacterSet()
public struct MIMEContentType {
public enum MIMEType: String {
case Application = "application"
case Audio = "audio"
case Example = "example"
case Image = "image"
case Message = "message"
case Model = "model"
case MultiPart = "multipart"
case Text = "text"
case Video = "video"
}
public enum RegistrationTree: String {
case Standards = ""
case Vendor = "vnd."
case Personal = "prs."
case Unregistered = "x."
static func splitStringWithRegistrationTree(string: String) ->
(RegistrationTree, String) {
if let dotIndex = string.characters.indexOf(".") {
let treeString = string[string.startIndex ... dotIndex]
if let tree = RegistrationTree(rawValue: treeString) {
return (tree, string[dotIndex.successor() ..< string.endIndex])
}
}
return (.Standards, string)
}
}
public enum Suffix: String {
case XML = "xml"
case JSON = "json"
case BER = "ber"
case DER = "der"
case FastInfoSet = "fastinfoset"
case WBXML = "wbxml"
case Zip = "zip"
case CBOR = "cbor"
}
public let type: MIMEType
public let tree: RegistrationTree
public let subtype: String
public let suffix: Suffix?
public let parameters: [String: String]
public init(type: MIMEType, tree: RegistrationTree = .Standards, subtype:
String, suffix: Suffix? = nil, parameters: [String: String] = [:]) {
self.type = type
self.tree = tree
self.subtype = subtype
self.suffix = suffix
self.parameters = parameters
}
public init?(contentType: String) {
let trimmedContentType =
contentType.stringByTrimmingCharactersInSet(trimmedMIMECharacters)
let colonSeparatedComponents =
trimmedContentType.componentsSeparatedByString(";").map({
$0.stringByTrimmingCharactersInSet(trimmedMIMECharacters) })
let (fullTypeString, parametersStrings) =
(colonSeparatedComponents.first!, colonSeparatedComponents.dropFirst())
let slashSeparatedComponents =
fullTypeString.componentsSeparatedByString("/")
if slashSeparatedComponents.count != 2 { return nil }
let (typeString, fullSubtypeString) = (slashSeparatedComponents.first!,
slashSeparatedComponents.last!)
if let type = MIMEType(rawValue: typeString) {
self.type = type
} else { return nil }
let (tree, afterTreeSubtypeString) =
RegistrationTree.splitStringWithRegistrationTree(fullSubtypeString)
self.tree = tree
let suffixSeparatedComponents =
afterTreeSubtypeString.componentsSeparatedByString("+")
if suffixSeparatedComponents.count > 2 { return nil }
let (subtypeString, suffixString) = (suffixSeparatedComponents.first!,
suffixSeparatedComponents.count > 1 ? suffixSeparatedComponents.last : nil)
self.subtype = subtypeString
self.suffix = suffixString.flatMap({ Suffix(rawValue: $0)})
let parametersStringsPairs = Array(parametersStrings).flatMap({
(parameterString: String) -> (String, String)? in
let parameterComponents =
parameterString.componentsSeparatedByString("=")
if parameterComponents.count != 2 { return nil }
return (parameterComponents.first!, parameterComponents.last!)
})
self.parameters = [String: String](parametersStringsPairs)
}
public var string: String {
return "\(type.rawValue)/\(tree.rawValue)\(subtype)" +
((suffix?.rawValue).map({ "+\($0)" }) ?? "") + parameters.map({ ";
\($0)=\($1)" }).joinWithSeparator("")
}
}
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
It's not a likely need, but I can think of a few examples:
Agree. Naming it
I'm not completely certain on this one. While I agree that it's probably an unnecessary cost to just structure the field for any incoming request, whenever the server is interested in comparing only a subset of it then it ultimately does the same computation: skip the type + compare the subtype. It actually saves comparing until the Your proposal is certainly working and stronger typed than just using I’m also not too convinced about defining the most common constants by "hiding" the rest, e.g. Also I think |
charset is not a part of MIMEType. I rename that struct to `MediaType`
already and added another `ContentType` which embeds charset and other
parameters in separate fields.
I implemented standard IANA types. There are aliases for json e.g as
you noted, but handling non-standard aliasess is out of scope. (for
now at least) However a server implementation can check `Accept`
header against all aliases in `switch` statement to workaround.
I prefer `.json` to `applicationJSON`, it's neater and doesn't expose
unnecessary information. The first part is always `application`
according to IANA, why we should include that in variable name.
MediaType struct conforms to `ExpressibleByStringLiteral` already.
…On Sun, Nov 19, 2017 at 10:48 PM, Davide De Franceschi ***@***.***> wrote:
First I don't know how a server can use parts of MIME.
It's not a likely need, but I can think of a few examples:
Using a third party library/another service for all image/ types, so
checking only on that before forwarding
Reading the charset parameter to use different String decodings
"Accept" and "Content-Type" should be "exactly" same I'm afraid.
Agree. Naming it MIMEContentType was just because when I wrote that I needed
it only for that header field, and I didn't know better.
Second, Parsing from/to string is a time-taking procedure for this struct,
which makes it unsuitable for a server with hundreds of request per second
I'm not completely certain on this one. While I agree that it's probably an
unnecessary cost to just structure the field for any incoming request,
whenever the server is interested in comparing only a subset of it then it
ultimately does the same computation: skip the type + compare the subtype.
It actually saves comparing until the /! (Though I think that enumerating
and comparing are very similar in performance terms)
Your proposal is certainly working and stronger typed than just using
String, but I feel like other types in this project try to expose the rules
around it as better as possible (see status codes and methods).
I’m also not too convinced about defining the most common constants by
"hiding" the rest, e.g. json. I know that's the grand majority of JSON
usage, but there are other things which might seem related. Even at the cost
of verbosity, I think .applicationJSON or .application_json would be better.
Also I think ExpressibleByStringLiteral is a good idea in your example ;)
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
If we are concerned about performance, we should not use Swift strings for headers in the first place. Very few HTTP headers allow or cary Unicode strings, and Swift Strings are very complex objects. Everything about them is pretty expensive, creating them (lots of copying here, no interning), working them, etc. Wrt the parsing. Parsing the byte buffers at the HTTP parser level (which has to look at every byte anyways) and before creating real objects is very likely cheaper than doing unicode parsing on unicode strings. Instead of creating 3(+!) objects and a copy tons of buffer: header = Header(String(cString: header), String(cString: value)) create a single object (e.g. an enum, or class) and parse ASCII byte buffers: header = .ContentLength(atoi(value)) Also remember that this is intended as a base library for higher level frameworks. Those will have different abstractions and APIs on how to access those things. |
There was a discussion here about @helje5 Our discussion was about cost/benefit of ContentType parsing overhead, not how to get better performance. |
Sorry if I expressed my point poorly. My specific point wasn't about
This statement contradicts itself. Not sure what you mean by "parsing MIME into tree". Do we want to parse MIME multiparts? That would be very useful but belongs into a separate library for sure. I think in here we really just want the flat HTTP subset of MIME, if at all. The parsing of low-level objects into very high level objects like Strings, could still be done on demand. I'm talking about the low level parsing. But again: All this has been discussed on list. Please just check the archives, it makes no sense to re-raise the discussion again. I think the conclusion was: we do |
@DeFrenZ you were right about design. I've changed it. |
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 aport
property inHTTPServer
class while with this design it have to beports
array. I propose relocatingport
argument fromstart()
to classinit()
method.HTTPResponse write methods
Personally I prefer something like
response.header.write()
instead ofresponse.writeHeader()
,response.body.write()
instead ofresponse.writeBody()
andresponse.trailer.write()
instead ofresponse.writeTrailer()
. Also we can keep room to extendheader
,body
andtrailer
properties later.HTTP version static variables
HTTP versions are limited, thus having
HTTPVersion.v1_1
andHTTPVersion.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
For Content-Type, we would define this struct in
HTTPHeaders
(should be extended with more types):And then we can set MIMEs this way:
For some headers that accept date or integer or url, we implement methods accordingly:
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:Obviously, we must have some methods to fetch values. For example
headers.contentLength
will return aInt64?
value, if it's defined by user, orheaders.cookies
will return a[HTTPCookie]
array,headers.contentType
will return aHTTPHeaders.MIMEType?
optional, and so on and so forth.I hope I can participate in implementing some parts if proposals are accepted
Best wish
The text was updated successfully, but these errors were encountered: