Skip to content

Commit

Permalink
Improved action handling
Browse files Browse the repository at this point in the history
  • Loading branch information
dileping committed Mar 3, 2016
1 parent 8f5c6ef commit 421bbcd
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 42 deletions.
23 changes: 14 additions & 9 deletions Express/Action.swift
Expand Up @@ -33,7 +33,11 @@ public protocol FlushableAction : AbstractActionType, FlushableType {
}

public protocol IntermediateActionType : AbstractActionType {
func nextAction<RequestContent : ConstructableContentType>(app:Express, routeId:String, request:Request<RequestContent>, out:DataConsumerType) -> Future<AbstractActionType, AnyError>
func nextAction<RequestContent : ConstructableContentType>(request:Request<RequestContent>) -> Future<(AbstractActionType, Request<RequestContent>?), AnyError>
}

public protocol SelfSufficientActionType : AbstractActionType {
func handle<RequestContent : ConstructableContentType>(app:Express, routeId:String, request:Request<RequestContent>, out:DataConsumerType) -> Future<Void, AnyError>
}

public class Action<C : FlushableContentType> : ActionType, AbstractActionType {
Expand Down Expand Up @@ -79,27 +83,27 @@ class RenderAction<C : FlushableContentType, Context> : Action<C>, IntermediateA
return Response(status: status, content: content, headers: headers)
}

func nextAction<RequestContent : ConstructableContentType>(app:Express, routeId:String, request:Request<RequestContent>, out:DataConsumerType) -> Future<AbstractActionType, AnyError> {
return app.views.render(view, context: context).map { content in
func nextAction<RequestContent : ConstructableContentType>(request:Request<RequestContent>) -> Future<(AbstractActionType, Request<RequestContent>?), AnyError> {
return request.app.views.render(view, context: context).map { content in
let response = Response(status: self.status, content: content, headers: self.headers)
return ResponseAction(response: response)
return (ResponseAction(response: response), nil)
}
}
}

class ChainAction<C : FlushableContentType, ReqC: ConstructableContentType> : Action<C>, IntermediateActionType {
class ChainAction<C : FlushableContentType, ReqC: ConstructableContentType> : Action<C>, SelfSufficientActionType {
let request:Request<ReqC>?

init(request:Request<ReqC>? = nil) {
self.request = request
}

func nextAction<RequestContent : ConstructableContentType>(app:Express, routeId:String, request:Request<RequestContent>, out:DataConsumerType) -> Future<AbstractActionType, AnyError> {
func handle<RequestContent : ConstructableContentType>(app:Express, routeId:String, request:Request<RequestContent>, out:DataConsumerType) -> Future<Void, AnyError> {
let req = self.request.map {$0 as RequestHeadType} .getOrElse(request)
let body = self.request.map {$0.body.map {$0 as ContentType}} .getOrElse(request.body)

let route = app.nextRoute(routeId, request: request)
return route.map { (r:(RouteType, [String: String]))->Future<AbstractActionType, AnyError> in
return route.map { (r:(RouteType, [String: String]))->Future<Void, AnyError> in
let req = req.withParams(r.1)
let transaction = r.0.factory(req, out)
for b in body {
Expand All @@ -111,9 +115,10 @@ class ChainAction<C : FlushableContentType, ReqC: ConstructableContentType> : Ac
}
}
}
return transaction.action
transaction.selfProcess()
return Future()
}.getOrElse {
future(ImmediateExecutionContext) {
future(ImmediateExecutionContext) { ()->Void in
throw ExpressError.PageNotFound(path: request.path)
}
}
Expand Down
4 changes: 4 additions & 0 deletions Express/Express.swift
Expand Up @@ -84,3 +84,7 @@ public extension Express {
}
}

public protocol AppContext {
var app:Express {get}
}

2 changes: 1 addition & 1 deletion Express/HttpServer.swift
Expand Up @@ -108,7 +108,7 @@ private func handle_request(req: EVHTPRequest, serv:ServerParams) {
let action = future(immediate) { () throws -> AbstractActionType in
throw ExpressError.RouteNotFound(path: head.path)
}
transaction.handleAction(action)
transaction.handleAction(action, request: Optional<Request<AnyContent>>.None)
try! transaction.dataEnd()
}
}
Expand Down
6 changes: 4 additions & 2 deletions Express/Request.swift
Expand Up @@ -100,18 +100,20 @@ public class RequestHead : HttpHead, RequestHeadType {
}
}

public protocol RequestType : RequestHeadType {
public protocol RequestType : RequestHeadType, AppContext {
typealias Content

var body:Content? {get}
}

public class Request<C> : RequestHead, RequestType {
public let app:Express
public let body:Content?

public typealias Content = C

init(head: RequestHeadType, body:Content?) {
init(app:Express, head: RequestHeadType, body:Content?) {
self.app = app
self.body = body
super.init(head: head)
}
Expand Down
44 changes: 24 additions & 20 deletions Express/Static.swift
Expand Up @@ -44,19 +44,23 @@ public class StaticFileProvider : StaticDataProviderType {
let file = fullPath(file)

return future {
let attributes = try self.fm.attributesOfItemAtPath(file)

guard let modificationDate = (attributes[NSFileModificationDate].flatMap{$0 as? NSDate}) else {
//TODO: throw different error
throw ExpressError.PageNotFound(path: file)
do {
let attributes = try self.fm.attributesOfItemAtPath(file)

guard let modificationDate = (attributes[NSFileModificationDate].flatMap{$0 as? NSDate}) else {
//TODO: throw different error
throw ExpressError.PageNotFound(path: file)
}

let timestamp = UInt64(modificationDate.timeIntervalSince1970 * 1000 * 1000)

//TODO: use MD5 of fileFromURI + timestamp
let etag = "\"" + String(timestamp) + "\""

return etag
} catch _ as NSError {
throw ExpressError.FileNotFound(filename: file)
}

let timestamp = UInt64(modificationDate.timeIntervalSince1970 * 1000 * 1000)

//TODO: use MD5 of fileFromURI + timestamp
let etag = "\"" + String(timestamp) + "\""

return etag
}
}

Expand Down Expand Up @@ -110,41 +114,41 @@ public class BaseStaticAction<C : FlushableContentType> : Action<C>, Intermediat
self.headers = headers
}

public func nextAction<RequestContent : ConstructableContentType>(app:Express, routeId:String, request:Request<RequestContent>, out:DataConsumerType) -> Future<AbstractActionType, AnyError> {
public func nextAction<RequestContent : ConstructableContentType>(request:Request<RequestContent>) -> Future<(AbstractActionType, Request<RequestContent>?), AnyError> {

if request.method != HttpMethod.Get.rawValue {
return Future<AbstractActionType, AnyError>(value: Action<AnyContent>.chain())
return Future<(AbstractActionType, Request<RequestContent>?), AnyError>(value: (Action<AnyContent>.chain(), nil))
}

guard let fileFromURI = request.params[self.param] else {
print("Can not find ", self.param, " group in regex")
return Future<AbstractActionType, AnyError>(value: Action<AnyContent>.chain())
return Future<(AbstractActionType, Request<RequestContent>?), AnyError>(value: (Action<AnyContent>.chain(), nil))
}

let etag = self.dataProvider.etag(fileFromURI)

return etag.flatMap { etag -> Future<AbstractActionType, AnyError> in
return etag.flatMap { etag -> Future<(AbstractActionType, Request<RequestContent>?), AnyError> in
let headers = self.headers ++ ["ETag": etag]

if let requestETag = request.headers["If-None-Match"] {
if requestETag == etag {
let action = Action<AnyContent>.response(.NotModified, content: nil, headers: headers)
return Future<AbstractActionType, AnyError>(value: action)
return Future<(AbstractActionType, Request<RequestContent>?), AnyError>(value: (action, nil))
}
}

let content = self.dataProvider.data(app, file: fileFromURI)
let content = self.dataProvider.data(request.app, file: fileFromURI)

return content.map { content in
let flushableContent = FlushableContent(content: content)

return Action.ok(flushableContent, headers: headers)
return (Action.ok(flushableContent, headers: headers), nil)
}
}.recoverWith { e in
switch e {
case ExpressError.PageNotFound(path: _): fallthrough
case ExpressError.FileNotFound(filename: _):
return Future(value: Action<AnyContent>.chain())
return Future(value: (Action<AnyContent>.chain(), nil))
default:
return Future(error: AnyError(cause: e))
}
Expand Down
39 changes: 29 additions & 10 deletions Express/Transaction.swift
Expand Up @@ -54,7 +54,7 @@ class Transaction<RequestContent : ConstructableContentType, ResponseContent : F
self.action = actionPromise.future
self.request = Promise<Request<RequestContent>, NoError>()
content.onSuccess(ExecutionContext.user) { content in
let request = Request<RequestContent>(head: head, body: content as? RequestContent)
let request = Request<RequestContent>(app: app, head: head, body: content as? RequestContent)
self.request.success(request)
}
content.onFailure { e in
Expand Down Expand Up @@ -82,36 +82,55 @@ class Transaction<RequestContent : ConstructableContentType, ResponseContent : F
self.actionPromise.failure(AnyError(cause: e))
}

func handleAction(action:Future<AbstractActionType, AnyError>) {
func handleActionWithRequest<C : ConstructableContentType>(actionAndRequest:Future<(AbstractActionType, Request<C>?), AnyError>) {
actionAndRequest.onComplete { result in
let action = Future(result: result.map {$0.0})
self.handleAction(action, request: result.value?.1)
}
}

func handleAction<C : ConstructableContentType>(action:Future<AbstractActionType, AnyError>, request:Request<C>?) {
action.onSuccess(ExecutionContext.action) { action in
//yes we certainly have request here
for request in self.request.future.value {
if let request = request {
self.processAction(action, request: request)
} else {
//yes we certainly have request here
for request in self.request.future.value {
self.processAction(action, request: request)
}
}
}
action.onFailure { e in
//yes, we always have at least the default error handler
let next = self.app.errorHandler.handle(e)!

//FIXME: get the request from intermediate action somehow as well, it could have changed
self.request.future.onSuccess { request in
if let request = request {
self.processAction(next, request: request)
} else {
self.request.future.onSuccess { request in
self.processAction(next, request: request)
}
}
}
}

func selfProcess() {
handleAction(action)
handleAction(action, request: Optional<Request<RequestContent>>.None)
}

func processAction<C : ConstructableContentType>(action:AbstractActionType, request:Request<C>) {
switch action {
case let flushableAction as FlushableAction: flushableAction.flushTo(out)
case let intermediateAction as IntermediateActionType:
let act = intermediateAction.nextAction(app, routeId: routeId, request: request, out: out)
//FIXME: get the request from intermediate action somehow as well, it could have changed
handleAction(act)
let actAndReq = intermediateAction.nextAction(request)
handleActionWithRequest(actAndReq)
case let selfSufficientAction as SelfSufficientActionType:
selfSufficientAction.handle(app, routeId: routeId, request: request, out: out).onFailure { e in
let action = Future<AbstractActionType, AnyError>(error: AnyError(cause: e))
self.handleAction(action, request: request)
}
default:
//TODO: handle server error
print("wierd action... can do nothing with it")
}
}
Expand Down

0 comments on commit 421bbcd

Please sign in to comment.