-
Notifications
You must be signed in to change notification settings - Fork 125
Add FileDownloadDelegate for simple file downloads #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add FileDownloadDelegate for simple file downloads #275
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this!
I'm not best placed to decide if this feature is in-scope or not, so I decided to review this as though it were in scope. I'll let others decide if the feature itself is worth adding.
As a quick note, I'd like to get more opinions on whether the feature itself should be included before I address the technical feedback provided by Cory. |
|
||
public func didFinishRequest(task: HTTPClient.Task<Response>) throws -> Response { | ||
self.writeFuture?.whenComplete { [weak self] _ in | ||
try! self?.handle.close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should crash if the close
fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made it try!
based on this suggestion from @weissi in the Swift Server Slack (#async-http-client
channel):
and you want to
try!
theclose.
The only reason for theclose
to fail is that you have already closed it. If you have already closed it, then that's a serious programmer error that need fixing.
Hi @artemredkin, do you think that this feature makes sense overall in AsyncHTTPClient? Just wondering if it's worth updating the PR to address the feedback, otherwise I'd close it. |
@MaxDesiatov Hi! Sorry I missed your question. I do think this feature would be useful |
Just as a heads up, the main development branch has been changed to This PR has been re-targeted to main and should just work. However when performing rebases etc please keep this in mind -- you may want to fetch the main branch and rebase onto the |
reportHeaders: ((HTTPHeaders) -> Void)? = nil, | ||
reportProgress: ((_ totalBytes: Int?, _ receivedBytes: Int) -> Void)? = nil | ||
) throws { | ||
self.handle = try NIOFileHandle(path: path, mode: .write, flags: .allowFileCreation()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is annoying, but open
is technically a blocking operation. We should consider making the handle available by a future using self.io.openFile(path:mode:flags:)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've also created apple/swift-nio#1630 to make this more clear in NIOFaileHandle
doc comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa BTW, there's no such thing as openFile(path:mode:flags:)
, there's only openFile(path:mode:flags:eventLoop:)
, where would you like the event loop in the initializer to come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll need to be the event loop that the delegate callbacks fire on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The event loop for delegate callbacks is passed to every callback separately as an argument, do you think there's a way to get access to that event loop in the initializer? Whoever creates the delegate doesn't have access to that event loop too, it's internal and owned by the HTTPClient
instance, to which the delegate doesn't have any direct access to (or even an indirect one, as far as I understand).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's possible that open
may have to be delayed until the first operation on the file occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great idea, I've implemented it and it's now ready for review.
} | ||
} | ||
|
||
func handlerAdded(context: ChannelHandlerContext) { | ||
guard context.channel.isActive else { | ||
self.failTaskAndNotifyDelegate(error: HTTPClientError.remoteConnectionClosed, self.delegate.didReceiveError) | ||
self.errorCaught(context: context, error: HTTPClientError.remoteConnectionClosed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a bunch of changes in this file that don't seem well motivated: what are they there for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These appeared after the base branch was changed from master
to main
, sorry about that, I'll clean it up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm changing the base branch back from main
to master
, because main
is outdated compared to master
in this repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ktoso What happened with the migration to main
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something's wrong... I have it noted that we completed the work here but seems master still was the main thing, wat?
Please do not target master
@MaxDesiatov, please keep targeting main.
I'll bring main up to date again and re-do the steps about branch protection.
CI is also ticked off to have been changed but I can't confirm that since no admin rights there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we/I missed to change the default branch so a few PRs were made against master and merged. I've brought main up to date again, fixed that, re targeted this at main and removed master.
Please keep using main from now-on, it should be exactly the same as master just was (it was behind a few commits indeed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've rebased it on top of main
. @Lukasa now ready for review.
This was noted in review of swift-server/async-http-client#275, but I don't think that this was clearly stated in the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I think this looks really good! I've left a few notes in the diff.
self.fileHandleFuture = nil | ||
} | ||
|
||
public func didFinishRequest(task: HTTPClient.Task<Response>) throws -> Response { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a test on the error path? I think we're missing cleanup in the didReceiveError
case.
@@ -531,7 +552,8 @@ internal final class HttpBinHandler: ChannelInboundHandler { | |||
context.writeAndFlush(wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok))), promise: nil) | |||
return | |||
case "/events/10/1": // TODO: parse path | |||
context.write(wrapOutboundOut(.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok))), promise: nil) | |||
let headers = HTTPHeaders([("Content-Length", "50")]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changes the behaviour of this endpoint (it previously automatically sent chunked encoding, now it doesn't). I don't think any of the tests rely on that behaviour, but as it's fairly cheap to add new endpoints I think I'd rather we added a new one of these.
We can keep the common code by factoring this out into a method that conditionally adds content-length if we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, I like it a lot!
`FileDownloadDelegate` was introduced in swift-server/async-http-client#275, which almost directly mirrors the implementation in `carton`. Now that it's available in a released version of AHC, let's delete some code here and replace it with the version from that package.
A simple test is included, let me know if you'd like it to be more sophisticated.