[swift-server-dev] HTTP API Sketch v2
Colin Barrett
colin at springsandstruts.com
Thu Apr 6 16:20:49 CDT 2017
On Thu, Apr 6, 2017 at 3:53 AM Johannes Weiß <johannesweiss at apple.com>
wrote:
Hi Colin,
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!
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.
I think having the HTTPResponse is nice. I would suggest these changes:
writeResponse(..., finishHeaders = true)
writeHeader(...)
finishHeaders() [-> Bool] // It is an error to call writeHeader after
finishHeaders. Calling finishHeaders twice is a no-op. (Return value could
differentiates those cases?)
writeBody(...) // Calls finishHeaders()
Sorry for the brevity of my sketch, let me know if that's unclear in any
way.
Thanks,
-Colin
--
Johannes
-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/b97c2b0b/attachment.html>
More information about the swift-server-dev
mailing list