Low-level issue http2 after negotiation complete | Server disconnected without sending a response. #3591
Unanswered
6A31
asked this question in
Potential Issue
Replies: 2 comments
-
|
I've created a WAY more advanced test script (partial AI used) First a It then tries different HTTP protocols, SSL verification and requests headers. import time
import httpx
import json
import subprocess
import os
# --- Configuration Constants ---
TIMEOUT_SECONDS = 15 # Max time to wait for a response in seconds
RETRIES_PER_TRIAL = 3 # How many times to retry each specific test configuration
API_URL = "https://users.roblox.com/v1/users/949472658"
# --- Header Definitions ---
# Your original headers, as they were initially.
HEADERS_ORIGINAL = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "close"
}
# Simplified headers, specifically for HTTP/2 contexts where 'Connection' is less relevant
# and 'br' (Brotli) encoding might be problematic with some server setups.
HEADERS_SIMPLIFIED_HTTP2 = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Encoding": "gzip, deflate", # Removed 'br'
"Accept-Language": "en-US,en;q=0.9",
# 'Connection: close' is not explicitly included for HTTP/2 as connections are persistent by default
}
# Headers designed to mimic common `curl` behavior.
# `*/*` is a very common default for `Accept` in command-line tools.
HEADERS_CURL_MIMIC = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "close" # Curl often adds this
}
# --- Helper Function for Curl Check ---
def run_curl_command(url, headers):
"""
Executes a curl command to verify API accessibility.
Returns True if curl receives an HTTP 2xx status, False otherwise.
"""
header_str = " ".join([f"-H '{k}: {v}'" for k, v in headers.items()])
command = f"curl -k -v --compressed -X GET {header_str} '{url}'"
print(f"\n{'='*70}\nCURL BASELINE CHECK: Verifying API is reachable outside of httpx\n{'='*70}")
print(f"Executing: {command}\n")
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=TIMEOUT_SECONDS,
check=False
)
print("Curl stdout:\n", result.stdout)
print("Curl stderr:\n", result.stderr)
if "HTTP/" in result.stderr:
for line in result.stderr.splitlines():
if line.startswith("< HTTP/") and (" 200 " in line or " 204 " in line):
print("\n" + "="*70)
print("CURL VERIFICATION SUCCESS: API is UP and accessible via curl (HTTP 2xx detected).")
print("="*70 + "\n")
return True
elif line.startswith("< HTTP/") and (" 4" in line or " 5" in line):
print(f"\n{'!'*70}\nCURL VERIFICATION FAILED: API returned an error status: {line}\n{'!'*70}\n")
return False
print("\n" + "*"*70)
print("CURL VERIFICATION UNCERTAIN: Could not confirm successful HTTP status from curl output.")
print("This might indicate a network problem or curl issue, not necessarily the API being down.")
print("*"*70 + "\n")
return False
except FileNotFoundError:
print(f"\n{'!'*70}\nERROR: 'curl' command not found. Please ensure curl is installed and in your system's PATH.\n{'!'*70}\n")
return False
except subprocess.TimeoutExpired:
print(f"\n{'!'*70}\nERROR: Curl command timed out. This suggests a severe network issue or very slow server.\n{'!'*70}\n")
return False
except Exception as e:
print(f"\n{'!'*70}\nUNEXPECTED ERROR running curl command: {e}\n{'!'*70}\n")
return False
# --- Main Testing Function ---
def run_all_httpx_trials():
print(f"Starting comprehensive httpx testing for URL: {API_URL}")
print(f"Each trial will run {RETRIES_PER_TRIAL} times with a timeout of {TIMEOUT_SECONDS} seconds per attempt.\n")
# Perform curl check first
api_is_up = run_curl_command(API_URL, HEADERS_ORIGINAL) # Use original headers for curl baseline
if not api_is_up:
print("\n*** Skipping httpx trials because curl verification failed or was uncertain. ***")
return
print("\n--- Proceeding with httpx trials... ---")
# --- TRIAL SET A: Forcing HTTP/1.1 (http2=False) ---
# We saw RemoteProtocolError here previously. This set aims to confirm if any H1.1 config works.
print(f"\n{'#'*80}\n### TRIAL SET A: Forcing HTTP/1.1 (http2=False) ###\n{'#'*80}")
# A1: Original Headers, verify=True
print(f"\n--- TRIAL A1: http2=False, verify=True, Headers: Original ---")
print("Testing standard HTTP/1.1 connection with full headers and SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=False, verify=True) as client:
resp = client.get(API_URL, headers=HEADERS_ORIGINAL)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return # Stop all trials on first success
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error (server understood but returned an error): {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error (low-level network problem): {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1) # Small pause between retries
print("Trial A1 failed after all attempts.")
# A2: Original Headers, verify=False
print(f"\n--- TRIAL A2: http2=False, verify=False, Headers: Original ---")
print("Testing standard HTTP/1.1 connection with full headers, but skipping SSL verification (for debugging).")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=False, verify=False) as client:
resp = client.get(API_URL, headers=HEADERS_ORIGINAL)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial A2 failed after all attempts.")
# A3: Simplified HTTP/2 Headers, verify=True
print(f"\n--- TRIAL A3: http2=False, verify=True, Headers: Simplified HTTP/2 ---")
print("Testing HTTP/1.1 with simplified headers (no 'br' encoding, no 'Connection' header) and SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=False, verify=True) as client:
resp = client.get(API_URL, headers=HEADERS_SIMPLIFIED_HTTP2)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial A3 failed after all attempts.")
# A4: Simplified HTTP/2 Headers, verify=False
print(f"\n--- TRIAL A4: http2=False, verify=False, Headers: Simplified HTTP/2 ---")
print("Testing HTTP/1.1 with simplified headers and skipping SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=False, verify=False) as client:
resp = client.get(API_URL, headers=HEADERS_SIMPLIFIED_HTTP2)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial A4 failed after all attempts.")
# A5: Curl-Mimic Headers, verify=True
print(f"\n--- TRIAL A5: http2=False, verify=True, Headers: Curl Mimic ---")
print("Testing HTTP/1.1 with headers designed to be more like curl's and SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=False, verify=True) as client:
resp = client.get(API_URL, headers=HEADERS_CURL_MIMIC)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial A5 failed after all attempts.")
# A6: Curl-Mimic Headers, verify=False
print(f"\n--- TRIAL A6: http2=False, verify=False, Headers: Curl Mimic ---")
print("Testing HTTP/1.1 with curl-mimicking headers and skipping SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=False, verify=False) as client:
resp = client.get(API_URL, headers=HEADERS_CURL_MIMIC)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial A6 failed after all attempts.")
# --- TRIAL SET B: Allowing HTTP/2 (http2=True) ---
# We saw ReadTimeout here previously. This set aims to confirm if any H2 config works.
print(f"\n{'#'*80}\n### TRIAL SET B: Allowing HTTP/2 (http2=True) ###\n{'#'*80}")
# B1: Original Headers, verify=True
print(f"\n--- TRIAL B1: http2=True, verify=True, Headers: Original ---")
print("Testing HTTP/2 connection with full headers and SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=True, verify=True) as client:
resp = client.get(API_URL, headers=HEADERS_ORIGINAL)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial B1 failed after all attempts.")
# B2: Original Headers, verify=False
print(f"\n--- TRIAL B2: http2=True, verify=False, Headers: Original ---")
print("Testing HTTP/2 connection with full headers, skipping SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=True, verify=False) as client:
resp = client.get(API_URL, headers=HEADERS_ORIGINAL)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial B2 failed after all attempts.")
# B3: Simplified HTTP/2 Headers, verify=True
print(f"\n--- TRIAL B3: http2=True, verify=True, Headers: Simplified HTTP/2 ---")
print("Testing HTTP/2 connection with simplified headers and SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=True, verify=True) as client:
resp = client.get(API_URL, headers=HEADERS_SIMPLIFIED_HTTP2)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial B3 failed after all attempts.")
# B4: Simplified HTTP/2 Headers, verify=False
print(f"\n--- TRIAL B4: http2=True, verify=False, Headers: Simplified HTTP/2 ---")
print("Testing HTTP/2 connection with simplified headers, skipping SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=True, verify=False) as client:
resp = client.get(API_URL, headers=HEADERS_SIMPLIFIED_HTTP2)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial B4 failed after all attempts.")
# B5: Curl-Mimic Headers, verify=True
print(f"\n--- TRIAL B5: http2=True, verify=True, Headers: Curl Mimic ---")
print("Testing HTTP/2 connection with curl-mimicking headers and SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=True, verify=True) as client:
resp = client.get(API_URL, headers=HEADERS_CURL_MIMIC)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial B5 failed after all attempts.")
# B6: Curl-Mimic Headers, verify=False
print(f"\n--- TRIAL B6: http2=True, verify=False, Headers: Curl Mimic ---")
print("Testing HTTP/2 connection with curl-mimicking headers, skipping SSL verification.")
for attempt in range(1, RETRIES_PER_TRIAL + 1):
try:
print(f"Attempt {attempt}/{RETRIES_PER_TRIAL}: Trying connection...")
with httpx.Client(timeout=TIMEOUT_SECONDS, http2=True, verify=False) as client:
resp = client.get(API_URL, headers=HEADERS_CURL_MIMIC)
resp.raise_for_status()
print("SUCCESS! Status code:", resp.status_code)
print("Response:", resp.json())
return
except httpx.HTTPStatusError as e:
print(f"Attempt {attempt}: HTTP Error: {e.response.status_code} - {e.response.text}")
except httpx.RequestError as e:
print(f"Attempt {attempt}: Connection Error: {type(e).__name__}: {e}")
except Exception as e:
print(f"Attempt {attempt}: Unexpected Error: {type(e).__name__}: {e}")
time.sleep(1)
print("Trial B6 failed after all attempts.")
print(f"\n{'='*80}\nALL HTTPX TRIALS COMPLETED. NO SUCCESSFUL RESPONSE RECEIVED.\n{'='*80}")
print("This indicates a persistent issue with httpx connecting to the Roblox API in this environment.")
if __name__ == "__main__":
run_all_httpx_trials()Script Result (Output) |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I started encountering an issue about 3 days ago, on the 22.06.2025
The issue started without a version or package change.
I have found this issue to be hard to replicate. So far, I've managed to replicate it on 3 systems.
I sorted out network based issues because it works fine on the same network on a windows based test.
curlhas no issues either.The issue
httpxfails to fetch data from an API.curlandrequestscan do it just fine.This issue only happens when fetching from the
RobloxAPI (eghttps://users.roblox.com/v1/users/949472658)I believe a remote change on Roblox's server to trigger a low level edge-case within
httpxRemember: During all these tests,
curlworks fine on the same endpointA simple connection test yields the following error:
Error:
For clarity, here's the output of
curl https://users.roblox.com/v1/users/949472658:I did more advanced tests, but my overall guess is as follows:
Guess:
The core issue is a highly specific, low-level incompatibility within
httpx's HTTP/2 implementation (httpcore/h2libraries) when interacting with the Roblox API. This isn't a simple certificate or header issue, but rather a nuanced difference in howhttpxcommunicates at the network protocol layer that Roblox's servers don't tolerate.Beta Was this translation helpful? Give feedback.
All reactions