One of the big features lost in Alamofire 5's new architecture is the ability to stream Data from a server rather than accumulate it in memory. In Alamofire 4, you could set a stream closure on a DataRequest that would be called whenever Data was received, but it was a rather dangerous way to do so. Instead of that sort of modification, I've created a DataStreamRequest type that's optimized for Data streaming. I've prototyped most of the important functionality, including Decodable stream parsing, as well as ensuring most of the other Alamofire features work as expected. You can try it out from the feature/data-streaming branch (requires Xcode 11). Here's a quick example:
AF.streamRequest("https://httpbin.org/stream/1").responseStreamString { output in
switch output {
case let .value(string): print(string)
case .error: print("Error parsing stream.")
case .complete: print("Stream complete.")
}
}
I'm still working on the exact naming and API for handling the stream callbacks, so comments are welcome, as well as any additional capabilities you'd like to see when streaming Data from a server.
I didn't see anything in the code for this, but is there a way to hook up a DataStreamRequest as an InputStream for an API that takes one? Iām thinking of something like making an XMLParserwith an InputStream as a good example of something this API could help with.
Seems like getBoundStreams would be used to take output from the responseStream closure into the created OutboundStream. I could see what an integrated API looks like.
I've added an asInputStream() method that internally creates the bound pair and streams data through to the InputStream as well as the various streaming closures. However, it seems pretty touchy to the connected bufferSize as to the ability for things like XMLParser to handle the incoming Data properly. e.g. At bufferSize = 512, the added test fails due to an XMLParser error, but at bufferSize = 1024, it works fine. I'm not really sure why, as I'm not super familiar with the Stream APIs. I'm also unsure whether I should be opening the InputStream before returning it, or whether that should be a requirement on the consumer.
DataStreamRequest has been updated with a new serialization system, support for content type validation, and various renamed types. Remaining works includes full usage documentation and additional EventMonitor events for streaming.
I believe DataStreamRequest is largely complete. If you haven't tried it yet, please do, we'd like any feedback, especially around the closure types and API, as well as response method naming.
So DataStreamRequest wasn't nearly as complete as I thought, as there was a lot of additional work around error handling and cancellation to do, but it's pretty close now.
Recent changes include:
Refactoring of the value passed to the Handler closure. It now captures both the Event enum as well as a CancellationToken which can be used to cancel an ongoing stream.
Handler closures now support throwing Errors, and the stream is cancelled when an Error is thrown, so custom actions which fail can end the stream.
DataStreamSerializers which produce errors can now automatically end the stream as well by passing the new automaticallyCancelOnStreamError parameter when creating streams.
There's still some question as to whether the error handling should be the same between closure errors and serializer errors, but I think that 's the last big design issue.
Here's an up to date example:
AF.streamRequest(...).responseStream { stream in
switch stream.event {
case let .stream(result): // Process incoming Data.
case let .complete(completion): // Process the completion of the stream.
}
// If you want to cancel the stream.
stream.cancel()
}
It also supports responseString for UTF8 strings, and responseDecodable for any Decodable type.
I'm wondering whether the implementation of writing to the bounded outputStream is valid.
Data is received by the session delegate and is written to the outputStream, if the outputStream is not ready to accept more bytes, then data will be skipped/overwritten.
Also when the session sends a didComplete (finish) closing the stream will indicate an endOccured, which might not be the case if there's data to write/read.
Yes, for simplicity's sake the handling of the OutputStream is necessarily naive. It does not check whether the stream can be written to, as doing so would require caching unwritten data into memory. This goes against the intent of the DataStreamRequest. If you need more robust handling I'd suggest implementing it yourself.
As for the completion, I don't believe there's anything to do to the OutputStream to indicate the request has completed, it's just closed when the request finishes.