我想要实现的目标:
在Swift 3.0中,我目前正在尝试生成一个大型XML文件,我想通过HTTPPOST请求将其直接发送到Web服务器。因为这个XML文件可能会变得非常大,所以我不想将它完全存储在内存中,也不想在将它发送到服务器时先将其写入磁盘,然后逐行读取。
我已经实现了生成XML文件的类,它可以写入OutputStream
。这样,流是否指向磁盘上的文件、内存中的Data
对象或(希望)HTTPPOST请求的主体都无关紧要。
我计划做什么:
在搜索了(有点稀少的)Swift文档中的URLSession
和Stream
类及其帮凶之后,我决定使用URLSession.uploadTask(withStreamedRequest)
任务。此请求需要通过以下委托方法之一传递InputStream
:
urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
在这个回调中,我使用Stream.getBoundStreams()
绑定了InputStream
和OutputStream
,然后将OutputStream
传递给生成XML的类,并从委托方法返回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的类可能需要一段时间才能将下一行写入OutputStream
。如果这种情况发生的时间太长,InputStream
可能会读取太多,以至于它实际上清除了整个缓冲区。当这种情况发生时,URLSession
框架(或者URLSessionUploadTask
本身)会认为请求现在已经完成,并"提交"或"最终确定"它。然而,这只是一种猜测,因为我不确定这些类的内部工作方式(文档似乎对我没有太大帮助)。这导致我的Web服务器接收到一个不完整的XML文件并返回一个500 Internal Server Error
。
我的问题:
有什么办法可以阻止我提前完成请求吗?最好是,我想在type.convert
调用的回调中"完成"输入流,因为我确信在这一点上不会再发生写入(事实上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!)
}
在聊天中跟进一点后的简短回答:
- 进行写入的对象没有被保留,所以当它被释放时,它会释放流,从而关闭它
- 进行写入的对象,即使它正确地检查了hasSpaceAvailable,也没有检测到短写入(因为可用空间比正在写入的对象少),因此数据在每次写入调用时都会丢失
- 执行写入操作的对象在结束时没有关闭流
顺便说一句,当人们使用基于流的网络API时,这些都是典型的错误行为。在使用相关的基础级套接字API时,我自己也犯过类似的错误。
IMO,API只缓冲一个对象,而不管它的长度如何,然后如果它在套接字的缓冲区中还有空间,就发送一个空间可用的消息,这将更有意义。这不需要对现有客户端进行任何更改,而且会减少很多麻烦。。。但我跑题了。