Почему URLSession истекает или завершается с ошибкой отсутствующего параметра? - PullRequest
0 голосов
/ 06 июля 2018

Я не могу понять, почему время ожидания этого вызова URLSession истекло.

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()

Сервер сразу завершает недействительные сессии. Это метод в rest_framework_social_oauth2, и я снабдил его инструкциями print, чтобы убедиться, что он выполняет то, что я ожидаю, возвращая код состояния 204:

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"

Да на стороне клиента (код выше), он зависает в течение 60 секунд, а затем печатает:

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.})

Есть идеи, почему клиент не получает ответ сразу, как я ожидаю? Он действует так, как будто он блокирует, ожидая больше данных, но я не понимаю, почему это так.

UPDATE

Когда я удаляю строку, в которой есть «application / json», я получаю ответ очень быстро, без таймаута. Однако в нем говорится, что «Это поле обязательно для заполнения», что означает «client_id». Очевидно, что проблема связана с тем, как я формулирую свой запрос URLsession. Я пытаюсь эмулировать эту команду curl:

curl -H "Authorization: Bearer <my access token>" -X POST -d "client_id=<my client id>" https://myapp.herokuapp.com/api-auth/invalidate-sessions/

Для ясности, я обнаружил, что приведенная выше команда curl работает. Это просто не работает, когда я пытаюсь перевести это в URLSession, хотя сейчас я попробовал это дюжиной разных способов. Либо он сразу возвращается с ошибкой 400, заявляя, что требуется client_id, либо он зависает до истечения времени ожидания.

ОБНОВЛЕНИЕ 2

Когда возвращается curl, он сразу возвращается. Вот его вывод:

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

Почтальон, с другой стороны, по какой-то причине возвращает 60 секунд, но, в отличие от URLsession, возвращается со статусом 204, а не с ошибкой.

Postman screenshot

И вкладка тела ...

Postman body tab open

ОБНОВЛЕНИЕ 3

А вот код Django, который выполняет сервер. Интересно, если Postman и URLsession зависают (но не завиваются), потому что, когда сервер успешно работает, он просто возвращает пустой ответ?

@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)
...