ConnectionResetError с запросами Python и библиотеками urllib при доступе к определенному URL-адресу

Я сталкиваюсь с ошибкой ConnectionResetError при попытке получить доступ к определенному URL-адресу, используя как запросы Python, так и библиотеки urllib. Несмотря на предоставление соответствующих заголовков, соединение принудительно закрывается удаленным хостом. Эта проблема возникает постоянно, и я ищу информацию о ее причине и возможных решениях.

Вот фрагмент кода, который я использую:

import requests

headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'en-US,en;q=0.9',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'DNT': '1',
    'Pragma': 'no-cache',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
    'sec-ch-ua': '"Google Chrome";v = "123", "Not:A-Brand";v = "8", "Chromium";v = "123"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
}

response = requests.get('https://newjersey.mylicense.com/verification/Search.aspx', headers=headers)

И вот ошибка, которую я получаю:

('Connection aborted.', ConnectionResetError(10054, 'An existing connection was forcibly closed by the remote host', None, 10054, None))

Используемые библиотеки:

  • запросы==2.31.0
  • urllib3==2.2.1

Я попытался получить доступ к URL-адресу, используя библиотеки Requests и urllib, предоставив необходимые заголовки для имитации запроса браузера. Я ожидал, что соединение будет установлено успешно, что позволит мне получить желаемый контент. Однако я постоянно получал ConnectionResetError, указывающий, что соединение было принудительно закрыто удаленным хостом.

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

Это очень странно. Я могу успешно отправить запрос GET, используя Powershell, но не Python.

James 11.04.2024 15:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
1
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы это выяснить, потребовалось чертовски много усилий по устранению неполадок. Основная проблема заключается в том, что шифр ('AES256-GCM-SHA384'), используемый сервером для TLS-соединения, не является одним из шифров по умолчанию, используемых пакетом ssl при установке защищенного соединения. Это приводит к сбою соединения при рукопожатии, что вызывает наблюдаемую нами ошибку.

Решение

Вам нужно использовать HTTPAdapter с правильным шифром.

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context

class SoleCipherAdapter(HTTPAdapter):
    """Custom adapter that only uses 1 cipher"""
    CIPHER = 'AES256-GCM-SHA384'

    def init_poolmanager(self, *args, **kwargs):
        context = create_urllib3_context(ciphers=self.CIPHER)
        kwargs['ssl_context'] = context
        return super().init_poolmanager(*args, **kwargs)

url = 'https://newjersey.mylicense.com/verification/Search.aspx'
sess = requests.Session()
sess.mount('https://', SoleCipherAdapter())
res = sess.get(url)
res.status_code
# returns:
# 200

Диагностика

Использование Curl работает, но не очень информативно.

C:\>curl "https://newjersey.mylicense.com/verification/Search.aspx" -vv --head
*   Trying 208.95.153.120:443...
* Connected to newjersey.mylicense.com (208.95.153.120) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server did not agree on a protocol. Uses default.
* using HTTP/1.x
> HEAD /verification/Search.aspx HTTP/1.1
> Host: newjersey.mylicense.com
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
...

Python терпит неудачу:

Попытка создать соединение вручную в Python завершается неудачей с той же ошибкой, которую вы видите, используя следующее:

import socket
import ssl

host = 'newjersey.mylicense.com'
context = ssl.create_default_context()

data = b"""HEAD /verification/Search.aspx HTTP/1.1
Host: newjersey.mylicense.com
User-Agent: python/3.11.8
Accept: */*

"""

with socket.create_connection((host, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=host) as secure_sock:
        secure_sock.send(data)
        print(secure_sock.read().decode())

# raises:
File ~\envs\test\Lib\ssl.py:1379, in SSLSocket.do_handshake(self, block)
   1377     if timeout == 0.0 and block:
   1378         self.settimeout(None)
-> 1379     self._sslobj.do_handshake()
   1380 finally:
   1381     self.settimeout(timeout)

ConnectionResetError: [WinError 10054] An existing connection was forcibly
closed by the remote host

Я обратился к созданию соединения вручную с помощью openssl. Здесь мы наконец находим необходимую информацию. (Это довольно многословно.)

C:\>openssl s_client -connect newjersey.mylicense.com:443

Соединение установлено успешно, и выводится следующая информация (для краткости я удалил ее фрагменты):

CONNECTED(000001B4)
depth=2 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", ...
verify return:1
...
---
Certificate chain
 0 s:CN = *.mylicense.com
   i:C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = ...
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: May 28 22:06:00 2023 GMT; NotAfter: Jun 28 07:22:12 2024 GMT
...
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIGkjCCBXqgAwIBAgIJAKhBrHwkidbVMA0GCSqGSIb3DQEBCwUAMIG0MQswCQYD
...
-----END CERTIFICATE-----
subject=CN = *.mylicense.com
issuer=...
---
No client certificate CA names sent
---
SSL handshake has read 4236 bytes and written 647 bytes
Verification: OK
---
New, TLSv1.2, Cipher is AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : AES256-GCM-SHA384
    Session-ID: ...
    Session-ID-ctx:
    Master-Key: ...
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1712847797
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: yes
---

После подключения мы можем отправить HTTP-запрос в качестве необработанных входных данных:

...
    Verify return code: 0 (ok)
    Extended master secret: yes
---
HEAD /verification/Search.aspx HTTP/1.1
Host: newjersey.mylicense.com
User-Agent: python/3.11.8
Accept: */*


И возвращает ответ на запрос HEAD:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 43543
Content-Type: text/html; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
Set-Cookie: ASP.NET_SessionId=...

Импортируемые части информации о подключении: SSL handshake has read 4236 bytes and written 647 bytes и TLSv1.2, Cipher is AES256-GCM-SHA384. Здесь рукопожатие прошло успешно, и оно сообщает нам версию TLS и использованный шифр. requests по умолчанию использует TLS 1.2, так что это то же самое. Осталось попробовать другой шифр.

На самом деле это всего лишь добавление одной строки к предыдущему коду Python:

import socket
import ssl

host = 'newjersey.mylicense.com'
context = ssl.create_default_context()
context.set_ciphers('AES256-GCM-SHA384')

data = b"""HEAD /verification/Search.aspx HTTP/1.1
Host: newjersey.mylicense.com
User-Agent: python/3.11.8
Accept: */*

"""

with socket.create_connection((host, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=host) as secure_sock:
        secure_sock.send(data)
        print(secure_sock.read().decode())

И, наконец, мы получаем ожидаемый результат:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 43543
Content-Type: text/html; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
Set-Cookie: ASP.NET_SessionId=iejmoxg

Другие вопросы по теме