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:
Batuhan Taskaya 2021-12-23 22:35:30 +03:00 committed by GitHub
parent be87da8bbd
commit e0e03f3237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 11 deletions

View File

@ -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 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 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 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)) - 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 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)) - Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/httpie/issues/1242))

View File

@ -2,6 +2,7 @@ import argparse
import os import os
import platform import platform
import sys import sys
import socket
from typing import List, Optional, Tuple, Union, Callable from typing import List, Optional, Tuple, Union, Callable
import requests import requests
@ -21,6 +22,7 @@ from .models import (
from .output.writer import write_message, write_stream, MESSAGE_SEPARATOR_BYTES from .output.writer import write_message, write_stream, MESSAGE_SEPARATOR_BYTES
from .plugins.registry import plugin_manager from .plugins.registry import plugin_manager
from .status import ExitStatus, http_status_to_exit_status from .status import ExitStatus, http_status_to_exit_status
from .utils import unwrap_context
# noinspection PyDefaultArgument # noinspection PyDefaultArgument
@ -41,6 +43,21 @@ def raw_main(
include_debug_info = '--debug' in args include_debug_info = '--debug' in args
include_traceback = include_debug_info or '--traceback' 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: if include_debug_info:
print_debug_info(env) print_debug_info(env)
if args == ['--debug']: if args == ['--debug']:
@ -90,19 +107,23 @@ def raw_main(
f'Too many redirects' f'Too many redirects'
f' (--max-redirects={parsed_args.max_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: except Exception as e:
# TODO: Further distinction between expected and unexpected errors. # TODO: Further distinction between expected and unexpected errors.
msg = str(e) handle_generic_error(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
exit_status = ExitStatus.ERROR exit_status = ExitStatus.ERROR
return exit_status return exit_status

View File

@ -229,3 +229,11 @@ def split(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], Lis
else: else:
right.append(item) right.append(item)
return left, right return left, right
def unwrap_context(exc: Exception) -> Optional[Exception]:
context = exc.__context__
if isinstance(context, Exception):
return unwrap_context(context)
else:
return exc

View File

@ -1,3 +1,5 @@
import pytest
import socket
from unittest import mock from unittest import mock
from pytest import raises from pytest import raises
from requests import Request from requests import Request
@ -31,6 +33,21 @@ def test_error_traceback(program):
http('--traceback', 'www.google.com') 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): def test_max_headers_limit(httpbin_both):
with raises(ConnectionError) as e: with raises(ConnectionError) as e:
http('--max-headers=1', httpbin_both + '/get') http('--max-headers=1', httpbin_both + '/get')