diff --git a/docs/hurl.1 b/docs/hurl.1 index 2e2262333..3beea2529 100644 --- a/docs/hurl.1 +++ b/docs/hurl.1 @@ -133,6 +133,12 @@ Read cookies from file (using the Netscape cookie file format). Combined with \fI-c, --cookie-jar\fP, you can simulate a cookie storage between successive Hurl runs. +.IP "--connect-timeout " + +Maximum time in seconds that you allow hurl's connection to take. + +See also \fI-m, --max-time\fP option. + .IP "-c, --cookie-jar " @@ -202,6 +208,12 @@ This option explicitly allows Hurl to perform "insecure" SSL connections and tra Follow redirect. You can limit the amount of redirects to follow by using the \fI--max-redirs\fP option. +.IP "-m, --max-time " + +Maximum time in seconds that you allow a request/response to take. This is the standard timeout. + +See also \fI--connect-timeout\fP option. + .IP "--max-redirs " diff --git a/docs/hurl.md b/docs/hurl.md index b77686dfe..bacc092f5 100644 --- a/docs/hurl.md +++ b/docs/hurl.md @@ -130,6 +130,12 @@ Read cookies from file (using the Netscape cookie file format). Combined with [-c, --cookie-jar](#cookie-jar), you can simulate a cookie storage between successive Hurl runs. +### --connect-timeout {#connect-timeout} + +Maximum time in seconds that you allow hurl's connection to take. + +See also [-m, --max-time](#max-time) option. + ### -c, --cookie-jar {#cookie-jar} @@ -199,6 +205,12 @@ This option explicitly allows Hurl to perform "insecure" SSL connections and tra Follow redirect. You can limit the amount of redirects to follow by using the [--max-redirs](#max-redirs) option. +### -m, --max-time {#ax-time} + +Maximum time in seconds that you allow a request/response to take. This is the standard timeout. + +See also [--connect-timeout](#connect-timeout) option. + ### --max-redirs {#max-redirs} diff --git a/integration/report/html/index.html b/integration/report/html/index.html index 7f9b30d83..a861aa04e 100644 --- a/integration/report/html/index.html +++ b/integration/report/html/index.html @@ -1,2 +1,2 @@ -Hurl Report

Hurl Report

Wed, 23 Sep 2020 20:34:45 +0200
filenameduration
tests/assert_base64.hurl0.002s
tests/assert_header.hurl0.002s
tests/assert_json.hurl0.008s
tests/assert_match.hurl0.016s
tests/assert_regex.hurl0.005s
tests/assert_xpath.hurl0.003s
tests/bytes.hurl0.002s
tests/capture_and_assert.hurl0.002s
tests/captures.hurl0.008s
tests/cookies.hurl0.016s
tests/cookie_storage.hurl0.003s
tests/delete.hurl0.001s
tests/empty.hurl0s
tests/encoding.hurl0.003s
tests/error_assert_base64.hurl0.002s
tests/error_assert_file.hurl0.002s
tests/error_assert_header_not_found.hurl0.002s
tests/error_assert_header_value.hurl0.002s
tests/error_assert_http_version.hurl0.001s
tests/error_assert_invalid_predicate_type.hurl0.003s
tests/error_assert_match_utf8.hurl0.002s
tests/error_assert_query_cookie.hurl0.004s
tests/error_assert_query_invalid_regex.hurl0.002s
tests/error_assert_query_invalid_xpath.hurl0.002s
tests/error_assert_status.hurl0.002s
tests/error_assert_template_variable_not_found.hurl0.002s
tests/error_assert_value_error.hurl0.004s
tests/error_assert_variable.hurl0.005s
tests/error_file_read_access.hurl0s
tests/error_http_connection.hurl0.01s
tests/error_invalid_jsonpath.hurl0.002s
tests/error_invalid_url.hurl0s
tests/error_invalid_xml.hurl0.002s
tests/error_multipart_form_data.hurl0s
tests/error_predicate.hurl0.012s
tests/error_query_header_not_found.hurl0.002s
tests/error_query_invalid_json.hurl0.002s
tests/error_query_invalid_utf8.hurl0.002s
tests/error_template_variable_not_found.hurl0s
tests/error_template_variable_not_renderable.hurl0.002s
tests/follow_redirect.hurl0.002s
tests/form_params.hurl0.004s
tests/headers.hurl0.011s
tests/hello.hurl0.004s
tests/multipart_form_data.hurl0.003s
tests/no_entry.hurl0s
tests/output.hurl0.003s
tests/patch.hurl0.002s
tests/post_base64.hurl0.002s
tests/post_file.hurl0.002s
tests/post_json.hurl0.015s
tests/post_multilines.hurl0.005s
tests/post_xml.hurl0.004s
tests/predicates-string.hurl0.005s
tests/put.hurl0.001s
tests/querystring_params.hurl0.007s
tests/redirect.hurl0.003s
tests/utf8.hurl0.002s
\ No newline at end of file +Hurl Report

Hurl Report

Thu, 24 Sep 2020 21:02:09 +0200
filenameduration
tests/assert_base64.hurl0.002s
tests/assert_header.hurl0.003s
tests/assert_json.hurl0.009s
tests/assert_match.hurl0.016s
tests/assert_regex.hurl0.004s
tests/assert_xpath.hurl0.002s
tests/bytes.hurl0.002s
tests/capture_and_assert.hurl0.002s
tests/captures.hurl0.008s
tests/cookies.hurl0.016s
tests/cookie_storage.hurl0.003s
tests/delete.hurl0.002s
tests/empty.hurl0s
tests/encoding.hurl0.006s
tests/error_assert_base64.hurl0.002s
tests/error_assert_file.hurl0.002s
tests/error_assert_header_not_found.hurl0.002s
tests/error_assert_header_value.hurl0.001s
tests/error_assert_http_version.hurl0.001s
tests/error_assert_invalid_predicate_type.hurl0.002s
tests/error_assert_match_utf8.hurl0.002s
tests/error_assert_query_cookie.hurl0.004s
tests/error_assert_query_invalid_regex.hurl0.002s
tests/error_assert_query_invalid_xpath.hurl0.002s
tests/error_assert_status.hurl0.002s
tests/error_assert_template_variable_not_found.hurl0.002s
tests/error_assert_value_error.hurl0.004s
tests/error_assert_variable.hurl0.005s
tests/error_file_read_access.hurl0s
tests/error_http_connection.hurl0.006s
tests/error_invalid_jsonpath.hurl0.002s
tests/error_invalid_url.hurl0s
tests/error_invalid_xml.hurl0.002s
tests/error_multipart_form_data.hurl0s
tests/error_predicate.hurl0.013s
tests/error_query_header_not_found.hurl0.002s
tests/error_query_invalid_json.hurl0.002s
tests/error_query_invalid_utf8.hurl0.002s
tests/error_template_variable_not_found.hurl0s
tests/error_template_variable_not_renderable.hurl0.002s
tests/error_timeout.hurl2.004s
tests/follow_redirect.hurl0.002s
tests/form_params.hurl0.004s
tests/headers.hurl0.011s
tests/hello.hurl0.003s
tests/multipart_form_data.hurl0.003s
tests/no_entry.hurl0s
tests/output.hurl0.003s
tests/patch.hurl0.002s
tests/post_base64.hurl0.002s
tests/post_file.hurl0.002s
tests/post_json.hurl0.015s
tests/post_multilines.hurl0.005s
tests/post_xml.hurl0.004s
tests/predicates-string.hurl0.005s
tests/put.hurl0.001s
tests/querystring_params.hurl0.007s
tests/redirect.hurl0.004s
tests/utf8.hurl0.002s
\ No newline at end of file diff --git a/integration/report/tests.json b/integration/report/tests.json index 87640cad5..ee54cc326 100644 --- a/integration/report/tests.json +++ b/integration/report/tests.json @@ -30,7 +30,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -113,7 +113,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -139,7 +139,7 @@ } ], "success": true, - "time": 2, + "time": 3, "cookies": [ { "domain": "localhost", @@ -201,7 +201,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -256,7 +256,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -303,7 +303,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -319,11 +319,11 @@ {}, {} ], - "time": 1 + "time": 2 } ], "success": true, - "time": 8, + "time": 9, "cookies": [] }, { @@ -357,7 +357,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -411,7 +411,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -429,7 +429,7 @@ } ], "success": true, - "time": 5, + "time": 4, "cookies": [] }, { @@ -463,7 +463,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -479,11 +479,11 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": true, - "time": 3, + "time": 2, "cookies": [] }, { @@ -517,7 +517,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -530,7 +530,7 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": true, @@ -568,7 +568,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -586,7 +586,7 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": true, @@ -632,7 +632,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -694,7 +694,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -736,7 +736,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -852,7 +852,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -894,7 +894,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -946,7 +946,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -989,7 +989,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1036,7 +1036,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1066,7 +1066,7 @@ { "name": "cookie2", "value": "", - "expires": "Wed, 23-Sep-2020 18:34:44 GMT", + "expires": "Thu, 24-Sep-2020 19:02:07 GMT", "max_age": 0, "path": "/" } @@ -1078,7 +1078,7 @@ }, { "name": "Set-Cookie", - "value": "cookie2=; Expires=Wed, 23-Sep-2020 18:34:44 GMT; Max-Age=0; Path=/" + "value": "cookie2=; Expires=Thu, 24-Sep-2020 19:02:07 GMT; Max-Age=0; Path=/" }, { "name": "Content-Length", @@ -1090,7 +1090,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1134,7 +1134,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1214,7 +1214,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1288,7 +1288,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1330,7 +1330,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1380,7 +1380,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1392,11 +1392,11 @@ }, {} ], - "time": 1 + "time": 2 } ], "success": true, - "time": 1, + "time": 2, "cookies": [] }, { @@ -1437,7 +1437,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1451,7 +1451,7 @@ {}, {} ], - "time": 1 + "time": 3 }, { "request": { @@ -1481,7 +1481,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1499,7 +1499,7 @@ } ], "success": true, - "time": 3, + "time": 6, "cookies": [] }, { @@ -1533,7 +1533,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1584,7 +1584,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1635,7 +1635,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1686,7 +1686,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1703,7 +1703,7 @@ } ], "success": false, - "time": 2, + "time": 1, "cookies": [] }, { @@ -1737,7 +1737,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1787,7 +1787,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1804,7 +1804,7 @@ } ], "success": false, - "time": 3, + "time": 2, "cookies": [] }, { @@ -1838,7 +1838,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1909,7 +1909,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -1925,7 +1925,7 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": false, @@ -1973,7 +1973,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2024,7 +2024,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2075,7 +2075,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2087,7 +2087,7 @@ }, {} ], - "time": 1 + "time": 2 } ], "success": false, @@ -2125,7 +2125,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2176,7 +2176,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2229,7 +2229,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2296,7 +2296,7 @@ } ], "success": false, - "time": 10, + "time": 6, "cookies": [] }, { @@ -2330,7 +2330,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2343,7 +2343,7 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": false, @@ -2402,7 +2402,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2466,7 +2466,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:44 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2493,7 +2493,7 @@ } ], "success": false, - "time": 12, + "time": 13, "cookies": [] }, { @@ -2527,7 +2527,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2578,7 +2578,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2629,7 +2629,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2693,7 +2693,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:07 GMT" } ] }, @@ -2726,6 +2726,50 @@ "time": 2, "cookies": [] }, + { + "filename": "tests/error_timeout.hurl", + "entries": [ + { + "request": { + "method": "GET", + "url": "http://localhost:8000/timeout", + "queryString": [], + "headers": [], + "cookies": [], + "body": "" + }, + "response": { + "httpVersion": "HTTP/1.0", + "status": 200, + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "text/html; charset=utf-8" + }, + { + "name": "Content-Length", + "value": "0" + }, + { + "name": "Server", + "value": "Werkzeug/0.16.1 Python/3.5.3" + }, + { + "name": "Date", + "value": "Thu, 24 Sep 2020 19:02:09 GMT" + } + ] + }, + "captures": [], + "asserts": [], + "time": 2003 + } + ], + "success": true, + "time": 2004, + "cookies": [] + }, { "filename": "tests/follow_redirect.hurl", "entries": [ @@ -2761,7 +2805,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -2774,7 +2818,7 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": false, @@ -2830,7 +2874,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -2842,7 +2886,7 @@ }, {} ], - "time": 2 + "time": 1 }, { "request": { @@ -2877,7 +2921,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -2927,7 +2971,58 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" + } + ] + }, + "captures": [], + "asserts": [ + { + "actual": "1.0", + "expected": "1.0" + }, + {} + ], + "time": 2 + }, + { + "request": { + "method": "GET", + "url": "http://localhost:8000/default-headers", + "queryString": [], + "headers": [ + { + "name": "User-Agent", + "value": "hurl/1.0" + }, + { + "name": "Host", + "value": "localhost:8000" + } + ], + "cookies": [], + "body": "" + }, + "response": { + "httpVersion": "HTTP/1.0", + "status": 200, + "cookies": [], + "headers": [ + { + "name": "Content-Type", + "value": "text/html; charset=utf-8" + }, + { + "name": "Content-Length", + "value": "0" + }, + { + "name": "Server", + "value": "Werkzeug/0.16.1 Python/3.5.3" + }, + { + "name": "Date", + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -2978,58 +3073,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" - } - ] - }, - "captures": [], - "asserts": [ - { - "actual": "1.0", - "expected": "1.0" - }, - {} - ], - "time": 1 - }, - { - "request": { - "method": "GET", - "url": "http://localhost:8000/default-headers", - "queryString": [], - "headers": [ - { - "name": "User-Agent", - "value": "hurl/1.0" - }, - { - "name": "Host", - "value": "localhost:8000" - } - ], - "cookies": [], - "body": "" - }, - "response": { - "httpVersion": "HTTP/1.0", - "status": 200, - "cookies": [], - "headers": [ - { - "name": "Content-Type", - "value": "text/html; charset=utf-8" - }, - { - "name": "Content-Length", - "value": "0" - }, - { - "name": "Server", - "value": "Werkzeug/0.16.1 Python/3.5.3" - }, - { - "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3092,7 +3136,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3139,7 +3183,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3185,7 +3229,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3236,7 +3280,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3249,7 +3293,7 @@ {}, {} ], - "time": 2 + "time": 1 }, { "request": { @@ -3279,7 +3323,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3292,11 +3336,11 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": true, - "time": 4, + "time": 3, "cookies": [] }, { @@ -3330,7 +3374,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3496,7 +3540,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3510,7 +3554,7 @@ {}, {} ], - "time": 2 + "time": 1 } ], "success": true, @@ -3548,7 +3592,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3598,7 +3642,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3648,7 +3692,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3690,7 +3734,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3732,7 +3776,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3774,7 +3818,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3816,7 +3860,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3858,7 +3902,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3900,7 +3944,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3947,7 +3991,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -3991,7 +4035,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4033,7 +4077,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4080,7 +4124,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4130,7 +4174,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4172,7 +4216,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4222,7 +4266,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4268,7 +4312,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4320,7 +4364,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4387,7 +4431,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4399,7 +4443,7 @@ }, {} ], - "time": 2 + "time": 1 }, { "request": { @@ -4429,7 +4473,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4484,7 +4528,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4531,7 +4575,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4585,7 +4629,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4598,7 +4642,7 @@ {}, {} ], - "time": 1 + "time": 2 }, { "request": { @@ -4628,7 +4672,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, @@ -4644,7 +4688,7 @@ } ], "success": true, - "time": 3, + "time": 4, "cookies": [] }, { @@ -4678,7 +4722,7 @@ }, { "name": "Date", - "value": "Wed, 23 Sep 2020 18:34:45 GMT" + "value": "Thu, 24 Sep 2020 19:02:09 GMT" } ] }, diff --git a/integration/tests/error_timeout.err b/integration/tests/error_timeout.err new file mode 100644 index 000000000..270191689 --- /dev/null +++ b/integration/tests/error_timeout.err @@ -0,0 +1,7 @@ +error: Http Connection + --> tests/error_timeout.hurl:1:5 + | + 1 | GET http://localhost:8000/timeout + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Timeout has been reached + | + diff --git a/integration/tests/error_timeout.exit b/integration/tests/error_timeout.exit new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/integration/tests/error_timeout.exit @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/integration/tests/error_timeout.hurl b/integration/tests/error_timeout.hurl new file mode 100644 index 000000000..2e99c31c7 --- /dev/null +++ b/integration/tests/error_timeout.hurl @@ -0,0 +1,3 @@ +GET http://localhost:8000/timeout + + diff --git a/integration/tests/error_timeout.options b/integration/tests/error_timeout.options new file mode 100644 index 000000000..e97871502 --- /dev/null +++ b/integration/tests/error_timeout.options @@ -0,0 +1 @@ +--max-time 1 \ No newline at end of file diff --git a/integration/tests/error_timeout.py b/integration/tests/error_timeout.py new file mode 100644 index 000000000..25061bdc0 --- /dev/null +++ b/integration/tests/error_timeout.py @@ -0,0 +1,9 @@ +from tests import app +import time + +@app.route('/timeout') +def timeout(): + time.sleep(2) + return '' + + diff --git a/src/bin/hurl.rs b/src/bin/hurl.rs index 8587683b6..9aa070f47 100644 --- a/src/bin/hurl.rs +++ b/src/bin/hurl.rs @@ -36,6 +36,7 @@ use hurl::parser; use hurl::runner; use hurl::runner::core::*; use hurl::runner::log_deserialize; +use std::time::Duration; #[derive(Clone, Debug, PartialEq, Eq)] pub struct CLIOptions { @@ -50,6 +51,8 @@ pub struct CLIOptions { pub proxy: Option, pub no_proxy: Option, pub cookie_input_file: Option, + pub timeout: Duration, + pub connect_timeout: Duration, } fn execute( @@ -114,6 +117,9 @@ fn execute( let proxy = cli_options.proxy; let no_proxy = cli_options.no_proxy; let cookie_input_file = cli_options.cookie_input_file; + + let timeout = cli_options.timeout; + let connect_timeout = cli_options.connect_timeout; let options = http::ClientOptions { follow_location, max_redirect, @@ -122,6 +128,8 @@ fn execute( no_proxy, verbose, insecure, + timeout, + connect_timeout, }; let mut client = http::Client::init(options); @@ -298,6 +306,12 @@ fn app() -> clap::App<'static, 'static> { .conflicts_with("no-color") .help("Colorize Output"), ) + .arg( + clap::Arg::with_name("connect_timeout") + .long("connect-timeout") + .value_name("SECONDS") + .help("Maximum time allowed for connection"), + ) .arg( clap::Arg::with_name("cookies_input_file") .short("b") @@ -357,6 +371,14 @@ fn app() -> clap::App<'static, 'static> { .long("location") .help("Follow redirects"), ) + .arg( + clap::Arg::with_name("max_time") + .long("max-time") + .short("m") + .value_name("NUM") + .allow_hyphen_values(true) + .help("Maximum time allowed for the transfer"), + ) .arg( clap::Arg::with_name("max_redirects") .long("max-redirs") @@ -447,7 +469,31 @@ fn parse_options( Err(_) => { return Err(cli::Error { message: "max_redirs option can not be parsed".to_string(), - }) + }); + } + }, + }; + + let timeout = match matches.value_of("max_time") { + None => Duration::from_secs(0), + Some(s) => match s.parse::() { + Ok(n) => Duration::from_secs(n), + Err(_) => { + return Err(cli::Error { + message: "max_time option can not be parsed".to_string(), + }); + } + }, + }; + + let connect_timeout = match matches.value_of("connect_timeout") { + None => Duration::from_secs(300), + Some(s) => match s.parse::() { + Ok(n) => Duration::from_secs(n), + Err(_) => { + return Err(cli::Error { + message: "connect-timeout option can not be parsed".to_string(), + }); } }, }; @@ -464,6 +510,8 @@ fn parse_options( proxy, no_proxy, cookie_input_file, + timeout, + connect_timeout, }) } diff --git a/src/http/client.rs b/src/http/client.rs index 7962968ef..e83929958 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -22,6 +22,7 @@ use std::str; use curl::easy; use encoding::all::ISO_8859_1; use encoding::{DecoderTrap, Encoding}; +use std::time::Duration; use super::core::*; use super::request::*; @@ -36,6 +37,7 @@ pub enum HttpError { CouldNotParseResponse, SSLCertificate, InvalidUrl, + Timeout, Other { description: String, code: u32 }, } @@ -61,6 +63,8 @@ pub struct ClientOptions { pub no_proxy: Option, pub verbose: bool, pub insecure: bool, + pub timeout: Duration, + pub connect_timeout: Duration, } impl Client { @@ -90,6 +94,9 @@ impl Client { h.ssl_verify_host(options.insecure).unwrap(); h.ssl_verify_peer(options.insecure).unwrap(); + h.timeout(options.timeout).unwrap(); + h.connect_timeout(options.connect_timeout).unwrap(); + Client { handle: Box::new(h), follow_location: options.follow_location, @@ -177,6 +184,7 @@ impl Client { 5 => Err(HttpError::CouldNotResolveProxyName), 6 => Err(HttpError::CouldNotResolveHost), 7 => Err(HttpError::FailToConnect), + 28 => Err(HttpError::Timeout), 60 => Err(HttpError::SSLCertificate), _ => Err(HttpError::Other { code: e.code(), diff --git a/src/runner/core.rs b/src/runner/core.rs index d3e44c7aa..1956b68d8 100644 --- a/src/runner/core.rs +++ b/src/runner/core.rs @@ -122,6 +122,7 @@ pub enum RunnerError { CouldNotResolveProxyName, CouldNotResolveHost, FailToConnect, + Timeout, TooManyRedirect, CouldNotParseResponse, SSLCertificate, @@ -192,6 +193,7 @@ impl FormatError for Error { RunnerError::CouldNotResolveProxyName => "Http Connection".to_string(), RunnerError::CouldNotResolveHost => "Http Connection".to_string(), RunnerError::FailToConnect => "Http Connection".to_string(), + RunnerError::Timeout => "Http Connection".to_string(), RunnerError::TooManyRedirect => "Http Connection".to_string(), RunnerError::CouldNotParseResponse => "Http Connection".to_string(), RunnerError::SSLCertificate => "Http Connection".to_string(), @@ -230,6 +232,7 @@ impl FormatError for Error { RunnerError::CouldNotResolveProxyName => "Could not resolve proxy name".to_string(), RunnerError::CouldNotResolveHost => "Could not resolve host".to_string(), RunnerError::FailToConnect => "Fail to connect".to_string(), + RunnerError::Timeout => "Timeout has been reached".to_string(), RunnerError::TooManyRedirect => "Too many redirect".to_string(), RunnerError::CouldNotParseResponse => "Could not parse response".to_string(), RunnerError::SSLCertificate => "SSl Certificate".to_string(), diff --git a/src/runner/entry.rs b/src/runner/entry.rs index c2372c47e..3366c3c50 100644 --- a/src/runner/entry.rs +++ b/src/runner/entry.rs @@ -105,6 +105,7 @@ pub fn run( HttpError::CouldNotResolveProxyName => RunnerError::CouldNotResolveProxyName, HttpError::CouldNotResolveHost => RunnerError::CouldNotResolveHost, HttpError::FailToConnect => RunnerError::FailToConnect, + HttpError::Timeout => RunnerError::Timeout, HttpError::TooManyRedirect => RunnerError::TooManyRedirect, HttpError::CouldNotParseResponse => RunnerError::CouldNotParseResponse, HttpError::SSLCertificate => RunnerError::SSLCertificate, diff --git a/src/runner/file.rs b/src/runner/file.rs index 8fdc0d889..6cb143557 100644 --- a/src/runner/file.rs +++ b/src/runner/file.rs @@ -53,6 +53,8 @@ use crate::core::common::FormatError; /// no_proxy: None, /// verbose: false, /// insecure: false, +/// timeout: Default::default(), +/// connect_timeout: Default::default() /// }; /// let mut client = http::Client::init(options); /// diff --git a/tests/libcurl.rs b/tests/libcurl.rs index 1700723bf..236b0a191 100644 --- a/tests/libcurl.rs +++ b/tests/libcurl.rs @@ -1,7 +1,7 @@ +use curl::easy::Easy; use std::fs::File; use std::io::prelude::*; - -use curl::easy::Easy; +use std::time::Duration; use hurl::http::*; @@ -63,6 +63,8 @@ fn default_client() -> Client { no_proxy: None, verbose: true, insecure: false, + timeout: Default::default(), + connect_timeout: Duration::from_secs(300), }; Client::init(options) } @@ -302,6 +304,8 @@ fn test_follow_location() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = Client::init(options); let response = client.execute(&request, 0).unwrap(); @@ -333,6 +337,8 @@ fn test_max_redirect() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/redirect".to_string()); @@ -447,6 +453,8 @@ fn test_error_fail_to_connect() { no_proxy: None, verbose: true, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/hello".to_string()); @@ -464,6 +472,8 @@ fn test_error_could_not_resolve_proxy_name() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/hello".to_string()); @@ -471,6 +481,46 @@ fn test_error_could_not_resolve_proxy_name() { assert_eq!(error, HttpError::CouldNotResolveProxyName); } +#[test] +fn test_timeout() { + let options = ClientOptions { + follow_location: false, + max_redirect: None, + cookie_input_file: None, + proxy: None, + no_proxy: None, + verbose: false, + insecure: false, + timeout: Duration::from_millis(100), + connect_timeout: Default::default(), + }; + let mut client = Client::init(options); + let request = default_get_request("http://localhost:8000/timeout".to_string()); + let error = client.execute(&request, 0).err().unwrap(); + assert_eq!(error, HttpError::Timeout); +} + +// +// TODO: find a way to test it locally +// #[test] +fn test_connect_timeout() { + let options = ClientOptions { + follow_location: false, + max_redirect: None, + cookie_input_file: None, + proxy: None, + no_proxy: None, + verbose: false, + insecure: false, + timeout: Default::default(), + connect_timeout: Duration::from_secs(1), + }; + let mut client = Client::init(options); + let request = default_get_request("http://example.com:81".to_string()); + let error = client.execute(&request, 0).err().unwrap(); + assert_eq!(error, HttpError::Timeout); +} + // endregion // region cookie @@ -564,6 +614,8 @@ fn test_cookie_file() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = Client::init(options); let request = default_get_request( @@ -589,6 +641,8 @@ fn test_proxy() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = Client::init(options); let request = default_get_request("http://localhost:8000/hello".to_string()); diff --git a/tests/runner.rs b/tests/runner.rs index 6a7493e95..bf8845a92 100644 --- a/tests/runner.rs +++ b/tests/runner.rs @@ -44,6 +44,8 @@ fn test_hurl_file() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = http::Client::init(options); let mut lines: Vec<&str> = regex::Regex::new(r"\n|\r\n") @@ -149,6 +151,8 @@ fn test_hello() { no_proxy: None, verbose: false, insecure: false, + timeout: Default::default(), + connect_timeout: Default::default(), }; let mut client = http::Client::init(options); let source_info = SourceInfo {