<div dir="ltr">I like to mention Edge reimplement HTTP include POSIX.<div><a href="https://github.com/SwiftOnEdge/Edge">https://github.com/SwiftOnEdge/Edge</a><br></div><div><br></div><div>Will SSS use POSIX?</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Sat, Mar 25, 2017 at 12:00 AM, Johannes Weiß via swift-server-dev <span dir="ltr"><<a href="mailto:swift-server-dev@swift.org" target="_blank">swift-server-dev@swift.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi swift-server-dev,<br>
<br>
First of all, sorry for the delay!<br>
<br>
On the last HTTP meeting we were talking about how to represent HTTP/1.1 and how to handle trailers and request/response body streaming. I was describing what we use internally. There was some interest to see the actual APIs, please find them below.<br>
<br>
The sketch doesn't cover the full spectrum of what we want to achieve. The main thing that's missing is that there is currently no data type for a HTTP response. We just build the response on the fly using the HTTPResponseWriter. IMHO that can easily be added by basically making the response headers, response code, etc a new type HTTPResponse. HTTPResponseWriter would then take a value of that.<br>
<br>
The main design choices we made are<br>
- value type for HTTP request<br>
- having the HTTP body (&trailers) not part of the request type as we need to support request body streaming<br>
- writing the response is asynchronous, the result (success/failure) of the operation reported in the callback<br>
- headers not being a dictionary but its own data structure, basically [(String, String)] but when queried with a String, you'll get all the values as a list of strings. (headers["set-cookie"] returns ["foo", "bar"] for "Set-Cookie: foo\r\nSET-COOKIE: bar\r\n")<br>
<br>
Not sure if that's interesting but our stuff is implemented on top of DispatchIO. We support both request body as well as response body streaming without sacrificing a thread per connection/request.<br>
<br>
For HTTP, this is our lowest level API, we built a higher-level web framework on top of that and so far we're happy with what we got.<br>
<br>
If you're interested, there's some very simple demo code (HTTP echo server) below the APIs.<br>
<br>
Please let us know what you think.<br>
<br>
--- SNIP ---<br>
/* a web app is a function that gets a HTTPRequest and a HTTPResponseWriter and returns a function which processes the HTTP request body in chunks as they arrive */<br>
public typealias WebApp = (HTTPRequest, HTTPResponseWriter) -> HTTPBodyProcessing<br>
<br>
public struct HTTPRequest {<br>
public let method : HTTPMethod<br>
public let target : String /* e.g. "/foo/bar?buz=qux" */<br>
public let httpVersion : HTTPVersion<br>
public let headers : HTTPHeaders<br>
}<br>
<br>
public protocol HTTPResponseWriter: class {<br>
func writeResponse(status: HTTPResponseStatus, transferEncoding: HTTPTransferEncoding)<br>
<br>
func writeHeader(key: String, value: String)<br>
<br>
func writeTrailer(key: String, value: String)<br>
<br>
func writeBody(data: DispatchData) /* convenience */<br>
func writeBody(data: Data) /* convenience */<br>
func writeBody(data: DispatchData, completion: @escaping (Result<POSIXError, ()>) -> Void)<br>
func writeBody(data: Data, completion: @escaping (Result<POSIXError, ()>) -> Void)<br>
<br>
func done() /* convenience */<br>
func done(completion: @escaping (Result<POSIXError, ()>) -> Void))<br>
func abort()<br>
}<br>
<br>
public typealias HTTPBodyHandler = (HTTPBodyChunk) -> Void<br>
<br>
public enum HTTPBodyProcessing {<br>
case discardBody /* if you're not interested in the body, equivalent to `.processBody { _ in }` */<br>
case processBody(handler: HTTPBodyHandler)<br>
}<br>
<br>
public enum HTTPBodyChunk {<br>
case chunk(data: DispatchData) /* a new bit of the HTTP request body has arrived */<br>
case failed(error: HTTPParserError) /* error while streaming the HTTP request body, eg. connection closed */<br>
case trailer(key: String, value: String) /* trailer has arrived (this we actually haven't implemented yet) */<br>
case end /* body and trailers finished */<br>
}<br>
<br>
public struct HTTPHeaders : Sequence {<br>
private let storage: [String:[String]] /* lower cased keys */<br>
private let original: [(String, String)] /* original casing */<br>
public var description: String { return original.description }<br>
<br>
public subscript(key: String) -> [String]<br>
public func makeIterator() -> IndexingIterator<Array<(<wbr>String, String)>><br>
}<br>
<br>
/* from here on just for completeness really */<br>
<br>
public typealias HTTPVersion = (Int, Int)<br>
<br>
public enum HTTPTransferEncoding {<br>
case identity(contentLength: UInt)<br>
case chunked<br>
}<br>
<br>
public enum HTTPResponseStatus: Equatable {<br>
/* use custom if you want to use a non-standard response code or<br>
have it available in a (UInt, String) pair from a higher-level web framework. */<br>
case custom(code: UInt, reasonPhrase: String)<br>
<br>
/* all the codes from <a href="http://www.iana.org/assignments/http-status-codes" rel="noreferrer" target="_blank">http://www.iana.org/<wbr>assignments/http-status-codes</a> */<br>
case `continue`<br>
case switchingProtocols<br>
case processing<br>
case ok<br>
case created<br>
case accepted<br>
case nonAuthoritativeInformation<br>
case noContent<br>
case resetContent<br>
case partialContent<br>
case multiStatus<br>
case alreadyReported<br>
case imUsed<br>
case multipleChoices<br>
case movedPermanently<br>
case found<br>
case seeOther<br>
case notModified<br>
case useProxy<br>
case temporaryRedirect<br>
case permanentRedirect<br>
case badRequest<br>
case unauthorized<br>
case paymentRequired<br>
case forbidden<br>
case notFound<br>
case methodNotAllowed<br>
case notAcceptable<br>
case proxyAuthenticationRequired<br>
case requestTimeout<br>
case conflict<br>
case gone<br>
case lengthRequired<br>
case preconditionFailed<br>
case payloadTooLarge<br>
case uriTooLong<br>
case unsupportedMediaType<br>
case rangeNotSatisfiable<br>
case expectationFailed<br>
case misdirectedRequest<br>
case unprocessableEntity<br>
case locked<br>
case failedDependency<br>
case upgradeRequired<br>
case preconditionRequired<br>
case tooManyRequests<br>
case requestHeaderFieldsTooLarge<br>
case unavailableForLegalReasons<br>
case internalServerError<br>
case notImplemented<br>
case badGateway<br>
case serviceUnavailable<br>
case gatewayTimeout<br>
case httpVersionNotSupported<br>
case variantAlsoNegotiates<br>
case insufficientStorage<br>
case loopDetected<br>
case notExtended<br>
case networkAuthenticationRequired<br>
}<br>
<br>
public enum HTTPMethod {<br>
/* everything that http_parser.[ch] supports */<br>
case DELETE<br>
case GET<br>
case HEAD<br>
case POST<br>
case PUT<br>
case CONNECT<br>
case OPTIONS<br>
case TRACE<br>
case COPY<br>
case LOCK<br>
case MKCOL<br>
case MOVE<br>
case PROPFIND<br>
case PROPPATCH<br>
case SEARCH<br>
case UNLOCK<br>
case BIND<br>
case REBIND<br>
case UNBIND<br>
case ACL<br>
case REPORT<br>
case MKACTIVITY<br>
case CHECKOUT<br>
case MERGE<br>
case MSEARCH<br>
case NOTIFY<br>
case SUBSCRIBE<br>
case UNSUBSCRIBE<br>
case PATCH<br>
case PURGE<br>
case MKCALENDAR<br>
case LINK<br>
case UNLINK<br>
}<br>
--- SNAP ---<br>
<br>
Here's the demo code for a simple echo server<br>
<br>
--- SNIP ---<br>
serve { (req, res) in<br>
if req.target == "/echo" {<br>
guard req.httpVersion == (1, 1) else {<br>
/* HTTP/1.0 doesn't support chunked encoding */<br>
res.writeResponse(status: .httpVersionNotSupported, transferEncoding: .identity(contentLength: 0))<br>
res.done()<br>
return .discardBody<br>
}<br>
res.writeResponse(status: .ok, transferEncoding: .chunked)<br>
return .processBody { chunk in<br>
switch chunk {<br>
case .chunk(let data):<br>
res.writeBody(data: data)<br>
case .end:<br>
res.done()<br>
default:<br>
res.abort()<br>
}<br>
}<br>
} else { ... }<br>
}<br>
--- SNAP ---<br>
<span class="HOEnZb"><font color="#888888"><br>
--<br>
Johannes<br>
<br>
______________________________<wbr>_________________<br>
swift-server-dev mailing list<br>
<a href="mailto:swift-server-dev@swift.org">swift-server-dev@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-server-dev" rel="noreferrer" target="_blank">https://lists.swift.org/<wbr>mailman/listinfo/swift-server-<wbr>dev</a><br>
</font></span></blockquote></div><br></div>