<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><meta http-equiv="content-type" content="text/html; charset=utf-8" class=""><div dir="auto" class=""><div class=""></div><div class="">My understanding is that anything middleware is not a goal at all, except for permitting a middleware framework to be built on top of this.</div><div class=""><br class=""></div><div class="">This is just the low level http output stream, is there anything which you think prohibits building that on top?</div><div class=""><br class=""></div><div class="">The writer can be passed around, it is a ref object until done is called?</div><div class=""><br class=""></div><div class="">hh</div><div class=""><br class="">On 6. Apr 2017, at 02:20, Colin Barrett via swift-server-dev <<a href="mailto:swift-server-dev@swift.org" class="">swift-server-dev@swift.org</a>> wrote:<br class=""><br class=""></div><blockquote type="cite" class=""><div class=""><div dir="ltr" class="">Hello all,<div class=""><br class=""></div><div class="">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 class=""><br class=""></div><div class="">-Colin</div></div><br class=""><div class="gmail_quote"><div dir="ltr" class="">On Wed, Apr 5, 2017 at 1:36 PM Johannes Weiß via swift-server-dev <<a href="mailto:swift-server-dev@swift.org" class="">swift-server-dev@swift.org</a>> wrote:<br class=""></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">
* 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'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">
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) -> 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. "/foo/bar?buz=qux" */<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<POSIXError, ()>) -> Void)<br class="gmail_msg">
func writeBody(data: Data, completion: @escaping (Result<POSIXError, ()>) -> Void)<br class="gmail_msg">
<br class="gmail_msg">
func done() /* convenience */<br class="gmail_msg">
func done(completion: @escaping (Result<POSIXError, ()>) -> 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) -> 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">
case discardBody /* if you'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: () -> 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'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) -> [String]<br class="gmail_msg">
func makeIterator() -> IndexingIterator<Array<(String, String)>><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'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 == "/echo" {<br class="gmail_msg">
guard req.httpVersion == (1, 1) else {<br class="gmail_msg">
/* HTTP/1.0 doesn'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([("X-foo": "bar")])))<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'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>
</div></blockquote><blockquote type="cite" class=""><div class=""><span class="">_______________________________________________</span><br class=""><span class="">swift-server-dev mailing list</span><br class=""><span class=""><a href="mailto:swift-server-dev@swift.org" class="">swift-server-dev@swift.org</a></span><br class=""><span class=""><a href="https://lists.swift.org/mailman/listinfo/swift-server-dev" class="">https://lists.swift.org/mailman/listinfo/swift-server-dev</a></span><br class=""></div></blockquote></div></body></html>