mirror of
https://github.com/httpie/cli.git
synced 2024-11-21 15:23:11 +01:00
Better DNS error handling (#1249)
* Better DNS error handling * Update httpie/core.py Co-authored-by: Batuhan Taskaya <isidentical@gmail.com> Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
This commit is contained in:
parent
be87da8bbd
commit
e0e03f3237
@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212))
|
||||
- Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/httpie/issues/376))
|
||||
- Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/httpie/issues/1237))
|
||||
- Added support for better error handling on DNS failures. ([#1248](https://github.com/httpie/httpie/issues/1248))
|
||||
- Broken plugins will no longer crash the whole application. ([#1204](https://github.com/httpie/httpie/issues/1204))
|
||||
- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/httpie/issues/1156))
|
||||
- Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/httpie/issues/1242))
|
||||
|
@ -2,6 +2,7 @@ import argparse
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import socket
|
||||
from typing import List, Optional, Tuple, Union, Callable
|
||||
|
||||
import requests
|
||||
@ -21,6 +22,7 @@ from .models import (
|
||||
from .output.writer import write_message, write_stream, MESSAGE_SEPARATOR_BYTES
|
||||
from .plugins.registry import plugin_manager
|
||||
from .status import ExitStatus, http_status_to_exit_status
|
||||
from .utils import unwrap_context
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
@ -41,6 +43,21 @@ def raw_main(
|
||||
include_debug_info = '--debug' in args
|
||||
include_traceback = include_debug_info or '--traceback' in args
|
||||
|
||||
def handle_generic_error(e, annotation=None):
|
||||
msg = str(e)
|
||||
if hasattr(e, 'request'):
|
||||
request = e.request
|
||||
if hasattr(request, 'url'):
|
||||
msg = (
|
||||
f'{msg} while doing a {request.method}'
|
||||
f' request to URL: {request.url}'
|
||||
)
|
||||
if annotation:
|
||||
msg += annotation
|
||||
env.log_error(f'{type(e).__name__}: {msg}')
|
||||
if include_traceback:
|
||||
raise
|
||||
|
||||
if include_debug_info:
|
||||
print_debug_info(env)
|
||||
if args == ['--debug']:
|
||||
@ -90,19 +107,23 @@ def raw_main(
|
||||
f'Too many redirects'
|
||||
f' (--max-redirects={parsed_args.max_redirects}).'
|
||||
)
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
annotation = None
|
||||
original_exc = unwrap_context(exc)
|
||||
if isinstance(original_exc, socket.gaierror):
|
||||
if original_exc.errno == socket.EAI_AGAIN:
|
||||
annotation = '\nCouldn\'t connect to a DNS server. Perhaps check your connection and try again.'
|
||||
elif original_exc.errno == socket.EAI_NONAME:
|
||||
annotation = '\nCouldn\'t resolve the given hostname. Perhaps check it and try again.'
|
||||
propagated_exc = original_exc
|
||||
else:
|
||||
propagated_exc = exc
|
||||
|
||||
handle_generic_error(propagated_exc, annotation=annotation)
|
||||
exit_status = ExitStatus.ERROR
|
||||
except Exception as e:
|
||||
# TODO: Further distinction between expected and unexpected errors.
|
||||
msg = str(e)
|
||||
if hasattr(e, 'request'):
|
||||
request = e.request
|
||||
if hasattr(request, 'url'):
|
||||
msg = (
|
||||
f'{msg} while doing a {request.method}'
|
||||
f' request to URL: {request.url}'
|
||||
)
|
||||
env.log_error(f'{type(e).__name__}: {msg}')
|
||||
if include_traceback:
|
||||
raise
|
||||
handle_generic_error(e)
|
||||
exit_status = ExitStatus.ERROR
|
||||
|
||||
return exit_status
|
||||
|
@ -229,3 +229,11 @@ def split(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], Lis
|
||||
else:
|
||||
right.append(item)
|
||||
return left, right
|
||||
|
||||
|
||||
def unwrap_context(exc: Exception) -> Optional[Exception]:
|
||||
context = exc.__context__
|
||||
if isinstance(context, Exception):
|
||||
return unwrap_context(context)
|
||||
else:
|
||||
return exc
|
||||
|
@ -1,3 +1,5 @@
|
||||
import pytest
|
||||
import socket
|
||||
from unittest import mock
|
||||
from pytest import raises
|
||||
from requests import Request
|
||||
@ -31,6 +33,21 @@ def test_error_traceback(program):
|
||||
http('--traceback', 'www.google.com')
|
||||
|
||||
|
||||
@mock.patch('httpie.core.program')
|
||||
@pytest.mark.parametrize("error_code, expected_message", [
|
||||
(socket.EAI_AGAIN, "check your connection"),
|
||||
(socket.EAI_NONAME, "check the URL"),
|
||||
])
|
||||
def test_error_custom_dns(program, error_code, expected_message):
|
||||
exc = ConnectionError('Connection aborted')
|
||||
exc.__context__ = socket.gaierror(error_code, "<test>")
|
||||
program.side_effect = exc
|
||||
|
||||
r = http('www.google.com', tolerate_error_exit_status=True)
|
||||
assert r.exit_status == ExitStatus.ERROR
|
||||
assert expected_message in r.stderr
|
||||
|
||||
|
||||
def test_max_headers_limit(httpbin_both):
|
||||
with raises(ConnectionError) as e:
|
||||
http('--max-headers=1', httpbin_both + '/get')
|
||||
|
Loading…
Reference in New Issue
Block a user