<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">&lt;<a href="mailto:swift-server-dev@swift.org" target="_blank">swift-server-dev@swift.org</a>&gt;</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&#39;t cover the full spectrum of what we want to achieve. The main thing that&#39;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 (&amp;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&#39;ll get all the values as a list of strings. (headers[&quot;set-cookie&quot;] returns [&quot;foo&quot;, &quot;bar&quot;] for &quot;Set-Cookie: foo\r\nSET-COOKIE: bar\r\n&quot;)<br>
<br>
Not sure if that&#39;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&#39;re happy with what we got.<br>
<br>
If you&#39;re interested, there&#39;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) -&gt; HTTPBodyProcessing<br>
<br>
public struct HTTPRequest {<br>
   public let method : HTTPMethod<br>
   public let target : String /* e.g. &quot;/foo/bar?buz=qux&quot; */<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&lt;POSIXError, ()&gt;) -&gt; Void)<br>
   func writeBody(data: Data, completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br>
<br>
   func done() /* convenience */<br>
   func done(completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void))<br>
   func abort()<br>
}<br>
<br>
public typealias HTTPBodyHandler = (HTTPBodyChunk) -&gt; Void<br>
<br>
public enum HTTPBodyProcessing {<br>
    case discardBody /* if you&#39;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&#39;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) -&gt; [String]<br>
   public func makeIterator() -&gt; IndexingIterator&lt;Array&lt;(<wbr>String, String)&gt;&gt;<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&#39;s the demo code for a simple echo server<br>
<br>
--- SNIP ---<br>
serve { (req, res) in<br>
    if req.target == &quot;/echo&quot; {<br>
        guard req.httpVersion == (1, 1) else {<br>
            /* HTTP/1.0 doesn&#39;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>