Skip to content

Commit d764007

Browse files
committed
HTTP/3
1 parent cc466a8 commit d764007

File tree

11 files changed

+3552
-82
lines changed

11 files changed

+3552
-82
lines changed

.travis.yml

+27-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
sudo: required
23
dist: xenial
34
group: edge
@@ -9,27 +10,47 @@ services:
910

1011
install:
1112
- ORIG_DIR=$PWD
13+
- export PATH=$PATH:$HOME/.cargo/bin
14+
- git clone --recursive https://github.com/cloudflare/quiche
15+
- cd quiche/deps/boringssl
16+
- mkdir build
17+
- cd build
18+
- cmake -DCMAKE_POSITION_INDEPENDENT_CODE=on ..
19+
- make -j`nproc`
20+
- cd ..
21+
- mkdir .openssl/lib -p
22+
- cp build/crypto/libcrypto.a build/ssl/libssl.a .openssl/lib
23+
- ln -s $PWD/include .openssl
24+
- cd ../..
25+
- QUICHE_BSSL_PATH=$PWD/deps/boringssl cargo build --release --features pkg-config-meta
26+
- cd ..
1227
- wget https://github.com/erlang/rebar3/releases/download/3.10.0/rebar3 && chmod 755 rebar3
13-
- wget https://github.com/curl/curl/releases/download/curl-7_64_1/curl-7.64.1.tar.gz && tar xf curl-7.64.1.tar.gz
14-
- cd curl-7.64.1 && ./configure && make && sudo make install
28+
- git clone https://github.com/curl/curl
29+
- cd curl
30+
- ./buildconf
31+
- ./configure LDFLAGS="-Wl,-rpath,$PWD/../quiche/target/release" --with-ssl=$PWD/../quiche/deps/boringssl/.openssl --with-quiche=$PWD/../quiche/target/release
32+
- make -j`nproc`
33+
- sudo make install
1534
- cd $ORIG_DIR
1635

1736
otp_release:
18-
- 20.3
1937
- 21.3
38+
- 22.0
2039

2140
script:
22-
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
41+
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
2342
- ./rebar3 update && ./rebar3 ct && ./rebar3 dialyzer && ./rebar3 coveralls send
2443

2544
before_install:
2645
- sudo ifconfig
2746
- sudo apt-get update -q
28-
- sudo apt-get install -qy build-essential libssl-dev libnghttp2-dev
47+
- sudo apt-get install -qy build-essential libssl-dev libnghttp2-dev curl git cmake
48+
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
49+
- sudo apt purge -y curl && sudo apt autoremove -y
2950

3051
cache:
3152
directories:
32-
- $HOME/.cache/rebar3/
53+
- $HOME/.cache/rebar3/
3354

3455
addons:
3556
apt:

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
katipo
22
=====
33

4-
An HTTP/HTTP2 client library for Erlang built around libcurl-multi and libevent.
4+
An HTTP/HTTP2/HTTP3 client library for Erlang built around libcurl-multi and libevent.
55

66
### Status
77

@@ -122,7 +122,7 @@ katipo:Method(Pool :: atom(), URL :: binary(), ReqOptions :: map()).
122122
| `unix_socket_path` | `binary()` | `undefined` | [docs](https://curl.haxx.se/libcurl/c/CURLOPT_UNIX_SOCKET_PATH.html) curl >= 7.40.0 |
123123
| `lock_data_ssl_session` | `boolean()` | `false` | [docs](https://curl.haxx.se/libcurl/c/curl_share_setopt.html) curl >= 7.23.0 |
124124
| `doh_url` | `binary()` | `undefined` | [docs](https://curl.haxx.se/libcurl/c/CURLOPT_DOH_URL.html) curl >= 7.62.0 |
125-
| `http_version` | `curl_http_version_none` <br> `curl_http_version_1_0` <br> `curl_http_version_1_1` <br> `curl_http_version_2_0` <br> `curl_http_version_2tls` <br> `curl_http_version_2_prior_knowledge` | `curl_http_version_none` | [docs](https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_VERSION.html) curl >= 7.62.0 |
125+
| `http_version` | `curl_http_version_none` <br> `curl_http_version_1_0` <br> `curl_http_version_1_1` <br> `curl_http_version_2_0` <br> `curl_http_version_2tls` <br> `curl_http_version_2_prior_knowledge` <br> `curl_http_version_3` | `curl_http_version_none` | [docs](https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_VERSION.html) curl >= 7.62.0 |
126126

127127
#### Responses
128128

c_src/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ else ifeq ($(UNAME_SYS), Linux)
2525
CC ?= gcc
2626
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
2727
CXXFLAGS ?= -O3 -finline-functions -Wall
28+
LDLIBS += -L /usr/local/lib
2829
endif
2930

3031
CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)

c_src/katipo.c

+24-7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef struct _GlobalInfo {
5656
CURLSH *shobject;
5757
int still_running;
5858
size_t to_get;
59+
curl_version_info_data *ver;
5960
} GlobalInfo;
6061

6162
typedef struct _ConnInfo {
@@ -665,19 +666,30 @@ static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data) {
665666

666667
static size_t header_cb(void *ptr, size_t size, size_t nmemb, void *data) {
667668
size_t realsize = size * nmemb;
669+
size_t adjusted_size = realsize;
668670
ConnInfo *conn = (ConnInfo *)data;
669671
char *header;
670672

671-
// the last two chars of headers are \r\n
673+
// the last two chars of headers are \r\n except for http3...
672674
if (realsize > 2) {
673675
if (conn->resp_headers && is_status_line(ptr)) {
674676
curl_slist_free_all(conn->resp_headers);
675677
conn->resp_headers = NULL;
676678
conn->num_headers = 0;
677679
}
678-
header = (char *)malloc(realsize - 1);
679-
strncpy(header, ptr, realsize - 2);
680-
header[realsize - 2] = '\0';
680+
// TODO: understand better what's going on with http3
681+
// headers. They don't seem to have the trailing \r\n that the
682+
// former HTTP versions do
683+
#if LIBCURL_VERSION_NUM >= 0x074200
684+
long http_version;
685+
curl_easy_getinfo(conn->easy, CURLINFO_HTTP_VERSION, &http_version);
686+
if (http_version == CURL_HTTP_VERSION_3) {
687+
adjusted_size += 1;
688+
}
689+
#endif
690+
header = (char *)malloc(adjusted_size - 1);
691+
strncpy(header, ptr, adjusted_size - 2);
692+
header[adjusted_size - 2] = '\0';
681693
conn->resp_headers = curl_slist_append(conn->resp_headers, header);
682694
free(header);
683695
conn->num_headers++;
@@ -802,9 +814,11 @@ static void new_conn(long method, char *url, struct curl_slist *req_headers,
802814
eopts.curlopt_interface);
803815
}
804816
#if LIBCURL_VERSION_NUM >= 0x072800 /* Available since 7.40.0 */
805-
if (eopts.curlopt_unix_socket_path != NULL) {
806-
curl_easy_setopt(conn->easy, CURLOPT_UNIX_SOCKET_PATH,
807-
eopts.curlopt_unix_socket_path);
817+
if (global->ver->features & CURL_VERSION_UNIX_SOCKETS) {
818+
if (eopts.curlopt_unix_socket_path != NULL) {
819+
curl_easy_setopt(conn->easy, CURLOPT_UNIX_SOCKET_PATH,
820+
eopts.curlopt_unix_socket_path);
821+
}
808822
}
809823
#endif
810824
#if LIBCURL_VERSION_NUM >= 0x073100 /* Available since 7.49.0 */
@@ -1131,6 +1145,7 @@ int main(int argc, char **argv) {
11311145
int option_index = 0;
11321146
int c;
11331147
long pipelining = 0;
1148+
curl_version_info_data *ver;
11341149

11351150
struct option long_options[] = {
11361151
{ "pipelining", required_argument, 0, 'p' },
@@ -1158,6 +1173,8 @@ int main(int argc, char **argv) {
11581173
}
11591174
global.timer_event = evtimer_new(global.evbase, timer_cb, &global);
11601175
global.to_get = 0;
1176+
ver = curl_version_info(CURLVERSION_NOW);
1177+
global.ver = ver;
11611178

11621179
curl_multi_setopt(global.multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
11631180
curl_multi_setopt(global.multi, CURLMOPT_SOCKETDATA, &global);

rebar.config

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
[{deps,
1616
[{jsx, "2.9.0"},
1717
{meck, "0.8.10"},
18-
{cowboy, "2.4.0"},
1918
{http_proxy, ".*", {git, "https://github.com/puzza007/http_proxy.git", {branch, "rebar3"}}}
2019
]}]
2120
}]

src/katipo.erl

+4-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@
253253
curl_http_version_1_1 |
254254
curl_http_version_2_0 |
255255
curl_http_version_2tls |
256-
curl_http_version_2_prior_knowledge.
256+
curl_http_version_2_prior_knowledge |
257+
curl_http_version_3.
257258
-type curlmopts() :: [{max_pipeline_length, non_neg_integer()} |
258259
{pipelining, pipelining()} |
259260
{max_total_connections, non_neg_integer()}].
@@ -652,6 +653,8 @@ opt(http_version, curl_http_version_2tls, {Req, Errors}) ->
652653
{Req#req{http_version=4}, Errors};
653654
opt(http_version, curl_http_version_2_prior_knowledge, {Req, Errors}) ->
654655
{Req#req{http_version=5}, Errors};
656+
opt(http_version, curl_http_version_3, {Req, Errors}) ->
657+
{Req#req{http_version=30}, Errors}; %% See https://github.com/curl/curl/blob/32d64b2e875f0d74cd433dff8bda9f8a98dcd44e/include/curl/curl.h#L1983
655658
opt(verbose, true, {Req, Errors}) ->
656659
{Req#req{verbose=?VERBOSE_TRUE}, Errors};
657660
opt(verbose, false, {Req, Errors}) ->

test/katipo_SUITE.erl

+18-17
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,8 @@ init_per_group(pool, Config) ->
2626
application:ensure_all_started(meck),
2727
Config;
2828
init_per_group(https, Config) ->
29-
application:ensure_all_started(cowboy),
30-
Dispatch = cowboy_router:compile([{'_', [{"/", get_handler, []}]}]),
3129
DataDir = ?config(data_dir, Config),
32-
CACert = filename:join(DataDir, "cowboy-ca.crt"),
33-
{ok, _} = cowboy:start_tls(ct_https,
34-
[{port, 8443},
35-
{cacertfile, CACert},
36-
{certfile, filename:join(DataDir, "server.crt")},
37-
{keyfile, filename:join(DataDir, "server.key")}],
38-
#{env => #{dispatch => Dispatch}}),
30+
CACert = filename:join(DataDir, "ca-bundle.crt"),
3931
[{cacert_file, list_to_binary(CACert)} | Config];
4032
init_per_group(proxy, Config) ->
4133
application:ensure_all_started(http_proxy),
@@ -148,7 +140,9 @@ groups() ->
148140
[metrics_true,
149141
metrics_false]},
150142
{http2, [parallel],
151-
[http2_get]}].
143+
[http2_get]},
144+
{http3, [parallel],
145+
[http3_get]}].
152146

153147
all() ->
154148
[{group, http},
@@ -158,7 +152,8 @@ all() ->
158152
{group, session},
159153
{group, port},
160154
{group, metrics},
161-
{group, http2}].
155+
{group, http2},
156+
{group, http3}].
162157

163158
get(_) ->
164159
{ok, #{status := 200, body := Body}} =
@@ -614,31 +609,31 @@ verify_host_verify_peer_ok(_) ->
614609

615610
verify_host_verify_peer_error(_) ->
616611
{error, #{code := Code}} =
617-
katipo:get(?POOL, <<"https://localhost:8443">>,
612+
katipo:get(?POOL, <<"https://self-signed.badssl.com/">>,
618613
#{ssl_verifyhost => true, ssl_verifypeer => true}),
619614
%% TODO: this could be made to reflect the ifdef from katipo.c...
620615
ok = case Code of
621616
ssl_cacert -> ok;
622617
peer_failed_verification -> ok
623618
end,
624619
{error, #{code := Code}} =
625-
katipo:get(?POOL, <<"https://localhost:8443">>,
620+
katipo:get(?POOL, <<"https://self-signed.badssl.com/">>,
626621
#{ssl_verifyhost => false, ssl_verifypeer => true}),
627622
ok = case Code of
628623
ssl_cacert -> ok;
629624
peer_failed_verification -> ok
630625
end,
631626
{ok, #{status := 200}} =
632-
katipo:get(?POOL, <<"https://localhost:8443">>,
627+
katipo:get(?POOL, <<"https://self-signed.badssl.com/">>,
633628
#{ssl_verifyhost => true, ssl_verifypeer => false}),
634629
{ok, #{status := 200}} =
635-
katipo:get(?POOL, <<"https://localhost:8443">>,
630+
katipo:get(?POOL, <<"https://self-signed.badssl.com/">>,
636631
#{ssl_verifyhost => false, ssl_verifypeer => false}).
637632

638633
cacert_self_signed(Config) ->
639634
CACert = ?config(cacert_file, Config),
640-
{ok, #{status := 200}} =
641-
katipo:get(?POOL, <<"https://localhost:8443">>,
635+
{ok, #{status := 301}} =
636+
katipo:get(?POOL, <<"https://google.com">>,
642637
#{ssl_verifyhost => true, ssl_verifypeer => true, cacert => CACert}).
643638

644639
badssl(_) ->
@@ -790,6 +785,12 @@ http2_get(_) ->
790785
Json = jsx:decode(Body),
791786
[{<<"a">>, <<"!@#$%^&*()_+">>}] = proplists:get_value(<<"args">>, Json).
792787

788+
http3_get(_) ->
789+
{ok, #{status := 404, headers := Headers}} =
790+
katipo:get(?POOL, <<"https://quic.tech:8443">>,
791+
#{http_version => curl_http_version_3, verbose => true}),
792+
<<"quiche">> = proplists:get_value(<<"server">>, Headers).
793+
793794
repeat_until_true(Fun) ->
794795
try
795796
case Fun() of

0 commit comments

Comments
 (0)