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 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))

View File

@ -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

View File

@ -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

View File

@ -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')