<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body dir="auto"><div></div><div>Hi Colin,</div><div><br></div><blockquote type="cite"><div dir="ltr"><div>Is writing compositional middleware a goal for this iteration of the API? I noticed that the HTTPResponseWriter doesn'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'm missing something though!</div></div></blockquote><div><br></div>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><br></div><div>--&nbsp;</div><div>&nbsp; Johannes</div><div><br><blockquote type="cite"><div dir="ltr"><div><br></div><div>-Colin</div></div><br><div class="gmail_quote"><div dir="ltr">On Wed, Apr 5, 2017 at 1:36 PM Johannes Weiß via swift-server-dev &lt;<a href="mailto:swift-server-dev@swift.org">swift-server-dev@swift.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" 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's suggestions:<br class="gmail_msg">
&nbsp; * proper backpressure support<br class="gmail_msg">
&nbsp; * 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't promise that I'll address it before the meeting as I'm in another office tomorrow for meetings and probably won'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">
&nbsp; 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">
&nbsp; public var method : HTTPMethod<br class="gmail_msg">
&nbsp; public var target : String /* e.g. "/foo/bar?buz=qux" */<br class="gmail_msg">
&nbsp; public var httpVersion : HTTPVersion<br class="gmail_msg">
&nbsp; public var headers : HTTPHeaders<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public struct HTTPResponse {<br class="gmail_msg">
&nbsp; public var httpVersion : HTTPVersion<br class="gmail_msg">
&nbsp; public var status: HTTPResponseStatus<br class="gmail_msg">
&nbsp; public var transferEncoding: HTTPTransferEncoding<br class="gmail_msg">
&nbsp; public var headers: HTTPHeaders<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public protocol HTTPResponseWriter: class {<br class="gmail_msg">
&nbsp; func writeContinue(headers: HTTPHeaders = HTTPHeaders()) /* to send an HTTP `100 Continue` */<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; func writeResponse(_ response: HTTPResponse)<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; func writeTrailer(key: String, value: String)<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; func writeBody(data: DispatchData) /* convenience */<br class="gmail_msg">
&nbsp; func writeBody(data: Data) /* convenience */<br class="gmail_msg">
&nbsp; func writeBody(data: DispatchData, completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br class="gmail_msg">
&nbsp; func writeBody(data: Data, completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; func done() /* convenience */<br class="gmail_msg">
&nbsp; func done(completion: @escaping (Result&lt;POSIXError, ()&gt;) -&gt; Void)<br class="gmail_msg">
&nbsp; 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't want to process anything further */<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPBodyProcessing {<br class="gmail_msg">
&nbsp; &nbsp;case discardBody /* if you're not interested in the body */<br class="gmail_msg">
&nbsp; &nbsp;case processBody(handler: HTTPBodyHandler)<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPBodyChunk {<br class="gmail_msg">
&nbsp; 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">
&nbsp; case failed(error: HTTPParserError) /* error while streaming the HTTP request body, eg. connection closed */<br class="gmail_msg">
&nbsp; case trailer(key: String, value: String) /* trailer has arrived (this we actually haven't implemented yet) */<br class="gmail_msg">
&nbsp; 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">
&nbsp; var storage: [String:[String]]&nbsp; &nbsp; &nbsp;/* lower cased keys */<br class="gmail_msg">
&nbsp; var original: [(String, String)]&nbsp; &nbsp;/* original casing */<br class="gmail_msg">
&nbsp; var description: String<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; subscript(key: String) -&gt; [String]<br class="gmail_msg">
&nbsp; func makeIterator() -&gt; IndexingIterator&lt;Array&lt;(String, String)&gt;&gt;<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; 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">
&nbsp; case identity(contentLength: UInt)<br class="gmail_msg">
&nbsp; case chunked<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPResponseStatus {<br class="gmail_msg">
&nbsp; /* use custom if you want to use a non-standard response code or<br class="gmail_msg">
&nbsp; &nbsp; &nbsp;have it available in a (UInt, String) pair from a higher-level web framework. */<br class="gmail_msg">
&nbsp; case custom(code: UInt, reasonPhrase: String)<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; /* 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">
&nbsp; case `continue`<br class="gmail_msg">
&nbsp; case switchingProtocols<br class="gmail_msg">
&nbsp; case processing<br class="gmail_msg">
&nbsp; case ok<br class="gmail_msg">
&nbsp; case created<br class="gmail_msg">
&nbsp; case accepted<br class="gmail_msg">
&nbsp; case nonAuthoritativeInformation<br class="gmail_msg">
&nbsp; case noContent<br class="gmail_msg">
&nbsp; case resetContent<br class="gmail_msg">
&nbsp; case partialContent<br class="gmail_msg">
&nbsp; case multiStatus<br class="gmail_msg">
&nbsp; case alreadyReported<br class="gmail_msg">
&nbsp; case imUsed<br class="gmail_msg">
&nbsp; case multipleChoices<br class="gmail_msg">
&nbsp; case movedPermanently<br class="gmail_msg">
&nbsp; case found<br class="gmail_msg">
&nbsp; case seeOther<br class="gmail_msg">
&nbsp; case notModified<br class="gmail_msg">
&nbsp; case useProxy<br class="gmail_msg">
&nbsp; case temporaryRedirect<br class="gmail_msg">
&nbsp; case permanentRedirect<br class="gmail_msg">
&nbsp; case badRequest<br class="gmail_msg">
&nbsp; case unauthorized<br class="gmail_msg">
&nbsp; case paymentRequired<br class="gmail_msg">
&nbsp; case forbidden<br class="gmail_msg">
&nbsp; case notFound<br class="gmail_msg">
&nbsp; case methodNotAllowed<br class="gmail_msg">
&nbsp; case notAcceptable<br class="gmail_msg">
&nbsp; case proxyAuthenticationRequired<br class="gmail_msg">
&nbsp; case requestTimeout<br class="gmail_msg">
&nbsp; case conflict<br class="gmail_msg">
&nbsp; case gone<br class="gmail_msg">
&nbsp; case lengthRequired<br class="gmail_msg">
&nbsp; case preconditionFailed<br class="gmail_msg">
&nbsp; case payloadTooLarge<br class="gmail_msg">
&nbsp; case uriTooLong<br class="gmail_msg">
&nbsp; case unsupportedMediaType<br class="gmail_msg">
&nbsp; case rangeNotSatisfiable<br class="gmail_msg">
&nbsp; case expectationFailed<br class="gmail_msg">
&nbsp; case misdirectedRequest<br class="gmail_msg">
&nbsp; case unprocessableEntity<br class="gmail_msg">
&nbsp; case locked<br class="gmail_msg">
&nbsp; case failedDependency<br class="gmail_msg">
&nbsp; case upgradeRequired<br class="gmail_msg">
&nbsp; case preconditionRequired<br class="gmail_msg">
&nbsp; case tooManyRequests<br class="gmail_msg">
&nbsp; case requestHeaderFieldsTooLarge<br class="gmail_msg">
&nbsp; case unavailableForLegalReasons<br class="gmail_msg">
&nbsp; case internalServerError<br class="gmail_msg">
&nbsp; case notImplemented<br class="gmail_msg">
&nbsp; case badGateway<br class="gmail_msg">
&nbsp; case serviceUnavailable<br class="gmail_msg">
&nbsp; case gatewayTimeout<br class="gmail_msg">
&nbsp; case httpVersionNotSupported<br class="gmail_msg">
&nbsp; case variantAlsoNegotiates<br class="gmail_msg">
&nbsp; case insufficientStorage<br class="gmail_msg">
&nbsp; case loopDetected<br class="gmail_msg">
&nbsp; case notExtended<br class="gmail_msg">
&nbsp; case networkAuthenticationRequired<br class="gmail_msg">
}<br class="gmail_msg">
<br class="gmail_msg">
public enum HTTPMethod {<br class="gmail_msg">
&nbsp; case custom(method: String)<br class="gmail_msg">
<br class="gmail_msg">
&nbsp; /* everything that http_parser.[ch] supports */<br class="gmail_msg">
&nbsp; case DELETE<br class="gmail_msg">
&nbsp; case GET<br class="gmail_msg">
&nbsp; case HEAD<br class="gmail_msg">
&nbsp; case POST<br class="gmail_msg">
&nbsp; case PUT<br class="gmail_msg">
&nbsp; case CONNECT<br class="gmail_msg">
&nbsp; case OPTIONS<br class="gmail_msg">
&nbsp; case TRACE<br class="gmail_msg">
&nbsp; case COPY<br class="gmail_msg">
&nbsp; case LOCK<br class="gmail_msg">
&nbsp; case MKCOL<br class="gmail_msg">
&nbsp; case MOVE<br class="gmail_msg">
&nbsp; case PROPFIND<br class="gmail_msg">
&nbsp; case PROPPATCH<br class="gmail_msg">
&nbsp; case SEARCH<br class="gmail_msg">
&nbsp; case UNLOCK<br class="gmail_msg">
&nbsp; case BIND<br class="gmail_msg">
&nbsp; case REBIND<br class="gmail_msg">
&nbsp; case UNBIND<br class="gmail_msg">
&nbsp; case ACL<br class="gmail_msg">
&nbsp; case REPORT<br class="gmail_msg">
&nbsp; case MKACTIVITY<br class="gmail_msg">
&nbsp; case CHECKOUT<br class="gmail_msg">
&nbsp; case MERGE<br class="gmail_msg">
&nbsp; case MSEARCH<br class="gmail_msg">
&nbsp; case NOTIFY<br class="gmail_msg">
&nbsp; case SUBSCRIBE<br class="gmail_msg">
&nbsp; case UNSUBSCRIBE<br class="gmail_msg">
&nbsp; case PATCH<br class="gmail_msg">
&nbsp; case PURGE<br class="gmail_msg">
&nbsp; case MKCALENDAR<br class="gmail_msg">
&nbsp; case LINK<br class="gmail_msg">
&nbsp; case UNLINK<br class="gmail_msg">
}<br class="gmail_msg">
--- SNAP ---<br class="gmail_msg">
<br class="gmail_msg">
Here'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">
&nbsp; &nbsp;if req.target == "/echo" {<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp;guard req.httpVersion == (1, 1) else {<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* HTTP/1.0 doesn't support chunked encoding */<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;res.writeResponse(HTTPResponse(version: req.version,<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; status: .httpVersionNotSupported,<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; transferEncoding: .identity(contentLength: 0)))<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;res.done()<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return .discardBody<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp;}<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp;res.writeResponse(HTTPResponse(version: req.version,<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; status: .ok,<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; transferEncoding: .chunked,<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; headers: SomeConcreteHTTPHeaders([("X-foo": "bar")])))<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp;return .processBody { (chunk, stop) in<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;switch chunk {<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case .chunk(let data, let finishedProcessing):<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;res.writeBody(data: data) { _ in<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;finishedProcessing()<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case .end:<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;res.done()<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;default:<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;stop = true /* don't call us anymore */<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;res.abort()<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<br class="gmail_msg">
&nbsp; &nbsp; &nbsp; &nbsp;}<br class="gmail_msg">
&nbsp; &nbsp;} 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></body></html>