Swift - URLSessionUploadTask 在整个流被写入请求正文之前完成请求
Swift - URLSessionUploadTask finishes request before whole stream has been written to request body
我想达到的目标:
在 Swift 3.0 中,我目前正在尝试生成一个大的 XML 文件,我想通过 HTTP POST请求。因为这个XML文件会变得很大,所以我不想把它完全放在内存中,或者先写入磁盘,然后在发送到服务器时再逐行读取。
我已经实现了生成 XML 文件的 class,它可以写入 OutputStream
。这样,无论该流指向磁盘上的文件、内存中的 Data
对象还是(希望如此)HTTP POST 请求的主体都无关紧要。
我打算做什么:
在搜索 URLSession
和 Stream
classes 及其同伙的(有点稀缺的)Swift 文档后,我决定使用 URLSession.uploadTask(withStreamedRequest)
任务。此请求需要通过以下委托方法之一传递 InputStream
:
urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
在此回调中,我使用 Stream.getBoundStreams()
绑定 InputStream
和 OutputStream
,然后将 OutputStream
传递给生成XML 和 return 来自委托方法的 InputStream
。因此,委托方法如下所示:
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
//Create the input and output stream and bind them, so that what the
//output stream writes ends up in the buffer of the input stream.
var input: InputStream? = nil
var output: OutputStream? = nil
let bufferSize: Int = 1024
Stream.getBoundStreams(withBufferSize: bufferSize, inputStream: &input, outputStream: &output)
//This part is not really important for you, it starts the generation of
//the XML, which is written directly to the output stream.
let converter = DatabaseConverterXml(prettyPrint: false)
let type = ConverterTypeSynchronization(progressAlert: nil)
type.convert(using: converter, writingTo: [Writable.Stream(output!)])
{
successfull in
print("Conversion Complete! Successfull: \(successfull)" )
}
//The input stream is then handed over via the
//completion handler of the delegate method.
completionHandler(input!)
}
我遇到的问题:
有时,生成 XML 的 class 可能需要一段时间才能将下一行写入 OutputStream
。如果这种情况发生的时间太长,InputStream
可能会读取太多内容,以至于它实际上会清除整个缓冲区。当这种情况发生时,URLSession
框架(或者可能是 URLSessionUploadTask
本身)认为请求现在已完成,并且 "submits" 或 "finalizes" 它。然而,这是一个猜测,因为我不确定这些 classes 的内部工作原理(而且文档似乎对我帮助不大)。这会导致我的网络服务器收到不完整的 XML 文件和 return 500 Internal Server Error
.
我的问题:
有什么方法可以阻止请求提前完成?最好是,我想在 type.convert
调用的回调中 "finalize" 输入流,因为我确信那时不会再发生写入(并且 OutputStream
在事实上关闭)。
奖励积分:
这是解决我要解决的问题的正确方法吗?有没有什么办法可以直接与写入 HTTP 正文的流进行交互?我对这个 URLSession
框架感到非常迷茫,我花了一天半的时间才走到这一步,所以非常感谢任何建议。我会请任何能帮我解决这个问题的人喝一两杯啤酒!
在此先感谢您的帮助!
编辑 1:
正如@dgatwood 所指出的,一些变量没有正确保留。我已进行以下更改以确保它们确实如此:
var mInput: InputStream? = nil
var mOutput: OutputStream? = nil
var mConverter: DatabaseConverterXml? = nil
var mType: ConverterTypeSynchronization? = nil
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
//Create the input and output stream and bind them, so that what the
//output stream writes ends up in the buffer of the input stream.
let bufferSize: Int = 1024
Stream.getBoundStreams(withBufferSize: bufferSize, inputStream: &mInput, outputStream: &mOutput)
//This part is not really important for you, it starts the generation of
//the XML, which is written directly to the output stream.
mConverter = DatabaseConverterXml(prettyPrint: false)
mType = ConverterTypeSynchronization(progressAlert: nil)
mType.convert(using: mConverter, writingTo: [Writable.Stream(mOutput!)])
{
successfull in
print("Conversion Complete! Successfull: \(successfull)" )
}
//The input stream is then handed over via the
//completion handler of the delegate method.
completionHandler(mInput!)
}
在聊天 follow-up 之后的简短回答:
- 执行写入的对象未被保留,因此当它被释放时,它会释放流,流将关闭。
- 进行写入的对象,即使它正确地检查了 hasSpaceAvailable,也没有检测到短写入(因为可用的 space 少于正在写入的对象),因此每次写入都会丢失数据打电话。
- 写入的对象最后没有关闭流。
顺便说一句,这些几乎是人们在使用 stream-based 网络 API 时常犯的错误。在使用相关的 Foundation-level 套接字 APIs.
时,我自己也犯过类似的错误
IMO,对于 API 只缓冲一个对象而不考虑其长度,然后发送一个 space 可用消息如果它在套接字中仍有空间会更有意义缓冲。这不需要对现有客户端进行任何更改,并且会减少很多麻烦……但我离题了。
我想达到的目标:
在 Swift 3.0 中,我目前正在尝试生成一个大的 XML 文件,我想通过 HTTP POST请求。因为这个XML文件会变得很大,所以我不想把它完全放在内存中,或者先写入磁盘,然后在发送到服务器时再逐行读取。
我已经实现了生成 XML 文件的 class,它可以写入 OutputStream
。这样,无论该流指向磁盘上的文件、内存中的 Data
对象还是(希望如此)HTTP POST 请求的主体都无关紧要。
我打算做什么:
在搜索 URLSession
和 Stream
classes 及其同伙的(有点稀缺的)Swift 文档后,我决定使用 URLSession.uploadTask(withStreamedRequest)
任务。此请求需要通过以下委托方法之一传递 InputStream
:
urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
在此回调中,我使用 Stream.getBoundStreams()
绑定 InputStream
和 OutputStream
,然后将 OutputStream
传递给生成XML 和 return 来自委托方法的 InputStream
。因此,委托方法如下所示:
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
//Create the input and output stream and bind them, so that what the
//output stream writes ends up in the buffer of the input stream.
var input: InputStream? = nil
var output: OutputStream? = nil
let bufferSize: Int = 1024
Stream.getBoundStreams(withBufferSize: bufferSize, inputStream: &input, outputStream: &output)
//This part is not really important for you, it starts the generation of
//the XML, which is written directly to the output stream.
let converter = DatabaseConverterXml(prettyPrint: false)
let type = ConverterTypeSynchronization(progressAlert: nil)
type.convert(using: converter, writingTo: [Writable.Stream(output!)])
{
successfull in
print("Conversion Complete! Successfull: \(successfull)" )
}
//The input stream is then handed over via the
//completion handler of the delegate method.
completionHandler(input!)
}
我遇到的问题:
有时,生成 XML 的 class 可能需要一段时间才能将下一行写入 OutputStream
。如果这种情况发生的时间太长,InputStream
可能会读取太多内容,以至于它实际上会清除整个缓冲区。当这种情况发生时,URLSession
框架(或者可能是 URLSessionUploadTask
本身)认为请求现在已完成,并且 "submits" 或 "finalizes" 它。然而,这是一个猜测,因为我不确定这些 classes 的内部工作原理(而且文档似乎对我帮助不大)。这会导致我的网络服务器收到不完整的 XML 文件和 return 500 Internal Server Error
.
我的问题:
有什么方法可以阻止请求提前完成?最好是,我想在 type.convert
调用的回调中 "finalize" 输入流,因为我确信那时不会再发生写入(并且 OutputStream
在事实上关闭)。
奖励积分:
这是解决我要解决的问题的正确方法吗?有没有什么办法可以直接与写入 HTTP 正文的流进行交互?我对这个 URLSession
框架感到非常迷茫,我花了一天半的时间才走到这一步,所以非常感谢任何建议。我会请任何能帮我解决这个问题的人喝一两杯啤酒!
在此先感谢您的帮助!
编辑 1:
正如@dgatwood 所指出的,一些变量没有正确保留。我已进行以下更改以确保它们确实如此:
var mInput: InputStream? = nil
var mOutput: OutputStream? = nil
var mConverter: DatabaseConverterXml? = nil
var mType: ConverterTypeSynchronization? = nil
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
//Create the input and output stream and bind them, so that what the
//output stream writes ends up in the buffer of the input stream.
let bufferSize: Int = 1024
Stream.getBoundStreams(withBufferSize: bufferSize, inputStream: &mInput, outputStream: &mOutput)
//This part is not really important for you, it starts the generation of
//the XML, which is written directly to the output stream.
mConverter = DatabaseConverterXml(prettyPrint: false)
mType = ConverterTypeSynchronization(progressAlert: nil)
mType.convert(using: mConverter, writingTo: [Writable.Stream(mOutput!)])
{
successfull in
print("Conversion Complete! Successfull: \(successfull)" )
}
//The input stream is then handed over via the
//completion handler of the delegate method.
completionHandler(mInput!)
}
在聊天 follow-up 之后的简短回答:
- 执行写入的对象未被保留,因此当它被释放时,它会释放流,流将关闭。
- 进行写入的对象,即使它正确地检查了 hasSpaceAvailable,也没有检测到短写入(因为可用的 space 少于正在写入的对象),因此每次写入都会丢失数据打电话。
- 写入的对象最后没有关闭流。
顺便说一句,这些几乎是人们在使用 stream-based 网络 API 时常犯的错误。在使用相关的 Foundation-level 套接字 APIs.
时,我自己也犯过类似的错误IMO,对于 API 只缓冲一个对象而不考虑其长度,然后发送一个 space 可用消息如果它在套接字中仍有空间会更有意义缓冲。这不需要对现有客户端进行任何更改,并且会减少很多麻烦……但我离题了。