<div dir="ltr"><div dir="ltr" class="gmail_msg"><div class="gmail_quote gmail_msg"><div dir="ltr" class="gmail_msg">On Thu, Apr 6, 2017 at 3:53 AM Johannes Weiß &lt;<a href="mailto:johannesweiss@apple.com" class="gmail_msg" target="_blank">johannesweiss@apple.com</a>&gt; wrote:<br class="gmail_msg"></div><blockquote class="gmail_quote gmail_msg" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="auto" class="gmail_msg"><div class="gmail_msg"></div><div class="gmail_msg">Hi Colin,</div></div><div dir="auto" class="gmail_msg"><div class="gmail_msg"><br class="gmail_msg"></div><blockquote type="cite" class="gmail_msg"><div dir="ltr" class="gmail_msg"><div class="gmail_msg">Is writing compositional middleware a goal for this iteration of the API? I noticed that the HTTPResponseWriter doesn&#39;t appear to allow for, e.g. writing a single header and then passing the writer on (as one would do in, say, an anti-CSRF middleware). Possible I&#39;m missing something though!</div></div></blockquote><div class="gmail_msg"><br class="gmail_msg"></div></div><div dir="auto" class="gmail_msg">Good point! The version 1 actually only had writeHeader(key:value:) and for the sake of having a HTTPResponse type (that could then be used by an HTTP client) I got rid of that. But maybe that was over the top and we should go back to what I had before.</div></blockquote><div class="gmail_msg"><br class="gmail_msg"></div></div></div><div dir="ltr" class="gmail_msg"><div class="gmail_quote gmail_msg"><div class="gmail_msg">I think having the HTTPResponse is nice. I would suggest these changes:</div><div class="gmail_msg"><br></div><div class="gmail_msg">writeResponse(..., finishHeaders = true)</div><div class="gmail_msg">writeHeader(...)</div><div class="gmail_msg">finishHeaders() [-&gt; Bool] // It is an error to call writeHeader after finishHeaders. Calling finishHeaders twice is a no-op. (Return value could differentiates those cases?)</div><div class="gmail_msg">writeBody(...) // Calls finishHeaders()</div><div class="gmail_msg"><br></div><div class="gmail_msg">Sorry for the brevity of my sketch, let me know if that&#39;s unclear in any way.</div><div class="gmail_msg"><br></div><div class="gmail_msg">Thanks,</div><div class="gmail_msg">-Colin</div><div class="gmail_msg"><br></div></div></div><div dir="ltr" class="gmail_msg"><div class="gmail_quote gmail_msg"><div class="gmail_msg"> </div><blockquote class="gmail_quote gmail_msg" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="auto" class="gmail_msg"><div class="gmail_msg"></div><div class="gmail_msg">-- </div><div class="gmail_msg">  Johannes</div></div><div dir="auto" class="gmail_msg"><div class="gmail_msg"><br class="gmail_msg"><blockquote type="cite" class="gmail_msg"><div dir="ltr" class="gmail_msg"><div class="gmail_msg"><br class="gmail_msg"></div><div class="gmail_msg">-Colin</div></div><br class="gmail_msg"><div class="gmail_quote gmail_msg"><div dir="ltr" class="gmail_msg">On Wed, Apr 5, 2017 at 1:36 PM Johannes Weiß via swift-server-dev &lt;<a href="mailto:swift-server-dev@swift.org" class="gmail_msg" target="_blank">swift-server-dev@swift.org</a>&gt; wrote:<br class="gmail_msg"></div><blockquote class="gmail_quote gmail_msg" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi,<br class="gmail_msg">
<br class="gmail_msg">
First of all, thanks very much to Helge for carefully analysing (and implementing) the first API sketch. As promised, I reworked it a bit.<br class="gmail_msg">
<br class="gmail_msg">
Changes:<br class="gmail_msg">
- extracted HTTPResponse as its own value type (so it could also be used for a client)<br class="gmail_msg">
- added Helge&#39;s suggestions:<br class="gmail_msg">
  * proper backpressure support<br class="gmail_msg">
  * being able to ignore further parts of the HTTP body (stop parameter)<br class="gmail_msg">
<br class="gmail_msg">
If I forgot something important, please let me know. I can&#39;t promise that I&#39;ll address it before the meeting as I&#39;m in another office tomorrow for meetings and probably won&#39;t be able to attend either.<br class="gmail_msg">
<br class="gmail_msg">
Please find everything below...<br class="gmail_msg">
<br class="gmail_msg">
Cheers,<br class="gmail_msg">
  Johannes<br class="gmail_msg">
<br class="gmail_msg">
--- SNIP ---<br class="gmail_msg">
/* 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 class="gmail_msg">
<br class="gmail_msg">
public typealias WebApp = (HTTPRequest, HTTPResponseWriter) -&gt; HTTPBodyProcessing<br class="gmail_msg">
<br class="gmail_msg">
public struct HTTPRequest {<br class="gmail_msg">
  public var method : HTTPMethod<br class="gmail_msg">
  public var target : String /* e.g. &quot;/foo/bar?buz=qux&quot; */<br class="gmail_msg">
  public var httpVersion : HTTPVersion<br class="gmail_msg">
  public var headers : HTTPHeaders<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public struct HTTPResponse {<br class="gmail_msg">
  public var httpVersion : HTTPVersion<br class="gmail_msg">
  public var status: HTTPResponseStatus<br class="gmail_msg">
  public var transferEncoding: HTTPTransferEncoding<br class="gmail_msg">
  public var headers: HTTPHeaders<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public protocol HTTPResponseWriter: class {<br class="gmail_msg">
  func writeContinue(headers: HTTPHeaders = HTTPHeaders()) /* to send an HTTP `100 Continue` */<br class="gmail_msg">
<br class="gmail_msg">
  func writeResponse(_ response: HTTPResponse)<br class="gmail_msg">
<br class="gmail_msg">
  func writeTrailer(key: String, value: String)<br class="gmail_msg">
<br class="gmail_msg">
  func writeBody(data: DispatchData) /* convenience */<br class="gmail_msg">
  func writeBody(data: Data) /* convenience */<br class="gmail_msg">
  func writeBody(data: DispatchData, completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br class="gmail_msg">
  func writeBody(data: Data, completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br class="gmail_msg">
<br class="gmail_msg">
  func done() /* convenience */<br class="gmail_msg">
  func done(completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br class="gmail_msg">
  func abort()<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public typealias HTTPBodyHandler = (HTTPBodyChunk, inout Bool) -&gt; Void /* the Bool can be set to true when we don&#39;t want to process anything further */<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPBodyProcessing {<br class="gmail_msg">
   case discardBody /* if you&#39;re not interested in the body */<br class="gmail_msg">
   case processBody(handler: HTTPBodyHandler)<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPBodyChunk {<br class="gmail_msg">
  case chunk(data: DispatchData, finishedProcessing: () -&gt; Void) /* a new bit of the HTTP request body has arrived, finishedProcessing() must be called when done with that chunk */<br class="gmail_msg">
  case failed(error: HTTPParserError) /* error while streaming the HTTP request body, eg. connection closed */<br class="gmail_msg">
  case trailer(key: String, value: String) /* trailer has arrived (this we actually haven&#39;t implemented yet) */<br class="gmail_msg">
  case end /* body and trailers finished */<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public struct HTTPHeaders {<br class="gmail_msg">
  var storage: [String:[String]]     /* lower cased keys */<br class="gmail_msg">
  var original: [(String, String)]   /* original casing */<br class="gmail_msg">
  var description: String<br class="gmail_msg">
<br class="gmail_msg">
  subscript(key: String) -&gt; [String]<br class="gmail_msg">
  func makeIterator() -&gt; IndexingIterator&lt;Array&lt;(String, String)&gt;&gt;<br class="gmail_msg">
<br class="gmail_msg">
  init(_ headers: [(String, String)] = [])<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public typealias HTTPVersion = (Int, Int)<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPTransferEncoding {<br class="gmail_msg">
  case identity(contentLength: UInt)<br class="gmail_msg">
  case chunked<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPResponseStatus {<br class="gmail_msg">
  /* use custom if you want to use a non-standard response code or<br class="gmail_msg">
     have it available in a (UInt, String) pair from a higher-level web framework. */<br class="gmail_msg">
  case custom(code: UInt, reasonPhrase: String)<br class="gmail_msg">
<br class="gmail_msg">
  /* all the codes from <a href="http://www.iana.org/assignments/http-status-codes" rel="noreferrer" class="gmail_msg" target="_blank">http://www.iana.org/assignments/http-status-codes</a> */<br class="gmail_msg">
  case `continue`<br class="gmail_msg">
  case switchingProtocols<br class="gmail_msg">
  case processing<br class="gmail_msg">
  case ok<br class="gmail_msg">
  case created<br class="gmail_msg">
  case accepted<br class="gmail_msg">
  case nonAuthoritativeInformation<br class="gmail_msg">
  case noContent<br class="gmail_msg">
  case resetContent<br class="gmail_msg">
  case partialContent<br class="gmail_msg">
  case multiStatus<br class="gmail_msg">
  case alreadyReported<br class="gmail_msg">
  case imUsed<br class="gmail_msg">
  case multipleChoices<br class="gmail_msg">
  case movedPermanently<br class="gmail_msg">
  case found<br class="gmail_msg">
  case seeOther<br class="gmail_msg">
  case notModified<br class="gmail_msg">
  case useProxy<br class="gmail_msg">
  case temporaryRedirect<br class="gmail_msg">
  case permanentRedirect<br class="gmail_msg">
  case badRequest<br class="gmail_msg">
  case unauthorized<br class="gmail_msg">
  case paymentRequired<br class="gmail_msg">
  case forbidden<br class="gmail_msg">
  case notFound<br class="gmail_msg">
  case methodNotAllowed<br class="gmail_msg">
  case notAcceptable<br class="gmail_msg">
  case proxyAuthenticationRequired<br class="gmail_msg">
  case requestTimeout<br class="gmail_msg">
  case conflict<br class="gmail_msg">
  case gone<br class="gmail_msg">
  case lengthRequired<br class="gmail_msg">
  case preconditionFailed<br class="gmail_msg">
  case payloadTooLarge<br class="gmail_msg">
  case uriTooLong<br class="gmail_msg">
  case unsupportedMediaType<br class="gmail_msg">
  case rangeNotSatisfiable<br class="gmail_msg">
  case expectationFailed<br class="gmail_msg">
  case misdirectedRequest<br class="gmail_msg">
  case unprocessableEntity<br class="gmail_msg">
  case locked<br class="gmail_msg">
  case failedDependency<br class="gmail_msg">
  case upgradeRequired<br class="gmail_msg">
  case preconditionRequired<br class="gmail_msg">
  case tooManyRequests<br class="gmail_msg">
  case requestHeaderFieldsTooLarge<br class="gmail_msg">
  case unavailableForLegalReasons<br class="gmail_msg">
  case internalServerError<br class="gmail_msg">
  case notImplemented<br class="gmail_msg">
  case badGateway<br class="gmail_msg">
  case serviceUnavailable<br class="gmail_msg">
  case gatewayTimeout<br class="gmail_msg">
  case httpVersionNotSupported<br class="gmail_msg">
  case variantAlsoNegotiates<br class="gmail_msg">
  case insufficientStorage<br class="gmail_msg">
  case loopDetected<br class="gmail_msg">
  case notExtended<br class="gmail_msg">
  case networkAuthenticationRequired<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPMethod {<br class="gmail_msg">
  case custom(method: String)<br class="gmail_msg">
<br class="gmail_msg">
  /* everything that http_parser.[ch] supports */<br class="gmail_msg">
  case DELETE<br class="gmail_msg">
  case GET<br class="gmail_msg">
  case HEAD<br class="gmail_msg">
  case POST<br class="gmail_msg">
  case PUT<br class="gmail_msg">
  case CONNECT<br class="gmail_msg">
  case OPTIONS<br class="gmail_msg">
  case TRACE<br class="gmail_msg">
  case COPY<br class="gmail_msg">
  case LOCK<br class="gmail_msg">
  case MKCOL<br class="gmail_msg">
  case MOVE<br class="gmail_msg">
  case PROPFIND<br class="gmail_msg">
  case PROPPATCH<br class="gmail_msg">
  case SEARCH<br class="gmail_msg">
  case UNLOCK<br class="gmail_msg">
  case BIND<br class="gmail_msg">
  case REBIND<br class="gmail_msg">
  case UNBIND<br class="gmail_msg">
  case ACL<br class="gmail_msg">
  case REPORT<br class="gmail_msg">
  case MKACTIVITY<br class="gmail_msg">
  case CHECKOUT<br class="gmail_msg">
  case MERGE<br class="gmail_msg">
  case MSEARCH<br class="gmail_msg">
  case NOTIFY<br class="gmail_msg">
  case SUBSCRIBE<br class="gmail_msg">
  case UNSUBSCRIBE<br class="gmail_msg">
  case PATCH<br class="gmail_msg">
  case PURGE<br class="gmail_msg">
  case MKCALENDAR<br class="gmail_msg">
  case LINK<br class="gmail_msg">
  case UNLINK<br class="gmail_msg">
}<br class="gmail_msg">
--- SNAP ---<br class="gmail_msg">
<br class="gmail_msg">
Here&#39;s the demo code for a simple echo server<br class="gmail_msg">
<br class="gmail_msg">
--- SNIP ---<br class="gmail_msg">
serve { (req, res) in<br class="gmail_msg">
   if req.target == &quot;/echo&quot; {<br class="gmail_msg">
       guard req.httpVersion == (1, 1) else {<br class="gmail_msg">
           /* HTTP/1.0 doesn&#39;t support chunked encoding */<br class="gmail_msg">
           res.writeResponse(HTTPResponse(version: req.version,<br class="gmail_msg">
                                          status: .httpVersionNotSupported,<br class="gmail_msg">
                                          transferEncoding: .identity(contentLength: 0)))<br class="gmail_msg">
           res.done()<br class="gmail_msg">
           return .discardBody<br class="gmail_msg">
       }<br class="gmail_msg">
       res.writeResponse(HTTPResponse(version: req.version,<br class="gmail_msg">
                                      status: .ok,<br class="gmail_msg">
                                      transferEncoding: .chunked,<br class="gmail_msg">
                                      headers: SomeConcreteHTTPHeaders([(&quot;X-foo&quot;: &quot;bar&quot;)])))<br class="gmail_msg">
       return .processBody { (chunk, stop) in<br class="gmail_msg">
           switch chunk {<br class="gmail_msg">
               case .chunk(let data, let finishedProcessing):<br class="gmail_msg">
                   res.writeBody(data: data) { _ in<br class="gmail_msg">
                       finishedProcessing()<br class="gmail_msg">
                   }<br class="gmail_msg">
               case .end:<br class="gmail_msg">
                   res.done()<br class="gmail_msg">
               default:<br class="gmail_msg">
                   stop = true /* don&#39;t call us anymore */<br class="gmail_msg">
                   res.abort()<br class="gmail_msg">
           }<br class="gmail_msg">
       }<br class="gmail_msg">
   } else { ... }<br class="gmail_msg">
}<br class="gmail_msg">
--- SNAP ---<br class="gmail_msg">
<br class="gmail_msg">
_______________________________________________<br class="gmail_msg">
swift-server-dev mailing list<br class="gmail_msg">
<a href="mailto:swift-server-dev@swift.org" class="gmail_msg" target="_blank">swift-server-dev@swift.org</a><br class="gmail_msg">
<a href="https://lists.swift.org/mailman/listinfo/swift-server-dev" rel="noreferrer" class="gmail_msg" target="_blank">https://lists.swift.org/mailman/listinfo/swift-server-dev</a><br class="gmail_msg">
</blockquote></div>
</blockquote></div></div></blockquote></div></div></div>