From daf1ced88de6c78aa630b942afafbc2deb542235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A8=D0=B8=D0=BF=D0=B8=D1=86=D1=8B=D0=BD=20=D0=90=D0=BD?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D0=BB=D0=B8=D0=B9?= <avsh@get-net.ru> Date: Tue, 9 Apr 2019 15:51:46 +0500 Subject: [PATCH] httpc: allow to don't auto-follow redirects While we are here also added forgotten option descriptions to httpc.lua. @TarantoolBot document Title: httpc: new 'follow_location' option When the option is set to `true` (which is default) and a response has 3xx code the http client will automatically issue another request to a location that a server sends in 'Location' header. If the new response is 3xx again, the http.client will issue a next request and so on in a loop until a non-3xx response will be received. This last response will be returned as a result. Setting this option to `false` allows to disable this behaviour. In this case the http client will return a 3xx response itself. See https://curl.haxx.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html --- src/httpc.c | 7 +++++++ src/httpc.h | 10 ++++++++++ src/lua/httpc.c | 5 +++++ src/lua/httpc.lua | 16 +++++++++++++--- test/app-tap/http_client.test.lua | 26 +++++++++++++++++++++++++- test/app-tap/httpd.py | 8 ++++++++ 6 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/httpc.c b/src/httpc.c index b673ec3e8b..65eeaa7439 100644 --- a/src/httpc.c +++ b/src/httpc.c @@ -326,6 +326,13 @@ httpc_set_interface(struct httpc_request *req, const char *interface) curl_easy_setopt(req->curl_request.easy, CURLOPT_INTERFACE, interface); } +void +httpc_set_follow_location(struct httpc_request *req, long follow) +{ + curl_easy_setopt(req->curl_request.easy, CURLOPT_FOLLOWLOCATION, + follow); +} + int httpc_execute(struct httpc_request *req, double timeout) { diff --git a/src/httpc.h b/src/httpc.h index 821c739550..7463ff7bad 100644 --- a/src/httpc.h +++ b/src/httpc.h @@ -302,6 +302,16 @@ httpc_set_ssl_cert(struct httpc_request *req, const char *ssl_cert); void httpc_set_interface(struct httpc_request *req, const char *interface); +/** + * Specify whether the client will follow 'Location' header that + * a server sends as part of an 3xx response. + * @param req request + * @param follow flag + * @see https://curl.haxx.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html + */ +void +httpc_set_follow_location(struct httpc_request *req, long follow); + /** * This function does async HTTP request * @param request - reference to request object with filled fields diff --git a/src/lua/httpc.c b/src/lua/httpc.c index 706b9d90be..c39a2f0dec 100644 --- a/src/lua/httpc.c +++ b/src/lua/httpc.c @@ -287,6 +287,11 @@ luaT_httpc_request(lua_State *L) httpc_set_interface(req, lua_tostring(L, -1)); lua_pop(L, 1); + lua_getfield(L, 5, "follow_location"); + if (!lua_isnil(L, -1) && lua_isboolean(L, -1)) + httpc_set_follow_location(req, lua_toboolean(L, -1)); + lua_pop(L, 1); + if (httpc_execute(req, timeout) != 0) { httpc_request_delete(req); return luaT_error(L); diff --git a/src/lua/httpc.lua b/src/lua/httpc.lua index a5d6af3a60..aa76328c92 100644 --- a/src/lua/httpc.lua +++ b/src/lua/httpc.lua @@ -237,13 +237,15 @@ end -- method - HTTP method, like GET, POST, PUT and so on -- url - HTTP url, like https://tarantool.org/doc -- body - this parameter is optional, you may use it for passing +-- data to a server. Like 'My text string!' -- options - this is a table of options. --- data to a server. Like 'My text string!' -- -- ca_path - a path to ssl certificate dir; -- -- ca_file - a path to ssl certificate file; -- +-- unix_socket - a path to Unix domain socket; +-- -- verify_host - set on/off verification of the certificate's name (CN) -- against host; -- @@ -269,11 +271,19 @@ end -- it is less than 2000 bytes/sec -- during 20 seconds; -- --- timeout - Time-out the read operation and +-- timeout - time-out the read operation and -- waiting for the curl api request -- after this amount of seconds; -- --- verbose - set on/off verbose mode +-- max_header_name_length - maximum length of a response header; +-- +-- verbose - set on/off verbose mode; +-- +-- interface - source interface for outgoing traffic; +-- +-- follow_location - whether the client will follow +-- 'Location' header that a server sends as part of an +-- 3xx response; -- -- Returns: -- { diff --git a/test/app-tap/http_client.test.lua b/test/app-tap/http_client.test.lua index 12c93399c9..413fc3400a 100755 --- a/test/app-tap/http_client.test.lua +++ b/test/app-tap/http_client.test.lua @@ -62,7 +62,7 @@ local function stop_server(test, server) end local function test_http_client(test, url, opts) - test:plan(11) + test:plan(12) -- gh-4136: confusing httpc usage error message local ok, err = pcall(client.request, client) @@ -84,6 +84,30 @@ local function test_http_client(test, url, opts) local r = client.request('GET', url, nil, opts) test:is(r.status, 200, 'request') + + -- gh-4119: specify whether to follow 'Location' header + test:test('gh-4119: follow location', function(test) + test:plan(7) + local endpoint = 'redirect' + + -- Verify that the default behaviour is to follow location. + local r = client.request('GET', url .. endpoint, nil, opts) + test:is(r.status, 200, 'default: status') + test:is(r.body, 'hello world', 'default: body') + + -- Verify {follow_location = true} behaviour. + local r = client.request('GET', url .. endpoint, nil, merge(opts, { + follow_location = true})) + test:is(r.status, 200, 'follow location: status') + test:is(r.body, 'hello world', 'follow location: body') + + -- Verify {follow_location = false} behaviour. + local r = client.request('GET', url .. endpoint, nil, merge(opts, { + follow_location = false})) + test:is(r.status, 302, 'do not follow location: status') + test:is(r.body, 'redirecting', 'do not follow location: body') + test:is(r.headers['location'], '/', 'do not follow location: header') + end) end -- diff --git a/test/app-tap/httpd.py b/test/app-tap/httpd.py index 25d64c093f..dbfddfdd85 100755 --- a/test/app-tap/httpd.py +++ b/test/app-tap/httpd.py @@ -15,6 +15,7 @@ def hello(): body = ["hello world"] headers = [('Content-Type', 'application/json')] return code, body, headers + def hello1(): code = "200 OK" body = [b"abc"] @@ -44,12 +45,19 @@ def long_query(): headers = [('Content-Type', 'application/json')] return code, body, headers +def redirect(): + code = "302 Found" + body = "redirecting" + headers = [('Location', '/')] + return code, body, headers + paths = { "/": hello, "/abc": hello1, "/absent": absent, "/headers": headers, "/long_query": long_query, + "/redirect": redirect, } def read_handle(env, response): -- GitLab