Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support net.http_delete with request body #173

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions nix/nginx/conf/custom.conf
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,24 @@ location /delete {
if ($request_method != 'DELETE'){
return 405;
}

# Reject any request body, assumes they get sent with a content-type header (sufficient for our use cases)
if ($http_content_type != "") {
return 400;
}

echo_duplicate 1 $echo_client_request_headers$is_args$query_string;
}

location /delete_w_body {
if ($request_method != 'DELETE'){
return 405;
}

echo_read_request_body;
echo $request_body;
}

location /redirect_me {
return 301 /to_here;
}
Expand Down
43 changes: 43 additions & 0 deletions sql/pg_net--0.14.0--0.15.0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
drop function net.http_delete (text, jsonb, jsonb, integer);

create function net.http_delete(
-- url for the request
url text,
-- key/value pairs to be url encoded and appended to the `url`
params jsonb default '{}'::jsonb,
-- key/values to be included in request headers
headers jsonb default '{}'::jsonb,
-- the maximum number of milliseconds the request may take before being cancelled
timeout_milliseconds int default 5000,
-- optional body of the request
body jsonb default NULL
)
-- request_id reference
returns bigint
volatile
parallel safe
language plpgsql
as $$
declare
request_id bigint;
params_array text[];
begin
select coalesce(array_agg(net._urlencode_string(key) || '=' || net._urlencode_string(value)), '{}')
into params_array
from jsonb_each_text(params);

-- Add to the request queue
insert into net.http_request_queue(method, url, headers, body, timeout_milliseconds)
values (
'DELETE',
net._encode_url_with_params_array(url, params_array),
headers,
convert_to(body::text, 'UTF8'),
timeout_milliseconds
)
returning id
into request_id;

return request_id;
end
$$;
8 changes: 5 additions & 3 deletions sql/pg_net.sql
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,12 @@ create or replace function net.http_delete(
-- key/values to be included in request headers
headers jsonb default '{}'::jsonb,
-- the maximum number of milliseconds the request may take before being cancelled
timeout_milliseconds int default 5000
timeout_milliseconds int default 5000,
-- optional body of the request
body jsonb default NULL
)
-- request_id reference
returns bigint
strict
volatile
parallel safe
language plpgsql
Expand All @@ -241,11 +242,12 @@ begin
from jsonb_each_text(params);

-- Add to the request queue
insert into net.http_request_queue(method, url, headers, timeout_milliseconds)
insert into net.http_request_queue(method, url, headers, body, timeout_milliseconds)
values (
'DELETE',
net._encode_url_with_params_array(url, params_array),
headers,
convert_to(body::text, 'UTF8'),
timeout_milliseconds
)
returning id
Expand Down
3 changes: 3 additions & 0 deletions src/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ static void init_curl_handle(CURLM *curl_mhandle, MemoryContext curl_memctx, int

if (strcasecmp(method, "DELETE") == 0) {
EREPORT_CURL_SETOPT(curl_ez_handle, CURLOPT_CUSTOMREQUEST, "DELETE");
if (reqBody) {
EREPORT_CURL_SETOPT(curl_ez_handle, CURLOPT_POSTFIELDS, reqBody);
}
}

EREPORT_CURL_SETOPT(curl_ez_handle, CURLOPT_WRITEFUNCTION, body_cb);
Expand Down
147 changes: 146 additions & 1 deletion test/test_http_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_http_delete_returns_id(sess):


def test_http_delete_collect_sync_success(sess):
"""Collect a response, waiting if it has not completed yet"""
"""test net.http_delete works"""

# Create a request
(request_id,) = sess.execute(text(
Expand Down Expand Up @@ -47,3 +47,148 @@ def test_http_delete_collect_sync_success(sess):
assert response[2] is not None
assert "X-Baz" in response[2]
assert "param-foo" in response[2]


def test_http_delete_positional_args(sess):
"""test net.http_delete works with positional arguments. This to ensure backwards compat when a new parameter is added to the function."""

(request_id,) = sess.execute(text(
"""
select net.http_delete(
'http://localhost:8080/delete'
);
"""
)).fetchone()

# Commit so background worker can start
sess.commit()

# Collect the response, waiting as needed
response = sess.execute(
text(
"""
select * from net._http_collect_response(:request_id, async:=false);
"""
),
{"request_id": request_id},
).fetchone()

assert response is not None
assert response[0] == "SUCCESS"
assert response[1] == "ok"


(request_id,) = sess.execute(text(
"""
select net.http_delete(
'http://localhost:8080/delete',
'{"param-foo": "bar"}'
);
"""
)).fetchone()

# Commit so background worker can start
sess.commit()

# Collect the response, waiting as needed
response = sess.execute(
text(
"""
select * from net._http_collect_response(:request_id, async:=false);
"""
),
{"request_id": request_id},
).fetchone()

assert response is not None
assert response[0] == "SUCCESS"
assert response[1] == "ok"


(request_id,) = sess.execute(text(
"""
select net.http_delete(
'http://localhost:8080/delete',
'{"param-foo": "bar"}',
'{"X-Baz": "foo"}'
);
"""
)).fetchone()

# Commit so background worker can start
sess.commit()

# Collect the response, waiting as needed
response = sess.execute(
text(
"""
select * from net._http_collect_response(:request_id, async:=false);
"""
),
{"request_id": request_id},
).fetchone()

assert response is not None
assert response[0] == "SUCCESS"
assert response[1] == "ok"


(request_id,) = sess.execute(text(
"""
select net.http_delete(
'http://localhost:8080/delete',
'{"param-foo": "bar"}',
'{"X-Baz": "foo"}',
5000
);
"""
)).fetchone()

# Commit so background worker can start
sess.commit()

# Collect the response, waiting as needed
response = sess.execute(
text(
"""
select * from net._http_collect_response(:request_id, async:=false);
"""
),
{"request_id": request_id},
).fetchone()

assert response is not None
assert response[0] == "SUCCESS"
assert response[1] == "ok"


def test_http_delete_with_body(sess):
"""delete with request body works"""

# Create a request
(request_id,) = sess.execute(text(
"""
select net.http_delete(
url :='http://localhost:8080/delete_w_body'
, body := '{"key": "val"}'
);
"""
)).fetchone()

# Commit so background worker can start
sess.commit()

# Collect the response, waiting as needed
(response_json,) = sess.execute(
text(
"""
select
((x.response).body)::jsonb body_json
from
net._http_collect_response(:request_id, async:=false) x;
"""
),
{"request_id": request_id},
).fetchone()

assert response_json["key"] == "val"
Loading