From 32fe34f0234863b9e5f6d4840f108294946cb6fc Mon Sep 17 00:00:00 2001
From: Paul Ramsey <pramsey@cleverelephant.ca>
Date: Wed, 22 Jan 2025 12:07:37 -0800
Subject: [PATCH] Replace most external calls to httpbin and others to local
 calls to a docker service providing the same API

---
 .github/workflows/ci.yml |   4 ++
 expected/http.out        | 130 +++++++++++++++++++--------------------
 sql/http.sql             |  88 +++++++++++++-------------
 3 files changed, 113 insertions(+), 109 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d706000..f6e9a39 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -45,6 +45,10 @@ jobs:
         sudo cp ./ci/pg_hba.conf $PGETC/pg_hba.conf
         sudo su postgres -c "$PGBIN/pg_ctl --pgdata $PGDATA start -o '-c config_file=$PGETC/postgresql.conf -p 5432'"
 
+    - name: 'Start HttpBin Docker'
+      run: |
+        docker run -d -p 9080:80 kennethreitz/httpbin
+
     - name: 'Build & Test'
       run: |
         export PATH=/usr/lib/postgresql/${{ matrix.ci.PGVER }}/bin/:$PATH
diff --git a/expected/http.out b/expected/http.out
index 1e4022b..2435844 100644
--- a/expected/http.out
+++ b/expected/http.out
@@ -8,7 +8,7 @@ SELECT http_set_curlopt('CURLOPT_TIMEOUT', '10');
 
 -- Status code
 SELECT status
-FROM http_get('https://httpbin.org/status/202');
+FROM http_get('http://localhost:9080/status/202');
  status 
 --------
     202
@@ -18,7 +18,7 @@ FROM http_get('https://httpbin.org/status/202');
 SELECT lower(field) AS field, value
 FROM (
 	SELECT (unnest(headers)).*
-	FROM http_get('https://httpbin.org/response-headers?Abcde=abcde')
+	FROM http_get('http://localhost:9080/response-headers?Abcde=abcde')
 ) a
 WHERE field ILIKE 'Abcde';
  field | value 
@@ -29,124 +29,124 @@ WHERE field ILIKE 'Abcde';
 -- GET
 SELECT status,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_get('https://httpbin.org/anything?foo=bar');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_get('http://localhost:9080/anything?foo=bar');
  status | args |                  url                   | method 
 --------+------+----------------------------------------+--------
-    200 | bar  | "https://httpbin.org/anything?foo=bar" | "GET"
+    200 | bar  | http://localhost:9080/anything?foo=bar | GET
 (1 row)
 
 -- GET with data
 SELECT status,
 content::json->'args'->>'this' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_get('https://httpbin.org/anything', jsonb_build_object('this', 'that'));
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_get('http://localhost:9080/anything', jsonb_build_object('this', 'that'));
  status | args |                   url                    | method 
 --------+------+------------------------------------------+--------
-    200 | that | "https://httpbin.org/anything?this=that" | "GET"
+    200 | that | http://localhost:9080/anything?this=that | GET
 (1 row)
 
 -- GET with data
 SELECT status,
-content::json->'args' as args,
+content::json->>'args' as args,
 (content::json)->>'data' as data,
-content::json->'url' as url,
-content::json->'method' as method
-FROM http(('GET', 'https://httpbin.org/anything', NULL, 'application/json', '{"search": "toto"}'));
+content::json->>'url' as url,
+content::json->>'method' as method
+FROM http(('GET', 'http://localhost:9080/anything', NULL, 'application/json', '{"search": "toto"}'));
  status | args |        data        |              url               | method 
 --------+------+--------------------+--------------------------------+--------
-    200 | {}   | {"search": "toto"} | "https://httpbin.org/anything" | "GET"
+    200 | {}   | {"search": "toto"} | http://localhost:9080/anything | GET
 (1 row)
 
 -- DELETE
 SELECT status,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_delete('https://httpbin.org/anything?foo=bar');
- status | args |                  url                   |  method  
---------+------+----------------------------------------+----------
-    200 | bar  | "https://httpbin.org/anything?foo=bar" | "DELETE"
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_delete('http://localhost:9080/anything?foo=bar');
+ status | args |                  url                   | method 
+--------+------+----------------------------------------+--------
+    200 | bar  | http://localhost:9080/anything?foo=bar | DELETE
 (1 row)
 
 -- DELETE with payload
 SELECT status,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method,
-content::json->'data' AS data
-FROM http_delete('https://httpbin.org/anything?foo=bar', 'payload', 'text/plain');
- status | args |                  url                   |  method  |   data    
---------+------+----------------------------------------+----------+-----------
-    200 | bar  | "https://httpbin.org/anything?foo=bar" | "DELETE" | "payload"
+content::json->>'url' AS url,
+content::json->>'method' AS method,
+content::json->>'data' AS data
+FROM http_delete('http://localhost:9080/anything?foo=bar', 'payload', 'text/plain');
+ status | args |                  url                   | method |  data   
+--------+------+----------------------------------------+--------+---------
+    200 | bar  | http://localhost:9080/anything?foo=bar | DELETE | payload
 (1 row)
 
 -- PUT
 SELECT status,
-content::json->'data' AS data,
+content::json->>'data' AS data,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_put('https://httpbin.org/anything?foo=bar','payload','text/plain');
- status |   data    | args |                  url                   | method 
---------+-----------+------+----------------------------------------+--------
-    200 | "payload" | bar  | "https://httpbin.org/anything?foo=bar" | "PUT"
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_put('http://localhost:9080/anything?foo=bar','payload','text/plain');
+ status |  data   | args |                  url                   | method 
+--------+---------+------+----------------------------------------+--------
+    200 | payload | bar  | http://localhost:9080/anything?foo=bar | PUT
 (1 row)
 
 -- PATCH
 SELECT status,
-content::json->'data' AS data,
+content::json->>'data' AS data,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_patch('https://httpbin.org/anything?foo=bar','{"this":"that"}','application/json');
- status |         data          | args |                  url                   | method  
---------+-----------------------+------+----------------------------------------+---------
-    200 | "{\"this\":\"that\"}" | bar  | "https://httpbin.org/anything?foo=bar" | "PATCH"
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_patch('http://localhost:9080/anything?foo=bar','{"this":"that"}','application/json');
+ status |      data       | args |                  url                   | method 
+--------+-----------------+------+----------------------------------------+--------
+    200 | {"this":"that"} | bar  | http://localhost:9080/anything?foo=bar | PATCH
 (1 row)
 
 -- POST
 SELECT status,
-content::json->'data' AS data,
+content::json->>'data' AS data,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_post('https://httpbin.org/anything?foo=bar','payload','text/plain');
- status |   data    | args |                  url                   | method 
---------+-----------+------+----------------------------------------+--------
-    200 | "payload" | bar  | "https://httpbin.org/anything?foo=bar" | "POST"
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_post('http://localhost:9080/anything?foo=bar','payload','text/plain');
+ status |  data   | args |                  url                   | method 
+--------+---------+------+----------------------------------------+--------
+    200 | payload | bar  | http://localhost:9080/anything?foo=bar | POST
 (1 row)
 
 -- POST with json data
 SELECT status,
 content::json->'form'->>'this' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_post('https://httpbin.org/anything', jsonb_build_object('this', 'that'));
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_post('http://localhost:9080/anything', jsonb_build_object('this', 'that'));
  status | args |              url               | method 
 --------+------+--------------------------------+--------
-    200 | that | "https://httpbin.org/anything" | "POST"
+    200 | that | http://localhost:9080/anything | POST
 (1 row)
 
 -- POST with data
 SELECT status,
 content::json->'form'->>'key1' AS key1,
 content::json->'form'->>'key2' AS key2,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_post('https://httpbin.org/anything', 'key1=value1&key2=value2','application/x-www-form-urlencoded');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_post('http://localhost:9080/anything', 'key1=value1&key2=value2','application/x-www-form-urlencoded');
  status |  key1  |  key2  |              url               | method 
 --------+--------+--------+--------------------------------+--------
-    200 | value1 | value2 | "https://httpbin.org/anything" | "POST"
+    200 | value1 | value2 | http://localhost:9080/anything | POST
 (1 row)
 
 -- HEAD
 SELECT lower(field) AS field, value
 FROM (
 	SELECT (unnest(headers)).*
-	FROM http_head('https://httpbin.org/response-headers?Abcde=abcde')
+	FROM http_head('http://localhost:9080/response-headers?Abcde=abcde')
 ) a
 WHERE field ILIKE 'Abcde';
  field | value 
@@ -156,11 +156,11 @@ WHERE field ILIKE 'Abcde';
 
 -- Follow redirect
 SELECT status,
-(content::json)->'url' AS url
-FROM http_get('https://httpbin.org/redirect-to?url=get');
+(content::json)->>'url' AS url
+FROM http_get('http://localhost:9080/redirect-to?url=get');
  status |            url            
 --------+---------------------------
-    200 | "https://httpbin.org/get"
+    200 | http://localhost:9080/get
 (1 row)
 
 -- Request image
@@ -191,7 +191,7 @@ SELECT http_set_curlopt('CURLOPT_PROXY', '127.0.0.1');
 -- Error because proxy is not there
 DO $$
 BEGIN
-    SELECT status FROM http_get('https://httpbin.org/status/555');
+    SELECT status FROM http_get('http://localhost:9080/status/555');
 EXCEPTION
     WHEN OTHERS THEN
         RAISE WARNING 'Failed to connect';
@@ -201,7 +201,7 @@ WARNING:  Failed to connect
 -- Still an error
 DO $$
 BEGIN
-    SELECT status FROM http_get('https://httpbin.org/status/555');
+    SELECT status FROM http_get('http://localhost:9080/status/555');
 EXCEPTION
     WHEN OTHERS THEN
         RAISE WARNING 'Failed to connect';
@@ -216,7 +216,7 @@ SELECT http_reset_curlopt();
 (1 row)
 
 -- Now it should work
-SELECT status FROM http_get('https://httpbin.org/status/555');
+SELECT status FROM http_get('http://localhost:9080/status/555');
  status 
 --------
     555
@@ -230,7 +230,7 @@ SELECT http_set_curlopt('CURLOPT_TIMEOUT_MS', '10000');
  t
 (1 row)
 
-SELECT status FROM http_get('https://httpbin.org/delay/7');
+SELECT status FROM http_get('http://localhost:9080/delay/7');
  status 
 --------
     200
diff --git a/sql/http.sql b/sql/http.sql
index 350da2c..34451b8 100644
--- a/sql/http.sql
+++ b/sql/http.sql
@@ -5,104 +5,104 @@ SELECT http_set_curlopt('CURLOPT_TIMEOUT', '10');
 
 -- Status code
 SELECT status
-FROM http_get('https://httpbin.org/status/202');
+FROM http_get('http://localhost:9080/status/202');
 
 -- Headers
 SELECT lower(field) AS field, value
 FROM (
 	SELECT (unnest(headers)).*
-	FROM http_get('https://httpbin.org/response-headers?Abcde=abcde')
+	FROM http_get('http://localhost:9080/response-headers?Abcde=abcde')
 ) a
 WHERE field ILIKE 'Abcde';
 
 -- GET
 SELECT status,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_get('https://httpbin.org/anything?foo=bar');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_get('http://localhost:9080/anything?foo=bar');
 
 -- GET with data
 SELECT status,
 content::json->'args'->>'this' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_get('https://httpbin.org/anything', jsonb_build_object('this', 'that'));
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_get('http://localhost:9080/anything', jsonb_build_object('this', 'that'));
 
 -- GET with data
 SELECT status,
-content::json->'args' as args,
+content::json->>'args' as args,
 (content::json)->>'data' as data,
-content::json->'url' as url,
-content::json->'method' as method
-FROM http(('GET', 'https://httpbin.org/anything', NULL, 'application/json', '{"search": "toto"}'));
+content::json->>'url' as url,
+content::json->>'method' as method
+FROM http(('GET', 'http://localhost:9080/anything', NULL, 'application/json', '{"search": "toto"}'));
 
 -- DELETE
 SELECT status,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_delete('https://httpbin.org/anything?foo=bar');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_delete('http://localhost:9080/anything?foo=bar');
 
 -- DELETE with payload
 SELECT status,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method,
-content::json->'data' AS data
-FROM http_delete('https://httpbin.org/anything?foo=bar', 'payload', 'text/plain');
+content::json->>'url' AS url,
+content::json->>'method' AS method,
+content::json->>'data' AS data
+FROM http_delete('http://localhost:9080/anything?foo=bar', 'payload', 'text/plain');
 
 -- PUT
 SELECT status,
-content::json->'data' AS data,
+content::json->>'data' AS data,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_put('https://httpbin.org/anything?foo=bar','payload','text/plain');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_put('http://localhost:9080/anything?foo=bar','payload','text/plain');
 
 -- PATCH
 SELECT status,
-content::json->'data' AS data,
+content::json->>'data' AS data,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_patch('https://httpbin.org/anything?foo=bar','{"this":"that"}','application/json');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_patch('http://localhost:9080/anything?foo=bar','{"this":"that"}','application/json');
 
 -- POST
 SELECT status,
-content::json->'data' AS data,
+content::json->>'data' AS data,
 content::json->'args'->>'foo' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_post('https://httpbin.org/anything?foo=bar','payload','text/plain');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_post('http://localhost:9080/anything?foo=bar','payload','text/plain');
 
 -- POST with json data
 SELECT status,
 content::json->'form'->>'this' AS args,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_post('https://httpbin.org/anything', jsonb_build_object('this', 'that'));
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_post('http://localhost:9080/anything', jsonb_build_object('this', 'that'));
 
 -- POST with data
 SELECT status,
 content::json->'form'->>'key1' AS key1,
 content::json->'form'->>'key2' AS key2,
-content::json->'url' AS url,
-content::json->'method' AS method
-FROM http_post('https://httpbin.org/anything', 'key1=value1&key2=value2','application/x-www-form-urlencoded');
+content::json->>'url' AS url,
+content::json->>'method' AS method
+FROM http_post('http://localhost:9080/anything', 'key1=value1&key2=value2','application/x-www-form-urlencoded');
 
 -- HEAD
 SELECT lower(field) AS field, value
 FROM (
 	SELECT (unnest(headers)).*
-	FROM http_head('https://httpbin.org/response-headers?Abcde=abcde')
+	FROM http_head('http://localhost:9080/response-headers?Abcde=abcde')
 ) a
 WHERE field ILIKE 'Abcde';
 
 -- Follow redirect
 SELECT status,
-(content::json)->'url' AS url
-FROM http_get('https://httpbin.org/redirect-to?url=get');
+(content::json)->>'url' AS url
+FROM http_get('http://localhost:9080/redirect-to?url=get');
 
 -- Request image
 WITH
@@ -123,7 +123,7 @@ SELECT http_set_curlopt('CURLOPT_PROXY', '127.0.0.1');
 -- Error because proxy is not there
 DO $$
 BEGIN
-    SELECT status FROM http_get('https://httpbin.org/status/555');
+    SELECT status FROM http_get('http://localhost:9080/status/555');
 EXCEPTION
     WHEN OTHERS THEN
         RAISE WARNING 'Failed to connect';
@@ -132,7 +132,7 @@ $$;
 -- Still an error
 DO $$
 BEGIN
-    SELECT status FROM http_get('https://httpbin.org/status/555');
+    SELECT status FROM http_get('http://localhost:9080/status/555');
 EXCEPTION
     WHEN OTHERS THEN
         RAISE WARNING 'Failed to connect';
@@ -141,9 +141,9 @@ $$;
 -- Reset options
 SELECT http_reset_curlopt();
 -- Now it should work
-SELECT status FROM http_get('https://httpbin.org/status/555');
+SELECT status FROM http_get('http://localhost:9080/status/555');
 
 -- Alter the default timeout and then run a query that is longer than
 -- the default (5s), but shorter than the new timeout
 SELECT http_set_curlopt('CURLOPT_TIMEOUT_MS', '10000');
-SELECT status FROM http_get('https://httpbin.org/delay/7');
+SELECT status FROM http_get('http://localhost:9080/delay/7');