I can’t figure out why this URLSession call times out.
let jsonUrlString = "https://myapp.herokuapp.com/api-auth/invalidate-sessions/"
guard let url = URL(string: jsonUrlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer " + accessToken!, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let params = ["client_id": djangoAppClientID,]
guard let httpBody = try? JSONSerialization.data(withJSONObject: params, options: []) else { return }
request.httpBody = httpBody
URLSession.shared.dataTask(with: request) { (data, response, err) in
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 204 {
print("Success!")
} else {
print("Failure, error = ", err as Any)
}
}
}.resume()
The server completes invalidate-sessions right away. It's a method in rest_framework_social_oauth2, and I’ve instrumented it with print statements to see that it does what I expect, returning the 204 status code:
2018-07-06T03:52:27.651829+00:00 app[web.1]: 10.11.239.176 - - [05/Jul/2018:20:52:27 -0700] "POST /api-auth/invalidate-sessions/ HTTP/1.1" 204 2 "-“ "myapp/1 CFNetwork/897.15 Darwin/17.6.0"
Yes on the client side (the code above), it hangs for 60 seconds and then prints:
Failure, error = Optional(Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x60800005a670 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x60000009fb80 [0x106f80c80]>{length = 16, capacity = 16, bytes = 0x100201bb22caef6d0000000000000000}, _kCFStreamErrorCodeKey=57, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=https://myapp.herokuapp.com/api-auth/invalidate-sessions/, NSErrorFailingURLKey=https://myapp.herokuapp.com/api-auth/invalidate-sessions/, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=57, NSLocalizedDescription=The network connection was lost.})
Any ideas why the client isn’t getting the response right away as I expect? It’s acts as if it’s blocking, waiting on more data, but I don’t see why it would.
UPDATE
When I remove the line that has "application/json" in it, I get a response very quickly, no timeout. It says however that “This field is required”, referring to “client_id". So clearly the issue has something to do with how I’m formulating my URLsession request. I’m trying to emulate this curl command:
curl -H "Authorization: Bearer <my access token>" -X POST -d "client_id=<my client id>" https://myapp.herokuapp.com/api-auth/invalidate-sessions/
To be clear, I’ve discovered the curl command above works. It’s simply not working when I try to translate this into an URLSession, though I’ve now tried it a dozen different ways. Either it returns right away with a 400 error, stating that that the client_id is required, or it hangs until it times out.
UPDATE 2
When the curl return runs, it returns right away. Here’s its output:
Moe:Server dylan$ curl -v -H "Authorization: Bearer yyyyyyyyyyyyyyy" -X POST -d "client_id=xxxxxxxxxxxxxxxxxxxxxxxxx" https://myapp.herokuapp.com/api-auth/invalidate-sessions/
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 52.20.65.241...
* TCP_NODELAY set
* Connected to myapp.herokuapp.com (52.20.65.241) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: C=US; ST=California; L=San Francisco; O=Heroku, Inc.; CN=*.herokuapp.com
* start date: Apr 19 00:00:00 2017 GMT
* expire date: Jun 22 12:00:00 2020 GMT
* subjectAltName: host "myapp.herokuapp.com" matched cert's "*.herokuapp.com"
* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert SHA2 High Assurance Server CA
* SSL certificate verify ok.
> POST /api-auth/invalidate-sessions/ HTTP/1.1
> Host: myapp.herokuapp.com
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: Bearer yyyyyyyyyyyyyyy
> Content-Length: 50
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 50 out of 50 bytes
< HTTP/1.1 204 No Content
< Connection: keep-alive
< Server: gunicorn/19.8.1
< Date: Sat, 07 Jul 2018 02:44:30 GMT
< Content-Type: application/json
< Vary: Accept
< Allow: OPTIONS, POST
< X-Frame-Options: SAMEORIGIN
< Content-Length: 2
< Via: 1.1 vegur
<
* Connection #0 to host myapp.herokuapp.com left intact
Postman, on the other hand, for some reason takes 60 seconds to return, but unlike the URLsession, it returns with status 204, not an error.
And the body tab...
UPDATE 3
And here’s the Django code that the server executes. I wonder if Postman and URLsession hang (but not curl) because when the server succeeds it simply returns an empty Response?
@api_view(['POST'])
@authentication_classes([OAuth2Authentication])
@permission_classes([permissions.IsAuthenticated])
def invalidate_sessions(request):
client_id = request.data.get("client_id", None)
if client_id is None:
return Response({
"client_id": ["This field is required."]
}, status=status.HTTP_400_BAD_REQUEST)
try:
app = Application.objects.get(client_id=client_id)
except Application.DoesNotExist:
return Response({
"detail": "The application linked to the provided client_id could not be found."
}, status=status.HTTP_400_BAD_REQUEST)
tokens = AccessToken.objects.filter(user=request.user, application=app)
tokens.delete()
return Response({}, status=status.HTTP_204_NO_CONTENT)