[swift-server-dev] HTTP API Sketch v2

Colin Barrett colin at springsandstruts.com
Wed Apr 5 19:20:27 CDT 2017


Hello all,

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!

-Colin

On Wed, Apr 5, 2017 at 1:36 PM Johannes Weiß via swift-server-dev <
swift-server-dev at swift.org> wrote:

> Hi,
>
> First of all, thanks very much to Helge for carefully analysing (and
> implementing) the first API sketch. As promised, I reworked it a bit.
>
> Changes:
> - extracted HTTPResponse as its own value type (so it could also be used
> for a client)
> - added Helge's suggestions:
>   * proper backpressure support
>   * being able to ignore further parts of the HTTP body (stop parameter)
>
> 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.
>
> Please find everything below...
>
> Cheers,
>   Johannes
>
> --- SNIP ---
> /* 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 */
>
> public typealias WebApp = (HTTPRequest, HTTPResponseWriter) ->
> HTTPBodyProcessing
>
> public struct HTTPRequest {
>   public var method : HTTPMethod
>   public var target : String /* e.g. "/foo/bar?buz=qux" */
>   public var httpVersion : HTTPVersion
>   public var headers : HTTPHeaders
> }
>
> public struct HTTPResponse {
>   public var httpVersion : HTTPVersion
>   public var status: HTTPResponseStatus
>   public var transferEncoding: HTTPTransferEncoding
>   public var headers: HTTPHeaders
> }
>
> public protocol HTTPResponseWriter: class {
>   func writeContinue(headers: HTTPHeaders = HTTPHeaders()) /* to send an
> HTTP `100 Continue` */
>
>   func writeResponse(_ response: HTTPResponse)
>
>   func writeTrailer(key: String, value: String)
>
>   func writeBody(data: DispatchData) /* convenience */
>   func writeBody(data: Data) /* convenience */
>   func writeBody(data: DispatchData, completion: @escaping
> (Result<POSIXError, ()>) -> Void)
>   func writeBody(data: Data, completion: @escaping (Result<POSIXError,
> ()>) -> Void)
>
>   func done() /* convenience */
>   func done(completion: @escaping (Result<POSIXError, ()>) -> Void)
>   func abort()
> }
>
> public typealias HTTPBodyHandler = (HTTPBodyChunk, inout Bool) -> Void /*
> the Bool can be set to true when we don't want to process anything further
> */
>
> public enum HTTPBodyProcessing {
>    case discardBody /* if you're not interested in the body */
>    case processBody(handler: HTTPBodyHandler)
> }
>
> public enum HTTPBodyChunk {
>   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 */
>   case failed(error: HTTPParserError) /* error while streaming the HTTP
> request body, eg. connection closed */
>   case trailer(key: String, value: String) /* trailer has arrived (this we
> actually haven't implemented yet) */
>   case end /* body and trailers finished */
> }
>
> public struct HTTPHeaders {
>   var storage: [String:[String]]     /* lower cased keys */
>   var original: [(String, String)]   /* original casing */
>   var description: String
>
>   subscript(key: String) -> [String]
>   func makeIterator() -> IndexingIterator<Array<(String, String)>>
>
>   init(_ headers: [(String, String)] = [])
> }
>
> public typealias HTTPVersion = (Int, Int)
>
> public enum HTTPTransferEncoding {
>   case identity(contentLength: UInt)
>   case chunked
> }
>
> public enum HTTPResponseStatus {
>   /* use custom if you want to use a non-standard response code or
>      have it available in a (UInt, String) pair from a higher-level web
> framework. */
>   case custom(code: UInt, reasonPhrase: String)
>
>   /* all the codes from http://www.iana.org/assignments/http-status-codes
> */
>   case `continue`
>   case switchingProtocols
>   case processing
>   case ok
>   case created
>   case accepted
>   case nonAuthoritativeInformation
>   case noContent
>   case resetContent
>   case partialContent
>   case multiStatus
>   case alreadyReported
>   case imUsed
>   case multipleChoices
>   case movedPermanently
>   case found
>   case seeOther
>   case notModified
>   case useProxy
>   case temporaryRedirect
>   case permanentRedirect
>   case badRequest
>   case unauthorized
>   case paymentRequired
>   case forbidden
>   case notFound
>   case methodNotAllowed
>   case notAcceptable
>   case proxyAuthenticationRequired
>   case requestTimeout
>   case conflict
>   case gone
>   case lengthRequired
>   case preconditionFailed
>   case payloadTooLarge
>   case uriTooLong
>   case unsupportedMediaType
>   case rangeNotSatisfiable
>   case expectationFailed
>   case misdirectedRequest
>   case unprocessableEntity
>   case locked
>   case failedDependency
>   case upgradeRequired
>   case preconditionRequired
>   case tooManyRequests
>   case requestHeaderFieldsTooLarge
>   case unavailableForLegalReasons
>   case internalServerError
>   case notImplemented
>   case badGateway
>   case serviceUnavailable
>   case gatewayTimeout
>   case httpVersionNotSupported
>   case variantAlsoNegotiates
>   case insufficientStorage
>   case loopDetected
>   case notExtended
>   case networkAuthenticationRequired
> }
>
> public enum HTTPMethod {
>   case custom(method: String)
>
>   /* everything that http_parser.[ch] supports */
>   case DELETE
>   case GET
>   case HEAD
>   case POST
>   case PUT
>   case CONNECT
>   case OPTIONS
>   case TRACE
>   case COPY
>   case LOCK
>   case MKCOL
>   case MOVE
>   case PROPFIND
>   case PROPPATCH
>   case SEARCH
>   case UNLOCK
>   case BIND
>   case REBIND
>   case UNBIND
>   case ACL
>   case REPORT
>   case MKACTIVITY
>   case CHECKOUT
>   case MERGE
>   case MSEARCH
>   case NOTIFY
>   case SUBSCRIBE
>   case UNSUBSCRIBE
>   case PATCH
>   case PURGE
>   case MKCALENDAR
>   case LINK
>   case UNLINK
> }
> --- SNAP ---
>
> Here's the demo code for a simple echo server
>
> --- SNIP ---
> serve { (req, res) in
>    if req.target == "/echo" {
>        guard req.httpVersion == (1, 1) else {
>            /* HTTP/1.0 doesn't support chunked encoding */
>            res.writeResponse(HTTPResponse(version: req.version,
>                                           status: .httpVersionNotSupported,
>                                           transferEncoding:
> .identity(contentLength: 0)))
>            res.done()
>            return .discardBody
>        }
>        res.writeResponse(HTTPResponse(version: req.version,
>                                       status: .ok,
>                                       transferEncoding: .chunked,
>                                       headers:
> SomeConcreteHTTPHeaders([("X-foo": "bar")])))
>        return .processBody { (chunk, stop) in
>            switch chunk {
>                case .chunk(let data, let finishedProcessing):
>                    res.writeBody(data: data) { _ in
>                        finishedProcessing()
>                    }
>                case .end:
>                    res.done()
>                default:
>                    stop = true /* don't call us anymore */
>                    res.abort()
>            }
>        }
>    } else { ... }
> }
> --- SNAP ---
>
> _______________________________________________
> swift-server-dev mailing list
> swift-server-dev at swift.org
> https://lists.swift.org/mailman/listinfo/swift-server-dev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-server-dev/attachments/20170406/00371031/attachment.html>


More information about the swift-server-dev mailing list