0

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.

Postman screenshot

And the body tab...

Postman body tab open

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)
Dylan
  • 2,315
  • 2
  • 20
  • 33
  • Possible duplicate of [Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost."](https://stackoverflow.com/questions/25372318/error-domain-nsurlerrordomain-code-1005-the-network-connection-was-lost) – Andreas Oetjen Jul 06 '18 at 07:10
  • I pursued this possibility for a little while and ruled it out. It turns out it’s something to do with my URLSession request. Please see the UPDATE above. – Dylan Jul 06 '18 at 19:22
  • Your curl request sends the `client_id` as `application/x-www-form-urlencoded`, not `application/json`. You postman image is not clear enough how you setup your request, especially you are hiding request body. But as you have included `client_id` in your URL parameter, it may be sending some different request than your Swift code or curl command. Are you sure your server accepts json? (I mean, not the response format, but the request content.) – OOPer Jul 07 '18 at 09:53
  • @OOPer, I’ve updated my post to show what appears in the body tab. I’ve tried Postman and URLsession a variety of ways, including setting “application/x-www-form-urlencoded” for “Content-Type” explicitly in the header. But I admit I’m grasping at straws. ...I also updated my post with the server-side code to show what it returns when successful (i.e. what happens on the server side during the hang). – Dylan Jul 07 '18 at 17:18
  • Sorry, but I'm not good at Django, I cannot say if your server code can accept `application/json`. But I can say your success case (curl) and half-success (postman) case both send the request in `application/x-www-form-urlencoded`. You say _setting “application/x-www-form-urlencoded” for “Content-Type” explicitly_, but have you set the request body properly? You cannot use `JSONSerialization` for `application/x-www-form-urlencoded`. – OOPer Jul 07 '18 at 20:22
  • I see. To be clear, I’ve tried `application/json` for `Content-Type` in the header when the request body was more like that shown above. I’ve since tried `urlencoded` using another request body format. I’ve also tried `URLsession.shared.datatask()` calls, `URLsession.shared.uploadTask()` calls... all sorts of things with no luck. (And yes, the server has accepted and sent JSON for other URL methods, so I know that mechanism works.) Basically, the `curl` invocation works, but I can’t get it to work from Swift. I feel like I’m missing something basic here. – Dylan Jul 07 '18 at 21:38

0 Answers0