Skip to content

[SR-3463][URLSession] config.httpAdditionalHeaders isn't picked up #927

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

Merged
merged 1 commit into from
Apr 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions Foundation/NSURLSession/NSURLSessionTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,31 @@ fileprivate extension URLSessionTask {

// HTTP Options:
easyHandle.set(followLocation: false)

// The httpAdditionalHeaders from session configuration has to be added to the request.
// The request.allHTTPHeaders can override the httpAdditionalHeaders elements. Add the
// httpAdditionalHeaders from session configuration first and then append/update the
// request.allHTTPHeaders so that request.allHTTPHeaders can override httpAdditionalHeaders.

let httpSession = session as! URLSession
var httpHeaders: [AnyHashable : Any]?

if let hh = httpSession.configuration.httpAdditionalHeaders {
httpHeaders = hh
}

if let hh = currentRequest?.allHTTPHeaderFields {
if httpHeaders == nil {
httpHeaders = hh
} else {
hh.forEach {
httpHeaders![$0] = $1
}
}
}

let customHeaders: [String]
let headersForRequest = curlHeaders(for: request)
let headersForRequest = curlHeaders(for: httpHeaders)
if ((request.httpMethod == "POST") && (request.value(forHTTPHeaderField: "Content-Type") == nil)) {
customHeaders = headersForRequest + ["Content-Type:application/x-www-form-urlencoded"]
} else {
Expand All @@ -570,8 +592,7 @@ fileprivate extension URLSessionTask {

//set the request timeout
//TODO: the timeout value needs to be reset on every data transfer
let s = session as! URLSession
let timeoutInterval = Int(s.configuration.timeoutIntervalForRequest) * 1000
let timeoutInterval = Int(httpSession.configuration.timeoutIntervalForRequest) * 1000
let timeoutHandler = DispatchWorkItem { [weak self] in
guard let currentTask = self else { fatalError("Timeout on a task that doesn't exist") } //this guard must always pass
currentTask.internalState = .transferFailed
Expand All @@ -597,10 +618,11 @@ fileprivate extension URLSessionTask {
/// expects.
///
/// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
func curlHeaders(for request: URLRequest) -> [String] {
func curlHeaders(for httpHeaders: [AnyHashable : Any]?) -> [String] {
var result: [String] = []
var names = Set<String>()
if let hh = currentRequest?.allHTTPHeaderFields {
if httpHeaders != nil {
let hh = httpHeaders as! [String:String]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to use an actual [AnyHashable : Any] dictionary and turn it's keys and values into strings using String(describing:)?
This would also require var names = Set<AnyHashable>(), but AnyHashable can handle strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naithar Thank you for reviewing the changes and providing your inputs. I will analyze that and get back to you. Sorry for the delay in response as I was on vacation.

Copy link
Contributor Author

@enasser enasser Apr 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@naithar I agree with you that String(describing:) is the preferred way to convert an instance of any type to a string as per the documentation. I think it makes sense if we need to convert only specific elements in the dictionary. Here, we need to process each element in the dictionary and hence converting the dictionary from any type to a string dictionary makes the processing simpler.

We cannot use .lowercased() and .isEmpty methods with the elements of the [AnyHashable : Any] dictionary. Hence, we need to convert them to string before applying those methods.

In order to process the httpHeaders as any type, we need to make following changes.

       var names = Set<String>()
        if httpHeaders != nil {
           // let hh = httpHeaders as! [String:String]
            httpHeaders?.forEach {
                let name = String(describing:$0.0).lowercased()
                guard !names.contains(name) else { return }
                names.insert(name)
                
                if String(describing:$0.1).isEmpty {
                    result.append(String(describing:$0.0) + ";")
                } else {
                    result.append(String(describing:$0.0) + ": " + String(describing:$0.1))
                }
            }
        }

As the element values are already converting to string, I did not change the names variable.

Please let me know your thoughts whether the existing changes are fine or you have any better way.

Copy link
Contributor

@naithar naithar Apr 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes look ok to me

hh.forEach {
let name = $0.0.lowercased()
guard !names.contains(name) else { return }
Expand Down
36 changes: 36 additions & 0 deletions TestFoundation/TestNSURLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class TestURLSession : XCTestCase {
("test_cancelTask", test_cancelTask),
("test_taskTimeout", test_taskTimeout),
("test_verifyRequestHeaders", test_verifyRequestHeaders),
("test_verifyHttpAdditionalHeaders", test_verifyHttpAdditionalHeaders),
]
}

Expand Down Expand Up @@ -349,6 +350,41 @@ class TestURLSession : XCTestCase {
waitForExpectations(timeout: 30)
}

// Verify httpAdditionalHeaders from session configuration are added to the request
// and whether it is overriden by Request.allHTTPHeaderFields.

func test_verifyHttpAdditionalHeaders() {
let serverReady = ServerSemaphore()
globalDispatchQueue.async {
do {
try self.runServer(with: serverReady)
} catch {
XCTAssertTrue(true)
return
}
}
serverReady.wait()
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 5
config.httpAdditionalHeaders = ["header2": "svalue2", "header3": "svalue3"]
let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
var expect = expectation(description: "download task with handler")
var req = URLRequest(url: URL(string: "http://127.0.0.1:\(serverPort)/requestHeaders")!)
let headers = ["header1": "rvalue1", "header2": "rvalue2"]
req.httpMethod = "POST"
req.allHTTPHeaderFields = headers
var task = session.dataTask(with: req) { (data, _, error) -> Void in
defer { expect.fulfill() }
let headers = String(data: data!, encoding: String.Encoding.utf8)!
XCTAssertNotNil(headers.range(of: "header1: rvalue1"))
XCTAssertNotNil(headers.range(of: "header2: rvalue2"))
XCTAssertNotNil(headers.range(of: "header3: svalue3"))
}
task.resume()

waitForExpectations(timeout: 30)
}

func test_taskTimeout() {
let serverReady = ServerSemaphore()
globalDispatchQueue.async {
Expand Down