mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Provide httpx default context for OS-provided certs (#11505)
Co-authored-by: Pierrick Hymbert <pierrick.hymbert@gmail.com>
This commit is contained in:
parent
818f743dc7
commit
4decd8b3e9
@ -32,6 +32,7 @@ from openhands.integrations.service_types import Repository
|
||||
from openhands.server.shared import server_config
|
||||
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
JIRA_CLOUD_API_URL = 'https://api.atlassian.com/ex/jira'
|
||||
|
||||
@ -408,7 +409,7 @@ class JiraManager(Manager):
|
||||
svc_acc_api_key: str,
|
||||
) -> Tuple[str, str]:
|
||||
url = f'{JIRA_CLOUD_API_URL}/{jira_cloud_id}/rest/api/2/issue/{job_context.issue_key}'
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.get(url, auth=(svc_acc_email, svc_acc_api_key))
|
||||
response.raise_for_status()
|
||||
issue_payload = response.json()
|
||||
@ -443,7 +444,7 @@ class JiraManager(Manager):
|
||||
f'{JIRA_CLOUD_API_URL}/{jira_cloud_id}/rest/api/2/issue/{issue_key}/comment'
|
||||
)
|
||||
data = {'body': message.message}
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(
|
||||
url, auth=(svc_acc_email, svc_acc_api_key), json=data
|
||||
)
|
||||
|
||||
@ -34,6 +34,7 @@ from openhands.integrations.service_types import Repository
|
||||
from openhands.server.shared import server_config
|
||||
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class JiraDcManager(Manager):
|
||||
@ -422,7 +423,7 @@ class JiraDcManager(Manager):
|
||||
"""Get issue details from Jira DC API."""
|
||||
url = f'{job_context.base_api_url}/rest/api/2/issue/{job_context.issue_key}'
|
||||
headers = {'Authorization': f'Bearer {svc_acc_api_key}'}
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
issue_payload = response.json()
|
||||
@ -452,7 +453,7 @@ class JiraDcManager(Manager):
|
||||
url = f'{base_api_url}/rest/api/2/issue/{issue_key}/comment'
|
||||
headers = {'Authorization': f'Bearer {svc_acc_api_key}'}
|
||||
data = {'body': message.message}
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
@ -31,6 +31,7 @@ from openhands.integrations.service_types import Repository
|
||||
from openhands.server.shared import server_config
|
||||
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.server.user_auth.user_auth import UserAuth
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class LinearManager(Manager):
|
||||
@ -408,7 +409,7 @@ class LinearManager(Manager):
|
||||
async def _query_api(self, query: str, variables: Dict, api_key: str) -> Dict:
|
||||
"""Query Linear GraphQL API."""
|
||||
headers = {'Authorization': api_key}
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(
|
||||
self.api_url,
|
||||
headers=headers,
|
||||
|
||||
@ -37,6 +37,7 @@ from storage.offline_token_store import OfflineTokenStore
|
||||
from tenacity import RetryCallState, retry, retry_if_exception_type, stop_after_attempt
|
||||
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
def _before_sleep_callback(retry_state: RetryCallState) -> None:
|
||||
@ -191,7 +192,7 @@ class TokenManager:
|
||||
access_token: str,
|
||||
idp: ProviderType,
|
||||
) -> dict[str, str | int]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
base_url = KEYCLOAK_SERVER_URL_EXT if self.external else KEYCLOAK_SERVER_URL
|
||||
url = f'{base_url}/realms/{KEYCLOAK_REALM_NAME}/broker/{idp.value}/token'
|
||||
headers = {
|
||||
@ -350,7 +351,7 @@ class TokenManager:
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(url, data=payload)
|
||||
response.raise_for_status()
|
||||
logger.info('Successfully refreshed GitHub token')
|
||||
@ -376,7 +377,7 @@ class TokenManager:
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(url, data=payload)
|
||||
response.raise_for_status()
|
||||
logger.info('Successfully refreshed GitLab token')
|
||||
@ -404,7 +405,7 @@ class TokenManager:
|
||||
'refresh_token': refresh_token,
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(url, data=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
logger.info('Successfully refreshed Bitbucket token')
|
||||
|
||||
@ -12,6 +12,7 @@ from storage.saas_settings_store import SaasSettingsStore
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.server.user_auth import get_user_id
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
# Helper functions for BYOR API key management
|
||||
@ -68,9 +69,10 @@ async def generate_byor_key(user_id: str) -> str | None:
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'x-goog-api-key': LITE_LLM_API_KEY,
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
response = await client.post(
|
||||
f'{LITE_LLM_API_URL}/key/generate',
|
||||
@ -120,9 +122,10 @@ async def delete_byor_key_from_litellm(user_id: str, byor_key: str) -> bool:
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'x-goog-api-key': LITE_LLM_API_KEY,
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
# Delete the key directly using the key value
|
||||
delete_url = f'{LITE_LLM_API_URL}/key/delete'
|
||||
|
||||
@ -27,6 +27,7 @@ from storage.saas_settings_store import SaasSettingsStore
|
||||
from storage.subscription_access import SubscriptionAccess
|
||||
|
||||
from openhands.server.user_auth import get_user_id
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
stripe.api_key = STRIPE_API_KEY
|
||||
billing_router = APIRouter(prefix='/api/billing')
|
||||
@ -110,7 +111,7 @@ def calculate_credits(user_info: LiteLlmUserInfo) -> float:
|
||||
async def get_credits(user_id: str = Depends(get_user_id)) -> GetCreditsResponse:
|
||||
if not stripe_service.STRIPE_API_KEY:
|
||||
return GetCreditsResponse()
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
user_json = await _get_litellm_user(client, user_id)
|
||||
credits = calculate_credits(user_json['user_info'])
|
||||
return GetCreditsResponse(credits=Decimal('{:.2f}'.format(credits)))
|
||||
@ -430,7 +431,7 @@ async def success_callback(session_id: str, request: Request):
|
||||
)
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
# Update max budget in litellm
|
||||
user_json = await _get_litellm_user(client, billing_session.user_id)
|
||||
amount_subtotal = stripe_session.amount_subtotal or 0
|
||||
|
||||
@ -11,6 +11,7 @@ from fastapi.responses import RedirectResponse
|
||||
from server.logger import logger
|
||||
|
||||
from openhands.server.shared import config
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
GITHUB_PROXY_ENDPOINTS = bool(os.environ.get('GITHUB_PROXY_ENDPOINTS'))
|
||||
|
||||
@ -87,7 +88,7 @@ def add_github_proxy_routes(app: FastAPI):
|
||||
]
|
||||
body = urlencode(query_params, doseq=True)
|
||||
url = 'https://github.com/login/oauth/access_token'
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(url, content=body)
|
||||
return Response(
|
||||
response.content,
|
||||
@ -101,7 +102,7 @@ def add_github_proxy_routes(app: FastAPI):
|
||||
logger.info(f'github_proxy_post:1:{path}')
|
||||
body = await request.body()
|
||||
url = f'https://github.com/{path}'
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.post(url, content=body, headers=request.headers)
|
||||
return Response(
|
||||
response.content,
|
||||
|
||||
@ -52,6 +52,7 @@ from openhands.storage.locations import (
|
||||
get_conversation_events_dir,
|
||||
)
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
from openhands.utils.import_utils import get_impl
|
||||
from openhands.utils.shutdown_listener import should_continue
|
||||
from openhands.utils.utils import create_registry_and_conversation_stats
|
||||
@ -266,9 +267,10 @@ class SaasNestedConversationManager(ConversationManager):
|
||||
):
|
||||
logger.info('starting_nested_conversation', extra={'sid': sid})
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': session_api_key,
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
await self._setup_nested_settings(client, api_url, settings)
|
||||
await self._setup_provider_tokens(client, api_url, settings)
|
||||
@ -484,9 +486,10 @@ class SaasNestedConversationManager(ConversationManager):
|
||||
raise ValueError(f'no_such_conversation:{sid}')
|
||||
nested_url = self._get_nested_url_for_runtime(runtime['runtime_id'], sid)
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': runtime['session_api_key'],
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
response = await client.post(f'{nested_url}/events', json=data)
|
||||
response.raise_for_status()
|
||||
@ -551,9 +554,10 @@ class SaasNestedConversationManager(ConversationManager):
|
||||
return None
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': session_api_key,
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
# Query the nested runtime for conversation info
|
||||
response = await client.get(nested_url)
|
||||
@ -828,6 +832,7 @@ class SaasNestedConversationManager(ConversationManager):
|
||||
@contextlib.asynccontextmanager
|
||||
async def _httpx_client(self):
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={'X-API-Key': self.config.sandbox.api_key or ''},
|
||||
timeout=_HTTP_TIMEOUT,
|
||||
) as client:
|
||||
|
||||
@ -31,6 +31,7 @@ from openhands.server.settings import Settings
|
||||
from openhands.storage import get_file_store
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -215,9 +216,10 @@ class SaasSettingsStore(SettingsStore):
|
||||
)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'x-goog-api-key': LITE_LLM_API_KEY,
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
# Get the previous max budget to prevent accidental loss
|
||||
# In Litellm a get always succeeds, regardless of whether the user actually exists
|
||||
|
||||
@ -14,6 +14,7 @@ from openhands.integrations.service_types import (
|
||||
ResourceNotFoundError,
|
||||
User,
|
||||
)
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class BitBucketMixinBase(BaseGitService, HTTPClient):
|
||||
@ -83,7 +84,7 @@ class BitBucketMixinBase(BaseGitService, HTTPClient):
|
||||
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
bitbucket_headers = await self._get_headers()
|
||||
response = await self.execute_request(
|
||||
client, url, bitbucket_headers, params, method
|
||||
|
||||
@ -11,6 +11,7 @@ from openhands.integrations.service_types import (
|
||||
UnknownException,
|
||||
User,
|
||||
)
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class GitHubMixinBase(BaseGitService, HTTPClient):
|
||||
@ -43,7 +44,7 @@ class GitHubMixinBase(BaseGitService, HTTPClient):
|
||||
method: RequestMethod = RequestMethod.GET,
|
||||
) -> tuple[Any, dict]: # type: ignore[override]
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
github_headers = await self._get_headers()
|
||||
|
||||
# Make initial request
|
||||
@ -83,7 +84,7 @@ class GitHubMixinBase(BaseGitService, HTTPClient):
|
||||
self, query: str, variables: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
github_headers = await self._get_headers()
|
||||
|
||||
response = await client.post(
|
||||
|
||||
@ -10,6 +10,7 @@ from openhands.integrations.service_types import (
|
||||
UnknownException,
|
||||
User,
|
||||
)
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class GitLabMixinBase(BaseGitService, HTTPClient):
|
||||
@ -41,7 +42,7 @@ class GitLabMixinBase(BaseGitService, HTTPClient):
|
||||
method: RequestMethod = RequestMethod.GET,
|
||||
) -> tuple[Any, dict]: # type: ignore[override]
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
gitlab_headers = await self._get_headers()
|
||||
|
||||
# Make initial request
|
||||
@ -99,7 +100,7 @@ class GitLabMixinBase(BaseGitService, HTTPClient):
|
||||
if variables is None:
|
||||
variables = {}
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
gitlab_headers = await self._get_headers()
|
||||
# Add content type header for GraphQL
|
||||
gitlab_headers['Content-Type'] = 'application/json'
|
||||
|
||||
@ -36,6 +36,7 @@ from openhands.integrations.service_types import (
|
||||
)
|
||||
from openhands.microagent.types import MicroagentContentResponse, MicroagentResponse
|
||||
from openhands.server.types import AppMode
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class ProviderToken(BaseModel):
|
||||
@ -174,7 +175,7 @@ class ProviderHandler:
|
||||
) -> SecretStr | None:
|
||||
"""Get latest token from service"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
resp = await client.get(
|
||||
self.REFRESH_TOKEN_URL,
|
||||
headers={
|
||||
|
||||
@ -10,6 +10,7 @@ from openhands.resolver.interfaces.issue import (
|
||||
ReviewThread,
|
||||
)
|
||||
from openhands.resolver.utils import extract_issue_references
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class BitbucketIssueHandler(IssueHandlerInterface):
|
||||
@ -91,7 +92,7 @@ class BitbucketIssueHandler(IssueHandlerInterface):
|
||||
An Issue object
|
||||
"""
|
||||
url = f'{self.base_url}/repositories/{self.owner}/{self.repo}/issues/{issue_number}'
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
|
||||
response = await client.get(url, headers=self.headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
@ -42,6 +42,7 @@ from openhands.runtime.runtime_status import RuntimeStatus
|
||||
from openhands.runtime.utils import find_available_tcp_port
|
||||
from openhands.runtime.utils.command import get_action_execution_server_startup_command
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
from openhands.utils.tenacity_stop import stop_if_should_exit
|
||||
|
||||
|
||||
@ -760,7 +761,7 @@ def _create_warm_server(
|
||||
)
|
||||
|
||||
# Wait for the server to be ready
|
||||
session = httpx.Client(timeout=30)
|
||||
session = httpx.Client(timeout=30, verify=httpx_verify_option())
|
||||
|
||||
# Use tenacity to retry the connection
|
||||
@tenacity.retry(
|
||||
|
||||
@ -42,6 +42,7 @@ from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.files import FileStore
|
||||
from openhands.storage.locations import get_conversation_dir
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
from openhands.utils.import_utils import get_impl
|
||||
from openhands.utils.utils import create_registry_and_conversation_stats
|
||||
|
||||
@ -200,9 +201,10 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
await call_sync_from_async(runtime.wait_until_alive)
|
||||
await call_sync_from_async(runtime.setup_initial_env)
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': self._get_session_api_key_for_conversation(sid)
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
# setup the settings...
|
||||
settings_json = settings.model_dump(context={'expose_secrets': True})
|
||||
@ -296,9 +298,10 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
|
||||
async def send_event_to_conversation(self, sid, data):
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': self._get_session_api_key_for_conversation(sid)
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
nested_url = self._get_nested_url(sid)
|
||||
response = await client.post(
|
||||
@ -319,9 +322,10 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
try:
|
||||
nested_url = self.get_nested_url_for_container(container)
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': self._get_session_api_key_for_conversation(sid)
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
# Stop conversation
|
||||
response = await client.post(
|
||||
@ -357,11 +361,12 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
verify=httpx_verify_option(),
|
||||
headers={
|
||||
'X-Session-API-Key': self._get_session_api_key_for_conversation(
|
||||
conversation_id
|
||||
)
|
||||
}
|
||||
},
|
||||
) as client:
|
||||
# Query the nested runtime for conversation info
|
||||
response = await client.get(nested_url)
|
||||
|
||||
@ -9,6 +9,7 @@ from openhands.storage.local import LocalFileStore
|
||||
from openhands.storage.memory import InMemoryFileStore
|
||||
from openhands.storage.s3 import S3FileStore
|
||||
from openhands.storage.web_hook import WebHookFileStore
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
def get_file_store(
|
||||
@ -38,7 +39,10 @@ def get_file_store(
|
||||
'SESSION_API_KEY'
|
||||
)
|
||||
|
||||
client = httpx.Client(headers=file_store_web_hook_headers or {})
|
||||
client = httpx.Client(
|
||||
headers=file_store_web_hook_headers or {},
|
||||
verify=httpx_verify_option(),
|
||||
)
|
||||
|
||||
if file_store_web_hook_batch:
|
||||
# Use batched webhook file store
|
||||
|
||||
@ -7,6 +7,7 @@ import tenacity
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.storage.files import FileStore
|
||||
from openhands.utils.async_utils import EXECUTOR
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
# Constants for batching configuration
|
||||
WEBHOOK_BATCH_TIMEOUT_SECONDS = 5.0
|
||||
@ -65,7 +66,7 @@ class BatchedWebHookFileStore(FileStore):
|
||||
self.file_store = file_store
|
||||
self.base_url = base_url
|
||||
if client is None:
|
||||
client = httpx.Client()
|
||||
client = httpx.Client(verify=httpx_verify_option())
|
||||
self.client = client
|
||||
|
||||
# Use provided values or default constants
|
||||
|
||||
@ -3,6 +3,7 @@ import tenacity
|
||||
|
||||
from openhands.storage.files import FileStore
|
||||
from openhands.utils.async_utils import EXECUTOR
|
||||
from openhands.utils.http_session import httpx_verify_option
|
||||
|
||||
|
||||
class WebHookFileStore(FileStore):
|
||||
@ -34,7 +35,7 @@ class WebHookFileStore(FileStore):
|
||||
self.file_store = file_store
|
||||
self.base_url = base_url
|
||||
if client is None:
|
||||
client = httpx.Client()
|
||||
client = httpx.Client(verify=httpx_verify_option())
|
||||
self.client = client
|
||||
|
||||
def write(self, path: str, contents: str | bytes) -> None:
|
||||
|
||||
@ -1,11 +1,34 @@
|
||||
import ssl
|
||||
from dataclasses import dataclass, field
|
||||
from threading import Lock
|
||||
from typing import MutableMapping
|
||||
|
||||
import httpx
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
CLIENT = httpx.Client()
|
||||
_client_lock = Lock()
|
||||
_verify_certificates: bool = True
|
||||
_client: httpx.Client | None = None
|
||||
|
||||
|
||||
def httpx_verify_option() -> ssl.SSLContext | bool:
|
||||
"""Return the verify option to pass when creating an HTTPX client."""
|
||||
|
||||
return ssl.create_default_context() if _verify_certificates else False
|
||||
|
||||
|
||||
def _build_client(verify: bool) -> httpx.Client:
|
||||
return httpx.Client(verify=ssl.create_default_context() if verify else False)
|
||||
|
||||
|
||||
def _get_client() -> httpx.Client:
|
||||
global _client
|
||||
if _client is None:
|
||||
with _client_lock:
|
||||
if _client is None:
|
||||
_client = _build_client(_verify_certificates)
|
||||
return _client
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -28,7 +51,7 @@ class HttpSession:
|
||||
headers = {**self.headers, **headers}
|
||||
kwargs['headers'] = headers
|
||||
logger.debug(f'HttpSession:request called with args {args} and kwargs {kwargs}')
|
||||
return CLIENT.request(*args, **kwargs)
|
||||
return _get_client().request(*args, **kwargs)
|
||||
|
||||
def stream(self, *args, **kwargs):
|
||||
if self._is_closed:
|
||||
@ -39,7 +62,7 @@ class HttpSession:
|
||||
headers = kwargs.get('headers') or {}
|
||||
headers = {**self.headers, **headers}
|
||||
kwargs['headers'] = headers
|
||||
return CLIENT.stream(*args, **kwargs)
|
||||
return _get_client().stream(*args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return self.request('GET', *args, **kwargs)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user