Почему HTTPBody запроса внутри подкласса NSURLProtocol всегда равен нулю?

У меня есть свой NSURLProtocol MyGreatProtocol. Я добавляю его в систему загрузки URL,

NSURLProtocol.registerClass(MyGreatProtocol)

Затем я начинаю получать события внутри MyGreatProtocol во время сетевых сеансов.

Предположим, я создаю сеанс после регистрации моего протокола,

let session = NSURLSession.sharedSession()
    let request = NSMutableURLRequest(URL: NSURL(string:"http://example.com")!, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: 2.0) //arbitrary 404
    request.HTTPBody = ([1, 3, 3, 7] as [UInt8]).toNSData
    print("body data: \(request.HTTPBody)") //prints `Optional(<01030307>)`
    request.HTTPMethod = "POST"
    session.dataTaskWithRequest(request) { (data, response, err) in
        print("data: \(data)\nresponse: \(response)\nerror\(err)")
    }.resume()

Я ожидаю, что тело HTTP-запроса 1/3/3/7 будет присутствовать внутри MyGreatProtocol, где его нет.

Внутри MyGreatProtocol я переопределяю следующие методы.

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
    print(request.HTTPBody) //prints nil
    return request
}

override class func canInitWithRequest(request: NSURLRequest) -> Bool {
    print(request.HTTPBody) //prints nil
    return true
}

override func startLoading() {
    print(self.request.HTTPBody) //prints nil
}

Другие свойства NSURLRequest, по-видимому, сохраняются. URL-адрес, HTTP-глагол, заголовки и т. д. — все это есть. Что-то конкретное в природе тела остается неуловимым.

Почему HTTP Body Nil внутри пользовательского NSURLProtocol?

Похоже, что ранее было какое-то подобное обсуждение радаров (например, https://bugs.webkit.org/show_bug.cgi?id=137299)


person James Graham    schedule 11.04.2016    source источник


Ответы (2)


Вот пример кода для чтения httpBody:

Свифт 5

extension URLRequest {

    func bodySteamAsJSON() -> Any? {

        guard let bodyStream = self.httpBodyStream else { return nil }

        bodyStream.open()

        // Will read 16 chars per iteration. Can use bigger buffer if needed
        let bufferSize: Int = 16

        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)

        var dat = Data()

        while bodyStream.hasBytesAvailable {

            let readDat = bodyStream.read(buffer, maxLength: bufferSize)
            dat.append(buffer, count: readDat)
        }

        buffer.deallocate()

        bodyStream.close()

        do {
            return try JSONSerialization.jsonObject(with: dat, options: JSONSerialization.ReadingOptions.allowFragments)
        } catch {

            print(error.localizedDescription)

            return nil
        }
    }
}

Затем в URLProtocol:

override func startLoading() {

    ...

    if let jsonBody = self.request.bodySteamAsJSON() {

        print("JSON \(jsonBody)")

    }

    ...
}
person bauerMusic    schedule 09.05.2019

IIRC, объекты данных тела прозрачно преобразуются в тела потокового стиля системой загрузки URL-адресов, прежде чем они достигнут вас. Если вам нужно прочитать данные:

  • Откройте объект HTTPBodyStream.
  • Считать с него данные тела

Есть одно предостережение: поток нельзя перематывать, поэтому не передавайте этот объект запроса какому-либо другому коду, которому впоследствии потребуется доступ к телу. К сожалению, также нет механизма для запроса нового основного потока (см. noreferrer">README из проекта примера кода CustomHTTPProtocol на веб-сайте Apple для ознакомления с другими ограничениями).

person dgatwood    schedule 02.05.2016
comment
HTTPBodyStream также не имеет данных. - person calql8edkos; 24.08.2016
comment
Тогда вы нашли ошибку. Это не должно быть возможно. - person dgatwood; 24.08.2016
comment
Я скопировал код из этого вопроса в canInitWithRequest: и получил возвращаемое значение -1 для inputStream.read stackoverflow.com/questions/25476770/ - person calql8edkos; 25.08.2016
comment
Вы сначала открыли стрим? - person dgatwood; 25.08.2016
comment
Блестящий ответ! Я не понимал, что мне нужно открыть поток, прежде чем читать из него. Большое спасибо :) - person Sedate Alien; 13.11.2016
comment
Пожалуйста, проголосуйте или примите этот ответ. Я хотел бы иметь возможность дублировать другой вопрос на этот, но не могу, потому что ответ не принят и не проголосован. :-) Спасибо. - person dgatwood; 31.05.2017
comment
Может ли кто-нибудь помочь мне с кодом для открытия httpBodyStream? - person amish; 18.01.2018
comment
Сначала установите делегата в потоке, затем вызовите open в потоке и вернитесь (чтобы запустить цикл выполнения). Когда метод stream:handleEvent: вашего делегата вызывается с NSStreamEventHasBytesAvailable, считывайте произвольное количество данных из потока и накапливайте его где-нибудь. Вызов чтения может вернуть меньше байтов, чем вы запросили, и вы могли видеть это событие более одного раза. Когда метод stream:handleEvent: вашего делегата вызывается с NSStreamEventEndEncountered, обработайте накопленные данные. Обратите внимание, что NSStream не сохраняет своего делегата; вы должны сохранить его сами. - person dgatwood; 18.01.2018
comment
Есть ли способ перемотать поток? Я хочу прочитать HTTPBodyStream в нескольких подклассах NSURLProtocol, но кажется, что только первый из них может прочитать поток. - person xi.lin; 02.03.2020
comment
Нет гарантии, что поток можно перемотать. Обычный подход состоит в том, чтобы создать связанную пару потоков и изменить поток запроса на конец чтения этой пары, когда вы отправляете его обратно, а затем записать данные в конец записи пары, когда вы читаете их из оригинала. ручей. Либо так, либо записывайте на диск по мере чтения, а затем заменяйте файловым потоком. Так или иначе. - person dgatwood; 02.03.2020