Skip to content
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

Executing multipart request on same Part instance can cause waiting reading eternally #273

Open
ecoopnet opened this issue Jun 17, 2019 · 0 comments

Comments

@ecoopnet
Copy link

ecoopnet commented Jun 17, 2019

This problem occurred on MultipartFormDataBodyParameters.Part(data: Data).

The declaration code was like below:

struct MyFormRequest: Request {
    let baseURL = URL(string: "https://example.com/")!

    let method: HTTPMethod = .get

    let path = "/"

    typealias Response = Void

    private let parts: [MultipartFormDataBodyParameters.Part]

    init(images: [UIImage]) throws {
        parts = try images.map {
            guard let data = $0.jpegData() else { throw AppError.invalidSession }
            return data
            }.enumerated().map { (i, data) in
                MultipartFormDataBodyParameters.Part(
                    data: data,
                    name: "images[]",
                    mimeType: "image/jpeg",
                    fileName: "imagae\(i).jpg")
        }
    }
    var bodyParameters: BodyParameters? {
        return MultipartFormDataBodyParameters(
            parts: parts,
            boundary: "0123456789abcdef")
    }

    func response(from object: Any, urlResponse: HTTPURLResponse) throws {
    }
}

Then, call it like below:

func execute(images: [UIImage]) {
   let request = MyFormRequest(images: images)
   Session.send(request: request) { result in
    if case .success = result { return }
    // retry the request. (with the same instance)
    Session.send(request: request) { result2 in
       // it will be never success or failure!!!
    }
   }
}

I noticed this problem occured because of the class MultipartFormDataBodyParameters reads InputStream without rewinding or renewing stream.

# MultipartFormDataBodyParameters.swift:

                case bodyRange:
                    if bodyPart.inputStream.streamStatus == .notOpen {
                        bodyPart.inputStream.open()
                    }

                    // this line never finished when calling `read` twice on same instance.
                    let readLength = bodyPart.inputStream.read(offsetBuffer, maxLength: availableLength) 

                    sentLength += readLength
                    totalSentLength += readLength

So I resolved it by changing Request class like below finally.

struct MyFormRequest: Request {
    let baseURL = URL(string: "https://example.com/")!

    let method: HTTPMethod = .get

    let path = "/"

    typealias Response = Void

    private let images: [Data] 

    init(images: [UIImage]) throws {
        images = try images.map {
            guard let data = $0.jpegData() else { throw AppError.invalidSession }
            return data
            }
        // Do not create Part instance while initialization.
    }
    var bodyParameters: BodyParameters? {
       // Recreate part instance for each request.
       let parts = images.enumerated().map { (i, data) in
                MultipartFormDataBodyParameters.Part(
                    data: data,
                    name: "images[]",
                    mimeType: "image/jpeg",
                    fileName: "imagae\(i).jpg")
        }
        return MultipartFormDataBodyParameters(
            parts: parts,
            boundary: "0123456789abcdef")
    }

    func response(from object: Any, urlResponse: HTTPURLResponse) throws {
    }
}

To begin with, I think the request instance should not be reuse.
But in our project, we have to reuse it. (We wrap APIKit with RxSwift. When the retryable error (network connection error or somethink) occurred, to retry the request, we have to re-subscribe the same stream(Observable) with same request instance)

I hope this post can help someone.

Environment:
APIKit version 4.0.0, iOS 12.1.4, Xcode 10.2.1(10E1001)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant