mirror of
https://github.com/curl/curl.git
synced 2026-06-01 21:24:33 +03:00
cf-setup: improve readability
Restructure the code in cf-setup connect to make it better readable what is happening for establishing the connection's filter chain. Closes #21827
This commit is contained in:
parent
c53426231d
commit
032b15c434
4 changed files with 238 additions and 178 deletions
|
|
@ -3449,17 +3449,6 @@ CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf,
|
|||
cf->next->conn = cf->conn;
|
||||
cf->next->sockindex = cf->sockindex;
|
||||
|
||||
if(ctx->udp_tunnel) {
|
||||
struct Curl_cfilter *cf_caps = NULL;
|
||||
result = Curl_cf_capsule_create(&cf_caps, data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
cf_caps->conn = conn;
|
||||
cf_caps->sockindex = cf->sockindex;
|
||||
cf_caps->next = cf;
|
||||
cf = cf_caps;
|
||||
}
|
||||
|
||||
out:
|
||||
*pcf = (!result) ? cf : NULL;
|
||||
if(result) {
|
||||
|
|
|
|||
361
lib/connect.c
361
lib/connect.c
|
|
@ -55,6 +55,7 @@
|
|||
#include "cfilters.h"
|
||||
#include "connect.h"
|
||||
#include "cf-dns.h"
|
||||
#include "cf-capsule.h"
|
||||
#include "cf-haproxy.h"
|
||||
#include "cf-https-connect.h"
|
||||
#include "cf-ip-happy.h"
|
||||
|
|
@ -342,14 +343,207 @@ struct cf_setup_ctx {
|
|||
uint8_t transport;
|
||||
};
|
||||
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
|
||||
static CURLcode cf_setup_add_haproxy(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_setup_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
|
||||
if(data->set.haproxyprotocol) {
|
||||
if(ctx->transport == TRNSPRT_QUIC) {
|
||||
failf(data, "haproxy protocol does not support QUIC");
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
}
|
||||
result = Curl_cf_haproxy_insert_after(cf, data);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding HAPROXY filter failed -> %d", result);
|
||||
return result;
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "added HAPROXY filter");
|
||||
}
|
||||
ctx->state = CF_SETUP_CNNCT_HAPROXY;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode cf_setup_add_socks(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_setup_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
|
||||
/* Add a SOCKS proxy to go through `first_peer` to `second_peer`*/
|
||||
struct Curl_peer *second_peer;
|
||||
|
||||
if(cf->conn->bits.httpproxy)
|
||||
second_peer = cf->conn->http_proxy.peer;
|
||||
else
|
||||
second_peer = Curl_conn_get_destination(cf->conn, cf->sockindex);
|
||||
if(!second_peer)
|
||||
return CURLE_FAILED_INIT;
|
||||
|
||||
result = Curl_cf_socks_proxy_insert_after(
|
||||
cf, data, second_peer, cf->conn->ip_version,
|
||||
cf->conn->socks_proxy.proxytype,
|
||||
cf->conn->socks_proxy.creds);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding SOCKS filter failed -> %d", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u",
|
||||
second_peer->hostname, second_peer->port);
|
||||
ctx->state = CF_SETUP_CNNCT_SOCKS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_setup_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
|
||||
#ifdef USE_SSL
|
||||
if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) &&
|
||||
!Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
|
||||
result = Curl_cf_ssl_proxy_insert_after(cf, data);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding SSL filter for HTTP proxy failed -> %d",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "added SSL filter for HTTP proxy");
|
||||
}
|
||||
#endif /* USE_SSL */
|
||||
|
||||
if(cf->conn->bits.tunnel_proxy) {
|
||||
struct Curl_peer *dest; /* where HTTP should tunnel to */
|
||||
dest = Curl_conn_get_destination(cf->conn, cf->sockindex);
|
||||
result = Curl_cf_http_proxy_insert_after(
|
||||
cf, data, dest, ctx->transport, cf->conn->http_proxy.proxytype);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter");
|
||||
}
|
||||
ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif /* !CURL_DISABLE_HTTP */
|
||||
#endif /* CURL_DISABLE_PROXY */
|
||||
|
||||
static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_setup_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
|
||||
/* What is the fist hop we directly connect to and what transport
|
||||
* do we use for it? Only on the first hop we can do Happy Eyeballs. */
|
||||
struct Curl_peer *first_peer =
|
||||
Curl_conn_get_first_peer(cf->conn, cf->sockindex);
|
||||
uint8_t first_transport = ctx->transport;
|
||||
bool tunnel_proxy = FALSE;
|
||||
|
||||
#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
||||
if(cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) {
|
||||
first_transport =
|
||||
Curl_http_proxy_transport(cf->conn->http_proxy.proxytype);
|
||||
if((first_transport == TRNSPRT_QUIC) && (cf->conn->bits.socksproxy)) {
|
||||
failf(data, "HTTP/3 proxy not possible via SOCKS");
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
}
|
||||
tunnel_proxy = TRUE;
|
||||
}
|
||||
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
|
||||
|
||||
result = cf_ip_happy_insert_after(cf, data, first_peer,
|
||||
ctx->transport, first_transport,
|
||||
tunnel_proxy);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if(tunnel_proxy && (first_transport == TRNSPRT_QUIC)) {
|
||||
CURL_TRC_CF(data, cf, "happy eyeballing to HTTP/3 proxy %s:%u",
|
||||
first_peer->hostname, first_peer->port);
|
||||
ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
|
||||
}
|
||||
else {
|
||||
CURL_TRC_CF(data, cf, "happy eyeballing to %s %s:%u",
|
||||
tunnel_proxy ? "proxy" : "origin",
|
||||
first_peer->hostname, first_peer->port);
|
||||
ctx->state = CF_SETUP_CNNCT_EYEBALLS;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct cf_setup_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
(void)data; /* not used in all builds */
|
||||
if(ctx->state < CF_SETUP_CNNCT_SSL) {
|
||||
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \
|
||||
!defined(CURL_DISABLE_PROXY)
|
||||
/* Wanting QUIC with a HTTP tunneling filter, we now need to add
|
||||
* the QUIC filter on top. Without tunneling, this has already
|
||||
* happened in the Happy Eyeball filter. */
|
||||
if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy &&
|
||||
cf->conn->bits.tunnel_proxy) {
|
||||
result = Curl_cf_capsule_insert_after(cf, data);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding capsule filter failed -> %d", result);
|
||||
return result;
|
||||
}
|
||||
result = Curl_cf_quic_insert_after(cf);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", result);
|
||||
return result;
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "added QUIC filter for origin");
|
||||
}
|
||||
else
|
||||
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && CURL_DISABLE_PROXY */
|
||||
#ifdef USE_SSL
|
||||
if((ctx->ssl_mode == CURL_CF_SSL_ENABLE ||
|
||||
(ctx->ssl_mode != CURL_CF_SSL_DISABLE &&
|
||||
cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */
|
||||
!Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
|
||||
result = Curl_cf_ssl_insert_after(cf, data);
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "added SSL filter for origin");
|
||||
}
|
||||
#endif /* USE_SSL */
|
||||
ctx->state = CF_SETUP_CNNCT_SSL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *done)
|
||||
{
|
||||
struct cf_setup_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
struct Curl_peer *first_peer =
|
||||
Curl_conn_get_first_peer(cf->conn, cf->sockindex);
|
||||
|
||||
if(cf->connected) {
|
||||
*done = TRUE;
|
||||
|
|
@ -366,150 +560,39 @@ connect_sub_chain:
|
|||
return result;
|
||||
}
|
||||
|
||||
if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
|
||||
/* What type of thing we do connect to first?
|
||||
* - without a proxy, `ctx->transport` defines it
|
||||
* - with non-tunneling proxy, `ctx->transport` also applies, but
|
||||
* for QUIC we need the cf-h3-proxy, not the standard vquic one
|
||||
* - with tunneling proxy, transport is defined by the proxytype
|
||||
* chosen and `ctx->transport` is tunneled through it.
|
||||
*/
|
||||
uint8_t transport_out = ctx->transport;
|
||||
bool tunnel_proxy = FALSE;
|
||||
#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
||||
CURL_TRC_CF(data, cf, "happy eyeballing, httpproxy=%d, type=%d, "
|
||||
"transport=%d",
|
||||
cf->conn->bits.httpproxy, cf->conn->http_proxy.proxytype,
|
||||
ctx->transport);
|
||||
if(cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) {
|
||||
transport_out =
|
||||
Curl_http_proxy_transport(cf->conn->http_proxy.proxytype);
|
||||
tunnel_proxy = TRUE;
|
||||
if((transport_out == TRNSPRT_QUIC) && (cf->conn->bits.socksproxy)) {
|
||||
failf(data, "HTTP/3 proxy not possible via SOCKS");
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
}
|
||||
}
|
||||
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
|
||||
result = cf_setup_add_ip_happy(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
|
||||
result = cf_ip_happy_insert_after(cf, data, first_peer,
|
||||
ctx->transport, transport_out,
|
||||
tunnel_proxy);
|
||||
if(result)
|
||||
return result;
|
||||
ctx->state = (tunnel_proxy && (transport_out == TRNSPRT_QUIC)) ?
|
||||
CF_SETUP_CNNCT_HTTP_PROXY : CF_SETUP_CNNCT_EYEBALLS;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
}
|
||||
|
||||
/* sub-chain connected, do we need to add more? */
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
|
||||
struct Curl_peer *dest; /* where SOCKS should tunnel to */
|
||||
|
||||
if(cf->conn->bits.httpproxy)
|
||||
dest = cf->conn->http_proxy.peer;
|
||||
else
|
||||
dest = Curl_conn_get_destination(cf->conn, cf->sockindex);
|
||||
if(!dest)
|
||||
return CURLE_FAILED_INIT;
|
||||
|
||||
result = Curl_cf_socks_proxy_insert_after(
|
||||
cf, data, dest, cf->conn->ip_version,
|
||||
cf->conn->socks_proxy.proxytype,
|
||||
cf->conn->socks_proxy.creds);
|
||||
|
||||
if(result) {
|
||||
/* 'dest' might be freed now so it can't be dereferenced */
|
||||
CURL_TRC_CF(data, cf, "added SOCKS filter failed -> %d", result);
|
||||
return result;
|
||||
}
|
||||
CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u -> %d",
|
||||
dest->hostname, dest->port, result);
|
||||
ctx->state = CF_SETUP_CNNCT_SOCKS;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
}
|
||||
|
||||
if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
|
||||
#ifdef USE_SSL
|
||||
if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) &&
|
||||
!Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
|
||||
result = Curl_cf_ssl_proxy_insert_after(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
#endif /* USE_SSL */
|
||||
result = cf_setup_add_socks(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
|
||||
#ifndef CURL_DISABLE_HTTP
|
||||
if(cf->conn->bits.tunnel_proxy) {
|
||||
struct Curl_peer *dest; /* where HTTP should tunnel to */
|
||||
dest = Curl_conn_get_destination(cf->conn, cf->sockindex);
|
||||
result = Curl_cf_http_proxy_insert_after(
|
||||
cf, data, dest, ctx->transport, cf->conn->http_proxy.proxytype);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
result = cf_setup_add_http_proxy(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
#endif /* !CURL_DISABLE_HTTP */
|
||||
ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
}
|
||||
|
||||
result = cf_setup_add_haproxy(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
#endif /* !CURL_DISABLE_PROXY */
|
||||
|
||||
if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
if(data->set.haproxyprotocol) {
|
||||
if(ctx->transport == TRNSPRT_QUIC) {
|
||||
failf(data, "haproxy protocol not support QUIC");
|
||||
return CURLE_UNSUPPORTED_PROTOCOL;
|
||||
}
|
||||
result = Curl_cf_haproxy_insert_after(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
#endif /* !CURL_DISABLE_PROXY */
|
||||
ctx->state = CF_SETUP_CNNCT_HAPROXY;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
}
|
||||
|
||||
/* Adding Curl_cf_quic_insert_after() because now we
|
||||
need the next filter to be QUIC/HTTP/3 (which has SSL) */
|
||||
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \
|
||||
defined(USE_PROXY_HTTP3)
|
||||
if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy &&
|
||||
cf->conn->bits.tunnel_proxy &&
|
||||
(data->state.http_neg.wanted == CURL_HTTP_V3x)) {
|
||||
if(ctx->state < CF_SETUP_CNNCT_SSL) {
|
||||
result = Curl_cf_quic_insert_after(cf);
|
||||
if(result)
|
||||
return result;
|
||||
ctx->state = CF_SETUP_CNNCT_SSL;
|
||||
}
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
}
|
||||
else
|
||||
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && USE_PROXY_HTTP3 */
|
||||
{
|
||||
if(ctx->state < CF_SETUP_CNNCT_SSL) {
|
||||
#ifdef USE_SSL
|
||||
if((ctx->ssl_mode == CURL_CF_SSL_ENABLE ||
|
||||
(ctx->ssl_mode != CURL_CF_SSL_DISABLE &&
|
||||
cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */
|
||||
!Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
|
||||
result = Curl_cf_ssl_insert_after(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
#endif /* USE_SSL */
|
||||
ctx->state = CF_SETUP_CNNCT_SSL;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
}
|
||||
}
|
||||
result = cf_setup_add_origin_filters(cf, data);
|
||||
if(result)
|
||||
return result;
|
||||
if(!cf->next || !cf->next->connected)
|
||||
goto connect_sub_chain;
|
||||
|
||||
ctx->state = CF_SETUP_DONE;
|
||||
cf->connected = TRUE;
|
||||
|
|
|
|||
|
|
@ -654,22 +654,8 @@ connect_sub:
|
|||
}
|
||||
else {
|
||||
/* subchain connected and we had already installed the protocol filter.
|
||||
* This means the protocol tunnel is established, we are done.
|
||||
*/
|
||||
* This means the protocol tunnel is established, we are done. */
|
||||
DEBUGASSERT(ctx->sub_filter_installed);
|
||||
if(ctx->udp_tunnel) {
|
||||
#ifdef USE_PROXY_HTTP3
|
||||
/* Insert capsule filter between us and the protocol sub-filter.
|
||||
* This handles encap/decap of UDP datagrams in capsule format. */
|
||||
result = Curl_cf_capsule_insert_after(cf, data);
|
||||
if(result)
|
||||
goto out;
|
||||
CURL_TRC_CF(data, cf, "installed capsule filter for UDP tunnel");
|
||||
#else
|
||||
result = CURLE_NOT_BUILT_IN;
|
||||
goto out;
|
||||
#endif /* USE_PROXY_HTTP3 */
|
||||
}
|
||||
result = CURLE_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,12 +195,12 @@ class TestH3Proxy:
|
|||
@pytest.mark.parametrize(
|
||||
["alpn_proto", "proxy_proto", "exp_err"],
|
||||
[
|
||||
#pytest.param(
|
||||
# "http/1.1",
|
||||
# "h3",
|
||||
# "could not connect to server",
|
||||
# id="fail_h1_over_h3_proxytunnel",
|
||||
#),
|
||||
pytest.param(
|
||||
"http/1.1",
|
||||
"h3",
|
||||
"could not connect to server",
|
||||
id="fail_h1_over_h3_proxytunnel",
|
||||
),
|
||||
pytest.param(
|
||||
"h2",
|
||||
"h3",
|
||||
|
|
@ -208,12 +208,12 @@ class TestH3Proxy:
|
|||
marks=MARK_NEEDS_NGHTTP2,
|
||||
id="fail_h2_over_h3_proxytunnel",
|
||||
),
|
||||
#pytest.param(
|
||||
# "h3",
|
||||
# "h3",
|
||||
# "could not connect to server",
|
||||
# id="fail_h3_over_h3_proxytunnel",
|
||||
#),
|
||||
pytest.param(
|
||||
"h3",
|
||||
"h3",
|
||||
"could not connect to server",
|
||||
id="fail_h3_over_h3_proxytunnel",
|
||||
),
|
||||
#pytest.param(
|
||||
# "h3",
|
||||
# "h2",
|
||||
|
|
@ -235,11 +235,13 @@ class TestH3Proxy:
|
|||
httpd,
|
||||
nghttpx,
|
||||
nghttpx_fwd,
|
||||
h2o_proxy,
|
||||
alpn_proto,
|
||||
proxy_proto,
|
||||
exp_err,
|
||||
):
|
||||
_require_available(httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd)
|
||||
_require_available(httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd,
|
||||
h2o_proxy=h2o_proxy)
|
||||
|
||||
curl = CurlClient(env=env)
|
||||
url = f"https://localhost:{env.https_port}/data.json"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue