├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── LICENSE ├── Makefile ├── README.asciidoc ├── doc └── src │ ├── guide │ ├── book.asciidoc │ ├── connect.asciidoc │ ├── gun.sty │ ├── http.asciidoc │ ├── internals_tls_over_tls.asciidoc │ ├── introduction.asciidoc │ ├── migrating_from_1.0.asciidoc │ ├── migrating_from_1.1.asciidoc │ ├── migrating_from_1.2.asciidoc │ ├── migrating_from_1.3.asciidoc │ ├── migrating_from_2.0.asciidoc │ ├── migrating_from_2.1.asciidoc │ ├── protocols.asciidoc │ ├── start.asciidoc │ └── websocket.asciidoc │ └── manual │ ├── gun.asciidoc │ ├── gun.await.asciidoc │ ├── gun.await_body.asciidoc │ ├── gun.await_up.asciidoc │ ├── gun.cancel.asciidoc │ ├── gun.close.asciidoc │ ├── gun.connect.asciidoc │ ├── gun.data.asciidoc │ ├── gun.delete.asciidoc │ ├── gun.flush.asciidoc │ ├── gun.get.asciidoc │ ├── gun.head.asciidoc │ ├── gun.headers.asciidoc │ ├── gun.info.asciidoc │ ├── gun.open.asciidoc │ ├── gun.open_unix.asciidoc │ ├── gun.options.asciidoc │ ├── gun.patch.asciidoc │ ├── gun.ping.asciidoc │ ├── gun.post.asciidoc │ ├── gun.put.asciidoc │ ├── gun.request.asciidoc │ ├── gun.set_owner.asciidoc │ ├── gun.shutdown.asciidoc │ ├── gun.stream_info.asciidoc │ ├── gun.update_flow.asciidoc │ ├── gun.ws_send.asciidoc │ ├── gun.ws_upgrade.asciidoc │ ├── gun_app.asciidoc │ ├── gun_cookies.asciidoc │ ├── gun_cookies.domain_match.asciidoc │ ├── gun_cookies.path_match.asciidoc │ ├── gun_cookies_list.asciidoc │ ├── gun_data.asciidoc │ ├── gun_down.asciidoc │ ├── gun_error.asciidoc │ ├── gun_event.asciidoc │ ├── gun_inform.asciidoc │ ├── gun_notify.asciidoc │ ├── gun_push.asciidoc │ ├── gun_response.asciidoc │ ├── gun_trailers.asciidoc │ ├── gun_tunnel_up.asciidoc │ ├── gun_up.asciidoc │ ├── gun_upgrade.asciidoc │ ├── gun_ws.asciidoc │ └── gun_ws_protocol.asciidoc ├── ebin └── gun.app ├── erlang.mk ├── rebar.config ├── src ├── gun.erl ├── gun_app.erl ├── gun_conns_sup.erl ├── gun_content_handler.erl ├── gun_cookies.erl ├── gun_cookies_list.erl ├── gun_data_h.erl ├── gun_default_event_h.erl ├── gun_event.erl ├── gun_http.erl ├── gun_http2.erl ├── gun_http3.erl ├── gun_pool.erl ├── gun_pool_events_h.erl ├── gun_pools_sup.erl ├── gun_protocols.erl ├── gun_public_suffix.erl ├── gun_public_suffix.erl.src ├── gun_quicer.erl ├── gun_raw.erl ├── gun_socks.erl ├── gun_sse_h.erl ├── gun_sup.erl ├── gun_tcp.erl ├── gun_tcp_proxy.erl ├── gun_tls.erl ├── gun_tls_proxy.erl ├── gun_tls_proxy_cb.erl ├── gun_tls_proxy_http2_connect.erl ├── gun_tunnel.erl ├── gun_ws.erl ├── gun_ws_h.erl └── gun_ws_protocol.erl └── test ├── event_SUITE.erl ├── flow_SUITE.erl ├── gun_SUITE.erl ├── gun_ct_hook.erl ├── gun_test.erl ├── gun_test_event_h.erl ├── gun_test_fun_event_h.erl ├── h2specd_SUITE.erl ├── handlers ├── cookie_echo_h.erl ├── cookie_informational_h.erl ├── cookie_parser_h.erl ├── cookie_parser_result_h.erl ├── cookie_set_h.erl ├── delayed_hello_h.erl ├── delayed_push_h.erl ├── empty_h.erl ├── hello_h.erl ├── inform_h.erl ├── pool_ws_handler.erl ├── proxied_h.erl ├── push_h.erl ├── sse_clock_close_h.erl ├── sse_clock_h.erl ├── sse_lone_id_h.erl ├── sse_mime_param_h.erl ├── stream_h.erl ├── trailers_h.erl ├── ws_cookie_h.erl ├── ws_echo_h.erl ├── ws_frozen_h.erl ├── ws_reject_h.erl ├── ws_subprotocol_h.erl └── ws_timeout_close_h.erl ├── ping_SUITE.erl ├── pool_SUITE.erl ├── raw_SUITE.erl ├── rfc6265bis_SUITE.erl ├── rfc7230_SUITE.erl ├── rfc7231_SUITE.erl ├── rfc7540_SUITE.erl ├── send_errors_SUITE.erl ├── shutdown_SUITE.erl ├── socks_SUITE.erl ├── sse_SUITE.erl ├── tunnel_SUITE.erl ├── wpt └── cookies │ ├── attributes_expires.json │ ├── attributes_invalid.json │ ├── attributes_max_age.json │ ├── attributes_path.json │ ├── attributes_secure.json │ ├── attributes_secure_non_secure.json │ ├── encoding_charset.json │ ├── name.json │ ├── size_attributes.json │ ├── size_name_and_value.json │ └── value.json ├── ws_SUITE.erl ├── ws_autobahn_SUITE.erl └── ws_autobahn_SUITE_data └── server.json /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | ## Use workflows from ninenines/ci.erlang.mk to test Gun. 2 | 3 | name: Check Gun 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | schedule: 11 | ## Every Monday at 2am. 12 | - cron: 0 2 * * 1 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | cleanup-master: 20 | name: Cleanup master build 21 | runs-on: ubuntu-latest 22 | permissions: 23 | actions: write 24 | steps: 25 | 26 | - name: Cleanup master build if necessary 27 | if: ${{ github.event_name == 'schedule' }} 28 | run: | 29 | gh cache delete Linux-X64-Erlang-master -R $REPO || true 30 | gh cache delete macOS-ARM64-Erlang-master -R $REPO || true 31 | env: 32 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | REPO: ${{ github.repository }} 34 | 35 | check: 36 | name: Gun 37 | needs: cleanup-master 38 | uses: ninenines/ci.erlang.mk/.github/workflows/ci.yaml@master 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .erlang.mk 2 | .*.plt 3 | *.d 4 | deps 5 | doc/guide.pdf 6 | doc/html 7 | doc/man* 8 | ebin/test 9 | ebin/*.beam 10 | logs 11 | test/*.beam 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025, Loïc Hoguin 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | = Gun 2 | 3 | Gun is an Erlang HTTP client with support for HTTP/1.1, 4 | HTTP/2, Websocket and more. 5 | 6 | == Goals 7 | 8 | Gun aims to provide an *easy to use* client compatible with 9 | HTTP/1.1, HTTP/2 and Websocket. Gun can connect through any 10 | combination of Socks and HTTP proxies. 11 | 12 | Gun is *always connected*. It will maintain a permanent 13 | connection to the server, reopening it as soon as the server 14 | closes it, saving time for the requests that come in. 15 | 16 | All connections are *supervised* automatically, allowing 17 | developers to focus on writing their code without worrying. 18 | 19 | == Sponsors 20 | 21 | Gun was previously sponsored by 22 | http://leo-project.net/leofs/[LeoFS Cloud Storage], 23 | https://sameroom.io/[Sameroom], 24 | and https://pleroma.social/[Pleroma]. 25 | 26 | == Online documentation 27 | 28 | * https://ninenines.eu/docs/en/gun/2.2/guide[User guide] 29 | * https://ninenines.eu/docs/en/gun/2.2/manual[Function reference] 30 | 31 | == Offline documentation 32 | 33 | * While still online, run `make docs` 34 | * User guide available in `doc/` in PDF and HTML formats 35 | * Function reference man pages available in `doc/man3/` and `doc/man7/` 36 | * Run `make install-docs` to install man pages on your system 37 | * Full documentation in Asciidoc available in `doc/src/` 38 | 39 | == Getting help 40 | 41 | * https://discord.gg/x25nNq2fFE[Discord server] 42 | * https://github.com/ninenines/gun/issues[Issues tracker] 43 | * https://ninenines.eu/services/[Commercial Support] 44 | -------------------------------------------------------------------------------- /doc/src/guide/book.asciidoc: -------------------------------------------------------------------------------- 1 | // a2x: --dblatex-opts "-P latex.output.revhistory=0 -P doc.publisher.show=0 -P index.numbered=0" 2 | // a2x: --dblatex-opts "-s gun" 3 | // a2x: -d book --attribute tabsize=4 4 | 5 | = Gun User Guide 6 | 7 | = Interface 8 | 9 | include::introduction.asciidoc[Introduction] 10 | 11 | include::start.asciidoc[Starting and stopping] 12 | 13 | include::protocols.asciidoc[Supported protocols] 14 | 15 | include::connect.asciidoc[Connection] 16 | 17 | include::http.asciidoc[Using HTTP] 18 | 19 | include::websocket.asciidoc[Using Websocket] 20 | 21 | = Advanced 22 | 23 | include::internals_tls_over_tls.asciidoc[Internals: TLS over TLS] 24 | 25 | = Additional information 26 | 27 | include::migrating_from_2.1.asciidoc[Migrating from Gun 2.1 to 2.2] 28 | 29 | include::migrating_from_2.0.asciidoc[Migrating from Gun 2.0 to 2.1] 30 | 31 | include::migrating_from_1.3.asciidoc[Migrating from Gun 1.3 to 2.0] 32 | 33 | include::migrating_from_1.2.asciidoc[Migrating from Gun 1.2 to 1.3] 34 | 35 | include::migrating_from_1.1.asciidoc[Migrating from Gun 1.1 to 1.2] 36 | 37 | include::migrating_from_1.0.asciidoc[Migrating from Gun 1.0 to 1.1] 38 | -------------------------------------------------------------------------------- /doc/src/guide/gun.sty: -------------------------------------------------------------------------------- 1 | \NeedsTeXFormat{LaTeX2e} 2 | \ProvidesPackage{asciidoc-dblatex}[2012/10/24 AsciiDoc DocBook Style] 3 | 4 | %% Just use the original package and pass the options. 5 | \RequirePackageWithOptions{docbook} 6 | 7 | %% Define an alias for make snippets to be compatible with source-highlighter. 8 | \lstalias{makefile}{make} 9 | -------------------------------------------------------------------------------- /doc/src/guide/introduction.asciidoc: -------------------------------------------------------------------------------- 1 | [[introduction]] 2 | == Introduction 3 | 4 | Gun is an HTTP client for Erlang/OTP. 5 | 6 | Gun supports the HTTP/2, HTTP/1.1 and Websocket protocols. 7 | 8 | === Prerequisites 9 | 10 | Knowledge of Erlang, but also of the HTTP/1.1, HTTP/2 and Websocket 11 | protocols is required in order to read this guide. 12 | 13 | === Supported platforms 14 | 15 | Gun is tested and supported on Linux, FreeBSD, Windows and OSX. 16 | 17 | Gun is developed for Erlang/OTP 22.0 and newer. 18 | 19 | === License 20 | 21 | Gun uses the ISC License. 22 | 23 | ---- 24 | Copyright (c) 2013-2025, Loïc Hoguin 25 | 26 | Permission to use, copy, modify, and/or distribute this software for any 27 | purpose with or without fee is hereby granted, provided that the above 28 | copyright notice and this permission notice appear in all copies. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 31 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 32 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 33 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 34 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 35 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 36 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 37 | ---- 38 | 39 | === Versioning 40 | 41 | Gun uses http://semver.org/[Semantic Versioning 2.0.0]. 42 | 43 | === Conventions 44 | 45 | In the HTTP protocol, the method name is case sensitive. All standard 46 | method names are uppercase. 47 | 48 | Header names are case insensitive. Gun converts all the header names 49 | to lowercase, including request headers provided by your application. 50 | -------------------------------------------------------------------------------- /doc/src/guide/migrating_from_1.0.asciidoc: -------------------------------------------------------------------------------- 1 | [appendix] 2 | == Migrating from Gun 1.0 to 1.1 3 | 4 | Gun 1.1 updates the Cowlib dependency to 2.5.1 and fixes a 5 | few problems with experimental features. 6 | 7 | === Features added 8 | 9 | * Update Cowlib to 2.5.1 10 | 11 | === Bugs fixed 12 | 13 | * A bug in the experimental `gun_sse_h` where lone id lines 14 | were not propagated has been fixed by updating the Cowlib 15 | dependency. 16 | 17 | * The status code was incorrectly given to the experimental 18 | content handlers as a binary. It has been fixed an an 19 | integer is now given as was intended. 20 | 21 | * A number of Dialyzer warnings have been fixed. 22 | -------------------------------------------------------------------------------- /doc/src/guide/migrating_from_1.1.asciidoc: -------------------------------------------------------------------------------- 1 | [appendix] 2 | == Migrating from Gun 1.1 to 1.2 3 | 4 | Gun 1.2 adds support for the CONNECT request over HTTP/1.1 5 | connections. 6 | 7 | === Features added 8 | 9 | * CONNECT requests can now be issued on HTTP/1.1 connections. 10 | The tunneled connection can use any of the protocols Gun 11 | supports: HTTP/1.1, HTTP/2 and Websocket over both TCP and 12 | TLS transports. Note that Gun currently does not support 13 | tunneling a TLS connection over a TLS connection due to 14 | limitations in Erlang/OTP. 15 | 16 | * Gun supports sending multiple CONNECT requests, allowing 17 | the tunnel to the origin server to go through multiple 18 | proxies. 19 | 20 | * Gun supports sending CONNECT requests with authorization 21 | credentials using the Basic authentication mechanism. 22 | 23 | * Update Cowlib to 2.6.0 24 | 25 | === Functions added 26 | 27 | * The functions `gun:connect/2,3,4` have been added. They can 28 | be used to initiate CONNECT requests on HTTP/1.1 connections. 29 | -------------------------------------------------------------------------------- /doc/src/guide/migrating_from_1.2.asciidoc: -------------------------------------------------------------------------------- 1 | [appendix] 2 | == Migrating from Gun 1.2 to 1.3 3 | 4 | Gun 1.3 improves the support for CONNECT requests 5 | introduced in the previous version and documents 6 | Websocket protocol negotiation. 7 | 8 | === Features added 9 | 10 | * The `protocols` CONNECT destination option has been added 11 | as a replacement for the now deprecated `protocol` option. 12 | 13 | * Add built-in support for Websocket protocol negotiation 14 | through the Websocket option `protocols`. The interface 15 | of the handler module currently remains undocumented and 16 | must be set to `gun_ws_h`. 17 | 18 | * Add the h2specd HTTP/2 test suite from the h2spec project. 19 | 20 | === Bugs fixed 21 | 22 | * Fix connecting to HTTP/2 over TLS origin servers via 23 | HTTP/1.1 CONNECT proxies. 24 | 25 | * Do not send the HTTP/1.1 keepalive while waiting for 26 | a response to a CONNECT request. 27 | 28 | * Do not crash on HTTP/2 HEADERS frames with the 29 | PRIORITY flag set. 30 | 31 | * Do not crash on HTTP/2 HEADERS frames when the 32 | END_HEADERS flag is not set. 33 | 34 | * Do not crash on unknown HTTP/2 frame types. 35 | 36 | * Reject HTTP/2 WINDOW_UPDATE frames when they would 37 | cause the window to overflow. 38 | 39 | * Send a GOAWAY frame on closing the HTTP/2 connection. 40 | -------------------------------------------------------------------------------- /doc/src/guide/migrating_from_2.0.asciidoc: -------------------------------------------------------------------------------- 1 | [appendix] 2 | == Migrating from Gun 2.0 to 2.1 3 | 4 | Gun 2.1 contains a small security improvement for 5 | the HTTP/2 protocol, as well as includes a small 6 | number of fixes and improvements. 7 | 8 | Gun 2.1 requires Erlang/OTP 22.0 or greater. 9 | 10 | === Features added 11 | 12 | * A new HTTP/2 option `max_fragmented_header_block_size` has 13 | been added to limit the size of header blocks that are 14 | sent over multiple HEADERS and CONTINUATION frames. 15 | 16 | * Update Cowlib to 2.13.0. 17 | 18 | === Bugs fixed 19 | 20 | * Gun will no longer configure the NPN TLS extension, 21 | which has long been replaced by ALPN. NPN is not 22 | compatible with TLS 1.3. 23 | 24 | * Gun will no longer crash when TLS connections close 25 | very early in the connection's life time. 26 | -------------------------------------------------------------------------------- /doc/src/guide/migrating_from_2.1.asciidoc: -------------------------------------------------------------------------------- 1 | [appendix] 2 | == Migrating from Gun 2.1 to 2.2 3 | 4 | Gun 2.2 contains many features and fixes, including 5 | an experimental HTTP/3 implementation. 6 | 7 | Gun 2.2 requires Erlang/OTP 24.0 or greater. 8 | 9 | === Features added 10 | 11 | * Gun will now do wildcard certificate matching by 12 | default, as required by the HTTP protocol, by 13 | setting the `customize_hostname_check` ssl option. 14 | 15 | * User pings are now supported for HTTP/2. User pings 16 | are PING frames sent by the user, with the 17 | corresponding PING_ACK resulting in a `gun_notify` 18 | message being sent to the user process. They can 19 | be used to measure latency or do other checks. 20 | 21 | * The `reply_to` request option has been extended to 22 | accept a fun or an MFA that will be called when a 23 | reply must be sent to the user process. 24 | 25 | * The `state_name`, `event_handler` and `event_handler_state` 26 | fields were added to `gun:info/1`. 27 | 28 | * Update Cowlib to 2.15.0. 29 | 30 | === Experimental features added 31 | 32 | * Experimental support for HTTP/3 has been added. 33 | Websocket over HTTP/3 is not currently implemented. 34 | HTTP/3 support is disabled by default; to enable, 35 | the environment variable GUN_QUICER must be set at 36 | compile-time. 37 | 38 | === Bugs fixed 39 | 40 | * TLS 1.3 performs certificate validation independently 41 | from the handshake, which means that certificate errors 42 | can arrive after the handshake has completed. This is 43 | in part to support post-handshake authentication. Gun 44 | will now replace `{error, closed}` and similar socket 45 | errors with the TLS alert when possible, including for 46 | TLS over TLS and TLS over HTTP/2 scenarios. 47 | 48 | * HTTP/2 tunneling has been improved: WINDOW_UPDATE 49 | frames are now properly sent, and flow control is 50 | handled. In addition closing the stream is better 51 | handled. This impacts both proxying scenarios as 52 | well as Websocket over HTTP/2. 53 | 54 | * HTTP/2 will now properly send a NO_ERROR "error" 55 | code when initiating graceful shutdown. 56 | 57 | * HTTP/2 will now properly track the number of 58 | streams currently running and immediately fail 59 | when the user tries to open a stream too many 60 | compared to what the server allows. 61 | 62 | * Sometimes stray HTTP/2 timeout messages could 63 | be received, producing a log message. This was 64 | because they were not cleaned up on disconnect. 65 | Now they are. 66 | 67 | * Socket errors were not properly handled when 68 | sending Websocket PONG frames. This has been 69 | corrected. 70 | 71 | * Trying to send a Websocket frame over an HTTP 72 | connection (before the Websocket upgrade) will 73 | now result in an error sent to the user process. 74 | 75 | * When connecting to HTTP via a Unix domain socket, 76 | Gun will now set the host header to "localhost" 77 | by default. Previously an invalid host header 78 | was sent. 79 | 80 | * Data could be lost when switching to the raw 81 | protocol. This has been corrected. 82 | 83 | * Documentation has been updated to describe the 84 | `notify_settings_changed` option; as well as 85 | to recommend disabling automatic reconnect when 86 | Websocket is used; and to provide context around 87 | potential security risks due to HTTP/2 compressed 88 | headers. 89 | -------------------------------------------------------------------------------- /doc/src/guide/start.asciidoc: -------------------------------------------------------------------------------- 1 | [[start]] 2 | == Starting and stopping 3 | 4 | This chapter describes how to start and stop the Gun application. 5 | 6 | === Setting up 7 | 8 | Specify Gun as a dependency to your application in your favorite 9 | build tool. 10 | 11 | With Erlang.mk this is done by adding `gun` to the `DEPS` variable 12 | in your Makefile. 13 | 14 | .Adding Gun as an Erlang.mk dependency 15 | [source,make] 16 | ---- 17 | DEPS = gun 18 | ---- 19 | 20 | === Starting 21 | 22 | Gun is an _OTP application_. It needs to be started before you can 23 | use it. 24 | 25 | .Starting Gun in an Erlang shell 26 | [source,erlang] 27 | ---- 28 | 1> application:ensure_all_started(gun). 29 | {ok,[crypto,cowlib,asn1,public_key,ssl,gun]} 30 | ---- 31 | 32 | === Stopping 33 | 34 | You can stop Gun using the `application:stop/1` function, however 35 | only Gun will be stopped. This is the reverse of `application:start/1`. 36 | The `application_ensure_all_started/1` function has no equivalent for 37 | stopping all applications. 38 | 39 | .Stopping Gun 40 | [source,erlang] 41 | ---- 42 | application:stop(gun). 43 | ---- 44 | -------------------------------------------------------------------------------- /doc/src/guide/websocket.asciidoc: -------------------------------------------------------------------------------- 1 | [[websocket]] 2 | == Websocket 3 | 4 | This chapter describes how to use the Gun client for 5 | communicating with a Websocket server. 6 | 7 | // @todo recovering from connection failure, reconnecting to Websocket etc. 8 | 9 | === HTTP upgrade 10 | 11 | Websocket is a protocol built on top of HTTP. To use Websocket, 12 | you must first request for the connection to be upgraded. Only 13 | HTTP/1.1 connections can be upgraded to Websocket, so you might 14 | need to restrict the protocol to HTTP/1.1 if you are planning 15 | to use Websocket over TLS. 16 | 17 | You must use the `gun:ws_upgrade/2,3,4` function to upgrade 18 | to Websocket. This function can be called anytime after connection, 19 | so you can send HTTP requests before upgrading to Websocket. 20 | 21 | .Upgrade to Websocket 22 | [source,erlang] 23 | ---- 24 | gun:ws_upgrade(ConnPid, "/websocket"). 25 | ---- 26 | 27 | Gun will set all the necessary headers for performing the 28 | Websocket upgrade, but you can specify additional headers 29 | if needed. For example you can authenticate. 30 | 31 | .Upgrade to Websocket using HTTP authentication 32 | [source,erlang] 33 | ---- 34 | gun:ws_upgrade(ConnPid, "/websocket", [ 35 | {<<"authorization">>, "Basic dXNlcm5hbWU6cGFzc3dvcmQ="} 36 | ]). 37 | ---- 38 | 39 | You can pass the Websocket options as part of the `gun:open/2,3` 40 | call when opening the connection, or using the `gun:ws_upgrade/4`. 41 | The fourth argument is those same options. 42 | 43 | Gun can negotiate the protocol to be used for the Websocket 44 | connection. The `protocols` option can be given with a list 45 | of protocols accepted and the corresponding handler module. 46 | Note that the interface for handler modules is currently 47 | undocumented and must be set to `gun_ws_h`. 48 | 49 | .Upgrade to Websocket with protocol negotiation 50 | [source,erlang] 51 | ---- 52 | StreamRef = gun:ws_upgrade(ConnPid, "/websocket", [] 53 | #{protocols => [{<<"xmpp">>, gun_ws_h}]}). 54 | ---- 55 | 56 | The upgrade will fail if the server cannot satisfy the 57 | protocol negotiation. 58 | 59 | When the upgrade succeeds, a `gun_upgrade` message is sent. 60 | If the server does not understand Websocket or refused the 61 | upgrade, a `gun_response` message is sent. If Gun couldn't 62 | perform the upgrade due to an error (for example attempting 63 | to upgrade to Websocket on an HTTP/1.0 connection) then a 64 | `gun_error` message is sent. 65 | 66 | When the server does not understand Websocket, it may send 67 | a meaningful response which should be processed. In the 68 | following example we however ignore it: 69 | 70 | [source,erlang] 71 | ---- 72 | receive 73 | {gun_upgrade, ConnPid, StreamRef, [<<"websocket">>], Headers} -> 74 | upgrade_success(ConnPid, StreamRef); 75 | {gun_response, ConnPid, _, _, Status, Headers} -> 76 | exit({ws_upgrade_failed, Status, Headers}); 77 | {gun_error, ConnPid, StreamRef, Reason} -> 78 | exit({ws_upgrade_failed, Reason}) 79 | %% More clauses here as needed. 80 | after 1000 -> 81 | exit(timeout) 82 | end. 83 | ---- 84 | 85 | === Sending data 86 | 87 | Once the Websocket upgrade has completed successfully, you no 88 | longer have access to functions for performing requests. You 89 | can only send and receive Websocket messages. 90 | 91 | Use `gun:ws_send/3` to send messages to the server. 92 | 93 | .Send a text frame 94 | [source,erlang] 95 | ---- 96 | gun:ws_send(ConnPid, StreamRef, {text, "Hello!"}). 97 | ---- 98 | 99 | .Send a text frame, a binary frame and then close the connection 100 | [source,erlang] 101 | ---- 102 | gun:ws_send(ConnPid, StreamRef, [ 103 | {text, "Hello!"}, 104 | {binary, BinaryValue}, 105 | close 106 | ]). 107 | ---- 108 | 109 | Note that if you send a close frame, Gun will close the connection 110 | cleanly but may attempt to reconnect afterwards depending on the 111 | `retry` configuration. 112 | 113 | === Receiving data 114 | 115 | Gun sends an Erlang message to the owner process for every 116 | Websocket message it receives. 117 | 118 | [source,erlang] 119 | ---- 120 | receive 121 | {gun_ws, ConnPid, StreamRef, Frame} -> 122 | handle_frame(ConnPid, StreamRef, Frame) 123 | end. 124 | ---- 125 | 126 | === Automatic reconnect gotchas 127 | 128 | It is recommended to disable automatic reconnect when 129 | Websocket is used because Gun cannot automatically upgrade 130 | to Websocket on reconnect, and so an undetected disconnect 131 | may lead to many error messages from Gun. 132 | 133 | This can be done by setting the `retry` option to `0` 134 | when opening a connection: 135 | 136 | [source,erlang] 137 | ---- 138 | {ok, ConnPid} = gun:open("localhost", 12345, #{ 139 | retry ⇒ 0 140 | }). 141 | ---- 142 | -------------------------------------------------------------------------------- /doc/src/manual/gun.await.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:await(3) 2 | 3 | == Name 4 | 5 | gun:await - Wait for a response 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | await(ConnPid, StreamRef) 12 | -> await(ConnPid, StreamRef, 5000, MonitorRef) 13 | 14 | await(ConnPid, StreamRef, MonitorRef) 15 | -> await(ConnPid, StreamRef, 5000, MonitorRef) 16 | 17 | await(ConnPid, StreamRef, Timeout) 18 | -> await(ConnPid, StreamRef, Timeout, MonitorRef) 19 | 20 | await(ConnPid, StreamRef, Timeout, MonitorRef) 21 | -> Result 22 | 23 | ConnPid :: pid() 24 | StreamRef :: gun:stream_ref() 25 | MonitorRef :: reference() 26 | Timeout :: timeout() 27 | Result :: tuple() - see below 28 | ---- 29 | 30 | Wait for a response. 31 | 32 | This function waits for a message from the given stream and 33 | returns it as a tuple. An error will be returned should the 34 | process fail or a relevant message is not received within 35 | the specified duration. 36 | 37 | == Arguments 38 | 39 | ConnPid:: 40 | 41 | The pid of the Gun connection process. 42 | 43 | StreamRef:: 44 | 45 | Identifier of the stream for the original request. 46 | 47 | Timeout:: 48 | 49 | How long to wait for a message, in milliseconds. 50 | 51 | MonitorRef:: 52 | 53 | Monitor for the Gun connection process. 54 | + 55 | A monitor is automatically created for the duration of this 56 | call when one is not provided. 57 | 58 | == Return value 59 | 60 | A number of different tuples can be returned. They correspond 61 | to the message of the same name and they contain the same 62 | elements minus the pid and stream reference. Error tuples 63 | may also be returned when a timeout or an error occur. 64 | 65 | [source,erlang] 66 | ---- 67 | Result :: {inform, Status, Headers} 68 | | {response, IsFin, Status, Headers} 69 | | {data, IsFin, Data} 70 | | {trailers, Trailers} 71 | | {push, NewStreamRef, Method, URI, Headers} 72 | | {upgrade, Protocols, Headers} 73 | | {ws, Frame} 74 | | {error, Reason} 75 | 76 | Reason :: {stream_error | connection_error | down, any()} 77 | | timeout 78 | ---- 79 | 80 | Because the messages and returned tuples are equivalent, 81 | please refer to the manual pages for each message for 82 | further information: 83 | 84 | * link:man:gun_inform(3)[gun_inform(3)] - Informational response 85 | * link:man:gun_response(3)[gun_response(3)] - Response 86 | * link:man:gun_data(3)[gun_data(3)] - Response body 87 | * link:man:gun_trailers(3)[gun_trailers(3)] - Response trailers 88 | * link:man:gun_push(3)[gun_push(3)] - Server-initiated push 89 | * link:man:gun_upgrade(3)[gun_upgrade(3)] - Successful protocol upgrade 90 | * link:man:gun_ws(3)[gun_ws(3)] - Websocket frame 91 | 92 | == Changelog 93 | 94 | * *2.0*: `upgrade` and `ws` tuples can now be returned. 95 | * *2.0*: The error tuple type now includes the type of error. 96 | * *1.0*: Function introduced. 97 | 98 | == Examples 99 | 100 | .Wait for a response 101 | [source,erlang] 102 | ---- 103 | StreamRef = gun:get(ConnPid, "/articles", [ 104 | {<<"accept">>, <<"text/html;q=1.0, application/xml;q=0.1">>} 105 | ]). 106 | {response, nofin, 200, _Headers} = gun:await(ConnPid, StreamRef). 107 | {data, fin, <<"Hello world!">>} = gun:await(ConnPid, StreamRef). 108 | ---- 109 | 110 | == See also 111 | 112 | link:man:gun(3)[gun(3)], 113 | link:man:gun:get(3)[gun:get(3)], 114 | link:man:gun:head(3)[gun:head(3)], 115 | link:man:gun:options(3)[gun:options(3)], 116 | link:man:gun:patch(3)[gun:patch(3)], 117 | link:man:gun:post(3)[gun:post(3)], 118 | link:man:gun:put(3)[gun:put(3)], 119 | link:man:gun:delete(3)[gun:delete(3)], 120 | link:man:gun:headers(3)[gun:headers(3)], 121 | link:man:gun:request(3)[gun:request(3)], 122 | link:man:gun:await_body(3)[gun:await_body(3)] 123 | -------------------------------------------------------------------------------- /doc/src/manual/gun.await_body.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:await_body(3) 2 | 3 | == Name 4 | 5 | gun:await_body - Wait for the complete response body 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | await_body(ConnPid, StreamRef) 12 | -> await_body(ConnPid, StreamRef, 5000, MonitorRef) 13 | 14 | await_body(ConnPid, StreamRef, MonitorRef) 15 | -> await_body(ConnPid, StreamRef, 5000, MonitorRef) 16 | 17 | await_body(ConnPid, StreamRef, Timeout) 18 | -> await_body(ConnPid, StreamRef, Timeout, MonitorRef) 19 | 20 | await_body(ConnPid, StreamRef, Timeout, MonitorRef) 21 | -> {ok, Body} | {ok, Body, Trailers} | {error, Reason} 22 | 23 | ConnPid :: pid() 24 | StreamRef :: gun:stream_ref() 25 | MonitorRef :: reference() 26 | Timeout :: timeout() 27 | Body :: binary() 28 | Trailers :: [{binary(), binary()}] 29 | Reason :: {stream_error | connection_error | down, any()} 30 | | timeout 31 | ---- 32 | 33 | Wait for the complete response body. 34 | 35 | == Arguments 36 | 37 | ConnPid:: 38 | 39 | The pid of the Gun connection process. 40 | 41 | StreamRef:: 42 | 43 | Identifier of the stream for the original request. 44 | 45 | Timeout:: 46 | 47 | How long to wait for each message, in milliseconds. 48 | 49 | MonitorRef:: 50 | 51 | Monitor for the Gun connection process. 52 | + 53 | A monitor is automatically created for the duration of this 54 | call when one is not provided. 55 | 56 | == Return value 57 | 58 | The body is returned, possibly with trailers if the 59 | request contained a `te: trailers` header. Error tuples 60 | may also be returned when a timeout or an error occur. 61 | 62 | == Changelog 63 | 64 | * *2.0*: The error tuple type now includes the type of error. 65 | * *1.0*: Function introduced. 66 | 67 | == Examples 68 | 69 | .Wait for the complete response body 70 | [source,erlang] 71 | ---- 72 | StreamRef = gun:get(ConnPid, "/articles", [ 73 | {<<"accept">>, <<"text/html;q=1.0, application/xml;q=0.1">>} 74 | ]). 75 | {response, nofin, 200, _Headers} = gun:await(ConnPid, StreamRef). 76 | {ok, _Body} = gun:await_body(ConnPid, StreamRef). 77 | ---- 78 | 79 | == See also 80 | 81 | link:man:gun(3)[gun(3)], 82 | link:man:gun:get(3)[gun:get(3)], 83 | link:man:gun:head(3)[gun:head(3)], 84 | link:man:gun:options(3)[gun:options(3)], 85 | link:man:gun:patch(3)[gun:patch(3)], 86 | link:man:gun:post(3)[gun:post(3)], 87 | link:man:gun:put(3)[gun:put(3)], 88 | link:man:gun:delete(3)[gun:delete(3)], 89 | link:man:gun:headers(3)[gun:headers(3)], 90 | link:man:gun:request(3)[gun:request(3)], 91 | link:man:gun:await(3)[gun:await(3)] 92 | -------------------------------------------------------------------------------- /doc/src/manual/gun.await_up.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:await_up(3) 2 | 3 | == Name 4 | 5 | gun:await_up - Wait for the connection to be up 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | await_up(ConnPid) 12 | -> await_up(ConnPid, 5000, MonitorRef) 13 | 14 | await_up(ConnPid, MonitorRef) 15 | -> await_up(ConnPid, 5000, MonitorRef) 16 | 17 | await_up(ConnPid, Timeout) 18 | -> await_up(ConnPid, Timeout, MonitorRef) 19 | 20 | await_up(ConnPid, Timeout, MonitorRef) 21 | -> {ok, Protocol} | {error, Reason} 22 | 23 | ConnPid :: pid() 24 | MonitorRef :: reference() 25 | Timeout :: timeout() 26 | Protocol :: http | http2 | socks 27 | Reason :: {down, any()} | timeout 28 | ---- 29 | 30 | Wait for the connection to be up. 31 | 32 | == Arguments 33 | 34 | ConnPid:: 35 | 36 | The pid of the Gun connection process. 37 | 38 | Timeout:: 39 | 40 | How long to wait for, in milliseconds. 41 | 42 | MonitorRef:: 43 | 44 | Monitor for the Gun connection process. 45 | + 46 | A monitor is automatically created for the duration of this 47 | call when one is not provided. 48 | 49 | == Return value 50 | 51 | The protocol selected for this connection. It can be used 52 | to determine the capabilities of the server. Error tuples 53 | may also be returned when a timeout or an error occur. 54 | 55 | == Changelog 56 | 57 | * *2.0*: The error tuple type now includes the type of error. 58 | * *1.0*: Function introduced. 59 | 60 | == Examples 61 | 62 | .Wait for the connection to be up 63 | [source,erlang] 64 | ---- 65 | {ok, ConnPid} = gun:open("example.org", 443). 66 | {ok, _} = gun:await_up(ConnPid). 67 | ---- 68 | 69 | == See also 70 | 71 | link:man:gun(3)[gun(3)], 72 | link:man:gun:open(3)[gun:open(3)], 73 | link:man:gun:open_unix(3)[gun:open_unix(3)], 74 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)], 75 | link:man:gun_up(3)[gun_up(3)] 76 | -------------------------------------------------------------------------------- /doc/src/manual/gun.cancel.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:cancel(3) 2 | 3 | == Name 4 | 5 | gun:cancel - Cancel the given stream 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | cancel(ConnPid, StreamRef) -> ok 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | ---- 16 | 17 | Cancel the given stream. 18 | 19 | The behavior of this function depends on the protocol 20 | selected. 21 | 22 | HTTP/1.1 does not support this feature. Gun will simply 23 | silence the stream and stop relaying messages. Gun may 24 | also decide to close the connection if the response body 25 | is too large, to avoid wasting time and bandwidth. 26 | 27 | HTTP/2 allows cancelling streams at any time. 28 | 29 | This function is asynchronous. Messages related to this 30 | stream may still be sent after the function returns. 31 | 32 | == Arguments 33 | 34 | ConnPid:: 35 | 36 | The pid of the Gun connection process. 37 | 38 | StreamRef:: 39 | 40 | Identifier of the stream for the original request. 41 | 42 | == Return value 43 | 44 | The atom `ok` is returned. 45 | 46 | == Changelog 47 | 48 | * *1.0*: Function introduced. 49 | 50 | == Examples 51 | 52 | .Cancel a stream 53 | [source,erlang] 54 | ---- 55 | gun:cancel(ConnPid, StreamRef). 56 | ---- 57 | 58 | == See also 59 | 60 | link:man:gun(3)[gun(3)], 61 | link:man:gun:get(3)[gun:get(3)], 62 | link:man:gun:head(3)[gun:head(3)], 63 | link:man:gun:options(3)[gun:options(3)], 64 | link:man:gun:patch(3)[gun:patch(3)], 65 | link:man:gun:post(3)[gun:post(3)], 66 | link:man:gun:put(3)[gun:put(3)], 67 | link:man:gun:delete(3)[gun:delete(3)], 68 | link:man:gun:headers(3)[gun:headers(3)], 69 | link:man:gun:request(3)[gun:request(3)], 70 | link:man:gun:stream_info(3)[gun:stream_info(3)] 71 | -------------------------------------------------------------------------------- /doc/src/manual/gun.close.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:close(3) 2 | 3 | == Name 4 | 5 | gun:close - Brutally close the connection 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | close(ConnPid) -> ok 12 | 13 | ConnPid :: pid() 14 | ---- 15 | 16 | Brutally close the connection. 17 | 18 | == Arguments 19 | 20 | ConnPid:: 21 | 22 | The pid of the Gun connection process. 23 | 24 | == Return value 25 | 26 | The atom `ok` is returned. 27 | 28 | == Changelog 29 | 30 | * *1.0*: Function introduced. 31 | 32 | == Examples 33 | 34 | .Close the connection 35 | [source,erlang] 36 | ---- 37 | ok = gun:close(ConnPid). 38 | ---- 39 | 40 | == See also 41 | 42 | link:man:gun(3)[gun(3)], 43 | link:man:gun:open(3)[gun:open(3)], 44 | link:man:gun:open_unix(3)[gun:open_unix(3)], 45 | link:man:gun:shutdown(3)[gun:shutdown(3)] 46 | -------------------------------------------------------------------------------- /doc/src/manual/gun.connect.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:connect(3) 2 | 3 | == Name 4 | 5 | gun:connect - Establish a tunnel to the origin server 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | connect(ConnPid, Destination) 12 | -> connect(ConnPid, Destination, [], #{}). 13 | 14 | connect(ConnPid, Destination, Headers) 15 | -> connect(ConnPid, Destination, Headers, #{}). 16 | 17 | connect(ConnPid, Destination, Headers, ReqOpts) 18 | -> StreamRef 19 | 20 | ConnPid :: pid() 21 | Destination :: gun:connect_destination() 22 | Headers :: gun:req_headers() 23 | ReqOpts :: gun:req_opts() 24 | StreamRef :: gun:stream_ref() 25 | ---- 26 | 27 | Establish a tunnel to the origin server. 28 | 29 | This feature is currently only available for HTTP/1.1 connections. 30 | Upon successful completion of the CONNECT request a tunnel is 31 | established and subsequent requests will go through the tunnel. 32 | 33 | Gun will not automatically re-issue the CONNECT request upon 34 | reconnection to the proxy server. The `gun_up` message can 35 | be used to know when the tunnel needs to be established again. 36 | 37 | == Arguments 38 | 39 | ConnPid:: 40 | 41 | The pid of the Gun connection process. 42 | 43 | Destination:: 44 | 45 | Destination of the CONNECT request. 46 | 47 | Headers:: 48 | 49 | Additional request headers. 50 | 51 | ReqOpts:: 52 | 53 | Request options. 54 | 55 | == Return value 56 | 57 | A reference that identifies the newly created stream is 58 | returned. It is this reference that must be passed in 59 | subsequent calls and will be received in messages related 60 | to this new stream. 61 | 62 | == Changelog 63 | 64 | * *1.2*: Function introduced. 65 | 66 | == Examples 67 | 68 | .Establish a tunnel 69 | [source,erlang] 70 | ---- 71 | {ok, ConnPid} = gun:open("proxy.example.org", 1080), 72 | {ok, http} = gun:await_up(ConnPid), 73 | StreamRef = gun:connect(ConnPid, #{ 74 | host => "origin-server.example.org", 75 | port => 80 76 | }), 77 | {response, fin, 200, _} = gun:await(ConnPid, StreamRef), 78 | %% Subsequent requests will be sent to origin-server.example.org. 79 | ---- 80 | 81 | .Establish a tunnel for a secure HTTP/2 connection 82 | [source,erlang] 83 | ---- 84 | {ok, ConnPid} = gun:open("proxy.example.org", 1080), 85 | {ok, http} = gun:await_up(ConnPid), 86 | StreamRef = gun:connect(ConnPid, #{ 87 | host => "origin-server.example.org", 88 | port => 443, 89 | protocols => [http2], 90 | transport => tls 91 | }), 92 | {response, fin, 200, _} = gun:await(ConnPid, StreamRef), 93 | %% Subsequent requests will be sent to origin-server.example.org. 94 | ---- 95 | 96 | .Establish a tunnel using proxy authorization 97 | [source,erlang] 98 | ---- 99 | {ok, ConnPid} = gun:open("proxy.example.org", 1080), 100 | {ok, http} = gun:await_up(ConnPid), 101 | StreamRef = gun:connect(ConnPid, #{ 102 | host => "origin-server.example.org", 103 | port => 80, 104 | username => "essen", 105 | password => "myrealpasswordis" 106 | }), 107 | {response, fin, 200, _} = gun:await(ConnPid, StreamRef), 108 | %% Subsequent requests will be sent to origin-server.example.org. 109 | ---- 110 | 111 | == See also 112 | 113 | link:man:gun(3)[gun(3)], 114 | link:man:gun:await_up(3)[gun:await_up(3)], 115 | link:man:gun_up(3)[gun_up(3)] 116 | -------------------------------------------------------------------------------- /doc/src/manual/gun.data.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:data(3) 2 | 3 | == Name 4 | 5 | gun:data - Stream the body of a request 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | data(ConnPid, StreamRef, IsFin, Data) -> ok 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | IsFin :: fin | nofin 16 | Data :: iodata() 17 | ---- 18 | 19 | Stream the body of a request. 20 | 21 | This function can only be used if the original request 22 | had headers indicating that a body would be streamed. 23 | 24 | All calls to this function must use the `nofin` flag 25 | except for the last which must use `fin` to indicate 26 | the end of the request body. 27 | 28 | Empty data is allowed regardless of the value of `IsFin`. 29 | Gun may or may not send empty data chunks, however. 30 | 31 | == Arguments 32 | 33 | ConnPid:: 34 | 35 | The pid of the Gun connection process. 36 | 37 | StreamRef:: 38 | 39 | Identifier of the stream for the original request. 40 | 41 | IsFin:: 42 | 43 | Whether this message terminates the request. 44 | 45 | Data:: 46 | 47 | All or part of the response body. 48 | 49 | == Return value 50 | 51 | The atom `ok` is returned. 52 | 53 | == Changelog 54 | 55 | * *1.0*: Function introduced. 56 | 57 | == Examples 58 | 59 | .Stream the body of a request 60 | [source,erlang] 61 | ---- 62 | StreamRef = gun:put(ConnPid, "/lang/fr_FR/hello", [ 63 | {<<"content-type">>, <<"text/plain">>} 64 | ]). 65 | gun:data(ConnPid, StreamRef, nofin, <<"Bonjour !\n">>). 66 | gun:data(ConnPid, StreamRef, fin, <<"Bonsoir !\n">>). 67 | ---- 68 | 69 | == See also 70 | 71 | link:man:gun(3)[gun(3)], 72 | link:man:gun:patch(3)[gun:patch(3)], 73 | link:man:gun:post(3)[gun:post(3)], 74 | link:man:gun:put(3)[gun:put(3)], 75 | link:man:gun:headers(3)[gun:headers(3)] 76 | -------------------------------------------------------------------------------- /doc/src/manual/gun.delete.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:delete(3) 2 | 3 | == Name 4 | 5 | gun:delete - Delete a resource 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | delete(ConnPid, Path) 12 | -> delete(ConnPid, Path, [], #{}). 13 | 14 | delete(ConnPid, Path, Headers) 15 | -> delete(ConnPid, Path, Headers, #{}) 16 | 17 | delete(ConnPid, Path, Headers, ReqOpts) 18 | -> StreamRef 19 | 20 | ConnPid :: pid() 21 | Path :: iodata() 22 | Headers :: gun:req_headers() 23 | ReqOpts :: gun:req_opts() 24 | StreamRef :: gun:stream_ref() 25 | ---- 26 | 27 | Delete a resource. 28 | 29 | == Arguments 30 | 31 | ConnPid:: 32 | 33 | The pid of the Gun connection process. 34 | 35 | Path:: 36 | 37 | Path to the resource. 38 | 39 | Headers:: 40 | 41 | Additional request headers. 42 | 43 | ReqOpts:: 44 | 45 | Request options. 46 | 47 | == Return value 48 | 49 | A reference that identifies the newly created stream is 50 | returned. It is this reference that must be passed in 51 | subsequent calls and will be received in messages related 52 | to this new stream. 53 | 54 | == Changelog 55 | 56 | * *1.0*: Function introduced. 57 | 58 | == Examples 59 | 60 | .Delete a resource 61 | [source,erlang] 62 | ---- 63 | StreamRef = gun:delete(ConnPid, "/drafts/123"). 64 | ---- 65 | 66 | .Delete a resource with request options 67 | [source,erlang] 68 | ---- 69 | StreamRef = gun:delete(ConnPid, "/drafts/123", [], 70 | #{reply_to => ReplyToPid}). 71 | ---- 72 | 73 | == See also 74 | 75 | link:man:gun(3)[gun(3)], 76 | link:man:gun:put(3)[gun:put(3)], 77 | link:man:gun:await(3)[gun:await(3)], 78 | link:man:gun:await_body(3)[gun:await_body(3)], 79 | link:man:gun_push(3)[gun_push(3)], 80 | link:man:gun_inform(3)[gun_inform(3)], 81 | link:man:gun_response(3)[gun_response(3)], 82 | link:man:gun_data(3)[gun_data(3)] 83 | -------------------------------------------------------------------------------- /doc/src/manual/gun.flush.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:flush(3) 2 | 3 | == Name 4 | 5 | gun:flush - Flush all messages related to a connection or a stream 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | flush(ConnPid) -> ok 12 | flush(StreamRef) -> ok 13 | 14 | ConnPid :: pid() 15 | StreamRef :: gun:stream_ref() 16 | ---- 17 | 18 | Flush all messages related to a connection or a stream. 19 | 20 | == Arguments 21 | 22 | Either of these arguments may be provided: 23 | 24 | ConnPid:: 25 | 26 | The pid of the Gun connection process. 27 | 28 | StreamRef:: 29 | 30 | Identifier of the stream for the original request. 31 | 32 | == Return value 33 | 34 | The atom `ok` is returned. 35 | 36 | == Changelog 37 | 38 | * *1.0*: Function introduced. 39 | 40 | == Examples 41 | 42 | .Flush all messages from a connection 43 | [source,erlang] 44 | ---- 45 | gun:flush(ConnPid). 46 | ---- 47 | 48 | .Flush messages from a single stream 49 | [source,erlang] 50 | ---- 51 | gun:flush(StreamRef). 52 | ---- 53 | 54 | == See also 55 | 56 | link:man:gun(3)[gun(3)], 57 | link:man:gun:await(3)[gun:await(3)], 58 | link:man:gun:await_body(3)[gun:await_body(3)], 59 | link:man:gun:await_up(3)[gun:await_up(3)] 60 | -------------------------------------------------------------------------------- /doc/src/manual/gun.get.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:get(3) 2 | 3 | == Name 4 | 5 | gun:get - Get a resource representation 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | get(ConnPid, Path) 12 | -> get(ConnPid, Path, [], #{}). 13 | 14 | get(ConnPid, Path, Headers) 15 | -> get(ConnPid, Path, Headers, #{}) 16 | 17 | get(ConnPid, Path, Headers, ReqOpts) 18 | -> StreamRef 19 | 20 | ConnPid :: pid() 21 | Path :: iodata() 22 | Headers :: gun:req_headers() 23 | ReqOpts :: gun:req_opts() 24 | StreamRef :: gun:stream_ref() 25 | ---- 26 | 27 | Get a resource representation. 28 | 29 | == Arguments 30 | 31 | ConnPid:: 32 | 33 | The pid of the Gun connection process. 34 | 35 | Path:: 36 | 37 | Path to the resource. 38 | 39 | Headers:: 40 | 41 | Additional request headers. 42 | 43 | ReqOpts:: 44 | 45 | Request options. 46 | 47 | == Return value 48 | 49 | A reference that identifies the newly created stream is 50 | returned. It is this reference that must be passed in 51 | subsequent calls and will be received in messages related 52 | to this new stream. 53 | 54 | == Changelog 55 | 56 | * *1.0*: Function introduced. 57 | 58 | == Examples 59 | 60 | .Get a resource representation 61 | [source,erlang] 62 | ---- 63 | StreamRef = gun:get(ConnPid, "/articles", [ 64 | {<<"accept">>, <<"text/html;q=1.0, application/xml;q=0.1">>} 65 | ]). 66 | ---- 67 | 68 | .Get a resource representation with request options 69 | [source,erlang] 70 | ---- 71 | StreamRef = gun:get(ConnPid, "/articles", [], #{ 72 | reply_to => ReplyToPid 73 | }). 74 | ---- 75 | 76 | == See also 77 | 78 | link:man:gun(3)[gun(3)], 79 | link:man:gun:head(3)[gun:head(3)], 80 | link:man:gun:await(3)[gun:await(3)], 81 | link:man:gun:await_body(3)[gun:await_body(3)], 82 | link:man:gun_push(3)[gun_push(3)], 83 | link:man:gun_inform(3)[gun_inform(3)], 84 | link:man:gun_response(3)[gun_response(3)], 85 | link:man:gun_data(3)[gun_data(3)] 86 | -------------------------------------------------------------------------------- /doc/src/manual/gun.head.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:head(3) 2 | 3 | == Name 4 | 5 | gun:head - Get headers of a resource representation 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | head(ConnPid, Path) 12 | -> head(ConnPid, Path, [], #{}). 13 | 14 | head(ConnPid, Path, Headers) 15 | -> head(ConnPid, Path, Headers, #{}) 16 | 17 | head(ConnPid, Path, Headers, ReqOpts) 18 | -> StreamRef 19 | 20 | ConnPid :: pid() 21 | Path :: iodata() 22 | Headers :: gun:req_headers() 23 | ReqOpts :: gun:req_opts() 24 | StreamRef :: gun:stream_ref() 25 | ---- 26 | 27 | Get headers of a resource representation. 28 | 29 | This function performs the same operation as 30 | link:man:gun:get(3)[gun:get(3)], except the server will not 31 | send the resource representation, only the response's status 32 | code and headers. 33 | 34 | While servers are supposed to send the same headers as for 35 | a GET request, they sometimes will not. For example the 36 | `content-length` header may be dropped from the response. 37 | 38 | == Arguments 39 | 40 | ConnPid:: 41 | 42 | The pid of the Gun connection process. 43 | 44 | Path:: 45 | 46 | Path to the resource. 47 | 48 | Headers:: 49 | 50 | Additional request headers. 51 | 52 | ReqOpts:: 53 | 54 | Request options. 55 | 56 | == Return value 57 | 58 | A reference that identifies the newly created stream is 59 | returned. It is this reference that must be passed in 60 | subsequent calls and will be received in messages related 61 | to this new stream. 62 | 63 | == Changelog 64 | 65 | * *1.0*: Function introduced. 66 | 67 | == Examples 68 | 69 | .Get headers of a resource representation 70 | [source,erlang] 71 | ---- 72 | StreamRef = gun:head(ConnPid, "/articles", [ 73 | {<<"accept">>, <<"text/html;q=1.0, application/xml;q=0.1">>} 74 | ]). 75 | ---- 76 | 77 | .Get headers of a resource representation with request options 78 | [source,erlang] 79 | ---- 80 | StreamRef = gun:head(ConnPid, "/articles", [], #{ 81 | reply_to => ReplyToPid 82 | }). 83 | ---- 84 | 85 | == See also 86 | 87 | link:man:gun(3)[gun(3)], 88 | link:man:gun:get(3)[gun:head(3)], 89 | link:man:gun:await(3)[gun:await(3)], 90 | link:man:gun_push(3)[gun_push(3)], 91 | link:man:gun_inform(3)[gun_inform(3)], 92 | link:man:gun_response(3)[gun_response(3)] 93 | -------------------------------------------------------------------------------- /doc/src/manual/gun.headers.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:headers(3) 2 | 3 | == Name 4 | 5 | gun:headers - Initiate the given request 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | headers(ConnPid, Method, Path, Headers) 12 | -> headers(ConnPid, Method, Path, Headers, #{}) 13 | 14 | headers(ConnPid, Method, Path, Headers, ReqOpts) 15 | -> StreamRef 16 | 17 | ConnPid :: pid() 18 | Method :: binary() 19 | Path :: iodata() 20 | Headers :: gun:req_headers() 21 | ReqOpts :: gun:req_opts() 22 | StreamRef :: gun:stream_ref() 23 | ---- 24 | 25 | Initiate the given request. 26 | 27 | This is a general purpose function that should only be 28 | used when other method-specific functions do not apply. 29 | 30 | The function `headers/4,5` initiates a request but does 31 | not send the request body. It must be sent separately 32 | using link:man:gun:data(3)[gun:data(3)]. 33 | 34 | == Arguments 35 | 36 | ConnPid:: 37 | 38 | The pid of the Gun connection process. 39 | 40 | Method:: 41 | 42 | Method to be used for the request. 43 | 44 | Path:: 45 | 46 | Path to the resource. 47 | 48 | Headers:: 49 | 50 | Additional request headers. 51 | 52 | ReqOpts:: 53 | 54 | Request options. 55 | 56 | == Return value 57 | 58 | A reference that identifies the newly created stream is 59 | returned. It is this reference that must be passed in 60 | subsequent calls and will be received in messages related 61 | to this new stream. 62 | 63 | == Changelog 64 | 65 | * *2.0*: Function introduced. 66 | 67 | == Examples 68 | 69 | .Initiate a request 70 | [source,erlang] 71 | ---- 72 | StreamRef = gun:headers(ConnPid, <<"PUT">>, 73 | "/lang/fr_FR/hello", 74 | [{<<"content-type">>, <<"text/plain">>}]). 75 | ---- 76 | 77 | == See also 78 | 79 | link:man:gun(3)[gun(3)], 80 | link:man:gun:request(3)[gun:request(3)], 81 | link:man:gun:await(3)[gun:await(3)], 82 | link:man:gun:await_body(3)[gun:await_body(3)], 83 | link:man:gun_push(3)[gun_push(3)], 84 | link:man:gun_inform(3)[gun_inform(3)], 85 | link:man:gun_response(3)[gun_response(3)], 86 | link:man:gun_data(3)[gun_data(3)] 87 | -------------------------------------------------------------------------------- /doc/src/manual/gun.info.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:info(3) 2 | 3 | == Name 4 | 5 | gun:info - Obtain information about the connection 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | info(ConnPid) -> Info 12 | 13 | ConnPid :: pid() 14 | Info :: #{ 15 | owner => pid(), 16 | socket => inet:socket() | ssl:sslsocket(), 17 | transport => tcp | tls, 18 | protocol => http | http2 | socks | ws, 19 | state_name => atom(), 20 | sock_ip => inet:ip_address(), 21 | sock_port => inet:port_number(), 22 | origin_scheme => binary() | undefined, 23 | origin_host => inet:hostname() | inet:ip_address(), 24 | origin_port => inet:port_number(), 25 | intermediaries => [Intermediary], 26 | cookie_store => gun_cookies:cookie_store(), 27 | event_handler => module(), 28 | event_handler_state => any() 29 | } 30 | Intermediary :: #{ 31 | type => connect | socks5, 32 | host => inet:hostname() | inet:ip_address(), 33 | port => inet:port_number(), 34 | transport => tcp | tls, 35 | protocol => http | http2 | socks | raw 36 | } 37 | ---- 38 | 39 | Obtain information about the connection. 40 | 41 | == Arguments 42 | 43 | ConnPid:: 44 | 45 | The pid of the Gun connection process. 46 | 47 | == Return value 48 | 49 | A map is returned containing various informations about 50 | the connection. 51 | 52 | == Changelog 53 | 54 | * *2.2*: The values `event_handler`, `event_handler_state` and 55 | `state_name` were added. 56 | * *2.0*: The values `owner`, `origin_scheme` and `cookie_store` were 57 | added. 58 | * *1.3*: The values `socket`, `transport`, `protocol`, `origin_host`, 59 | `origin_port` and `intermediaries` were added. 60 | * *1.0*: Function introduced. 61 | 62 | == Examples 63 | 64 | .Obtain information about the connection 65 | [source,erlang] 66 | ---- 67 | Info = gun:info(ConnPid). 68 | ---- 69 | 70 | == See also 71 | 72 | link:man:gun(3)[gun(3)], 73 | link:man:gun:open(3)[gun:open(3)], 74 | link:man:gun:open_unix(3)[gun:open_unix(3)] 75 | -------------------------------------------------------------------------------- /doc/src/manual/gun.open.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:open(3) 2 | 3 | == Name 4 | 5 | gun:open - Open a connection to the given host and port 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | open(Host, Port) -> open(Host, Port, #{}) 12 | open(Host, Port, Opts) -> {ok, pid()} | {error, Reason} 13 | 14 | Host :: inet:hostname() | inet:ip_address() 15 | Port :: inet:port_number() 16 | Opts :: gun:opts() 17 | Reason :: {options, OptName} 18 | | {options, {http | http2 | socks | ws, OptName}} 19 | | any() 20 | OptName :: atom() 21 | ---- 22 | 23 | Open a connection to the given host and port. 24 | 25 | == Arguments 26 | 27 | Host:: 28 | 29 | Host or IP address to connect to. 30 | 31 | Port:: 32 | 33 | Port to connect to. 34 | 35 | Opts:: 36 | 37 | Options for this connection. 38 | 39 | == Return value 40 | 41 | The pid of the newly created Gun process is returned. 42 | Note that this does not indicate that the connection 43 | has been successfully opened; the link:man:gun_up(3)[gun_up(3)] 44 | message will be sent for that. 45 | 46 | == Changelog 47 | 48 | * *1.0*: Function introduced. 49 | 50 | == Examples 51 | 52 | .Connect to a server 53 | [source,erlang] 54 | ---- 55 | {ok, ConnPid} = gun:open("example.org", 443). 56 | ---- 57 | 58 | .Connect to a server with custom options 59 | [source,erlang] 60 | ---- 61 | {ok, ConnPid} = gun:open("example.org", 443, 62 | #{protocols => [http2]}). 63 | ---- 64 | 65 | .Connect to a server using its IP address 66 | [source,erlang] 67 | ---- 68 | {ok, ConnPid} = gun:open({127,0,0,1}, 443). 69 | ---- 70 | 71 | == See also 72 | 73 | link:man:gun(3)[gun(3)], 74 | link:man:gun:open_unix(3)[gun:open_unix(3)], 75 | link:man:gun:await_up(3)[gun:await_up(3)], 76 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)], 77 | link:man:gun_up(3)[gun_up(3)] 78 | -------------------------------------------------------------------------------- /doc/src/manual/gun.open_unix.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:open_unix(3) 2 | 3 | == Name 4 | 5 | gun:open_unix - Open a connection to the given Unix domain socket 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | open_unix(SocketPath, Opts) -> {ok, pid()} | {error, Reason} 12 | 13 | SocketPath :: string() 14 | Opts :: gun:opts() 15 | Reason :: {options, OptName} 16 | | {options, {http | http2 | socks | ws, OptName}} 17 | | any() 18 | OptName :: atom() 19 | ---- 20 | 21 | Open a connection to the given Unix domain socket. 22 | 23 | == Arguments 24 | 25 | SocketPath:: 26 | 27 | Path to the Unix domain socket to connect to. 28 | 29 | Opts:: 30 | 31 | Options for this connection. 32 | 33 | == Return value 34 | 35 | The pid of the newly created Gun process is returned. 36 | Note that this does not indicate that the connection 37 | has been successfully opened; the link:man:gun_up(3)[gun_up(3)] 38 | message will be sent for that. 39 | 40 | == Changelog 41 | 42 | * *1.0*: Function introduced. 43 | 44 | == Examples 45 | 46 | .Connect to a server via a Unix domain socket 47 | [source,erlang] 48 | ---- 49 | {ok, ConnPid} = gun:open_unix("/var/run/dbus/system_bus_socket", #{}). 50 | ---- 51 | 52 | .Connect to a server via a Unix domain socket with custom options 53 | [source,erlang] 54 | ---- 55 | {ok, ConnPid} = gun:open_unix("/var/run/dbus/system_bus_socket", 56 | #{protocols => [http2]}). 57 | ---- 58 | 59 | == See also 60 | 61 | link:man:gun(3)[gun(3)], 62 | link:man:gun:open(3)[gun:open(3)], 63 | link:man:gun:await_up(3)[gun:await_up(3)], 64 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)], 65 | link:man:gun_up(3)[gun_up(3)] 66 | -------------------------------------------------------------------------------- /doc/src/manual/gun.options.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:options(3) 2 | 3 | == Name 4 | 5 | gun:options - Query the capabilities of the server or a resource 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | options(ConnPid, Path) 12 | -> options(ConnPid, Path, [], #{}). 13 | 14 | options(ConnPid, Path, Headers) 15 | -> options(ConnPid, Path, Headers, #{}) 16 | 17 | options(ConnPid, Path, Headers, ReqOpts) 18 | -> StreamRef 19 | 20 | ConnPid :: pid() 21 | Path :: iodata() 22 | Headers :: gun:req_headers() 23 | ReqOpts :: gun:req_opts() 24 | StreamRef :: gun:stream_ref() 25 | ---- 26 | 27 | Query the capabilities of the server or a resource. 28 | 29 | The special path `"*"` can be used to obtain information about 30 | the server as a whole. Any other path will return information 31 | about that resource specifically. 32 | 33 | == Arguments 34 | 35 | ConnPid:: 36 | 37 | The pid of the Gun connection process. 38 | 39 | Path:: 40 | 41 | Path to the resource. 42 | 43 | Headers:: 44 | 45 | Additional request headers. 46 | 47 | ReqOpts:: 48 | 49 | Request options. 50 | 51 | == Return value 52 | 53 | A reference that identifies the newly created stream is 54 | returned. It is this reference that must be passed in 55 | subsequent calls and will be received in messages related 56 | to this new stream. 57 | 58 | == Changelog 59 | 60 | * *1.0*: Function introduced. 61 | 62 | == Examples 63 | 64 | .Query the capabilities of the server 65 | [source,erlang] 66 | ---- 67 | StreamRef = gun:options(ConnPid, "*"). 68 | ---- 69 | 70 | .Query the capabilities of a resource 71 | [source,erlang] 72 | ---- 73 | StreamRef = gun:options(ConnPid, "/articles"). 74 | ---- 75 | 76 | == See also 77 | 78 | link:man:gun(3)[gun(3)], 79 | link:man:gun:await(3)[gun:await(3)], 80 | link:man:gun:await_body(3)[gun:await_body(3)], 81 | link:man:gun_inform(3)[gun_inform(3)], 82 | link:man:gun_response(3)[gun_response(3)], 83 | link:man:gun_data(3)[gun_data(3)] 84 | -------------------------------------------------------------------------------- /doc/src/manual/gun.patch.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:patch(3) 2 | 3 | == Name 4 | 5 | gun:patch - Apply a set of changes to a resource 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | patch(ConnPid, Path, Headers) 12 | -> patch(ConnPid, Path, Headers, #{}) 13 | 14 | patch(ConnPid, Path, Headers, ReqOpts) 15 | -> StreamRef 16 | 17 | patch(ConnPid, Path, Headers, Body) 18 | -> patch(ConnPid, Path, Headers, Body, #{}) 19 | 20 | patch(ConnPid, Path, Headers, Body, ReqOpts) 21 | -> StreamRef 22 | 23 | ConnPid :: pid() 24 | Path :: iodata() 25 | Headers :: gun:req_headers() 26 | Body :: iodata() 27 | ReqOpts :: gun:req_opts() 28 | StreamRef :: gun:stream_ref() 29 | ---- 30 | 31 | Apply a set of changes to a resource. 32 | 33 | The behavior of this function varies depending on whether 34 | a body is provided. 35 | 36 | The function `patch/3,4` does not send a body. It must be 37 | sent separately using link:man:gun:data(3)[gun:data(3)]. 38 | 39 | The function `patch/4,5` sends the entire request, including 40 | the request body, immediately. It is therefore not possible 41 | to use link:man:gun:data(3)[gun:data(3)] after that. You 42 | should provide a content-type header. Gun will set the 43 | content-length header automatically. 44 | 45 | The body sent in this request should be a patch document 46 | with instructions on how to update the resource. 47 | 48 | == Arguments 49 | 50 | ConnPid:: 51 | 52 | The pid of the Gun connection process. 53 | 54 | Path:: 55 | 56 | Path to the resource. 57 | 58 | Headers:: 59 | 60 | Additional request headers. 61 | 62 | Body:: 63 | 64 | Request body. 65 | 66 | ReqOpts:: 67 | 68 | Request options. 69 | 70 | == Return value 71 | 72 | A reference that identifies the newly created stream is 73 | returned. It is this reference that must be passed in 74 | subsequent calls and will be received in messages related 75 | to this new stream. 76 | 77 | == Changelog 78 | 79 | * *2.0*: Implicit body detection has been removed. The body 80 | must now be provided either directly (even if empty) 81 | or using separate calls. 82 | * *1.0*: Function introduced. 83 | 84 | == Examples 85 | 86 | .Patch a resource 87 | [source,erlang] 88 | ---- 89 | StreamRef = gun:patch(ConnPid, "/users/1", 90 | [{<<"content-type">>, <<"application/json-patch+json">>}], 91 | <<"[{\"op\":\"add\",\"path\":\"/baz\",\"value\":\"qux\"}]">>). 92 | ---- 93 | 94 | .Patch a resource in multiple calls 95 | [source,erlang] 96 | ---- 97 | StreamRef = gun:patch(ConnPid, "/users/1", [ 98 | {<<"content-type">>, <<"application/json-patch+json">>} 99 | ]). 100 | gun:data(ConnPid, StreamRef, fin, 101 | <<"[{\"op\":\"add\",\"path\":\"/baz\",\"value\":\"qux\"}]">>). 102 | ---- 103 | 104 | .Patch a resource with request options 105 | [source,erlang] 106 | ---- 107 | StreamRef = gun:patch(ConnPid, "/users/1", 108 | [{<<"content-type">>, <<"application/json-patch+json">>}], 109 | <<"[{\"op\":\"add\",\"path\":\"/baz\",\"value\":\"qux\"}]">>, 110 | #{reply_to => ReplyToPid}). 111 | ---- 112 | 113 | == See also 114 | 115 | link:man:gun(3)[gun(3)], 116 | link:man:gun:post(3)[gun:post(3)], 117 | link:man:gun:put(3)[gun:put(3)], 118 | link:man:gun:await(3)[gun:await(3)], 119 | link:man:gun:await_body(3)[gun:await_body(3)], 120 | link:man:gun_push(3)[gun_push(3)], 121 | link:man:gun_inform(3)[gun_inform(3)], 122 | link:man:gun_response(3)[gun_response(3)], 123 | link:man:gun_data(3)[gun_data(3)] 124 | -------------------------------------------------------------------------------- /doc/src/manual/gun.ping.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:ping(3) 2 | 3 | == Name 4 | 5 | gun:ping - Check the health or RTT of the connection 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | ping(ConnPid) 12 | -> ping(ConnPid, #{}) 13 | 14 | ping(ConnPid, ReqOpts) 15 | -> PingRef 16 | 17 | ConnPid :: pid() 18 | ReqOpts :: gun:req_opts() 19 | PingRef :: reference() 20 | ---- 21 | 22 | Check the health or round-trip time of the connection. 23 | 24 | On protocols that support it, Gun will send a PING 25 | frame and wait for its corresponding acknowledgement. 26 | When it receives that acknowledgement the user process 27 | gets notified via a `gun_notify` message. 28 | 29 | As this happens asynchronously and multiple pings 30 | can be sent concurrently, Gun returns a unique 31 | reference to the caller that can be used to identify 32 | the related acknowledgement. 33 | 34 | The following protocols implement PING mechanisms 35 | and are supported by this function: HTTP/2. 36 | 37 | The following protocols implement PING mechanisms 38 | but are not yet supported by this function: HTTP/3 39 | and Websocket. Note that in the case of Websocket, 40 | the user can set `silence_pings` to `false` and 41 | send and receive PING frames. 42 | 43 | The following protocols do not implement PING 44 | mechanisms: HTTP/1.1, raw and SOCKS5. 45 | 46 | == Arguments 47 | 48 | ConnPid:: 49 | 50 | The pid of the Gun connection process. 51 | 52 | ReqOpts:: 53 | 54 | Request options. Only the `reply_to` and `tunnel` options 55 | are relevant. 56 | 57 | == Return value 58 | 59 | A reference that identifies the ping is returned. This 60 | reference is included in the notification received when 61 | a ping ack is received from the server. 62 | 63 | == Changelog 64 | 65 | * *2.2*: Function introduced. 66 | 67 | == Examples 68 | 69 | .Send a ping and receive an ack 70 | [source,erlang] 71 | ---- 72 | PingRef = gun:ping(ConnPid). 73 | {notify, ping_ack, PingRef} = gun:await(ConnPid, undefined). 74 | ---- 75 | 76 | == See also 77 | 78 | link:man:gun(3)[gun(3)], 79 | link:man:gun:await(3)[gun:await(3)], 80 | link:man:gun_notify(3)[gun_notify(3)] 81 | -------------------------------------------------------------------------------- /doc/src/manual/gun.post.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:post(3) 2 | 3 | == Name 4 | 5 | gun:post - Process the enclosed representation according to a resource's own semantics 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | post(ConnPid, Path, Headers) 12 | -> post(ConnPid, Path, Headers, #{}) 13 | 14 | post(ConnPid, Path, Headers, ReqOpts) 15 | -> StreamRef 16 | 17 | post(ConnPid, Path, Headers, Body) 18 | -> post(ConnPid, Path, Headers, Body, #{}) 19 | 20 | post(ConnPid, Path, Headers, Body, ReqOpts) 21 | -> StreamRef 22 | 23 | ConnPid :: pid() 24 | Path :: iodata() 25 | Headers :: gun:req_headers() 26 | Body :: iodata() 27 | ReqOpts :: gun:req_opts() 28 | StreamRef :: gun:stream_ref() 29 | ---- 30 | 31 | Process the enclosed representation according to a resource's 32 | own semantics. 33 | 34 | The behavior of this function varies depending on whether 35 | a body is provided. 36 | 37 | The function `post/3,4` does not send a body. It must be 38 | sent separately using link:man:gun:data(3)[gun:data(3)]. 39 | 40 | The function `post/4,5` sends the entire request, including 41 | the request body, immediately. It is therefore not possible 42 | to use link:man:gun:data(3)[gun:data(3)] after that. You 43 | should provide a content-type header. Gun will set the 44 | content-length header automatically. 45 | 46 | == Arguments 47 | 48 | ConnPid:: 49 | 50 | The pid of the Gun connection process. 51 | 52 | Path:: 53 | 54 | Path to the resource. 55 | 56 | Headers:: 57 | 58 | Additional request headers. 59 | 60 | Body:: 61 | 62 | Request body. 63 | 64 | ReqOpts:: 65 | 66 | Request options. 67 | 68 | == Return value 69 | 70 | A reference that identifies the newly created stream is 71 | returned. It is this reference that must be passed in 72 | subsequent calls and will be received in messages related 73 | to this new stream. 74 | 75 | == Changelog 76 | 77 | * *2.0*: Implicit body detection has been removed. The body 78 | must now be provided either directly (even if empty) 79 | or using separate calls. 80 | * *1.0*: Function introduced. 81 | 82 | == Examples 83 | 84 | .Post to a resource 85 | [source,erlang] 86 | ---- 87 | StreamRef = gun:post(ConnPid, "/search", 88 | [{<<"content-type">>, <<"application/x-www-form-urlencoded">>}], 89 | <<"q=nine%20nines">>). 90 | ---- 91 | 92 | .Post to a resource in multiple calls 93 | [source,erlang] 94 | ---- 95 | StreamRef = gun:post(ConnPid, "/search", [ 96 | {<<"content-type">>, <<"application/x-www-form-urlencoded">>} 97 | ]). 98 | gun:data(ConnPid, StreamRef, fin, <<"q=nine%20nines">>). 99 | ---- 100 | 101 | .Post to a resource with request options 102 | [source,erlang] 103 | ---- 104 | StreamRef = gun:post(ConnPid, "/search", 105 | [{<<"content-type">>, <<"application/x-www-form-urlencoded">>}], 106 | <<"q=nine%20nines">>, 107 | #{reply_to => ReplyToPid}). 108 | ---- 109 | 110 | == See also 111 | 112 | link:man:gun(3)[gun(3)], 113 | link:man:gun:patch(3)[gun:patch(3)], 114 | link:man:gun:put(3)[gun:put(3)], 115 | link:man:gun:await(3)[gun:await(3)], 116 | link:man:gun:await_body(3)[gun:await_body(3)], 117 | link:man:gun_push(3)[gun_push(3)], 118 | link:man:gun_inform(3)[gun_inform(3)], 119 | link:man:gun_response(3)[gun_response(3)], 120 | link:man:gun_data(3)[gun_data(3)] 121 | -------------------------------------------------------------------------------- /doc/src/manual/gun.put.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:put(3) 2 | 3 | == Name 4 | 5 | gun:put - Create or replace a resource 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | put(ConnPid, Path, Headers) 12 | -> put(ConnPid, Path, Headers, #{}) 13 | 14 | put(ConnPid, Path, Headers, ReqOpts) 15 | -> StreamRef 16 | 17 | put(ConnPid, Path, Headers, Body) 18 | -> put(ConnPid, Path, Headers, Body, #{}) 19 | 20 | put(ConnPid, Path, Headers, Body, ReqOpts) 21 | -> StreamRef 22 | 23 | ConnPid :: pid() 24 | Path :: iodata() 25 | Headers :: gun:req_headers() 26 | Body :: iodata() 27 | ReqOpts :: gun:req_opts() 28 | StreamRef :: gun:stream_ref() 29 | ---- 30 | 31 | Create or replace a resource. 32 | 33 | The behavior of this function varies depending on whether 34 | a body is provided. 35 | 36 | The function `put/3,4` does not send a body. It must be 37 | sent separately using link:man:gun:data(3)[gun:data(3)]. 38 | 39 | The function `put/4,5` sends the entire request, including 40 | the request body, immediately. It is therefore not possible 41 | to use link:man:gun:data(3)[gun:data(3)] after that. You 42 | should provide a content-type header. Gun will set the 43 | content-length header automatically. 44 | 45 | == Arguments 46 | 47 | ConnPid:: 48 | 49 | The pid of the Gun connection process. 50 | 51 | Path:: 52 | 53 | Path to the resource. 54 | 55 | Headers:: 56 | 57 | Additional request headers. 58 | 59 | Body:: 60 | 61 | Request body. 62 | 63 | ReqOpts:: 64 | 65 | Request options. 66 | 67 | == Return value 68 | 69 | A reference that identifies the newly created stream is 70 | returned. It is this reference that must be passed in 71 | subsequent calls and will be received in messages related 72 | to this new stream. 73 | 74 | == Changelog 75 | 76 | * *2.0*: Implicit body detection has been removed. The body 77 | must now be provided either directly (even if empty) 78 | or using separate calls. 79 | * *1.0*: Function introduced. 80 | 81 | == Examples 82 | 83 | .Put a resource 84 | [source,erlang] 85 | ---- 86 | StreamRef = gun:put(ConnPid, "/lang/fr_FR/hello", 87 | [{<<"content-type">>, <<"text/plain">>}], 88 | <<"Bonjour !">>). 89 | ---- 90 | 91 | .Put a resource in multiple calls 92 | [source,erlang] 93 | ---- 94 | StreamRef = gun:put(ConnPid, "/lang/fr_FR/hello", [ 95 | {<<"content-type">>, <<"text/plain">>} 96 | ]). 97 | gun:data(ConnPid, StreamRef, fin, <<"Bonjour !">>). 98 | ---- 99 | 100 | .Put a resource with request options 101 | [source,erlang] 102 | ---- 103 | StreamRef = gun:put(ConnPid, "/lang/fr_FR/hello", 104 | [{<<"content-type">>, <<"text/plain">>}], 105 | <<"Bonjour !">>, 106 | #{reply_to => ReplyToPid}). 107 | ---- 108 | 109 | == See also 110 | 111 | link:man:gun(3)[gun(3)], 112 | link:man:gun:patch(3)[gun:patch(3)], 113 | link:man:gun:post(3)[gun:post(3)], 114 | link:man:gun:await(3)[gun:await(3)], 115 | link:man:gun:await_body(3)[gun:await_body(3)], 116 | link:man:gun_push(3)[gun_push(3)], 117 | link:man:gun_inform(3)[gun_inform(3)], 118 | link:man:gun_response(3)[gun_response(3)], 119 | link:man:gun_data(3)[gun_data(3)] 120 | -------------------------------------------------------------------------------- /doc/src/manual/gun.request.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:request(3) 2 | 3 | == Name 4 | 5 | gun:request - Perform the given request 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | request(ConnPid, Method, Path, Headers, Body) 12 | -> request(ConnPid, Method, Path, Headers, Body, #{}) 13 | 14 | request(ConnPid, Method, Path, Headers, Body, ReqOpts) 15 | -> StreamRef 16 | 17 | ConnPid :: pid() 18 | Method :: binary() 19 | Path :: iodata() 20 | Headers :: gun:req_headers() 21 | Body :: iodata() 22 | ReqOpts :: gun:req_opts() 23 | StreamRef :: gun:stream_ref() 24 | ---- 25 | 26 | Perform the given request. 27 | 28 | This is a general purpose function that should only be 29 | used when other method-specific functions do not apply. 30 | 31 | The function `request/5,6` sends the entire request, including 32 | the request body, immediately. It is therefore not possible 33 | to use link:man:gun:data(3)[gun:data(3)] after that. You 34 | should provide a content-type header. Gun will set the 35 | content-length header automatically. 36 | 37 | == Arguments 38 | 39 | ConnPid:: 40 | 41 | The pid of the Gun connection process. 42 | 43 | Method:: 44 | 45 | Method to be used for the request. 46 | 47 | Path:: 48 | 49 | Path to the resource. 50 | 51 | Headers:: 52 | 53 | Additional request headers. 54 | 55 | Body:: 56 | 57 | Request body. 58 | 59 | ReqOpts:: 60 | 61 | Request options. 62 | 63 | == Return value 64 | 65 | A reference that identifies the newly created stream is 66 | returned. It is this reference that must be passed in 67 | subsequent calls and will be received in messages related 68 | to this new stream. 69 | 70 | == Changelog 71 | 72 | * *2.0*: Implicit body detection has been removed. The body 73 | must now be provided either directly (even if empty) 74 | or using link:man:gun:headers(3)[gun:headers(3)]. 75 | * *1.0*: Function introduced. 76 | 77 | == Examples 78 | 79 | .Perform a request 80 | [source,erlang] 81 | ---- 82 | StreamRef = gun:request(ConnPid, <<"PUT">>, 83 | "/lang/fr_FR/hello", 84 | [{<<"content-type">>, <<"text/plain">>}], 85 | <<"Bonjour !">>). 86 | ---- 87 | 88 | == See also 89 | 90 | link:man:gun(3)[gun(3)], 91 | link:man:gun:headers(3)[gun:headers(3)], 92 | link:man:gun:await(3)[gun:await(3)], 93 | link:man:gun:await_body(3)[gun:await_body(3)], 94 | link:man:gun_push(3)[gun_push(3)], 95 | link:man:gun_inform(3)[gun_inform(3)], 96 | link:man:gun_response(3)[gun_response(3)], 97 | link:man:gun_data(3)[gun_data(3)] 98 | -------------------------------------------------------------------------------- /doc/src/manual/gun.set_owner.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:set_owner(3) 2 | 3 | == Name 4 | 5 | gun:set_owner - Set a new owner for the connection 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | set_owner(ConnPid, OwnerPid) -> ok 12 | 13 | ConnPid :: pid() 14 | OwnerPid :: pid() 15 | ---- 16 | 17 | Set a new owner for the connection. 18 | 19 | Only the current owner of the connection can set a new 20 | owner. 21 | 22 | Gun monitors the owner of the connection and automatically 23 | shuts down gracefully when the owner exits. 24 | 25 | == Arguments 26 | 27 | ConnPid:: 28 | 29 | The pid of the Gun connection process. 30 | 31 | OwnerPid:: 32 | 33 | The pid of the new owner for the connection. 34 | 35 | == Return value 36 | 37 | The atom `ok` is returned. 38 | 39 | == Changelog 40 | 41 | * *2.0*: Function introduced. 42 | 43 | == Examples 44 | 45 | .Set a new owner for the connection 46 | [source,erlang] 47 | ---- 48 | ok = gun:set_owner(ConnPid, OwnerPid). 49 | ---- 50 | 51 | == See also 52 | 53 | link:man:gun(3)[gun(3)], 54 | link:man:gun:open(3)[gun:open(3)], 55 | link:man:gun:open_unix(3)[gun:open_unix(3)], 56 | link:man:gun:shutdown(3)[gun:shutdown(3)], 57 | link:man:gun:close(3)[gun:close(3)] 58 | -------------------------------------------------------------------------------- /doc/src/manual/gun.shutdown.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:shutdown(3) 2 | 3 | == Name 4 | 5 | gun:shutdown - Gracefully close the connection 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | shutdown(ConnPid) -> ok 12 | 13 | ConnPid :: pid() 14 | ---- 15 | 16 | Gracefully close the connection. 17 | 18 | Gun will wait for up to `closing_timeout` milliseconds 19 | before brutally closing the connection. The graceful 20 | shutdown mechanism varies between the different protocols: 21 | 22 | * For HTTP/1.1 there is no such mechanism and Gun will 23 | close the connection once the current response is 24 | received. Any pipelined requests are immediately 25 | terminated. 26 | 27 | * For HTTP/2 Gun will send a GOAWAY frame and wait for 28 | the existing streams to terminate. 29 | 30 | * For Websocket Gun will send a close frame and wait 31 | for the server's close frame before closing the 32 | connection. 33 | 34 | The function returns immediately. The connection may 35 | therefore still be up for some time after this call. 36 | 37 | Gun will not attempt to reconnect once graceful 38 | shutdown has been initiated. 39 | 40 | == Arguments 41 | 42 | ConnPid:: 43 | 44 | The pid of the Gun connection process. 45 | 46 | == Return value 47 | 48 | The atom `ok` is returned. 49 | 50 | == Changelog 51 | 52 | * *2.0*: Function introduced. 53 | 54 | == Examples 55 | 56 | .Gracefully shutdown the connection 57 | [source,erlang] 58 | ---- 59 | ok = gun:shutdown(ConnPid). 60 | ---- 61 | 62 | == See also 63 | 64 | link:man:gun(3)[gun(3)], 65 | link:man:gun:open(3)[gun:open(3)], 66 | link:man:gun:open_unix(3)[gun:open_unix(3)], 67 | link:man:gun:close(3)[gun:close(3)] 68 | -------------------------------------------------------------------------------- /doc/src/manual/gun.stream_info.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:stream_info(3) 2 | 3 | == Name 4 | 5 | gun:stream_info - Obtain information about a stream 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | stream_info(ConnPid, StreamRef) -> {ok, undefined | Info} | {error, not_connected} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Info :: #{ 16 | ref => gun:stream_ref(), 17 | reply_to => pid(), 18 | state => running | stopping, 19 | intermediaries => [Intermediary], 20 | tunnel => Tunnel 21 | } 22 | Intermediary :: #{ 23 | type => connect | socks5, 24 | host => inet:hostname() | inet:ip_address(), 25 | port => inet:port_number(), 26 | transport => tcp | tls, 27 | protocol => http | http2 | socks | raw 28 | } 29 | Tunnel :: #{ 30 | transport => tcp | tls, 31 | protocol => http | http2 | socks | raw, 32 | origin_scheme => binary() | undefined, 33 | origin_host => inet:hostname() | inet:ip_address(), 34 | origin_port => inet:port_number() 35 | } 36 | ---- 37 | 38 | Obtain information about a stream. 39 | 40 | == Arguments 41 | 42 | ConnPid:: 43 | 44 | The pid of the Gun connection process. 45 | 46 | StreamRef:: 47 | 48 | Identifier of the stream for the original request. 49 | 50 | == Return value 51 | 52 | A map is returned containing various informations about 53 | the stream. 54 | 55 | == Changelog 56 | 57 | * *2.0*: Function introduced. 58 | 59 | == Examples 60 | 61 | .Obtain information about a stream 62 | [source,erlang] 63 | ---- 64 | Info = gun:stream_info(ConnPid, StreamRef). 65 | ---- 66 | 67 | == See also 68 | 69 | link:man:gun(3)[gun(3)], 70 | link:man:gun:get(3)[gun:get(3)], 71 | link:man:gun:head(3)[gun:head(3)], 72 | link:man:gun:options(3)[gun:options(3)], 73 | link:man:gun:patch(3)[gun:patch(3)], 74 | link:man:gun:post(3)[gun:post(3)], 75 | link:man:gun:put(3)[gun:put(3)], 76 | link:man:gun:delete(3)[gun:delete(3)], 77 | link:man:gun:headers(3)[gun:headers(3)], 78 | link:man:gun:request(3)[gun:request(3)], 79 | link:man:gun:cancel(3)[gun:cancel(3)] 80 | -------------------------------------------------------------------------------- /doc/src/manual/gun.update_flow.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:update_flow(3) 2 | 3 | == Name 4 | 5 | gun:update_flow - Update a stream's flow control value 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | update_flow(ConnPid, StreamRef, Flow) -> ok 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Flow :: pos_integer() 16 | ---- 17 | 18 | Update a stream's flow control value. 19 | 20 | The flow value can only ever be incremented. 21 | 22 | This function does nothing for streams that have flow 23 | control disabled (which is the default). 24 | 25 | == Arguments 26 | 27 | ConnPid:: 28 | 29 | The pid of the Gun connection process. 30 | 31 | StreamRef:: 32 | 33 | Identifier of the stream for the original request. 34 | 35 | Flow:: 36 | 37 | Flow control value increment. 38 | 39 | == Return value 40 | 41 | The atom `ok` is returned. 42 | 43 | == Changelog 44 | 45 | * *2.0*: Function introduced. 46 | 47 | == Examples 48 | 49 | .Update a stream's flow control value 50 | [source,erlang] 51 | ---- 52 | gun:update_flow(ConnPid, StreamRef, 10). 53 | ---- 54 | 55 | == See also 56 | 57 | link:man:gun(3)[gun(3)], 58 | link:man:gun:get(3)[gun:get(3)], 59 | link:man:gun:head(3)[gun:head(3)], 60 | link:man:gun:options(3)[gun:options(3)], 61 | link:man:gun:patch(3)[gun:patch(3)], 62 | link:man:gun:post(3)[gun:post(3)], 63 | link:man:gun:put(3)[gun:put(3)], 64 | link:man:gun:delete(3)[gun:delete(3)], 65 | link:man:gun:headers(3)[gun:headers(3)], 66 | link:man:gun:request(3)[gun:request(3)], 67 | link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)] 68 | -------------------------------------------------------------------------------- /doc/src/manual/gun.ws_send.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:ws_send(3) 2 | 3 | == Name 4 | 5 | gun:ws_send - Send Websocket frames 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | ws_send(ConnPid, StreamRef, Frames) -> ok 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Frames :: Frame | [Frame] 16 | Frame :: close | ping | pong 17 | | {text | binary | close | ping | pong, iodata()} 18 | | {close, non_neg_integer(), iodata()} 19 | ---- 20 | 21 | Send Websocket frames. 22 | 23 | The connection must first be upgraded to Websocket using 24 | the function link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)]. 25 | 26 | == Arguments 27 | 28 | ConnPid:: 29 | 30 | The pid of the Gun connection process. 31 | 32 | StreamRef:: 33 | 34 | Identifier of the stream that was upgraded to Websocket. 35 | 36 | Frames:: 37 | 38 | One or more Websocket frame(s). 39 | 40 | == Return value 41 | 42 | The atom `ok` is returned. 43 | 44 | == Changelog 45 | 46 | * *2.0*: The mandatory `StreamRef` argument was added. 47 | * *2.0*: It is now possible to send multiple frames at once. 48 | * *1.0*: Function introduced. 49 | 50 | == Examples 51 | 52 | .Send a single frame 53 | [source,erlang] 54 | ---- 55 | gun:ws_send(ConnPid, StreamRef, {text, <<"Hello world!">>}). 56 | ---- 57 | 58 | .Send many frames including a close frame 59 | [source,erlang] 60 | ---- 61 | gun:ws_send(ConnPid, StreamRef, [ 62 | {text, <<"See you later, world!">>}, 63 | close 64 | ]). 65 | ---- 66 | 67 | == See also 68 | 69 | link:man:gun(3)[gun(3)], 70 | link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)], 71 | link:man:gun_upgrade(3)[gun_upgrade(3)], 72 | link:man:gun_ws(3)[gun_ws(3)] 73 | -------------------------------------------------------------------------------- /doc/src/manual/gun.ws_upgrade.asciidoc: -------------------------------------------------------------------------------- 1 | = gun:ws_upgrade(3) 2 | 3 | == Name 4 | 5 | gun:ws_upgrade - Upgrade to Websocket 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | ws_upgrade(ConnPid, Path) 12 | -> ws_upgrade(ConnPid, Path, []) 13 | 14 | ws_upgrade(ConnPid, Path, Headers) 15 | -> StreamRef 16 | 17 | ws_upgrade(ConnPid, Path, Headers, WsOpts) 18 | -> StreamRef 19 | 20 | ConnPid :: pid() 21 | Path :: iodata() 22 | Headers :: gun:req_headers() 23 | WsOpts :: gun:ws_opts() 24 | StreamRef :: gun:stream_ref() 25 | ---- 26 | 27 | Upgrade to Websocket. 28 | 29 | The behavior of this function depends on the protocol 30 | selected. 31 | 32 | HTTP/1.1 cannot handle Websocket and HTTP requests 33 | concurrently. The upgrade, if successful, will result 34 | in the complete takeover of the connection. Any 35 | subsequent HTTP requests will be rejected. 36 | 37 | Gun does not currently support Websocket over HTTP/2. 38 | 39 | By default Gun will take the Websocket options from 40 | the connection's `ws_opts`. 41 | 42 | Websocket subprotocol negotiation is enabled when 43 | the `protocols` option is given. It takes a subprotocol 44 | name and a module implementing the 45 | link:man:gun_ws_protocol(3)[gun_ws_protocol(3)] behavior. 46 | 47 | == Arguments 48 | 49 | ConnPid:: 50 | 51 | The pid of the Gun connection process. 52 | 53 | Path:: 54 | 55 | Path to the resource. 56 | 57 | Headers:: 58 | 59 | Additional request headers. 60 | 61 | WsOpts:: 62 | 63 | Configuration for the Websocket protocol. 64 | 65 | == Return value 66 | 67 | A reference that identifies the newly created stream is 68 | returned. It is this reference that must be passed in 69 | subsequent calls and will be received in messages related 70 | to this new stream. 71 | 72 | == Changelog 73 | 74 | * *1.0*: Function introduced. 75 | 76 | == Examples 77 | 78 | .Upgrade to Websocket 79 | [source,erlang] 80 | ---- 81 | StreamRef = gun:ws_upgrade(ConnPid, "/ws", [ 82 | {<<"sec-websocket-protocol">>, <<"chat">>} 83 | ]). 84 | receive 85 | {gun_upgrade, ConnPid, StreamRef, [<<"websocket">>], _} -> 86 | ok 87 | after 5000 -> 88 | error(timeout) 89 | end. 90 | ---- 91 | 92 | .Upgrade to Websocket with different options 93 | [source,erlang] 94 | ---- 95 | StreamRef = gun:ws_upgrade(ConnPid, "/ws", [], #{ 96 | compress => false 97 | }). 98 | ---- 99 | 100 | .Upgrade to Websocket with protocol negotiation 101 | [source,erlang] 102 | ---- 103 | StreamRef = gun:ws_upgrade(ConnPid, "/ws", [], #{ 104 | protocols => [ 105 | {<<"mqtt">>, gun_ws_mqtt_h}, 106 | {<<"v12.stomp">>, gun_ws_stomp_h} 107 | ] 108 | }). 109 | ---- 110 | 111 | == See also 112 | 113 | link:man:gun(3)[gun(3)], 114 | link:man:gun:ws_send(3)[gun:ws_send(3)], 115 | link:man:gun_upgrade(3)[gun_upgrade(3)], 116 | link:man:gun_ws(3)[gun_ws(3)], 117 | link:man:gun_ws_protocol(3)[gun_ws_protocol(3)] 118 | -------------------------------------------------------------------------------- /doc/src/manual/gun_app.asciidoc: -------------------------------------------------------------------------------- 1 | = gun(7) 2 | 3 | == Name 4 | 5 | gun - HTTP/1.1, HTTP/2 and Websocket client for Erlang/OTP 6 | 7 | == Description 8 | 9 | Gun is an HTTP client for Erlang/OTP with support for the 10 | HTTP/1.1, HTTP/2 and Websocket protocols. 11 | 12 | Gun aims to provide an easy to use, asynchronous and 13 | always-connected client. It maintains a permanent connection 14 | to the server and reconnects automatically when necessary. 15 | 16 | == Modules 17 | 18 | * link:man:gun(3)[gun(3)] - Asynchronous HTTP client 19 | * link:man:gun_cookies(3)[gun_cookies(3)] - Cookie store engine 20 | * link:man:gun_cookies_list(3)[gun_cookies_list(3)] - Cookie store backend: in-memory, per connection 21 | * link:man:gun_event(3)[gun_event(3)] - Events 22 | * link:man:gun_ws_protocol(3)[gun_ws_protocol(3)] - Websocket subprotocols 23 | 24 | == Dependencies 25 | 26 | * link:man:cowlib(7)[cowlib(7)] - Support library for manipulating Web protocols 27 | * ssl - Secure communication over sockets 28 | 29 | All these applications must be started before the `gun` 30 | application. To start Gun and all dependencies at once: 31 | 32 | [source,erlang] 33 | ---- 34 | {ok, _} = application:ensure_all_started(gun). 35 | ---- 36 | 37 | == Environment 38 | 39 | The `gun` application does not define any application 40 | environment configuration parameters. 41 | 42 | == See also 43 | 44 | link:man:cowlib(7)[cowlib(7)] 45 | -------------------------------------------------------------------------------- /doc/src/manual/gun_cookies.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_cookies(3) 2 | 3 | == Name 4 | 5 | gun_cookies - Cookie store engine 6 | 7 | == Description 8 | 9 | The `gun_cookies` module implements a cookie store engine. 10 | It will be used by Gun when a cookie store is configured. 11 | It also defines the interface and provides functions used 12 | to implement cookie store backends. 13 | 14 | == Callbacks 15 | 16 | Cookie store backends implement the following interface. 17 | Functions are organized by theme: initialization, querying, 18 | storing and garbage collecting: 19 | 20 | === init 21 | 22 | [source,erlang] 23 | ---- 24 | init(Opts :: any()) -> gun_cookies:store() 25 | ---- 26 | 27 | Initialize the cookie store. 28 | 29 | === query 30 | 31 | [source,erlang] 32 | ---- 33 | query(State, URI) -> {ok, [Cookie], State} 34 | 35 | URI :: uri_string:uri_map() 36 | Cookie :: gun_cookies:cookie() 37 | State :: any() 38 | ---- 39 | 40 | Query the store for the cookies for the given URI. 41 | 42 | === set_cookie_secure_match 43 | 44 | [source,erlang] 45 | ---- 46 | set_cookie_secure_match(State, Match) -> match | nomatch 47 | 48 | State :: any() 49 | Match :: #{ 50 | name := binary(), 51 | % secure_only := true, 52 | domain := binary(), 53 | path := binary() 54 | } 55 | ---- 56 | 57 | Perform a secure match against cookies already in the store. 58 | This is part of the heuristics that the cookie store engine 59 | applies to decide whether the cookie must be stored. 60 | 61 | The `secure_only` attribute is implied, it is not actually 62 | passed in the argument. 63 | 64 | === set_cookie_get_exact_match 65 | 66 | [source,erlang] 67 | ---- 68 | set_cookie_get_exact_match(State, Match) 69 | -> {ok, gun_cookies:cookie(), State} | error 70 | 71 | State :: any() 72 | Match :: #{ 73 | name := binary(), 74 | domain := binary(), 75 | host_only := boolean(), 76 | path := binary() 77 | } 78 | ---- 79 | 80 | Perform an exact match against cookies already in the store. 81 | This is part of the heuristics that the cookie store engine 82 | applies to decide whether the cookie must be stored. 83 | 84 | When a cookie is found, it must be returned so that it gets 85 | updated. When nothing is found a new cookie will be stored. 86 | 87 | === store 88 | 89 | [source,erlang] 90 | ---- 91 | store(State, gun_cookies:cookie()) 92 | -> {ok, State} | {error, any()} 93 | 94 | State :: any() 95 | ---- 96 | 97 | Unconditionally store the cookie into the cookie store. 98 | 99 | === gc 100 | 101 | [source,erlang] 102 | ---- 103 | gc(State) -> {ok, State} 104 | 105 | State :: any() 106 | ---- 107 | 108 | Remove all cookies from the cookie store that are expired. 109 | 110 | Other cookies may be removed as well, at the discretion 111 | of the cookie store. For example excess cookies may be 112 | removed to reduce the memory footprint. 113 | 114 | === session_gc 115 | 116 | [source,erlang] 117 | ---- 118 | session_gc(State) -> {ok, State} 119 | 120 | State :: any() 121 | ---- 122 | 123 | Remove all cookies from the cookie store that have the 124 | `persistent` flag set to `false`. 125 | 126 | == Exports 127 | 128 | * link:man:gun_cookies:domain_match(3)[gun_cookies:domain_match(3)] - Cookie domain match 129 | * link:man:gun_cookies:path_match(3)[gun_cookies:path_match(3)] - Cookie path match 130 | 131 | == Types 132 | 133 | === cookie() 134 | 135 | [source,erlang] 136 | ---- 137 | cookie() :: #{ 138 | name := binary(), 139 | value := binary(), 140 | domain := binary(), 141 | path := binary(), 142 | creation_time := calendar:datetime(), 143 | last_access_time := calendar:datetime(), 144 | expiry_time := calendar:datetime() | infinity, 145 | persistent := boolean(), 146 | host_only := boolean(), 147 | secure_only := boolean(), 148 | http_only := boolean(), 149 | same_site := strict | lax | none 150 | } 151 | ---- 152 | 153 | A cookie. 154 | 155 | This contains the cookie name, value, attributes and flags. 156 | This is the representation that the cookie store engine 157 | and Gun expects. Cookies do not have to be kept in this 158 | format in the cookie store backend. 159 | 160 | === store() 161 | 162 | [source,erlang] 163 | ---- 164 | store() :: {module(), StoreState :: any()} 165 | ---- 166 | 167 | The cookie store. 168 | 169 | This is a tuple containing the cookie store backend module 170 | and its current state. 171 | 172 | == Changelog 173 | 174 | * *2.0*: Module introduced. 175 | 176 | == See also 177 | 178 | link:man:gun(7)[gun(7)], 179 | link:man:gun_cookies_list(3)[gun_cookies_list(3)] 180 | -------------------------------------------------------------------------------- /doc/src/manual/gun_cookies.domain_match.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_cookies:domain_match(3) 2 | 3 | == Name 4 | 5 | gun_cookies:domain_match - Cookie domain match 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | domain_match(String, DomainString) -> boolean() 12 | 13 | String :: binary() 14 | DomainString :: binary() 15 | ---- 16 | 17 | Cookie domain match. 18 | 19 | This function can be used when implementing the 20 | `set_cookie_secure_match` callback of a cookie store. 21 | 22 | == Arguments 23 | 24 | String:: 25 | 26 | The string to match. 27 | 28 | DomainString:: 29 | 30 | The domain string that will be matched against. 31 | 32 | == Return value 33 | 34 | Returns `true` when `String` domain-matches `DomainString`, 35 | and `false` otherwise. 36 | 37 | == Changelog 38 | 39 | * *2.0*: Function introduced. 40 | 41 | == Examples 42 | 43 | .Perform a domain match 44 | [source,erlang] 45 | ---- 46 | Match = gun_cookies:domain_match(Domain, CookieDomain). 47 | ---- 48 | 49 | == See also 50 | 51 | link:man:gun_cookies(3)[gun_cookies(3)], 52 | link:man:gun_cookies:path_match(3)[gun_cookies:path_match(3)] 53 | -------------------------------------------------------------------------------- /doc/src/manual/gun_cookies.path_match.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_cookies:path_match(3) 2 | 3 | == Name 4 | 5 | gun_cookies:path_match - Cookie path match 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | path_match(ReqPath, CookiePath) -> boolean() 12 | 13 | ReqPath :: binary() 14 | CookiePath :: binary() 15 | ---- 16 | 17 | Cookie path match. 18 | 19 | This function can be used when implementing the 20 | `set_cookie_secure_match` callback of a cookie store. 21 | 22 | == Arguments 23 | 24 | ReqPath:: 25 | 26 | The request path to match. 27 | 28 | CookiePath:: 29 | 30 | The cookie path that will be matched against. 31 | 32 | == Return value 33 | 34 | Returns `true` when `ReqPath` path-matches `CookiePath`, 35 | and `false` otherwise. 36 | 37 | == Changelog 38 | 39 | * *2.0*: Function introduced. 40 | 41 | == Examples 42 | 43 | .Perform a path match 44 | [source,erlang] 45 | ---- 46 | Match = gun_cookies:path_match(ReqPath, CookiePath). 47 | ---- 48 | 49 | == See also 50 | 51 | link:man:gun_cookies(3)[gun_cookies(3)], 52 | link:man:gun_cookies:domain_match(3)[gun_cookies:domain_match(3)] 53 | -------------------------------------------------------------------------------- /doc/src/manual/gun_cookies_list.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_cookies_list(3) 2 | 3 | == Name 4 | 5 | gun_cookies_list - Cookie store backend: in-memory, per connection 6 | 7 | == Description 8 | 9 | The `gun_cookies_list` module implements a cookie store 10 | backend that keeps all the cookie data in-memory and tied 11 | to a specific connection. 12 | 13 | It is possible to implement a custom backend on top of 14 | `gun_cookies_list` in order to add persistence or sharing 15 | properties. 16 | 17 | == Exports 18 | 19 | This module implements the callbacks defined in 20 | link:man:gun_cookies(3)[gun_cookies(3)]. 21 | 22 | == Types 23 | 24 | === opts() 25 | 26 | [source,erlang] 27 | ---- 28 | opts() :: #{ 29 | } 30 | ---- 31 | 32 | Cookie store backend options. 33 | 34 | There are currently no options available for this backend. 35 | 36 | // The default value is given next to the option name: 37 | 38 | == Changelog 39 | 40 | * *2.0*: Module introduced. 41 | 42 | == Examples 43 | 44 | .Open a connection with a cookie store configured 45 | [source,erlang] 46 | ---- 47 | {ok, ConnPid} = gun:open(Host, Port, #{ 48 | cookie_store => gun_cookies_list:init(#{}) 49 | }) 50 | ---- 51 | 52 | == See also 53 | 54 | link:man:gun(7)[gun(7)], 55 | link:man:gun_cookies(3)[gun_cookies(3)] 56 | -------------------------------------------------------------------------------- /doc/src/manual/gun_data.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_data(3) 2 | 3 | == Name 4 | 5 | gun_data - Response body 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_data, ConnPid, StreamRef, IsFin, Data} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | IsFin :: fin | nofin 16 | Data :: binary() 17 | ---- 18 | 19 | Response body. 20 | 21 | This message informs the relevant process that the server 22 | sent a all or part of the body for the response to the 23 | original request. 24 | 25 | A data message is always preceded by a response message. 26 | 27 | The response body may be terminated either by a data 28 | message with the flag `fin` set or by a 29 | link:man:gun_trailers(3)[gun_trailers(3)] message. 30 | 31 | == Elements 32 | 33 | ConnPid:: 34 | 35 | The pid of the Gun connection process. 36 | 37 | StreamRef:: 38 | 39 | Identifier of the stream for the original request. 40 | 41 | IsFin:: 42 | 43 | Whether this message terminates the response. 44 | 45 | Data:: 46 | 47 | All or part of the response body. 48 | 49 | == Changelog 50 | 51 | * *1.0*: Message introduced. 52 | 53 | == Examples 54 | 55 | .Receive a gun_data message in a gen_server 56 | [source,erlang] 57 | ---- 58 | handle_info({gun_data, ConnPid, _StreamRef, 59 | _IsFin, _Data}, 60 | State=#state{conn_pid=ConnPid}) -> 61 | %% Do something. 62 | {noreply, State}. 63 | ---- 64 | 65 | == See also 66 | 67 | link:man:gun(3)[gun(3)], 68 | link:man:gun:get(3)[gun:get(3)], 69 | link:man:gun:head(3)[gun:head(3)], 70 | link:man:gun:patch(3)[gun:patch(3)], 71 | link:man:gun:post(3)[gun:post(3)], 72 | link:man:gun:put(3)[gun:put(3)], 73 | link:man:gun:delete(3)[gun:delete(3)], 74 | link:man:gun:options(3)[gun:options(3)], 75 | link:man:gun:headers(3)[gun:headers(3)], 76 | link:man:gun:request(3)[gun:request(3)], 77 | link:man:gun_response(3)[gun_response(3)], 78 | link:man:gun_trailers(3)[gun_trailers(3)] 79 | -------------------------------------------------------------------------------- /doc/src/manual/gun_down.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_down(3) 2 | 3 | == Name 4 | 5 | gun_down - The connection is down 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_down, ConnPid, Protocol, Reason, KilledStreams} 12 | 13 | ConnPid :: pid() 14 | Protocol :: http | http2 | socks | ws 15 | Reason :: any() 16 | KilledStreams :: [gun:stream_ref()] 17 | ---- 18 | 19 | The connection is down. 20 | 21 | This message informs the owner process that the connection 22 | was lost. Depending on the `retry` and `retry_timeout` 23 | options Gun may automatically attempt to reconnect. 24 | 25 | When the connection goes back up, Gun will not attempt to retry 26 | requests. It will also not upgrade to Websocket automatically 27 | if that was the protocol in use when the connection was lost. 28 | 29 | == Elements 30 | 31 | ConnPid:: 32 | 33 | The pid of the Gun connection process. 34 | 35 | Protocol:: 36 | 37 | The protocol that was selected for this connection 38 | or upgraded to during the course of the connection. 39 | 40 | Reason:: 41 | 42 | The reason for the loss of the connection. 43 | + 44 | It is present for debugging purposes only. You should not 45 | rely on this value to perform operations programmatically. 46 | 47 | KilledStreams:: 48 | 49 | List of streams that have been brutally terminated. 50 | + 51 | They are active streams that did not complete before the closing 52 | of the connection. Whether they can be retried safely depends 53 | on the protocol used and the idempotence property of the requests. 54 | 55 | == Changelog 56 | 57 | * *2.0*: The last element of the message's tuple, `UnprocessedStreams` 58 | has been removed. 59 | * *1.0*: Message introduced. 60 | 61 | == Examples 62 | 63 | .Receive a gun_down message in a gen_server 64 | [source,erlang] 65 | ---- 66 | handle_info({gun_down, ConnPid, _Protocol, _Reason, _Killed}, 67 | State=#state{conn_pid=ConnPid}) -> 68 | %% Do something. 69 | {noreply, State}. 70 | ---- 71 | 72 | == See also 73 | 74 | link:man:gun(3)[gun(3)], 75 | link:man:gun:open(3)[gun:open(3)], 76 | link:man:gun:open_unix(3)[gun:open_unix(3)], 77 | link:man:gun_up(3)[gun_up(3)], 78 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)], 79 | link:man:gun_error(3)[gun_error(3)] 80 | -------------------------------------------------------------------------------- /doc/src/manual/gun_error.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_error(3) 2 | 3 | == Name 4 | 5 | gun_error - Stream or connection-wide error 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_error, ConnPid, StreamRef, Reason} 12 | {gun_error, ConnPid, Reason} 13 | 14 | ConnPid :: pid() 15 | StreamRef :: gun:stream_ref() 16 | Reason :: any() 17 | ---- 18 | 19 | Stream or connection-wide error. 20 | 21 | These messages inform the relevant process that an error 22 | occurred. A reference is given when the error pertains 23 | to a specific stream. Connection-wide errors do not 24 | imply that the connection is no longer usable, they are 25 | used for all errors that are not specific to a stream. 26 | 27 | == Elements 28 | 29 | ConnPid:: 30 | 31 | The pid of the Gun connection process. 32 | 33 | StreamRef:: 34 | 35 | Identifier of the stream that resulted in an error. 36 | 37 | Reason:: 38 | 39 | The reason for the error. 40 | + 41 | It is present for debugging purposes only. You should not 42 | rely on this value to perform operations programmatically. 43 | 44 | == Changelog 45 | 46 | * *1.0*: Message introduced. 47 | 48 | == Examples 49 | 50 | .Receive a gun_error message in a gen_server 51 | [source,erlang] 52 | ---- 53 | handle_info({gun_error, ConnPid, _Reason}, 54 | State=#state{conn_pid=ConnPid}) -> 55 | %% Do something. 56 | {noreply, State}; 57 | handle_info({gun_error, ConnPid, _StreamRef, _Reason}, 58 | State=#state{conn_pid=ConnPid}) -> 59 | %% Do something. 60 | {noreply, State}. 61 | ---- 62 | 63 | == See also 64 | 65 | link:man:gun(3)[gun(3)], 66 | link:man:gun_up(3)[gun_up(3)], 67 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)], 68 | link:man:gun_down(3)[gun_down(3)] 69 | -------------------------------------------------------------------------------- /doc/src/manual/gun_inform.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_inform(3) 2 | 3 | == Name 4 | 5 | gun_inform - Informational response 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_inform, ConnPid, StreamRef, Status, Headers} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Status :: 100..199 16 | Headers :: [{binary(), binary()}] 17 | ---- 18 | 19 | Informational response. 20 | 21 | This message informs the relevant process that the server 22 | sent an informational response to the original request. 23 | 24 | Informational responses are only intermediate responses 25 | and provide no guarantees as to what the final response 26 | will be. An informational response always precedes the 27 | response to the original request. 28 | 29 | == Elements 30 | 31 | ConnPid:: 32 | 33 | The pid of the Gun connection process. 34 | 35 | StreamRef:: 36 | 37 | Identifier of the stream for the original request. 38 | 39 | Status:: 40 | 41 | Status code for the informational response. 42 | 43 | Headers:: 44 | 45 | Headers sent with the informational response. 46 | 47 | == Changelog 48 | 49 | * *1.0*: Message introduced. 50 | 51 | == Examples 52 | 53 | .Receive a gun_inform message in a gen_server 54 | [source,erlang] 55 | ---- 56 | handle_info({gun_inform, ConnPid, _StreamRef, 57 | _Status, _Headers}, 58 | State=#state{conn_pid=ConnPid}) -> 59 | %% Do something. 60 | {noreply, State}. 61 | ---- 62 | 63 | == See also 64 | 65 | link:man:gun(3)[gun(3)], 66 | link:man:gun:get(3)[gun:get(3)], 67 | link:man:gun:patch(3)[gun:patch(3)], 68 | link:man:gun:post(3)[gun:post(3)], 69 | link:man:gun:put(3)[gun:put(3)], 70 | link:man:gun_response(3)[gun_response(3)] 71 | -------------------------------------------------------------------------------- /doc/src/manual/gun_notify.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_notify(3) 2 | 3 | == Name 4 | 5 | gun_notify - Optional event notification 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_notify, ConnPid, settings_changed, Settings} 12 | 13 | ConnPid :: pid() 14 | Settings :: map() 15 | ---- 16 | 17 | Optional event notification. 18 | 19 | Only two event notifications currently exist: 20 | 21 | * `settings_changed` informs the user that the server has 22 | modified its connection settings. 23 | 24 | * `ping_ack` informs the user that acknowledgement for a 25 | user ping was received. 26 | 27 | == Elements 28 | 29 | ConnPid:: 30 | 31 | The pid of the Gun connection process. 32 | 33 | Event:: 34 | 35 | Identifier for the event. Currently can only be 36 | `settings_changed` or `ping_ack`. 37 | 38 | Data:: 39 | 40 | Data for the event. Currently can only be the 41 | new connection settings, or the ping reference. 42 | 43 | == Changelog 44 | 45 | * *2.2*: Message introduced. 46 | 47 | == Examples 48 | 49 | .Receive a gun_notify message in a gen_server 50 | [source,erlang] 51 | ---- 52 | handle_info({gun_notify, ConnPid, settings_changed, Settings}, 53 | State=#state{conn_pid=ConnPid}) -> 54 | %% Do something. 55 | {noreply, State}. 56 | ---- 57 | 58 | == See also 59 | 60 | link:man:gun(3)[gun(3)], 61 | link:man:gun:ping(3)[gun:ping(3)] 62 | -------------------------------------------------------------------------------- /doc/src/manual/gun_push.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_push(3) 2 | 3 | == Name 4 | 5 | gun_push - Server-initiated push 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_push, ConnPid, StreamRef, NewStreamRef, Method, URI, Headers} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | NewStreamRef :: gun:stream_ref() 16 | Method :: binary() 17 | URI :: binary() 18 | Headers :: [{binary(), binary()}] 19 | ---- 20 | 21 | Server-initiated push. 22 | 23 | This message informs the relevant process that the server 24 | is pushing a resource related to the effective target URI 25 | of the original request. 26 | 27 | A server-initiated push message always precedes the response 28 | to the original request. 29 | 30 | This message will not be sent when using the HTTP/1.1 protocol 31 | because it lacks the concept of server-initiated push. 32 | 33 | == Elements 34 | 35 | ConnPid:: 36 | 37 | The pid of the Gun connection process. 38 | 39 | StreamRef:: 40 | 41 | Identifier of the stream for the original request. 42 | 43 | NewStreamRef:: 44 | 45 | Identifier of the stream being pushed. 46 | 47 | Method:: 48 | 49 | Method of the equivalent HTTP request. 50 | 51 | URI:: 52 | 53 | URI of the resource being pushed. 54 | 55 | Headers:: 56 | 57 | Headers of the equivalent HTTP request. 58 | 59 | == Changelog 60 | 61 | * *1.0*: Message introduced. 62 | 63 | == Examples 64 | 65 | .Receive a gun_push message in a gen_server 66 | [source,erlang] 67 | ---- 68 | handle_info({gun_push, ConnPid, _StreamRef, 69 | _NewStreamRef, _Method, _URI, _Headers}, 70 | State=#state{conn_pid=ConnPid}) -> 71 | %% Do something. 72 | {noreply, State}. 73 | ---- 74 | 75 | .Cancel an unwanted push 76 | [source,erlang] 77 | ---- 78 | handle_info({gun_push, ConnPid, _StreamRef, 79 | NewStreamRef, _Method, _URI, _Headers}, 80 | State=#state{conn_pid=ConnPid}) -> 81 | gun:cancel(ConnPid, NewStreamRef), 82 | {noreply, State}. 83 | ---- 84 | 85 | == See also 86 | 87 | link:man:gun(3)[gun(3)], 88 | link:man:gun:get(3)[gun:get(3)], 89 | link:man:gun:cancel(3)[gun:cancel(3)], 90 | link:man:gun_response(3)[gun_response(3)] 91 | -------------------------------------------------------------------------------- /doc/src/manual/gun_response.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_response(3) 2 | 3 | == Name 4 | 5 | gun_response - Response 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_response, ConnPid, StreamRef, IsFin, Status, Headers} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | IsFin :: fin | nofin 16 | Status :: non_neg_integer() 17 | Headers :: [{binary(), binary()}] 18 | ---- 19 | 20 | Response. 21 | 22 | This message informs the relevant process that the server 23 | sent a response to the original request. 24 | 25 | == Elements 26 | 27 | ConnPid:: 28 | 29 | The pid of the Gun connection process. 30 | 31 | StreamRef:: 32 | 33 | Identifier of the stream for the original request. 34 | 35 | IsFin:: 36 | 37 | Whether this message terminates the response. 38 | 39 | Status:: 40 | 41 | Status code for the response. 42 | 43 | Headers:: 44 | 45 | Headers sent with the response. 46 | 47 | == Changelog 48 | 49 | * *1.0*: Message introduced. 50 | 51 | == Examples 52 | 53 | .Receive a gun_response message in a gen_server 54 | [source,erlang] 55 | ---- 56 | handle_info({gun_response, ConnPid, _StreamRef, 57 | _IsFin, _Status, _Headers}, 58 | State=#state{conn_pid=ConnPid}) -> 59 | %% Do something. 60 | {noreply, State}. 61 | ---- 62 | 63 | == See also 64 | 65 | link:man:gun(3)[gun(3)], 66 | link:man:gun:get(3)[gun:get(3)], 67 | link:man:gun:head(3)[gun:head(3)], 68 | link:man:gun:patch(3)[gun:patch(3)], 69 | link:man:gun:post(3)[gun:post(3)], 70 | link:man:gun:put(3)[gun:put(3)], 71 | link:man:gun:delete(3)[gun:delete(3)], 72 | link:man:gun:options(3)[gun:options(3)], 73 | link:man:gun:headers(3)[gun:headers(3)], 74 | link:man:gun:request(3)[gun:request(3)], 75 | link:man:gun_inform(3)[gun_inform(3)], 76 | link:man:gun_push(3)[gun_push(3)] 77 | -------------------------------------------------------------------------------- /doc/src/manual/gun_trailers.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_trailers(3) 2 | 3 | == Name 4 | 5 | gun_trailers - Response trailers 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_trailers, ConnPid, StreamRef, Headers} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Headers :: [{binary(), binary()}] 16 | ---- 17 | 18 | Response trailers. 19 | 20 | This message informs the relevant process that the server 21 | sent response trailers for the response to the original 22 | request. 23 | 24 | A trailers message terminates the response. 25 | 26 | == Elements 27 | 28 | ConnPid:: 29 | 30 | The pid of the Gun connection process. 31 | 32 | StreamRef:: 33 | 34 | Identifier of the stream for the original request. 35 | 36 | Headers:: 37 | 38 | Trailing headers sent after the response body. 39 | 40 | == Changelog 41 | 42 | * *1.0*: Message introduced. 43 | 44 | == Examples 45 | 46 | .Receive a gun_trailers message in a gen_server 47 | [source,erlang] 48 | ---- 49 | handle_info({gun_trailers, ConnPid, _StreamRef, _Headers}, 50 | State=#state{conn_pid=ConnPid}) -> 51 | %% Do something. 52 | {noreply, State}. 53 | ---- 54 | 55 | == See also 56 | 57 | link:man:gun(3)[gun(3)], 58 | link:man:gun:get(3)[gun:get(3)], 59 | link:man:gun:head(3)[gun:head(3)], 60 | link:man:gun:patch(3)[gun:patch(3)], 61 | link:man:gun:post(3)[gun:post(3)], 62 | link:man:gun:put(3)[gun:put(3)], 63 | link:man:gun:delete(3)[gun:delete(3)], 64 | link:man:gun:options(3)[gun:options(3)], 65 | link:man:gun:headers(3)[gun:headers(3)], 66 | link:man:gun:request(3)[gun:request(3)], 67 | link:man:gun_response(3)[gun_response(3)], 68 | link:man:gun_data(3)[gun_data(3)] 69 | -------------------------------------------------------------------------------- /doc/src/manual/gun_tunnel_up.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_tunnel_up(3) 2 | 3 | == Name 4 | 5 | gun_tunnel_up - The tunnel is up 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_tunnel_up, ConnPid, StreamRef, Protocol} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() | undefined 15 | Protocol :: http | http2 | socks 16 | ---- 17 | 18 | The tunnel is up. 19 | 20 | This message informs the owner/calling process that the connection 21 | completed through the SOCKS or CONNECT proxy. 22 | 23 | If Gun is configured to connect to another SOCKS server, then the 24 | connection is not usable yet. One or more 25 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)] messages will follow. 26 | 27 | == Elements 28 | 29 | ConnPid:: 30 | 31 | The pid of the Gun connection process. 32 | 33 | StreamRef:: 34 | 35 | The stream reference the tunnel is running on, or `undefined` 36 | if there are no underlying stream. 37 | 38 | Protocol:: 39 | 40 | The protocol selected for this connection. It can be used 41 | to determine the capabilities of the server. 42 | 43 | == Changelog 44 | 45 | * *2.0*: Message introduced. 46 | 47 | == Examples 48 | 49 | .Receive a gun_tunnel_up message in a gen_server 50 | [source,erlang] 51 | ---- 52 | handle_info({gun_tunnel_up, ConnPid, _StreamRef, _Protocol}, 53 | State=#state{conn_pid=ConnPid}) -> 54 | %% Do something. 55 | {noreply, State}. 56 | ---- 57 | 58 | == See also 59 | 60 | link:man:gun(3)[gun(3)], 61 | link:man:gun:open(3)[gun:open(3)], 62 | link:man:gun:open_unix(3)[gun:open_unix(3)], 63 | link:man:gun:await_up(3)[gun:await_up(3)], 64 | link:man:gun_up(3)[gun_up(3)], 65 | link:man:gun_down(3)[gun_down(3)], 66 | link:man:gun_error(3)[gun_error(3)] 67 | -------------------------------------------------------------------------------- /doc/src/manual/gun_up.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_up(3) 2 | 3 | == Name 4 | 5 | gun_up - The connection is up 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_up, ConnPid, Protocol} 12 | 13 | ConnPid :: pid() 14 | Protocol :: http | http2 | raw | socks 15 | ---- 16 | 17 | The connection is up. 18 | 19 | This message informs the owner process that the connection or 20 | reconnection completed. 21 | 22 | If Gun is configured to connect to a Socks server, then the 23 | connection is not usable yet. One or more 24 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)] messages will follow. 25 | 26 | Otherwise, Gun will start processing the messages it received while 27 | waiting for the connection to be up. If this is a reconnection, 28 | then this may not be desirable for all requests. Those requests 29 | should be cancelled when the connection goes down, and any 30 | subsequent messages ignored. 31 | 32 | == Elements 33 | 34 | ConnPid:: 35 | 36 | The pid of the Gun connection process. 37 | 38 | Protocol:: 39 | 40 | The protocol selected for this connection. It can be used 41 | to determine the capabilities of the server. 42 | 43 | == Changelog 44 | 45 | * *1.0*: Message introduced. 46 | 47 | == Examples 48 | 49 | .Receive a gun_up message in a gen_server 50 | [source,erlang] 51 | ---- 52 | handle_info({gun_up, ConnPid, _Protocol}, 53 | State=#state{conn_pid=ConnPid}) -> 54 | %% Do something. 55 | {noreply, State}. 56 | ---- 57 | 58 | == See also 59 | 60 | link:man:gun(3)[gun(3)], 61 | link:man:gun:open(3)[gun:open(3)], 62 | link:man:gun:open_unix(3)[gun:open_unix(3)], 63 | link:man:gun:await_up(3)[gun:await_up(3)], 64 | link:man:gun_tunnel_up(3)[gun_tunnel_up(3)], 65 | link:man:gun_down(3)[gun_down(3)], 66 | link:man:gun_error(3)[gun_error(3)] 67 | -------------------------------------------------------------------------------- /doc/src/manual/gun_upgrade.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_upgrade(3) 2 | 3 | == Name 4 | 5 | gun_upgrade - Successful protocol upgrade 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_upgrade, ConnPid, StreamRef, Protocols, Headers} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Protocols :: [<<"websocket">>] 16 | Headers :: [{binary(), binary()}] 17 | ---- 18 | 19 | Successful protocol upgrade. 20 | 21 | This message informs the relevant process that the server 22 | accepted to upgrade to one or more protocols given in the 23 | original request. 24 | 25 | The exact semantics of this message depend on the original 26 | protocol. HTTP/1.1 upgrades apply to the entire connection. 27 | HTTP/2 uses a different mechanism which allows switching 28 | specific streams to a different protocol. 29 | 30 | Gun currently only supports upgrading HTTP/1.1 connections 31 | to the Websocket protocol. 32 | 33 | == Elements 34 | 35 | ConnPid:: 36 | 37 | The pid of the Gun connection process. 38 | 39 | StreamRef:: 40 | 41 | Identifier of the stream that resulted in an upgrade. 42 | 43 | Protocols:: 44 | 45 | List of protocols this stream was upgraded to. 46 | 47 | Headers:: 48 | 49 | Headers sent with the upgrade response. 50 | 51 | == Changelog 52 | 53 | * *1.0*: Message introduced. 54 | 55 | == Examples 56 | 57 | .Receive a gun_upgrade message in a gen_server 58 | [source,erlang] 59 | ---- 60 | handle_info({gun_upgrade, ConnPid, _StreamRef, 61 | _Protocols, _Headers}, 62 | State=#state{conn_pid=ConnPid}) -> 63 | %% Do something. 64 | {noreply, State}. 65 | ---- 66 | 67 | == See also 68 | 69 | link:man:gun(3)[gun(3)], 70 | link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)], 71 | link:man:gun:ws_send(3)[gun:ws_send(3)], 72 | link:man:gun_ws(3)[gun_ws(3)] 73 | -------------------------------------------------------------------------------- /doc/src/manual/gun_ws.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_ws(3) 2 | 3 | == Name 4 | 5 | gun_ws - Websocket frame 6 | 7 | == Description 8 | 9 | [source,erlang] 10 | ---- 11 | {gun_ws, ConnPid, StreamRef, Frame} 12 | 13 | ConnPid :: pid() 14 | StreamRef :: gun:stream_ref() 15 | Frame :: close | ping | pong 16 | | {text | binary | close, binary()} 17 | | {close, non_neg_integer(), binary()} 18 | | {ping | pong, binary()} 19 | ---- 20 | 21 | Websocket frame. 22 | 23 | This message informs the relevant process that the server 24 | sent the enclosed frame. 25 | 26 | This message can only be sent on streams that were upgraded 27 | to the Websocket protocol. 28 | 29 | == Elements 30 | 31 | ConnPid:: 32 | 33 | The pid of the Gun connection process. 34 | 35 | StreamRef:: 36 | 37 | Identifier of the stream that was upgraded to Websocket. 38 | 39 | Frame:: 40 | 41 | The Websocket frame in question. 42 | 43 | == Changelog 44 | 45 | * *2.0*: Depending on the option `silence_pings`, ping and 46 | pong frames may be sent as well. 47 | * *1.0*: Message introduced. 48 | 49 | == Examples 50 | 51 | .Receive a gun_ws message in a gen_server 52 | [source,erlang] 53 | ---- 54 | handle_info({gun_ws, ConnPid, _StreamRef, _Frame}, 55 | State=#state{conn_pid=ConnPid}) -> 56 | %% Do something. 57 | {noreply, State}. 58 | ---- 59 | 60 | == See also 61 | 62 | link:man:gun(3)[gun(3)], 63 | link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)], 64 | link:man:gun:ws_send(3)[gun:ws_send(3)], 65 | link:man:gun_upgrade(3)[gun_upgrade(3)] 66 | -------------------------------------------------------------------------------- /doc/src/manual/gun_ws_protocol.asciidoc: -------------------------------------------------------------------------------- 1 | = gun_ws_protocol(3) 2 | 3 | == Name 4 | 5 | gun_ws_protocol - Websocket subprotocols 6 | 7 | == Description 8 | 9 | The `gun_ws_protocol` module provides the callback interface 10 | and types for implementing Websocket subprotocols. 11 | 12 | == Callbacks 13 | 14 | Websocket subprotocols implement the following interface. 15 | 16 | === init 17 | 18 | [source,erlang] 19 | ---- 20 | init(ReplyTo, StreamRef, Headers, Opts) -> {ok, State} 21 | 22 | ReplyTo :: pid() 23 | StreamRef :: reference() 24 | Headers :: cow_http:headers() 25 | Opts :: gun:ws_opts() 26 | State :: protocol_state() 27 | ---- 28 | 29 | Initialize the Websocket protocol. 30 | 31 | ReplyTo:: 32 | 33 | The pid of the process that owns the stream and to 34 | which messages will be sent to. 35 | 36 | StreamRef:: 37 | 38 | The reference for the stream. Must be sent in messages 39 | to distinguish between different streams. 40 | 41 | Headers:: 42 | 43 | Headers that were sent in the response establishing 44 | the Websocket connection. 45 | 46 | Opts:: 47 | 48 | Websocket options. Custom options can be provided in 49 | the `user_opts` key. 50 | 51 | State:: 52 | 53 | State for the protocol. 54 | 55 | === handle 56 | 57 | [source,erlang] 58 | ---- 59 | handle(Frame, State) -> {ok, FlowDec, State} 60 | 61 | Frame :: cow_ws:frame() 62 | State :: protocol_state() 63 | FlowDec :: non_neg_integer() 64 | ---- 65 | 66 | Handle a Websocket frame. 67 | 68 | This callback may receive fragmented frames depending 69 | on the protocol and may need to rebuild the full 70 | frame to process it. 71 | 72 | Frame:: 73 | 74 | Websocket frame. 75 | 76 | State:: 77 | 78 | State for the protocol. 79 | 80 | FlowDec:: 81 | 82 | How many messages were sent. Used to update the flow 83 | control state when the feature is enabled. 84 | 85 | == Types 86 | 87 | === protocol_state() 88 | 89 | [source,erlang] 90 | ---- 91 | protocol_state() :: any() 92 | ---- 93 | 94 | State for the protocol. 95 | 96 | As this part of the implementation of the protocol 97 | the type may differ between different Websocket 98 | protocol modules. 99 | 100 | == Changelog 101 | 102 | * *2.0*: Module introduced. 103 | 104 | == See also 105 | 106 | link:man:gun(7)[gun(7)], 107 | link:man:gun(3)[gun(3)], 108 | link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)] 109 | -------------------------------------------------------------------------------- /ebin/gun.app: -------------------------------------------------------------------------------- 1 | {application, 'gun', [ 2 | {description, "HTTP/1.1, HTTP/2 and Websocket client for Erlang/OTP."}, 3 | {vsn, "2.2.0"}, 4 | {modules, ['gun','gun_app','gun_conns_sup','gun_content_handler','gun_cookies','gun_cookies_list','gun_data_h','gun_default_event_h','gun_event','gun_http','gun_http2','gun_http3','gun_pool','gun_pool_events_h','gun_pools_sup','gun_protocols','gun_public_suffix','gun_quicer','gun_raw','gun_socks','gun_sse_h','gun_sup','gun_tcp','gun_tcp_proxy','gun_tls','gun_tls_proxy','gun_tls_proxy_cb','gun_tls_proxy_http2_connect','gun_tunnel','gun_ws','gun_ws_h','gun_ws_protocol']}, 5 | {registered, [gun_sup]}, 6 | {applications, [kernel,stdlib,public_key,ssl,cowlib]}, 7 | {optional_applications, []}, 8 | {mod, {gun_app, []}}, 9 | {env, []} 10 | ]}. -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {cowlib,".*",{git,"https://github.com/ninenines/cowlib",{tag,"2.15.0"}}} 3 | ]}. 4 | {erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard]}. 5 | -------------------------------------------------------------------------------- /src/gun_app.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | %% @private 16 | -module(gun_app). 17 | -behaviour(application). 18 | 19 | %% API. 20 | -export([start/2]). 21 | -export([stop/1]). 22 | 23 | %% API. 24 | 25 | start(_Type, _Args) -> 26 | gun_pools = ets:new(gun_pools, [ordered_set, public, named_table]), 27 | gun_sup:start_link(). 28 | 29 | stop(_State) -> 30 | ok. 31 | -------------------------------------------------------------------------------- /src/gun_conns_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_conns_sup). 16 | -behaviour(supervisor). 17 | 18 | %% API. 19 | -export([start_link/0]). 20 | 21 | %% supervisor. 22 | -export([init/1]). 23 | 24 | %% API. 25 | 26 | -spec start_link() -> {ok, pid()}. 27 | start_link() -> 28 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 29 | 30 | %% supervisor. 31 | 32 | init([]) -> 33 | Procs = [ 34 | #{id => gun, start => {gun, start_link, []}, restart => temporary} 35 | ], 36 | {ok, {#{strategy => simple_one_for_one}, Procs}}. 37 | -------------------------------------------------------------------------------- /src/gun_content_handler.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_content_handler). 16 | 17 | -export([init/5]). 18 | -export([handle/3]). 19 | -export([check_option/1]). 20 | 21 | -type opt() :: [module() | {module(), map()}]. 22 | -export_type([opt/0]). 23 | 24 | -type state() :: opt() | [{module(), any()}]. 25 | -export_type([state/0]). 26 | 27 | -callback init(pid(), any(), cow_http:status(), 28 | cow_http:headers(), map()) -> {ok, any()} | disable. 29 | %% @todo Make fin | nofin its own type. 30 | -callback handle(fin | nofin, any(), State) 31 | -> {ok, any(), non_neg_integer(), State} 32 | | {done, non_neg_integer(), State} 33 | when State::any(). 34 | 35 | -spec init(pid(), any(), cow_http:status(), 36 | cow_http:headers(), State) -> State when State::state(). 37 | init(_, _, _, _, []) -> 38 | []; 39 | init(ReplyTo, StreamRef, Status, Headers, [Handler|Tail]) -> 40 | {Mod, Opts} = case Handler of 41 | Tuple = {_, _} -> Tuple; 42 | Atom -> {Atom, #{}} 43 | end, 44 | case Mod:init(ReplyTo, StreamRef, Status, Headers, Opts) of 45 | {ok, State} -> [{Mod, State}|init(ReplyTo, StreamRef, Status, Headers, Tail)]; 46 | disable -> init(ReplyTo, StreamRef, Status, Headers, Tail) 47 | end. 48 | 49 | -spec handle(fin | nofin, any(), State) -> {ok, non_neg_integer(), State} when State::state(). 50 | handle(IsFin, Data, State) -> 51 | handle(IsFin, Data, State, 0, []). 52 | 53 | handle(_, _, [], Flow, Acc) -> 54 | {ok, Flow, lists:reverse(Acc)}; 55 | handle(IsFin, Data0, [{Mod, State0}|Tail], Flow, Acc) -> 56 | case Mod:handle(IsFin, Data0, State0) of 57 | {ok, Data, Inc, State} -> 58 | handle(IsFin, Data, Tail, Flow + Inc, [{Mod, State}|Acc]); 59 | {done, Inc, State} -> 60 | {ok, Flow + Inc, lists:reverse([{Mod, State}|Acc], Tail)} 61 | end. 62 | 63 | -spec check_option(list()) -> ok | error. 64 | check_option([]) -> 65 | error; 66 | check_option(Opt) -> 67 | check_option1(Opt). 68 | 69 | check_option1([]) -> 70 | ok; 71 | check_option1([Atom|Tail]) when is_atom(Atom) -> 72 | check_option1(Tail); 73 | check_option1([{Atom, #{}}|Tail]) when is_atom(Atom) -> 74 | check_option1(Tail); 75 | check_option1(_) -> 76 | error. 77 | -------------------------------------------------------------------------------- /src/gun_data_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_data_h). 16 | -behavior(gun_content_handler). 17 | 18 | -export([init/5]). 19 | -export([handle/3]). 20 | 21 | -record(state, { 22 | reply_to :: pid(), 23 | stream_ref :: gun:stream_ref() 24 | }). 25 | 26 | -spec init(pid(), gun:stream_ref(), _, _, _) -> {ok, #state{}}. 27 | init(ReplyTo, StreamRef, _, _, _) -> 28 | {ok, #state{reply_to=ReplyTo, stream_ref=StreamRef}}. 29 | 30 | -spec handle(fin | nofin, binary(), State) -> {done, 1, State} when State::#state{}. 31 | handle(IsFin, Data, State=#state{reply_to=ReplyTo, stream_ref=StreamRef}) -> 32 | gun:reply(ReplyTo, {gun_data, self(), StreamRef, IsFin, Data}), 33 | {done, 1, State}. 34 | -------------------------------------------------------------------------------- /src/gun_default_event_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_default_event_h). 16 | -behavior(gun_event). 17 | 18 | -export([init/2]). 19 | -export([domain_lookup_start/2]). 20 | -export([domain_lookup_end/2]). 21 | -export([connect_start/2]). 22 | -export([connect_end/2]). 23 | -export([tls_handshake_start/2]). 24 | -export([tls_handshake_end/2]). 25 | -export([request_start/2]). 26 | -export([request_headers/2]). 27 | -export([request_end/2]). 28 | -export([push_promise_start/2]). 29 | -export([push_promise_end/2]). 30 | -export([response_start/2]). 31 | -export([response_inform/2]). 32 | -export([response_headers/2]). 33 | -export([response_trailers/2]). 34 | -export([response_end/2]). 35 | -export([ws_upgrade/2]). 36 | -export([ws_recv_frame_start/2]). 37 | -export([ws_recv_frame_header/2]). 38 | -export([ws_recv_frame_end/2]). 39 | -export([ws_send_frame_start/2]). 40 | -export([ws_send_frame_end/2]). 41 | -export([protocol_changed/2]). 42 | -export([origin_changed/2]). 43 | -export([cancel/2]). 44 | -export([disconnect/2]). 45 | -export([terminate/2]). 46 | 47 | init(_EventData, State) -> 48 | State. 49 | 50 | domain_lookup_start(_EventData, State) -> 51 | State. 52 | 53 | domain_lookup_end(_EventData, State) -> 54 | State. 55 | 56 | connect_start(_EventData, State) -> 57 | State. 58 | 59 | connect_end(_EventData, State) -> 60 | State. 61 | 62 | tls_handshake_start(_EventData, State) -> 63 | State. 64 | 65 | tls_handshake_end(_EventData, State) -> 66 | State. 67 | 68 | request_start(_EventData, State) -> 69 | State. 70 | 71 | request_headers(_EventData, State) -> 72 | State. 73 | 74 | request_end(_EventData, State) -> 75 | State. 76 | 77 | push_promise_start(_EventData, State) -> 78 | State. 79 | 80 | push_promise_end(_EventData, State) -> 81 | State. 82 | 83 | response_start(_EventData, State) -> 84 | State. 85 | 86 | response_inform(_EventData, State) -> 87 | State. 88 | 89 | response_headers(_EventData, State) -> 90 | State. 91 | 92 | response_trailers(_EventData, State) -> 93 | State. 94 | 95 | response_end(_EventData, State) -> 96 | State. 97 | 98 | ws_upgrade(_EventData, State) -> 99 | State. 100 | 101 | ws_recv_frame_start(_EventData, State) -> 102 | State. 103 | 104 | ws_recv_frame_header(_EventData, State) -> 105 | State. 106 | 107 | ws_recv_frame_end(_EventData, State) -> 108 | State. 109 | 110 | ws_send_frame_start(_EventData, State) -> 111 | State. 112 | 113 | ws_send_frame_end(_EventData, State) -> 114 | State. 115 | 116 | protocol_changed(_EventData, State) -> 117 | State. 118 | 119 | origin_changed(_EventData, State) -> 120 | State. 121 | 122 | cancel(_EventData, State) -> 123 | State. 124 | 125 | disconnect(_EventData, State) -> 126 | State. 127 | 128 | terminate(_EventData, State) -> 129 | State. 130 | -------------------------------------------------------------------------------- /src/gun_pools_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_pools_sup). 16 | -behaviour(supervisor). 17 | 18 | %% API. 19 | -export([start_link/0]). 20 | 21 | %% supervisor. 22 | -export([init/1]). 23 | 24 | %% API. 25 | 26 | -spec start_link() -> {ok, pid()}. 27 | start_link() -> 28 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 29 | 30 | %% supervisor. 31 | 32 | init([]) -> 33 | %% @todo Review restart strategies. 34 | Procs = [ 35 | #{id => gun_pool, start => {gun_pool, start_link, []}, restart => transient} 36 | ], 37 | {ok, {#{strategy => simple_one_for_one}, Procs}}. 38 | -------------------------------------------------------------------------------- /src/gun_protocols.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_protocols). 16 | 17 | -export([add_stream_ref/2]). 18 | -export([handler/1]). 19 | -export([handler_and_opts/2]). 20 | -export([negotiated/2]). 21 | -export([stream_ref/1]). 22 | 23 | -spec add_stream_ref(Protocol, undefined | gun:stream_ref()) 24 | -> Protocol when Protocol :: gun:protocol(). 25 | add_stream_ref(Protocol, undefined) -> 26 | Protocol; 27 | add_stream_ref({ProtocolName, ProtocolOpts}, StreamRef) -> 28 | {ProtocolName, ProtocolOpts#{stream_ref => StreamRef}}; 29 | add_stream_ref(ProtocolName, StreamRef) -> 30 | {ProtocolName, #{stream_ref => StreamRef}}. 31 | 32 | -spec handler(gun:protocol()) -> module(). 33 | handler(http) -> gun_http; 34 | handler({http, _}) -> gun_http; 35 | handler(http2) -> gun_http2; 36 | handler({http2, _}) -> gun_http2; 37 | handler(http3) -> gun_http3; 38 | handler({http3, _}) -> gun_http3; 39 | handler(raw) -> gun_raw; 40 | handler({raw, _}) -> gun_raw; 41 | handler(socks) -> gun_socks; 42 | handler({socks, _}) -> gun_socks; 43 | handler(ws) -> gun_ws; 44 | handler({ws, _}) -> gun_ws. 45 | 46 | -spec handler_and_opts(gun:protocol(), map()) -> {module(), map()}. 47 | handler_and_opts({ProtocolName, ProtocolOpts}, _) -> 48 | {handler(ProtocolName), ProtocolOpts}; 49 | handler_and_opts(ProtocolName, Opts) -> 50 | Protocol = handler(ProtocolName), 51 | {Protocol, maps:get(Protocol:opts_name(), Opts, #{})}. 52 | 53 | -spec negotiated({ok, binary()} | {error, protocol_not_negotiated}, gun:protocols()) 54 | -> gun:protocol(). 55 | negotiated({ok, <<"h2">>}, Protocols) -> 56 | lists:foldl(fun 57 | (E = http2, _) -> E; 58 | (E = {http2, _}, _) -> E; 59 | (_, Acc) -> Acc 60 | end, http2, Protocols); 61 | negotiated({ok, <<"http/1.1">>}, Protocols) -> 62 | lists:foldl(fun 63 | (E = http, _) -> E; 64 | (E = {http, _}, _) -> E; 65 | (_, Acc) -> Acc 66 | end, http, Protocols); 67 | negotiated({error, protocol_not_negotiated}, [Protocol]) -> 68 | Protocol; 69 | negotiated({error, protocol_not_negotiated}, _) -> 70 | http. 71 | 72 | -spec stream_ref(gun:protocol()) -> undefined | gun:stream_ref(). 73 | stream_ref({_, ProtocolOpts}) -> maps:get(stream_ref, ProtocolOpts, undefined); 74 | stream_ref(_) -> undefined. 75 | -------------------------------------------------------------------------------- /src/gun_public_suffix.erl.src: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_public_suffix). 16 | -compile(no_type_opt). %% Until at least OTP-23. 17 | 18 | -export([match/1]). 19 | 20 | -spec match(binary()) -> boolean(). 21 | match(Domain) -> 22 | Subdomains = string:split(Domain, <<".">>, all), 23 | m(Subdomains). 24 | 25 | %% GENERATED_M 26 | m(_) -> false. 27 | 28 | %% GENERATED_E 29 | e(_) -> true. 30 | -------------------------------------------------------------------------------- /src/gun_raw.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_raw). 16 | 17 | -export([check_options/1]). 18 | -export([name/0]). 19 | -export([opts_name/0]). 20 | -export([has_keepalive/0]). 21 | -export([init/4]). 22 | -export([handle/5]). 23 | -export([update_flow/4]). 24 | -export([closing/4]). 25 | -export([close/4]). 26 | -export([data/7]). 27 | -export([down/1]). 28 | 29 | -record(raw_state, { 30 | ref :: undefined | gun:stream_ref(), 31 | reply_to :: pid(), 32 | socket :: inet:socket() | ssl:sslsocket(), 33 | transport :: module(), 34 | flow :: integer() | infinity 35 | }). 36 | 37 | check_options(Opts) -> 38 | do_check_options(maps:to_list(Opts)). 39 | 40 | do_check_options([]) -> 41 | ok; 42 | do_check_options([{flow, Flow}|Opts]) when is_integer(Flow); Flow == infinity -> 43 | do_check_options(Opts); 44 | do_check_options([Opt|_]) -> 45 | {error, {options, {raw, Opt}}}. 46 | 47 | name() -> raw. 48 | opts_name() -> raw_opts. 49 | has_keepalive() -> false. 50 | 51 | init(ReplyTo, Socket, Transport, Opts) -> 52 | StreamRef = maps:get(stream_ref, Opts, undefined), 53 | InitialFlow = maps:get(flow, Opts, infinity), 54 | {ok, connected_data_only, #raw_state{ref=StreamRef, reply_to=ReplyTo, 55 | socket=Socket, transport=Transport, flow=InitialFlow}}. 56 | 57 | handle(Data, State=#raw_state{ref=StreamRef, reply_to=ReplyTo, flow=Flow0}, 58 | CookieStore, _, EvHandlerState) -> 59 | %% When we take over the entire connection there is no stream reference. 60 | gun:reply(ReplyTo, {gun_data, self(), StreamRef, nofin, Data}), 61 | Flow = case Flow0 of 62 | infinity -> infinity; 63 | _ -> Flow0 - 1 64 | end, 65 | {[ 66 | {state, State#raw_state{flow=Flow}}, 67 | {active, Flow > 0} 68 | ], CookieStore, EvHandlerState}. 69 | 70 | update_flow(State=#raw_state{flow=Flow0}, _ReplyTo, _StreamRef, Inc) -> 71 | Flow = case Flow0 of 72 | infinity -> infinity; 73 | _ -> Flow0 + Inc 74 | end, 75 | [ 76 | {state, State#raw_state{flow=Flow}}, 77 | {active, Flow > 0} 78 | ]. 79 | 80 | %% We can always close immediately. 81 | closing(_, _, _, EvHandlerState) -> 82 | {close, EvHandlerState}. 83 | 84 | close(_, _, _, EvHandlerState) -> 85 | EvHandlerState. 86 | 87 | %% @todo Initiate closing on IsFin=fin. 88 | data(#raw_state{ref=StreamRef, socket=Socket, transport=Transport}, StreamRef, 89 | _ReplyTo, _IsFin, Data, _EvHandler, EvHandlerState) -> 90 | case Transport:send(Socket, Data) of 91 | ok -> {[], EvHandlerState}; 92 | Error={error, _} -> {Error, EvHandlerState} 93 | end. 94 | 95 | %% raw has no concept of streams. 96 | down(_) -> 97 | []. 98 | -------------------------------------------------------------------------------- /src/gun_sse_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_sse_h). 16 | -behavior(gun_content_handler). 17 | 18 | -export([init/5]). 19 | -export([handle/3]). 20 | 21 | -record(state, { 22 | reply_to :: gun:reply_to(), 23 | stream_ref :: reference(), 24 | sse_state :: cow_sse:state() 25 | }). 26 | 27 | %% @todo In the future we want to allow different media types. 28 | 29 | -spec init(pid(), reference(), _, cow_http:headers(), _) 30 | -> {ok, #state{}} | disable. 31 | init(ReplyTo, StreamRef, _, Headers, _) -> 32 | case lists:keyfind(<<"content-type">>, 1, Headers) of 33 | {_, ContentType} -> 34 | case cow_http_hd:parse_content_type(ContentType) of 35 | {<<"text">>, <<"event-stream">>, _Ignored} -> 36 | {ok, #state{reply_to=ReplyTo, stream_ref=StreamRef, 37 | sse_state=cow_sse:init()}}; 38 | _ -> 39 | disable 40 | end; 41 | _ -> 42 | disable 43 | end. 44 | 45 | -spec handle(_, binary(), State) -> {done, non_neg_integer(), State} when State::#state{}. 46 | handle(IsFin, Data, State) -> 47 | handle(IsFin, Data, State, 0). 48 | 49 | handle(IsFin, Data, State=#state{reply_to=ReplyTo, stream_ref=StreamRef, sse_state=SSE0}, Flow) -> 50 | case cow_sse:parse(Data, SSE0) of 51 | {event, Event, SSE} -> 52 | gun:reply(ReplyTo, {gun_sse, self(), StreamRef, Event}), 53 | handle(IsFin, <<>>, State#state{sse_state=SSE}, Flow + 1); 54 | {more, SSE} -> 55 | Inc = case IsFin of 56 | fin -> 57 | gun:reply(ReplyTo, {gun_sse, self(), StreamRef, fin}), 58 | 1; 59 | _ -> 60 | 0 61 | end, 62 | {done, Flow + Inc, State#state{sse_state=SSE}} 63 | end. 64 | -------------------------------------------------------------------------------- /src/gun_sup.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_sup). 16 | -behaviour(supervisor). 17 | 18 | %% API. 19 | -export([start_link/0]). 20 | 21 | %% supervisor. 22 | -export([init/1]). 23 | 24 | %% API. 25 | 26 | -spec start_link() -> {ok, pid()}. 27 | start_link() -> 28 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 29 | 30 | %% supervisor. 31 | 32 | init([]) -> 33 | Procs = [ 34 | #{id => gun_conns_sup, start => {gun_conns_sup, start_link, []}, type => supervisor}, 35 | #{id => gun_pools_sup, start => {gun_pools_sup, start_link, []}, type => supervisor} 36 | ], 37 | {ok, {#{}, Procs}}. 38 | -------------------------------------------------------------------------------- /src/gun_tcp.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_tcp). 16 | 17 | -export([name/0]). 18 | -export([messages/0]). 19 | -export([domain_lookup/4]). 20 | -export([connect/2]). 21 | -export([send/2]). 22 | -export([setopts/2]). 23 | -export([sockname/1]). 24 | -export([close/1]). 25 | 26 | -type lookup_info() :: #{ 27 | ip_addresses := [inet:ip_address()], 28 | port := inet:port_number(), 29 | tcp_module := module(), 30 | tcp_opts := [gen_tcp:connect_option()] 31 | }. 32 | -export_type([lookup_info/0]). 33 | 34 | name() -> tcp. 35 | 36 | messages() -> {tcp, tcp_closed, tcp_error}. 37 | 38 | %% The functions domain_lookup/4 and connect/2 are very similar 39 | %% to gen_tcp:connect/4 except the logic is split in order to 40 | %% be able to trigger events between the domain lookup step 41 | %% and the actual connect step. 42 | 43 | -spec domain_lookup(inet:ip_address() | inet:hostname(), 44 | inet:port_number(), [gen_tcp:connect_option()], timeout()) 45 | -> {ok, lookup_info()} | {error, atom()}. 46 | domain_lookup(Address, Port0, Opts0, Timeout) -> 47 | {Mod, Opts} = inet:tcp_module(Opts0, Address), 48 | Timer = inet:start_timer(Timeout), 49 | try Mod:getaddrs(Address, Timer) of 50 | {ok, IPs} -> 51 | case Mod:getserv(Port0) of 52 | {ok, Port} -> 53 | {ok, #{ 54 | ip_addresses => IPs, 55 | port => Port, 56 | tcp_module => Mod, 57 | tcp_opts => Opts ++ [binary, {active, false}, {packet, raw}] 58 | }}; 59 | Error -> 60 | maybe_exit(Error) 61 | end; 62 | Error -> 63 | maybe_exit(Error) 64 | after 65 | _ = inet:stop_timer(Timer) 66 | end. 67 | 68 | -spec connect(lookup_info(), timeout()) 69 | -> {ok, inet:socket()} | {error, atom()}. 70 | connect(#{ip_addresses := IPs, port := Port, tcp_module := Mod, tcp_opts := Opts}, Timeout) -> 71 | Timer = inet:start_timer(Timeout), 72 | Res = try 73 | try_connect(IPs, Port, Opts, Timer, Mod, {error, einval}) 74 | after 75 | _ = inet:stop_timer(Timer) 76 | end, 77 | case Res of 78 | {ok, S} -> {ok, S}; 79 | Error -> maybe_exit(Error) 80 | end. 81 | 82 | try_connect([IP|IPs], Port, Opts, Timer, Mod, _) -> 83 | Timeout = inet:timeout(Timer), 84 | case Mod:connect(IP, Port, Opts, Timeout) of 85 | {ok, S} -> {ok, S}; 86 | {error, einval} -> {error, einval}; 87 | {error, timeout} -> {error, timeout}; 88 | Error -> try_connect(IPs, Port, Opts, Timer, Mod, Error) 89 | end; 90 | try_connect([], _, _, _, _, Error) -> 91 | Error. 92 | 93 | maybe_exit({error, einval}) -> exit(badarg); 94 | maybe_exit({error, eaddrnotavail}) -> exit(badarg); 95 | maybe_exit(Error) -> Error. 96 | 97 | -spec send(inet:socket(), iodata()) -> ok | {error, atom()}. 98 | send(Socket, Packet) -> 99 | gen_tcp:send(Socket, Packet). 100 | 101 | -spec setopts(inet:socket(), list()) -> ok | {error, atom()}. 102 | setopts(Socket, Opts) -> 103 | inet:setopts(Socket, Opts). 104 | 105 | -spec sockname(inet:socket()) 106 | -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 107 | sockname(Socket) -> 108 | inet:sockname(Socket). 109 | 110 | -spec close(inet:socket()) -> ok. 111 | close(Socket) -> 112 | gen_tcp:close(Socket). 113 | -------------------------------------------------------------------------------- /src/gun_tcp_proxy.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_tcp_proxy). 16 | 17 | -export([name/0]). 18 | -export([messages/0]). 19 | -export([connect/3]). 20 | -export([connect/4]). 21 | -export([send/2]). 22 | -export([setopts/2]). 23 | -export([sockname/1]). 24 | -export([close/1]). 25 | 26 | -type socket() :: #{ 27 | %% The pid of the Gun connection. 28 | gun_pid := pid(), 29 | 30 | %% The pid of the process that gets replies for this tunnel. 31 | reply_to := pid(), 32 | 33 | %% The full stream reference for this tunnel. 34 | stream_ref := gun:stream_ref() 35 | }. 36 | 37 | name() -> tcp_proxy. 38 | 39 | messages() -> {tcp_proxy, tcp_proxy_closed, tcp_proxy_error}. 40 | 41 | -spec connect(_, _, _) -> no_return(). 42 | connect(_, _, _) -> 43 | error(not_implemented). 44 | 45 | -spec connect(_, _, _, _) -> no_return(). 46 | connect(_, _, _, _) -> 47 | error(not_implemented). 48 | 49 | -spec send(socket(), iodata()) -> ok. 50 | send(#{gun_pid := GunPid, reply_to := ReplyTo, stream_ref := StreamRef, 51 | handle_continue_stream_ref := ContinueStreamRef}, Data) -> 52 | GunPid ! {handle_continue, ContinueStreamRef, {data, ReplyTo, StreamRef, nofin, Data}}, 53 | ok; 54 | send(#{reply_to := ReplyTo, stream_ref := StreamRef}, Data) -> 55 | gen_statem:cast(self(), {data, ReplyTo, StreamRef, nofin, Data}). 56 | 57 | -spec setopts(_, _) -> no_return(). 58 | setopts(#{handle_continue_stream_ref := _}, _) -> 59 | %% We send messages automatically regardless of active mode. 60 | ok; 61 | setopts(_, _) -> 62 | error(not_implemented). 63 | 64 | -spec sockname(_) -> no_return(). 65 | sockname(_) -> 66 | error(not_implemented). 67 | 68 | -spec close(socket()) -> ok. 69 | close(_) -> 70 | ok. 71 | -------------------------------------------------------------------------------- /src/gun_tls.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_tls). 16 | 17 | -export([name/0]). 18 | -export([messages/0]). 19 | -export([connect/3]). 20 | -export([send/2]). 21 | -export([setopts/2]). 22 | -export([sockname/1]). 23 | -export([close/1]). 24 | 25 | name() -> tls. 26 | 27 | messages() -> {ssl, ssl_closed, ssl_error}. 28 | 29 | -spec connect(inet:socket(), any(), timeout()) 30 | -> {ok, ssl:sslsocket()} | {error, atom()}. 31 | connect(Socket, Opts, Timeout) -> 32 | ssl:connect(Socket, Opts, Timeout). 33 | 34 | -spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}. 35 | send(Socket, Packet) -> 36 | ssl:send(Socket, Packet). 37 | 38 | -spec setopts(ssl:sslsocket(), list()) -> ok | {error, any()}. 39 | setopts(Socket, Opts) -> 40 | ssl:setopts(Socket, Opts). 41 | 42 | -spec sockname(ssl:sslsocket()) 43 | -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 44 | sockname(Socket) -> 45 | ssl:sockname(Socket). 46 | 47 | -spec close(ssl:sslsocket()) -> ok. 48 | close(Socket) -> 49 | ssl:close(Socket). 50 | -------------------------------------------------------------------------------- /src/gun_tls_proxy_cb.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | %% Transport callback for ssl. 16 | 17 | -module(gun_tls_proxy_cb). 18 | 19 | -export([connect/4]). 20 | -export([controlling_process/2]). 21 | -export([send/2]). 22 | -export([setopts/2]). 23 | -export([close/1]). 24 | 25 | %% The connect/4 function is called by the process 26 | %% that calls ssl:connect/2,3,4. 27 | connect(_Address, _Port, Opts, _Timeout) -> 28 | {_, GunPid} = lists:keyfind(gun_tls_proxy, 1, Opts), 29 | {ok, GunPid}. 30 | 31 | controlling_process(Socket, ControllingPid) -> 32 | gun_tls_proxy:cb_controlling_process(Socket, ControllingPid). 33 | 34 | send(Socket, Data) -> 35 | gun_tls_proxy:cb_send(Socket, Data). 36 | 37 | setopts(Socket, Opts) -> 38 | gun_tls_proxy:cb_setopts(Socket, Opts). 39 | 40 | close(_) -> 41 | ok. 42 | -------------------------------------------------------------------------------- /src/gun_tls_proxy_http2_connect.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_tls_proxy_http2_connect). 16 | 17 | -export([name/0]). 18 | -export([messages/0]). 19 | -export([connect/3]). 20 | -export([connect/4]). 21 | -export([send/2]). 22 | -export([setopts/2]). 23 | -export([sockname/1]). 24 | -export([close/1]). 25 | 26 | -type socket() :: #{ 27 | %% The pid of the Gun connection. 28 | gun_pid := pid(), 29 | 30 | %% The pid of the process that gets replies for this tunnel. 31 | reply_to := pid(), 32 | 33 | %% The full stream reference for this tunnel. 34 | stream_ref := gun:stream_ref(), 35 | 36 | %% The full stream reference for the responsible HTTP/2 stream. 37 | handle_continue_stream_ref := gun:stream_ref() 38 | }. 39 | 40 | name() -> tls_proxy_http2_connect. 41 | 42 | messages() -> {tls_proxy_http2_connect, tls_proxy_http2_connect_closed, tls_proxy_http2_connect_error}. 43 | 44 | -spec connect(_, _, _) -> no_return(). 45 | connect(_, _, _) -> 46 | error(not_implemented). 47 | 48 | -spec connect(_, _, _, _) -> no_return(). 49 | connect(_, _, _, _) -> 50 | error(not_implemented). 51 | 52 | -spec send(socket(), iodata()) -> ok. 53 | send(#{gun_pid := GunPid, reply_to := ReplyTo, stream_ref := DataStreamRef, 54 | handle_continue_stream_ref := StreamRef}, Data) -> 55 | GunPid ! {handle_continue, StreamRef, {data, ReplyTo, DataStreamRef, nofin, Data}}, 56 | ok. 57 | 58 | -spec setopts(_, _) -> no_return(). 59 | setopts(_, _) -> 60 | %% We send messages automatically regardless of active mode. 61 | ok. 62 | 63 | -spec sockname(_) -> no_return(). 64 | sockname(_) -> 65 | error(not_implemented). 66 | 67 | -spec close(socket()) -> ok. 68 | close(_) -> 69 | ok. 70 | -------------------------------------------------------------------------------- /src/gun_ws_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_ws_h). 16 | -behavior(gun_ws_protocol). 17 | 18 | -export([init/4]). 19 | -export([handle/2]). 20 | 21 | -record(state, { 22 | reply_to :: gun:reply_to(), 23 | stream_ref :: reference(), 24 | frag_buffer = <<>> :: binary(), 25 | silence_pings :: boolean() 26 | }). 27 | 28 | init(ReplyTo, StreamRef, _, Opts) -> 29 | {ok, #state{reply_to=ReplyTo, stream_ref=StreamRef, 30 | silence_pings=maps:get(silence_pings, Opts, true)}}. 31 | 32 | handle({fragment, nofin, _, Payload}, 33 | State=#state{frag_buffer=SoFar}) -> 34 | {ok, 0, State#state{frag_buffer= << SoFar/binary, Payload/binary >>}}; 35 | handle({fragment, fin, Type, Payload}, 36 | State=#state{reply_to=ReplyTo, stream_ref=StreamRef, frag_buffer=SoFar}) -> 37 | gun:reply(ReplyTo, {gun_ws, self(), StreamRef, {Type, << SoFar/binary, Payload/binary >>}}), 38 | {ok, 1, State#state{frag_buffer= <<>>}}; 39 | handle(Frame, State=#state{silence_pings=true}) when Frame =:= ping; Frame =:= pong; 40 | element(1, Frame) =:= ping; element(1, Frame) =:= pong -> 41 | {ok, 0, State}; 42 | handle(Frame, State=#state{reply_to=ReplyTo, stream_ref=StreamRef}) -> 43 | gun:reply(ReplyTo, {gun_ws, self(), StreamRef, Frame}), 44 | {ok, 1, State}. 45 | -------------------------------------------------------------------------------- /src/gun_ws_protocol.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_ws_protocol). 16 | 17 | -type protocol_state() :: any(). 18 | -export_type([protocol_state/0]). 19 | 20 | -callback init(pid(), reference(), cow_http:headers(), gun:ws_opts()) 21 | -> {ok, protocol_state()}. 22 | 23 | -callback handle(cow_ws:frame(), State) 24 | -> {ok, non_neg_integer(), State} 25 | when State::protocol_state(). 26 | -------------------------------------------------------------------------------- /test/gun_ct_hook.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_ct_hook). 16 | 17 | -export([init/2]). 18 | 19 | init(_, _) -> 20 | ct_helper:start([cowboy, gun]), 21 | ct_helper:make_certs_in_ets(), 22 | {ok, undefined}. 23 | -------------------------------------------------------------------------------- /test/gun_test.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_test). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | %% Cowboy listeners. 20 | 21 | init_cowboy_tcp(Ref, ProtoOpts, Config) -> 22 | {ok, _} = cowboy:start_clear(Ref, [{port, 0}], ProtoOpts), 23 | [{ref, Ref}, {port, ranch:get_port(Ref)}|Config]. 24 | 25 | init_cowboy_tls(Ref, ProtoOpts, Config) -> 26 | Opts = ct_helper:get_certs_from_ets(), 27 | {ok, _} = cowboy:start_tls(Ref, 28 | [{verify, verify_none}, {fail_if_no_peer_cert, false}] 29 | ++ Opts ++ [{port, 0}], ProtoOpts), 30 | [{ref, Ref}, {port, ranch:get_port(Ref)}|Config]. 31 | 32 | %% Origin server helpers. 33 | 34 | init_origin(Transport) -> 35 | init_origin(Transport, http). 36 | 37 | init_origin(Transport, Protocol) -> 38 | init_origin(Transport, Protocol, fun loop_origin/4). 39 | 40 | init_origin(Transport, Protocol, Fun) -> 41 | Pid = spawn_link(?MODULE, init_origin, [self(), Transport, Protocol, Fun]), 42 | Port = receive_from(Pid), 43 | {ok, Pid, Port}. 44 | 45 | init_origin(Parent, Transport, Protocol, Fun) 46 | when Transport =:= tcp; Transport =:= tcp6 -> 47 | InetOpt = case Transport of 48 | tcp -> inet; 49 | tcp6 -> inet6 50 | end, 51 | {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}, InetOpt]), 52 | {ok, {_, Port}} = inet:sockname(ListenSocket), 53 | Parent ! {self(), Port}, 54 | {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), 55 | case Protocol of 56 | http2 -> http2_handshake(ClientSocket, gen_tcp); 57 | _ -> ok 58 | end, 59 | Parent ! {self(), handshake_completed}, 60 | Fun(Parent, ListenSocket, ClientSocket, gen_tcp); 61 | init_origin(Parent, tls, Protocol, Fun) -> 62 | Opts0 = ct_helper:get_certs_from_ets(), 63 | Opts1 = case Protocol of 64 | http2 -> [{alpn_preferred_protocols, [<<"h2">>]}|Opts0]; 65 | _ -> Opts0 66 | end, 67 | %% sni_hosts is necessary for SNI tests to succeed. 68 | Opts = [{sni_hosts, [{net_adm:localhost(), []}]}|Opts1], 69 | {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}, 70 | {fail_if_no_peer_cert, false}|Opts]), 71 | {ok, {_, Port}} = ssl:sockname(ListenSocket), 72 | Parent ! {self(), Port}, 73 | {ok, ClientSocket0} = ssl:transport_accept(ListenSocket, 5000), 74 | {ok, ClientSocket} = ssl:handshake(ClientSocket0, 5000), 75 | case Protocol of 76 | http2 -> 77 | {ok, <<"h2">>} = ssl:negotiated_protocol(ClientSocket), 78 | http2_handshake(ClientSocket, ssl); 79 | _ -> 80 | ok 81 | end, 82 | Parent ! {self(), handshake_completed}, 83 | Fun(Parent, ListenSocket, ClientSocket, ssl). 84 | 85 | http2_handshake(Socket, Transport) -> 86 | %% Send a valid preface. 87 | ok = Transport:send(Socket, cow_http2:settings(#{})), 88 | %% Receive the fixed sequence from the preface. 89 | Preface = <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>, 90 | {ok, Preface} = Transport:recv(Socket, byte_size(Preface), 5000), 91 | %% Receive the SETTINGS from the preface. 92 | {ok, <>} = Transport:recv(Socket, 3, 5000), 93 | {ok, <<4:8, 0:40, _:Len/binary>>} = Transport:recv(Socket, 6 + Len, 5000), 94 | %% Receive the WINDOW_UPDATE sent with the preface. 95 | {ok, <<4:24, 8:8, 0:40, _:32>>} = Transport:recv(Socket, 13, 5000), 96 | %% Send the SETTINGS ack. 97 | ok = Transport:send(Socket, cow_http2:settings_ack()), 98 | %% Receive the SETTINGS ack. 99 | {ok, <<0:24, 4:8, 1:8, 0:32>>} = Transport:recv(Socket, 9, 5000), 100 | ok. 101 | 102 | loop_origin(Parent, ListenSocket, ClientSocket, ClientTransport) -> 103 | case ClientTransport:recv(ClientSocket, 0, 5000) of 104 | {ok, Data} -> 105 | Parent ! {self(), Data}, 106 | loop_origin(Parent, ListenSocket, ClientSocket, ClientTransport); 107 | {error, closed} -> 108 | ok 109 | end. 110 | 111 | %% Common helpers. 112 | 113 | receive_from(Pid) -> 114 | receive_from(Pid, 5000). 115 | 116 | receive_from(Pid, Timeout) -> 117 | receive 118 | {Pid, Msg} -> 119 | Msg 120 | after Timeout -> 121 | error(timeout) 122 | end. 123 | 124 | receive_all_from(Pid, Timeout) -> 125 | receive_all_from(Pid, Timeout, <<>>). 126 | 127 | receive_all_from(Pid, Timeout, Acc) -> 128 | try 129 | More = receive_from(Pid, Timeout), 130 | receive_all_from(Pid, Timeout, <>) 131 | catch error:timeout -> 132 | Acc 133 | end. 134 | -------------------------------------------------------------------------------- /test/gun_test_event_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_test_event_h). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | init(Event, State) -> common(?FUNCTION_NAME, Event, State). 20 | domain_lookup_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 21 | domain_lookup_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 22 | connect_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 23 | connect_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 24 | tls_handshake_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 25 | tls_handshake_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 26 | request_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 27 | request_headers(Event, State) -> common(?FUNCTION_NAME, Event, State). 28 | request_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 29 | push_promise_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 30 | push_promise_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 31 | response_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 32 | response_inform(Event, State) -> common(?FUNCTION_NAME, Event, State). 33 | response_headers(Event, State) -> common(?FUNCTION_NAME, Event, State). 34 | response_trailers(Event, State) -> common(?FUNCTION_NAME, Event, State). 35 | response_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 36 | ws_upgrade(Event, State) -> common(?FUNCTION_NAME, Event, State). 37 | ws_recv_frame_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 38 | ws_recv_frame_header(Event, State) -> common(?FUNCTION_NAME, Event, State). 39 | ws_recv_frame_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 40 | ws_send_frame_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 41 | ws_send_frame_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 42 | protocol_changed(Event, State) -> common(?FUNCTION_NAME, Event, State). 43 | origin_changed(Event, State) -> common(?FUNCTION_NAME, Event, State). 44 | cancel(Event, State) -> common(?FUNCTION_NAME, Event, State). 45 | disconnect(Event, State) -> common(?FUNCTION_NAME, Event, State). 46 | terminate(Event, State) -> common(?FUNCTION_NAME, Event, State). 47 | 48 | common(EventType, Event, State=Pid) -> 49 | Pid ! {self(), EventType, Event#{ 50 | ts => erlang:system_time(millisecond) 51 | }}, 52 | State. 53 | 54 | receive_event(Pid) -> 55 | receive 56 | Msg = {Pid, EventType, Event} when is_atom(EventType), is_map(Event) -> 57 | Msg 58 | end. 59 | 60 | receive_event(Pid, EventType) -> 61 | receive 62 | Msg = {Pid, EventType, Event} when is_map(Event) -> 63 | Msg 64 | end. 65 | -------------------------------------------------------------------------------- /test/gun_test_fun_event_h.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(gun_test_fun_event_h). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | init(Event, State) -> common(?FUNCTION_NAME, Event, State). 20 | domain_lookup_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 21 | domain_lookup_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 22 | connect_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 23 | connect_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 24 | tls_handshake_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 25 | tls_handshake_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 26 | request_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 27 | request_headers(Event, State) -> common(?FUNCTION_NAME, Event, State). 28 | request_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 29 | push_promise_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 30 | push_promise_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 31 | response_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 32 | response_inform(Event, State) -> common(?FUNCTION_NAME, Event, State). 33 | response_headers(Event, State) -> common(?FUNCTION_NAME, Event, State). 34 | response_trailers(Event, State) -> common(?FUNCTION_NAME, Event, State). 35 | response_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 36 | ws_upgrade(Event, State) -> common(?FUNCTION_NAME, Event, State). 37 | ws_recv_frame_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 38 | ws_recv_frame_header(Event, State) -> common(?FUNCTION_NAME, Event, State). 39 | ws_recv_frame_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 40 | ws_send_frame_start(Event, State) -> common(?FUNCTION_NAME, Event, State). 41 | ws_send_frame_end(Event, State) -> common(?FUNCTION_NAME, Event, State). 42 | protocol_changed(Event, State) -> common(?FUNCTION_NAME, Event, State). 43 | origin_changed(Event, State) -> common(?FUNCTION_NAME, Event, State). 44 | cancel(Event, State) -> common(?FUNCTION_NAME, Event, State). 45 | disconnect(Event, State) -> common(?FUNCTION_NAME, Event, State). 46 | terminate(Event, State) -> common(?FUNCTION_NAME, Event, State). 47 | 48 | common(EventType, Event, State=EventFunsMap) -> 49 | case EventFunsMap of 50 | #{EventType := Fun} -> 51 | Fun(EventType, Event), 52 | State; 53 | _ -> 54 | State 55 | end. 56 | -------------------------------------------------------------------------------- /test/h2specd_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(h2specd_SUITE). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | -import(ct_helper, [config/2]). 20 | -import(ct_helper, [doc/1]). 21 | 22 | %% ct. 23 | 24 | all() -> 25 | [h2specd]. 26 | 27 | init_per_suite(Config) -> 28 | case os:getenv("H2SPECD") of 29 | false -> {skip, "$H2SPECD isn't set."}; 30 | H2specd -> 31 | case filelib:is_file(H2specd) of 32 | false -> {skip, "$H2SPECD file not found."}; 33 | true -> 34 | %% We ensure that SASL is started for this test suite 35 | %% to have the crash reports in the CT logs. 36 | {ok, Apps} = application:ensure_all_started(sasl), 37 | [{sasl_started, Apps =/= []}|Config] 38 | end 39 | end. 40 | 41 | end_per_suite(Config) -> 42 | case config(sasl_started, Config) of 43 | true -> application:stop(sasl); 44 | false -> ok 45 | end. 46 | 47 | %% Tests. 48 | 49 | h2specd(Config) -> 50 | doc("h2specd test suite for the HTTP/2 protocol."), 51 | Self = self(), 52 | Pid = spawn_link(fun() -> start_port(Config, Self) end), 53 | receive ready -> ok after 10000 -> error(timeout) end, 54 | try 55 | run_tests(), 56 | timer:sleep(100), 57 | maybe_fail() 58 | after 59 | unlink(Pid), 60 | os:cmd("killall h2specd") 61 | end. 62 | 63 | start_port(Config, Pid) -> 64 | H2specd = os:getenv("H2SPECD"), 65 | Port = open_port( 66 | {spawn, H2specd ++ " -S -p 45678"}, 67 | [{line, 10000}, {cd, config(priv_dir, Config)}, binary, exit_status]), 68 | Pid ! ready, 69 | receive_infinity(Port, []). 70 | 71 | receive_infinity(Port, Acc) -> 72 | receive 73 | {Port, {data, {eol, Line}}} -> 74 | ct:log("~ts", [Line]), 75 | %% Somehow we may receive the same line multiple times. 76 | %% We therefore only print if it's a line we didn't print before. 77 | case lists:member(Line, Acc) of 78 | false -> io:format(user, "~s~n", [Line]); 79 | true -> ok 80 | end, 81 | receive_infinity(Port, [Line|Acc]); 82 | {Port, Reason={exit_status, _}} -> 83 | ct:log("~ts", [[[L, $\n] || L <- lists:reverse(Acc)]]), 84 | exit({shutdown, Reason}) 85 | end. 86 | 87 | run_tests() -> 88 | timer:sleep(1000), 89 | Tests = scrape_tests(), 90 | ct:pal("Test ports: ~p~n", [Tests]), 91 | run_tests(Tests), 92 | timer:sleep(1000). 93 | 94 | run_tests([]) -> 95 | ok; 96 | run_tests([Port|Tail]) -> 97 | try 98 | {ok, Conn} = gun:open("127.0.0.1", Port, #{ 99 | protocols => [http2], 100 | retry => 0, 101 | tcp_opts => [{nodelay, true}] 102 | }), 103 | MRef = monitor(process, Conn), 104 | {ok, http2} = gun:await_up(Conn), 105 | StreamRef = gun:get(Conn, "/"), 106 | receive 107 | {gun_response, Conn, StreamRef, _, _, _} -> 108 | timer:sleep(100); 109 | {'DOWN', MRef, process, Conn, _} -> 110 | ok 111 | after 100 -> 112 | ok 113 | end 114 | after 115 | run_tests(Tail) 116 | end. 117 | 118 | scrape_tests() -> 119 | {ok, Conn} = gun:open("127.0.0.1", 45678), 120 | {ok, http} = gun:await_up(Conn), 121 | StreamRef = gun:get(Conn, "/"), 122 | {response, nofin, 200, _} = gun:await(Conn, StreamRef), 123 | {ok, Body} = gun:await_body(Conn, StreamRef), 124 | ok = gun:close(Conn), 125 | {match, Matches} = re:run(Body, ">] <- Matches]. 128 | 129 | maybe_fail() -> 130 | {ok, Conn} = gun:open("127.0.0.1", 45678), 131 | {ok, http} = gun:await_up(Conn), 132 | StreamRef = gun:get(Conn, "/report", [{<<"accept">>, "text/plain"}]), 133 | {response, nofin, 200, _} = gun:await(Conn, StreamRef), 134 | {ok, Body} = gun:await_body(Conn, StreamRef), 135 | ok = gun:close(Conn), 136 | case binary:match(Body, <<"0 skipped, 0 failed">>) of 137 | nomatch -> exit(failed); 138 | _ -> ok 139 | end. 140 | -------------------------------------------------------------------------------- /test/handlers/cookie_echo_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(cookie_echo_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, State) -> 8 | {ok, cowboy_req:reply(200, 9 | #{<<"content-type">> => <<"text/plain">>}, 10 | cowboy_req:header(<<"cookie">>, Req, <<"UNDEF">>), 11 | Req), State}. 12 | -------------------------------------------------------------------------------- /test/handlers/cookie_informational_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(cookie_informational_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req0, State) -> 8 | cowboy_req:inform(103, #{<<"set-cookie">> => [<<"informational=1">>]}, Req0), 9 | Req = cowboy_req:reply(204, #{<<"set-cookie">> => [<<"final=1">>]}, Req0), 10 | {ok, Req, State}. 11 | -------------------------------------------------------------------------------- /test/handlers/cookie_parser_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(cookie_parser_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req0=#{qs := Qs}, State) -> 8 | %% Hardcoded path, but I doubt it's going to break anytime soon. 9 | TestFile = iolist_to_binary(["../../test/wpt/cookies/", Qs, "-test"]), 10 | {ok, Test} = file:read_file(TestFile), 11 | %% We don't want the final empty line. 12 | Lines = lists:reverse(tl(lists:reverse(string:split(Test, <<"\n">>, all)))), 13 | Req = lists:foldl(fun 14 | (<<"Set-Cookie: ",SetCookie/bits>>, Req1) -> 15 | %% We do not use set_resp_cookie because we want to preserve ordering. 16 | SetCookieList = cowboy_req:resp_header(<<"set-cookie">>, Req1, []), 17 | cowboy_req:set_resp_header(<<"set-cookie">>, SetCookieList ++ [SetCookie], Req1); 18 | (<<"Set-Cookie:">>, Req1) -> 19 | Req1; 20 | (<<"Location: ",Location/bits>>, Req1) -> 21 | cowboy_req:set_resp_header(<<"location">>, Location, Req1) 22 | end, Req0, Lines), 23 | {ok, cowboy_req:reply(204, Req), State}. 24 | -------------------------------------------------------------------------------- /test/handlers/cookie_parser_result_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(cookie_parser_result_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req=#{qs := Qs}, State) -> 8 | %% Hardcoded path, but I doubt it's going to break anytime soon. 9 | ExpectedFile = iolist_to_binary(["../../test/wpt/cookies/", Qs, "-expected"]), 10 | CookieHd = cowboy_req:header(<<"cookie">>, Req), 11 | case file:read_file(ExpectedFile) of 12 | {ok, Expected} when Expected =:= <<>>; Expected =:= <<"\n">> -> 13 | undefined = CookieHd, 14 | ok; 15 | {ok, <<"Cookie: ",CookiesBin0/bits>>} -> 16 | %% We only care about the first line. 17 | [CookiesBin, <<>>|_] = string:split(CookiesBin0, <<"\n">>, all), 18 | CookiesBin = CookieHd, 19 | ok 20 | end, 21 | %% We echo back the cookie header in order to log it. 22 | {ok, cowboy_req:reply(204, case CookieHd of 23 | undefined -> #{<<"x-no-cookie-received">> => <<"Cookie header missing.">>}; 24 | _ -> #{<<"x-cookie-received">> => CookieHd} 25 | end, Req), State}. 26 | -------------------------------------------------------------------------------- /test/handlers/cookie_set_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(cookie_set_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req0, State) -> 8 | SetCookieList = set_cookie_list(Req0), 9 | Req = cowboy_req:set_resp_header(<<"set-cookie">>, SetCookieList, Req0), 10 | {ok, cowboy_req:reply(204, Req), State}. 11 | 12 | -define(HOST, "web-platform.test"). 13 | 14 | set_cookie_list(#{qs := <<"domain_with_and_without_leading_period">>}) -> 15 | [ 16 | <<"a=b; Path=/; Domain=." ?HOST>>, 17 | <<"a=c; Path=/; Domain=" ?HOST>> 18 | ]; 19 | set_cookie_list(#{qs := <<"domain_with_leading_period">>}) -> 20 | [<<"a=b; Path=/; Domain=." ?HOST>>]; 21 | set_cookie_list(#{qs := <<"domain_matches_host">>}) -> 22 | [<<"a=b; Path=/; Domain=" ?HOST>>]; 23 | set_cookie_list(#{qs := <<"domain_missing">>}) -> 24 | [<<"a=b; Path=/;">>]; 25 | set_cookie_list(#{qs := <<"path_default">>}) -> 26 | [<<"cookie-path-default=1">>]; 27 | set_cookie_list(#{qs := <<"path_default_expire">>}) -> 28 | [<<"cookie-path-default=1; Max-Age=0">>]; 29 | set_cookie_list(#{qs := <<"path=",Path/bits>>}) -> 30 | [[<<"a=b; Path=">>, Path]]; 31 | set_cookie_list(Req=#{qs := <<"prefix">>}) -> 32 | [cowboy_req:header(<<"please-set-cookie">>, Req)]; 33 | set_cookie_list(#{qs := <<"secure_http">>}) -> 34 | [<<"secure_from_nonsecure_http=1; Secure; Path=/">>]; 35 | set_cookie_list(#{qs := <<"secure_https">>}) -> 36 | [<<"secure_from_secure_http=1; Secure; Path=/">>]; 37 | set_cookie_list(Req=#{qs := <<"ttb=",_/bits>>}) -> 38 | #{ttb := SetCookies} = cowboy_req:match_qs([ttb], Req), 39 | case binary_to_term(SetCookies) of 40 | List when is_list(List) -> List; 41 | Bin -> [Bin] 42 | end. 43 | -------------------------------------------------------------------------------- /test/handlers/delayed_hello_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(delayed_hello_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, Timeout) -> 8 | timer:sleep(Timeout), 9 | {ok, cowboy_req:reply(200, #{ 10 | <<"content-type">> => <<"text/plain">> 11 | }, <<"Hello world!">>, Req), Timeout}. 12 | -------------------------------------------------------------------------------- /test/handlers/delayed_push_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(delayed_push_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, Timeout) -> 8 | timer:sleep(Timeout), 9 | cowboy_req:push("/", #{<<"accept">> => <<"text/plain">>}, Req), 10 | cowboy_req:push("/empty", #{<<"accept">> => <<"text/plain">>}, Req), 11 | {ok, cowboy_req:reply(200, #{ 12 | <<"content-type">> => <<"text/plain">> 13 | }, <<"Hello world!">>, Req), Timeout}. 14 | -------------------------------------------------------------------------------- /test/handlers/empty_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(empty_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, State) -> 8 | {ok, cowboy_req:reply(200, #{ 9 | <<"content-type">> => <<"text/plain">> 10 | }, Req), State}. 11 | 12 | -------------------------------------------------------------------------------- /test/handlers/hello_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(hello_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, State) -> 8 | {ok, cowboy_req:reply(200, #{ 9 | <<"content-type">> => <<"text/plain">> 10 | }, <<"Hello world!">>, Req), State}. 11 | -------------------------------------------------------------------------------- /test/handlers/inform_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(inform_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, State) -> 8 | cowboy_req:inform(103, #{ 9 | <<"content-type">> => <<"text/plain">> 10 | }, Req), 11 | cowboy_req:inform(103, #{ 12 | <<"content-type">> => <<"text/plain">> 13 | }, Req), 14 | {ok, cowboy_req:reply(200, #{ 15 | <<"content-type">> => <<"text/plain">> 16 | }, <<"Hello world!">>, Req), State}. 17 | -------------------------------------------------------------------------------- /test/handlers/pool_ws_handler.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(pool_ws_handler). 4 | 5 | -export([init/4]). 6 | -export([handle/2]). 7 | 8 | init(_, _, _, #{user_opts := ReplyTo}) -> 9 | {ok, ReplyTo}. 10 | 11 | handle(Frame, ReplyTo) -> 12 | ReplyTo ! Frame, 13 | {ok, 0, ReplyTo}. 14 | -------------------------------------------------------------------------------- /test/handlers/proxied_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(proxied_h). 4 | 5 | -export([init/2]). 6 | 7 | -spec init(cowboy_req:req(), _) -> no_return(). 8 | init(Req, _) -> 9 | _ = cowboy_req:stream_reply(200, #{<<"content-type">> => <<"text/plain">>}, Req), 10 | %% We never return to allow querying the stream_info. 11 | receive after infinity -> ok end. 12 | -------------------------------------------------------------------------------- /test/handlers/push_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(push_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req, State) -> 8 | cowboy_req:push("/", #{<<"accept">> => <<"text/plain">>}, Req), 9 | cowboy_req:push("/empty", #{<<"accept">> => <<"text/plain">>}, Req), 10 | {ok, cowboy_req:reply(200, #{ 11 | <<"content-type">> => <<"text/plain">> 12 | }, <<"Hello world!">>, Req), State}. 13 | -------------------------------------------------------------------------------- /test/handlers/sse_clock_close_h.erl: -------------------------------------------------------------------------------- 1 | %% This module implements a loop handler that sends 2 | %% the current time every second using SSE. In contrast 3 | %% to sse_clock_h, this one sends a "Connection: close" 4 | %% header. 5 | 6 | -module(sse_clock_close_h). 7 | 8 | -export([init/2]). 9 | -export([info/3]). 10 | 11 | init(Req, State) -> 12 | self() ! timeout, 13 | {cowboy_loop, cowboy_req:stream_reply(200, #{ 14 | <<"content-type">> => <<"text/event-stream">>, 15 | <<"connection">> => <<"close">> 16 | }, Req), State}. 17 | 18 | info(timeout, Req, State) -> 19 | erlang:send_after(1000, self(), timeout), 20 | cowboy_req:stream_events(#{ 21 | data => cowboy_clock:rfc1123() 22 | }, nofin, Req), 23 | {ok, Req, State}. 24 | -------------------------------------------------------------------------------- /test/handlers/sse_clock_h.erl: -------------------------------------------------------------------------------- 1 | %% This module implements a loop handler that sends 2 | %% the current time every second using SSE. 3 | 4 | -module(sse_clock_h). 5 | 6 | -export([init/2]). 7 | -export([info/3]). 8 | 9 | init(Req, State) -> 10 | self() ! timeout, 11 | {cowboy_loop, cowboy_req:stream_reply(200, #{ 12 | <<"content-type">> => <<"text/event-stream">> 13 | }, Req), State}. 14 | 15 | info(timeout, Req, State) -> 16 | erlang:send_after(1000, self(), timeout), 17 | cowboy_req:stream_events(#{ 18 | data => data(State) 19 | }, nofin, Req), 20 | {ok, Req, State}. 21 | 22 | data(date) -> 23 | cowboy_clock:rfc1123(); 24 | data(Size) when is_integer(Size) -> 25 | lists:duplicate(Size, $0). 26 | -------------------------------------------------------------------------------- /test/handlers/sse_lone_id_h.erl: -------------------------------------------------------------------------------- 1 | %% This module implements a loop handler that sends 2 | %% a lone id: line. 3 | 4 | -module(sse_lone_id_h). 5 | 6 | -export([init/2]). 7 | -export([info/3]). 8 | 9 | init(Req, State) -> 10 | self() ! timeout, 11 | {cowboy_loop, cowboy_req:stream_reply(200, #{ 12 | <<"content-type">> => <<"text/event-stream">> 13 | }, Req), State}. 14 | 15 | info(timeout, Req, State) -> 16 | cowboy_req:stream_events(#{ 17 | id => <<"hello">> 18 | }, nofin, Req), 19 | {stop, Req, State}. 20 | -------------------------------------------------------------------------------- /test/handlers/sse_mime_param_h.erl: -------------------------------------------------------------------------------- 1 | %% This module implements a loop handler that sends 2 | %% a lone id: line. 3 | 4 | -module(sse_mime_param_h). 5 | 6 | -export([init/2]). 7 | -export([info/3]). 8 | 9 | init(Req, State) -> 10 | self() ! timeout, 11 | {cowboy_loop, cowboy_req:stream_reply(200, #{ 12 | <<"content-type">> => <<"text/event-stream;encoding=UTF-8">> 13 | }, Req), State}. 14 | 15 | info(timeout, Req, State) -> 16 | cowboy_req:stream_events(#{ 17 | id => <<"hello">> 18 | }, nofin, Req), 19 | {stop, Req, State}. 20 | -------------------------------------------------------------------------------- /test/handlers/stream_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(stream_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req0, State) -> 8 | Req = cowboy_req:stream_reply(200, #{ 9 | <<"content-type">> => <<"text/plain">> 10 | }, Req0), 11 | cowboy_req:stream_body(<<"Hello ">>, nofin, Req), 12 | cowboy_req:stream_body(<<"world!">>, nofin, Req), 13 | %% The stream will be closed by Cowboy. 14 | {ok, Req, State}. 15 | -------------------------------------------------------------------------------- /test/handlers/trailers_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(trailers_h). 4 | 5 | -export([init/2]). 6 | 7 | init(Req0, State) -> 8 | Req = cowboy_req:stream_reply(200, #{ 9 | <<"content-type">> => <<"text/plain">>, 10 | <<"trailer">> => <<"expires">> 11 | }, Req0), 12 | cowboy_req:stream_body(<<"Hello ">>, nofin, Req), 13 | cowboy_req:stream_body(<<"world!">>, nofin, Req), 14 | cowboy_req:stream_trailers(#{ 15 | <<"expires">> => <<"Sun, 10 Dec 2017 19:13:47 GMT">> 16 | }, Req), 17 | {ok, Req, State}. 18 | 19 | -------------------------------------------------------------------------------- /test/handlers/ws_cookie_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(ws_cookie_h). 4 | 5 | -export([init/2]). 6 | -export([websocket_handle/2]). 7 | -export([websocket_info/2]). 8 | 9 | init(Req0, _) -> 10 | Req = cowboy_req:set_resp_header(<<"set-cookie">>, 11 | [<<"ws_cookie=1; Secure; path=/">>], Req0), 12 | {cowboy_websocket, Req, undefined, #{ 13 | compress => true 14 | }}. 15 | 16 | websocket_handle({text, Data}, State) -> 17 | {[{text, Data}], State}; 18 | websocket_handle({binary, Data}, State) -> 19 | {[{binary, Data}], State}; 20 | websocket_handle(_Frame, State) -> 21 | {[], State}. 22 | 23 | websocket_info(_Info, State) -> 24 | {[], State}. 25 | -------------------------------------------------------------------------------- /test/handlers/ws_echo_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(ws_echo_h). 4 | 5 | -export([init/2]). 6 | -export([websocket_handle/2]). 7 | -export([websocket_info/2]). 8 | 9 | init(Req, _) -> 10 | {cowboy_websocket, Req, undefined, #{ 11 | compress => true 12 | }}. 13 | 14 | websocket_handle({text, Data}, State) -> 15 | {[{text, Data}], State}; 16 | websocket_handle({binary, Data}, State) -> 17 | {[{binary, Data}], State}; 18 | websocket_handle(_Frame, State) -> 19 | {[], State}. 20 | 21 | websocket_info(_Info, State) -> 22 | {[], State}. 23 | -------------------------------------------------------------------------------- /test/handlers/ws_frozen_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(ws_frozen_h). 4 | 5 | -export([init/2]). 6 | -export([websocket_init/1]). 7 | -export([websocket_handle/2]). 8 | -export([websocket_info/2]). 9 | 10 | init(Req, State) -> 11 | {cowboy_websocket, Req, State, #{ 12 | compress => true 13 | }}. 14 | 15 | websocket_init(Timeout) -> 16 | timer:sleep(Timeout), 17 | {ok, undefined}. 18 | 19 | websocket_handle(_Frame, State) -> 20 | {[], State}. 21 | 22 | websocket_info(_Info, State) -> 23 | {[], State}. 24 | -------------------------------------------------------------------------------- /test/handlers/ws_reject_h.erl: -------------------------------------------------------------------------------- 1 | %% This handler rejects all Websocket connections. 2 | -module(ws_reject_h). 3 | 4 | -export([init/2]). 5 | 6 | init(Req0, Env) -> 7 | {ok, cowboy_req:reply(400, #{}, <<"Upgrade rejected">>, Req0), Env}. 8 | -------------------------------------------------------------------------------- /test/handlers/ws_subprotocol_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(ws_subprotocol_h). 4 | 5 | -export([init/2]). 6 | -export([websocket_handle/2]). 7 | -export([websocket_info/2]). 8 | 9 | init(Req, State) -> 10 | Protos = cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req), 11 | init_protos(Req, State, Protos). 12 | 13 | init_protos(Req, State, undefined) -> 14 | {ok, cowboy_req:reply(400, #{}, <<"undefined">>, Req), State}; 15 | init_protos(Req, State, []) -> 16 | {ok, cowboy_req:reply(400, #{}, <<"nomatch">>, Req), State}; 17 | init_protos(Req0, State, [<<"echo">> | _]) -> 18 | Req = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, <<"echo">>, Req0), 19 | {cowboy_websocket, Req, State}; 20 | init_protos(Req, State, [_ | Protos]) -> 21 | init_protos(Req, State, Protos). 22 | 23 | websocket_handle({text, Data}, State) -> 24 | {[{text, Data}], State}; 25 | websocket_handle({binary, Data}, State) -> 26 | {[{binary, Data}], State}; 27 | websocket_handle(_Frame, State) -> 28 | {[], State}. 29 | 30 | websocket_info(_Info, State) -> 31 | {[], State}. 32 | -------------------------------------------------------------------------------- /test/handlers/ws_timeout_close_h.erl: -------------------------------------------------------------------------------- 1 | %% Feel free to use, reuse and abuse the code in this file. 2 | 3 | -module(ws_timeout_close_h). 4 | 5 | -export([init/2]). 6 | -export([websocket_init/1]). 7 | -export([websocket_handle/2]). 8 | -export([websocket_info/2]). 9 | 10 | init(Req, State) -> 11 | {cowboy_websocket, Req, State, #{ 12 | compress => true 13 | }}. 14 | 15 | websocket_init(Timeout) -> 16 | _ = erlang:send_after(Timeout, self(), timeout_close), 17 | {[], undefined}. 18 | 19 | websocket_handle(_Frame, State) -> 20 | {[], State}. 21 | 22 | websocket_info(timeout_close, State) -> 23 | {[{close, 3333, <<>>}], State}; 24 | websocket_info(_Info, State) -> 25 | {[], State}. 26 | -------------------------------------------------------------------------------- /test/ping_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(ping_SUITE). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | -import(ct_helper, [config/2]). 20 | -import(ct_helper, [doc/1]). 21 | -import(gun_test, [init_origin/3]). 22 | -import(gun_test, [receive_from/1]). 23 | 24 | all() -> 25 | ct_helper:all(?MODULE). 26 | 27 | %% Tests. 28 | 29 | h1_user_ping(Config0) -> 30 | doc("The PING frame cannot be used to test an HTTP/1.1 connection."), 31 | Config = gun_test:init_cowboy_tcp(?FUNCTION_NAME, #{}, Config0), 32 | OriginPort = config(port, Config), 33 | {ok, ConnPid} = gun:open("localhost", OriginPort, #{ 34 | protocols => [http] 35 | }), 36 | {ok, http} = gun:await_up(ConnPid), 37 | PingRef = gun:ping(ConnPid), 38 | receive 39 | {gun_down, ConnPid, http, {error, {ping_unsupported_by_protocol, PingRef}}, []} -> 40 | gun:close(ConnPid) 41 | after 1000 -> 42 | ct:pal("~p", [process_info(self(), messages)]), 43 | error(timeout) 44 | end. 45 | 46 | h2_user_ping(_) -> 47 | doc("The PING frame may be used to easily test an HTTP/2 connection."), 48 | {ok, OriginPid, OriginPort} = init_origin(tcp, http2, fun (_, _, Socket, Transport) -> 49 | {ok, Data} = Transport:recv(Socket, 9, infinity), 50 | <> = Data, 53 | {ok, Payload} = Transport:recv(Socket, Len, 1000), 54 | 8 = Len = byte_size(Payload), 55 | Ack = <<8:24, 6:8, %% PING 56 | 1:8, %% Ack flag 57 | 0:1, 0:31, Payload/binary>>, 58 | ok = Transport:send(Socket, Ack) 59 | end), 60 | {ok, ConnPid} = gun:open("localhost", OriginPort, #{ 61 | protocols => [http2] 62 | }), 63 | {ok, http2} = gun:await_up(ConnPid), 64 | handshake_completed = receive_from(OriginPid), 65 | PingRef = gun:ping(ConnPid), 66 | {notify, ping_ack, PingRef} = gun:await(ConnPid, undefined), 67 | gun:close(ConnPid). 68 | 69 | h2c_user_ping_via_http(_) -> 70 | doc("The PING frame may be used to easily test an HTTP/2 connection."), 71 | do_h2c_user_ping_tunnel(http). 72 | 73 | h2c_user_ping_via_https(_) -> 74 | doc("The PING frame may be used to easily test an HTTP/2 connection."), 75 | do_h2c_user_ping_tunnel(https). 76 | 77 | h2c_user_ping_via_h2c(_) -> 78 | doc("The PING frame may be used to easily test an HTTP/2 connection."), 79 | do_h2c_user_ping_tunnel(h2c). 80 | 81 | h2c_user_ping_via_h2(_) -> 82 | doc("The PING frame may be used to easily test an HTTP/2 connection."), 83 | do_h2c_user_ping_tunnel(h2). 84 | 85 | do_h2c_user_ping_tunnel(ProxyType) -> 86 | {ok, OriginPid, OriginPort} = init_origin(tcp, http2, fun (_, _, Socket, Transport) -> 87 | {ok, Data} = Transport:recv(Socket, 9, infinity), 88 | <> = Data, 91 | {ok, Payload} = Transport:recv(Socket, Len, 1000), 92 | 8 = Len = byte_size(Payload), 93 | Ack = <<8:24, 6:8, %% PING 94 | 1:8, %% Ack flag 95 | 0:1, 0:31, Payload/binary>>, 96 | ok = Transport:send(Socket, Ack) 97 | end), 98 | {ok, ProxyPid, ProxyPort} = tunnel_SUITE:do_proxy_start(ProxyType), 99 | {ProxyTransport, ProxyProtocol} = tunnel_SUITE:do_type(ProxyType), 100 | {ok, ConnPid} = gun:open("localhost", ProxyPort, #{ 101 | transport => ProxyTransport, 102 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], 103 | protocols => [ProxyProtocol] 104 | }), 105 | {ok, ProxyProtocol} = gun:await_up(ConnPid), 106 | tunnel_SUITE:do_handshake_completed(ProxyProtocol, ProxyPid), 107 | StreamRef = gun:connect(ConnPid, #{ 108 | host => "localhost", 109 | port => OriginPort, 110 | transport => tcp, 111 | protocols => [http2] 112 | }), 113 | {response, fin, 200, _} = gun:await(ConnPid, StreamRef), 114 | handshake_completed = receive_from(OriginPid), 115 | {up, http2} = gun:await(ConnPid, StreamRef), 116 | PingRef = gun:ping(ConnPid, #{tunnel => StreamRef}), 117 | {notify, ping_ack, PingRef} = gun:await(ConnPid, undefined), 118 | gun:close(ConnPid). 119 | -------------------------------------------------------------------------------- /test/rfc7230_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(rfc7230_SUITE). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | -import(ct_helper, [doc/1]). 20 | -import(gun_test, [init_origin/2]). 21 | -import(gun_test, [init_origin/3]). 22 | -import(gun_test, [receive_from/1]). 23 | 24 | all() -> 25 | ct_helper:all(?MODULE). 26 | 27 | %% Tests. 28 | 29 | host_default_port_http(_) -> 30 | doc("The default port for http should not be sent in the host header. (RFC7230 2.7.1)"), 31 | do_host_port(tcp, 80, <<>>). 32 | 33 | host_default_port_https(_) -> 34 | doc("The default port for https should not be sent in the host header. (RFC7230 2.7.2)"), 35 | do_host_port(tls, 443, <<>>). 36 | 37 | host_ipv6(_) -> 38 | doc("When connecting to a server using an IPv6 address the host " 39 | "header must wrap the address with brackets. (RFC7230 5.4, RFC3986 3.2.2)"), 40 | {ok, OriginPid, OriginPort} = init_origin(tcp6, http), 41 | {ok, ConnPid} = gun:open({0,0,0,0,0,0,0,1}, OriginPort, #{transport => tcp}), 42 | {ok, http} = gun:await_up(ConnPid), 43 | _ = gun:get(ConnPid, "/"), 44 | handshake_completed = receive_from(OriginPid), 45 | Data = receive_from(OriginPid), 46 | Lines = binary:split(Data, <<"\r\n">>, [global]), 47 | [<<"host: [::1]", _/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], 48 | gun:close(ConnPid). 49 | 50 | host_other_port_http(_) -> 51 | doc("Non-default ports for http must be sent in the host header. (RFC7230 2.7.1)"), 52 | do_host_port(tcp, 443, <<":443">>). 53 | 54 | host_other_port_https(_) -> 55 | doc("Non-default ports for https must be sent in the host header. (RFC7230 2.7.2)"), 56 | do_host_port(tls, 80, <<":80">>). 57 | 58 | do_host_port(Transport, DefaultPort, HostHeaderPort) -> 59 | {ok, OriginPid, OriginPort} = init_origin(Transport, http), 60 | {ok, ConnPid} = gun:open("localhost", OriginPort, #{ 61 | transport => Transport, 62 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}] 63 | }), 64 | {ok, http} = gun:await_up(ConnPid), 65 | %% Change the origin's port in the state to trigger the default port behavior. 66 | _ = sys:replace_state(ConnPid, fun({StateName, StateData}) -> 67 | {StateName, setelement(8, StateData, DefaultPort)} 68 | end, 5000), 69 | %% Confirm the default port is not sent in the request. 70 | _ = gun:get(ConnPid, "/"), 71 | handshake_completed = receive_from(OriginPid), 72 | Data = receive_from(OriginPid), 73 | Lines = binary:split(Data, <<"\r\n">>, [global]), 74 | [<<"host: localhost", Rest/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], 75 | HostHeaderPort = Rest, 76 | gun:close(ConnPid). 77 | 78 | transfer_encoding_overrides_content_length(_) -> 79 | doc("When both transfer-encoding and content-length are provided, " 80 | "content-length must be ignored. (RFC7230 3.3.3)"), 81 | {ok, _, OriginPort} = init_origin(tcp, http, 82 | fun(_, _, ClientSocket, ClientTransport) -> 83 | {ok, _} = ClientTransport:recv(ClientSocket, 0, 1000), 84 | ClientTransport:send(ClientSocket, 85 | "HTTP/1.1 200 OK\r\n" 86 | "content-length: 12\r\n" 87 | "transfer-encoding: chunked\r\n" 88 | "\r\n" 89 | "6\r\n" 90 | "hello \r\n" 91 | "6\r\n" 92 | "world!\r\n" 93 | "0\r\n\r\n" 94 | ) 95 | end), 96 | {ok, ConnPid} = gun:open("localhost", OriginPort), 97 | {ok, http} = gun:await_up(ConnPid), 98 | StreamRef = gun:get(ConnPid, "/"), 99 | {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), 100 | {ok, <<"hello world!">>} = gun:await_body(ConnPid, StreamRef), 101 | gun:close(ConnPid). 102 | -------------------------------------------------------------------------------- /test/send_errors_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Björn Svensson 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(send_errors_SUITE). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | -import(ct_helper, [doc/1]). 20 | -import(gun_test, [http2_handshake/2]). 21 | 22 | suite() -> 23 | [{timetrap, 180000}]. 24 | 25 | all() -> 26 | [{group, gun}]. 27 | 28 | groups() -> 29 | [{gun, [parallel], ct_helper:all(?MODULE)}]. 30 | 31 | init_per_suite(Config) -> 32 | case os:type() of 33 | {_, linux} -> Config; 34 | _ -> {skip, "This test suite is Linux-only due to socket juggling."} 35 | end. 36 | 37 | end_per_suite(_) -> ok. 38 | 39 | %% Tests. 40 | 41 | http2_send_request_fail(_) -> 42 | doc("Handle send failures of requests in HTTP/2."), 43 | {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), 44 | {ok, {_, Port}} = inet:sockname(ListenSocket), 45 | %% Socket buffers needs to be smaller than local_window/ConnWindow 46 | {ok, Pid} = gun:open("localhost", Port, #{ 47 | protocols => [http2], 48 | tcp_opts => [ 49 | {send_timeout, 250}, 50 | {send_timeout_close, true}, 51 | {sndbuf, 2048}, 52 | {nodelay, true} 53 | ] 54 | }), 55 | {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), 56 | inet:setopts(ClientSocket, [{recbuf, 512}]), 57 | http2_handshake(ClientSocket, gen_tcp), 58 | {ok, http2} = gun:await_up(Pid), 59 | post_loop(Pid, 1000), %% Fill buffer 60 | receive 61 | {gun_error, Pid, _, {closed, {error, _}}} -> 62 | gun:close(Pid); 63 | Msg -> 64 | error({fail, Msg}) 65 | after 5000 -> 66 | error(timeout) 67 | end. 68 | 69 | http2_send_ping_fail(_) -> 70 | doc("Handle send failures of ping in HTTP/2."), 71 | {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), 72 | {ok, {_, Port}} = inet:sockname(ListenSocket), 73 | {ok, Pid} = gun:open("localhost", Port, #{ 74 | protocols => [http2], 75 | http2_opts => #{keepalive => 1}, 76 | tcp_opts => [ 77 | {send_timeout, 250}, 78 | {send_timeout_close, true}, 79 | {sndbuf, 256}, 80 | {nodelay, true} 81 | ] 82 | }), 83 | {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), 84 | inet:setopts(ClientSocket, [{recbuf, 256}]), 85 | http2_handshake(ClientSocket, gen_tcp), 86 | {ok, http2} = gun:await_up(Pid), 87 | receive 88 | {gun_down, Pid, http2, {error, _}, []} -> 89 | gun:close(Pid); 90 | Msg -> 91 | error({fail, Msg}) 92 | after 15000 -> 93 | error(timeout) 94 | end. 95 | 96 | http2_send_ping_ack_fail(_) -> 97 | doc("Handle send failures of ping ack in HTTP/2."), 98 | {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), 99 | {ok, {_, Port}} = inet:sockname(ListenSocket), 100 | {ok, Pid} = gun:open("localhost", Port, #{ 101 | protocols => [http2], 102 | http2_opts => #{keepalive => infinity}, 103 | tcp_opts => [ 104 | {send_timeout, 250}, 105 | {send_timeout_close, true}, 106 | {sndbuf, 256}, 107 | {nodelay, true} 108 | ] 109 | }), 110 | {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), 111 | inet:setopts(ClientSocket, [{recbuf, 256}]), 112 | http2_handshake(ClientSocket, gen_tcp), 113 | {ok, http2} = gun:await_up(Pid), 114 | ping_loop(ClientSocket, 5000), %% Send pings triggering ping acks 115 | receive 116 | {gun_down, Pid, http2, {error, _}, []} -> 117 | gun:close(Pid); 118 | Msg -> 119 | error({fail, Msg}) 120 | after 15000 -> 121 | error(timeout) 122 | end. 123 | 124 | %% Helpers 125 | 126 | post_loop(_Pid, 0) -> 127 | ok; 128 | post_loop(Pid, Loops) -> 129 | Body = <<0:1000>>, 130 | gun:post(Pid, "/organizations/ninenines", 131 | [{<<"content-type">>, "application/octet-stream"}], 132 | Body), 133 | post_loop(Pid, Loops - 1). 134 | 135 | ping_loop(_Socket, 0) -> 136 | ok; 137 | ping_loop(Socket, Loops) -> 138 | gun_tcp:send(Socket, cow_http2:ping(0)), 139 | ping_loop(Socket, Loops - 1). 140 | -------------------------------------------------------------------------------- /test/sse_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) Loïc Hoguin 2 | %% 3 | %% Permission to use, copy, modify, and/or distribute this software for any 4 | %% purpose with or without fee is hereby granted, provided that the above 5 | %% copyright notice and this permission notice appear in all copies. 6 | %% 7 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -module(sse_SUITE). 16 | -compile(export_all). 17 | -compile(nowarn_export_all). 18 | 19 | -import(ct_helper, [config/2]). 20 | 21 | all() -> 22 | [http_clock, http2_clock, lone_id, with_mime_param, http_clock_close]. 23 | 24 | init_per_suite(Config) -> 25 | gun_test:init_cowboy_tls(?MODULE, #{ 26 | env => #{dispatch => cowboy_router:compile(init_routes())} 27 | }, Config). 28 | 29 | end_per_suite(Config) -> 30 | cowboy:stop_listener(config(ref, Config)). 31 | 32 | init_routes() -> [ 33 | {"localhost", [ 34 | {"/clock", sse_clock_h, date}, 35 | {"/lone_id", sse_lone_id_h, []}, 36 | {"/with_mime_param", sse_mime_param_h, []}, 37 | {"/connection_close", sse_clock_close_h, []} 38 | ]} 39 | ]. 40 | 41 | http_clock(Config) -> 42 | {ok, Pid} = gun:open("localhost", config(port, Config), #{ 43 | transport => tls, 44 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], 45 | protocols => [http], 46 | http_opts => #{content_handlers => [gun_sse_h, gun_data_h]} 47 | }), 48 | {ok, http} = gun:await_up(Pid), 49 | do_clock_common(Pid, "/clock"). 50 | 51 | http2_clock(Config) -> 52 | {ok, Pid} = gun:open("localhost", config(port, Config), #{ 53 | transport => tls, 54 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], 55 | protocols => [http2], 56 | http2_opts => #{content_handlers => [gun_sse_h, gun_data_h]} 57 | }), 58 | {ok, http2} = gun:await_up(Pid), 59 | do_clock_common(Pid, "/clock"). 60 | 61 | http_clock_close(Config) -> 62 | {ok, Pid} = gun:open("localhost", config(port, Config), #{ 63 | transport => tls, 64 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], 65 | protocols => [http], 66 | http_opts => #{ 67 | content_handlers => [gun_sse_h, gun_data_h], 68 | closing_timeout => 1000 69 | } 70 | }), 71 | {ok, http} = gun:await_up(Pid), 72 | do_clock_common(Pid, "/connection_close"). 73 | 74 | do_clock_common(Pid, Path) -> 75 | Ref = gun:get(Pid, Path, [ 76 | {<<"host">>, <<"localhost">>}, 77 | {<<"accept">>, <<"text/event-stream">>} 78 | ]), 79 | receive 80 | {gun_response, Pid, Ref, nofin, 200, Headers} -> 81 | {_, <<"text/event-stream">>} 82 | = lists:keyfind(<<"content-type">>, 1, Headers), 83 | event_loop(Pid, Ref, 3) 84 | after 5000 -> 85 | error(timeout) 86 | end. 87 | 88 | event_loop(Pid, _, 0) -> 89 | gun:close(Pid); 90 | event_loop(Pid, Ref, N) -> 91 | receive 92 | {gun_sse, Pid, Ref, Event} -> 93 | ct:pal("Event: ~p~n", [Event]), 94 | #{ 95 | last_event_id := <<>>, 96 | event_type := <<"message">>, 97 | data := Data 98 | } = Event, 99 | true = is_list(Data) orelse is_binary(Data), 100 | event_loop(Pid, Ref, N - 1); 101 | Other -> 102 | ct:pal("Other: ~p~n", [Other]) 103 | after 10000 -> 104 | error(timeout) 105 | end. 106 | 107 | lone_id(Config) -> 108 | {ok, Pid} = gun:open("localhost", config(port, Config), #{ 109 | transport => tls, 110 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], 111 | protocols => [http], 112 | http_opts => #{content_handlers => [gun_sse_h, gun_data_h]} 113 | }), 114 | {ok, http} = gun:await_up(Pid), 115 | Ref = gun:get(Pid, "/lone_id", [ 116 | {<<"host">>, <<"localhost">>}, 117 | {<<"accept">>, <<"text/event-stream">>} 118 | ]), 119 | receive 120 | {gun_response, Pid, Ref, nofin, 200, Headers} -> 121 | {_, <<"text/event-stream">>} 122 | = lists:keyfind(<<"content-type">>, 1, Headers), 123 | receive 124 | {gun_sse, Pid, Ref, Event} -> 125 | #{last_event_id := <<"hello">>} = Event, 126 | 1 = maps:size(Event), 127 | gun:close(Pid) 128 | after 10000 -> 129 | error(timeout) 130 | end 131 | after 5000 -> 132 | error(timeout) 133 | end. 134 | 135 | with_mime_param(Config) -> 136 | {ok, Pid} = gun:open("localhost", config(port, Config), #{ 137 | transport => tls, 138 | tls_opts => [{verify, verify_none}, {versions, ['tlsv1.2']}], 139 | protocols => [http], 140 | http_opts => #{content_handlers => [gun_sse_h, gun_data_h]} 141 | }), 142 | {ok, http} = gun:await_up(Pid), 143 | Ref = gun:get(Pid, "/with_mime_param", [ 144 | {<<"host">>, <<"localhost">>}, 145 | {<<"accept">>, <<"text/event-stream">>} 146 | ]), 147 | receive 148 | {gun_response, Pid, Ref, nofin, 200, Headers} -> 149 | {_, <<"text/event-stream;", _Params/binary>>} 150 | = lists:keyfind(<<"content-type">>, 1, Headers), 151 | receive 152 | {gun_sse, Pid, Ref, Event} -> 153 | #{last_event_id := <<"hello">>} = Event, 154 | 1 = maps:size(Event), 155 | gun:close(Pid) 156 | after 10000 -> 157 | error(timeout) 158 | end 159 | after 5000 -> 160 | error(timeout) 161 | end. 162 | -------------------------------------------------------------------------------- /test/wpt/cookies/attributes_expires.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1; Expires=Fri, 01 Jan 2038 00:00:00 GMT","expected":"test=1","name":"Set cookie with expires value containing a comma"},{"cookie":"test=2; Expires=Fri 01 Jan 2038 00:00:00 GMT, baz=qux","expected":"test=2","name":"Set cookie with expires value followed by comma"},{"cookie":"test=3; Expires=Fri, 01 Jan 2038 00:00:00 GMT","expected":"test=3","name":"Set cookie with future expiration"},{"cookie":["test=expired; Expires=Fri, 07 Aug 2007 08:04:19 GMT","test=4; Expires=Fri, 07 Aug 2027 08:04:19 GMT"],"expected":"test=4","name":"Set expired cookie along with valid cookie"},{"cookie":"test=5; expires=Thu, 10 Apr 1980 16:33:12 GMT","expected":"","name":"Don't set cookie with expires set to the past"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/attributes_invalid.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1; lol; Path=/","expected":"test=1","name":"Set cookie with invalid attribute","defaultPath":false},{"cookie":"test=2; Path=/; lol","expected":"test=2","name":"Set cookie ending with invalid attribute.","defaultPath":false},{"cookie":"test=3; Path=/; 'lol'","expected":"test=3","name":"Set cookie ending with quoted invalid attribute.","defaultPath":false},{"cookie":"test=4; Path=/; \"lol\"","expected":"test=4","name":"Set cookie ending with double-quoted invalid attribute.","defaultPath":false},{"cookie":"test=5; Path=/; lol=","expected":"test=5","name":"Set cookie ending with invalid attribute equals.","defaultPath":false},{"cookie":"test=6; lol=\"aaa;bbb\"; Path=/","expected":"test=6","name":"Set cookie with two invalid attributes (lol=\"aaa and bbb).","defaultPath":false},{"cookie":"test=7; Path=/; lol=\"aaa;bbb\"","expected":"test=7","name":"Set cookie ending with two invalid attributes (lol=\"aaa and bbb).","defaultPath":false},{"cookie":"test=8; \"Secure\"","expected":"test=8","name":"Set cookie for quoted Secure attribute"},{"cookie":"test=9; Secure qux","expected":"test=9","name":"Set cookie for Secure qux"},{"cookie":"test=10; b,az=qux","expected":"test=10","name":"Ignore invalid attribute name with comma"},{"cookie":"test=11; baz=q,ux","expected":"test=11","name":"Ignore invalid attribute value with comma"},{"cookie":" test = 12 ;foo;;; bar","expected":"test=12","name":"Set cookie ignoring multiple invalid attributes, whitespace, and semicolons"},{"cookie":" test=== 13 ;foo;;; bar","expected":"test=== 13","name":"Set cookie with multiple '='s in its value, ignoring multiple invalid attributes, whitespace, and semicolons"},{"cookie":"test=14; version=1;","expected":"test=14","name":"Set cookie with (invalid) version=1 attribute"},{"cookie":"test=15; version=1000;","expected":"test=15","name":"Set cookie with (invalid) version=1000 attribute"},{"cookie":"test=16; customvalue='1000 or more';","expected":"test=16","name":"Set cookie ignoring anything after ; (which looks like an invalid attribute)"},{"cookie":"test=17; customvalue='1000 or more'","expected":"test=17","name":"Set cookie ignoring anything after ; (which looks like an invalid attribute, with no trailing semicolon)"},{"cookie":"test=18; foo=bar, a=b","expected":"test=18","name":"Ignore keys after semicolon"},{"cookie":"test=19;max-age=3600, c=d;path=/","expected":"test=19","name":"Ignore attributes after semicolon","defaultPath":false},{"cookie":["testA=20","=","testb=20"],"expected":"testA=20; testb=20","name":"Ignore `Set-Cookie: =`"},{"cookie":["test=21",""],"expected":"test=21","name":"Ignore empty cookie string"},{"cookie":["test22","="],"expected":"test22","name":"Ignore `Set-Cookie: =` with other `Set-Cookie` headers"},{"cookie":["testA23","; testB23"],"expected":"testA23","name":"Ignore name- and value-less `Set-Cookie: ; bar`"},{"cookie":["test24"," "],"expected":"test24","name":"Ignore name- and value-less `Set-Cookie: `"},{"cookie":["test25","\t"],"expected":"test25","name":"Ignore name- and value-less `Set-Cookie: \\t`"},{"cookie":"test=26; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;","expected":"","name":"Ignore cookie with domain that won't domain match (along with other invalid noise)"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/attributes_max_age.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1; Max-Age=50,399","expected":"test=1","name":"Ignore max-age attribute with invalid non-zero-digit (containing a comma)"},{"cookie":"test=2; max-age=10000","expected":"test=2","name":"Set cookie with age"},{"cookie":"test=3; max-age=0","expected":"","name":"Set no cookie with max-age=0"},{"cookie":"test=4; max-age=-1","expected":"","name":"Set no cookie with max-age=-1"},{"cookie":"test=5; max-age=-20","expected":"","name":"Set no cookie with max-age=-20"},{"cookie":["testA=6; max-age=60","testB=6; max-age=60"],"expected":"testA=6; testB=6","name":"Set multiple cookies with max-age attribute"},{"cookie":["testA=7; max-age=60","testB=7; max-age=60","testA=differentvalue; max-age=0"],"expected":"testB=7","name":"Expire later cookie with same name and max-age=0"},{"cookie":["testA=8; max-age=60","testB=8; max-age=60","testA=differentvalue; max-age=0","testC=8; max-age=0"],"expected":"testB=8","name":"Expire later cookie with same name and max-age=0, and don't set cookie with max-age=0"},{"cookie":["test=\"9! = foo;bar\";\" parser; max-age=6","test9; max-age=2.63,"],"expected":"test=\"9! = foo; test9","name":"Set mulitiple cookies with valid max-age values"},{"cookie":["test=10; max-age=0","test10; max-age=0"],"expected":"","name":"Don't set multiple cookies with max-age=0"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/attributes_path.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1; Path","expected":"test=1","name":"Set cookie for bare Path"},{"cookie":"test=2; Path=","expected":"test=2","name":"Set cookie for Path="},{"cookie":"test=3; Path=/","expected":"test=3","name":"Set cookie for Path=/","defaultPath":false},{"cookie":"test=4; Path=/qux","expected":"","name":"No cookie returned for mismatched path","defaultPath":false},{"cookie":"test=5; Path =/qux","expected":"","name":"No cookie returned for path space equals mismatched path","defaultPath":false},{"cookie":"test=6; Path= /qux","expected":"","name":"No cookie returned for path equals space mismatched path","defaultPath":false},{"cookie":"test=7; Path=/qux ; taz","expected":"","name":"No cookie returned for mismatched path and attribute","defaultPath":false},{"cookie":"test=8; Path=/qux; Path=/","expected":"test=8","name":"Set cookie for mismatched and root path"},{"cookie":"test=9; Path=/; Path=/qux","expected":"","name":"No cookie returned for root and mismatched path","defaultPath":false},{"cookie":"test=10; Path=/lol; Path=/qux","expected":"","name":"No cookie returned for multiple mismatched paths","defaultPath":false},{"cookie":["testA=11; path=/","testB=11; path=/cookies/attributes"],"expected":"testB=11; testA=11","name":"Return 2 cookies sorted by matching path length (earlier name with shorter path set first)","defaultPath":false},{"cookie":["testB=12; path=/","testA=12; path=/cookies/attributes"],"expected":"testA=12; testB=12","name":"Return 2 cookies sorted by matching path length (later name with shorter path set first)","defaultPath":false},{"cookie":["testA=13; path=/cookies/attributes","testB=13; path=/"],"expected":"testA=13; testB=13","name":"Return 2 cookies sorted by matching path length (earlier name with longer path set first)","defaultPath":false},{"cookie":["testB=14; path=/cookies/attributes","testA=14; path=/"],"expected":"testB=14; testA=14","name":"Return 2 cookies sorted by matching path length (later name with longer path set first)","defaultPath":false},{"cookie":["test=15; path=/cookies/attributes/foo"],"expected":"","name":"No cookie returned for partial path match","defaultPath":false},{"cookie":["test=16","test=0; path=/cookies/attributes/foo"],"expected":"test=16","name":"No cookie returned for partial path match, return cookie for default path"},{"cookie":["test=17; path= /"],"expected":"test=17","name":"Return cookie for path= / (whitespace after equals)"},{"cookie":["test=18; path=/cookies/ATTRIBUTES"],"expected":"","name":"No cookie returned for case mismatched path","defaultPath":false},{"cookie":["testA=19; \tpath\t=\t/cookies/attributes","testB=19; \tpath\t=\t/book"],"expected":"testA=19","name":"Return cookie A on path match, no cookie returned for path mismatch (plus whitespace)","defaultPath":false},{"cookie":["test=20; path=; path=/dog"],"expected":"","name":"No cookie returned for mismatched path (after bare path=)","defaultPath":false},{"cookie":["test=21; path=/dog; path="],"expected":"test=21","name":"Return cookie for bare path= (after mismatched path)"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/attributes_secure.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1; Secure","expected":"test=1","name":"Set cookie for Secure attribute"},{"cookie":"test=2; seCURe","expected":"test=2","name":"Set cookie for seCURe attribute"},{"cookie":"test=3; Secure=","expected":"test=3","name":"Set cookie for for Secure= attribute"},{"cookie":"test=4; Secure=aaaa","expected":"test=4","name":"Set cookie for Secure=aaaa"},{"cookie":"test=5; Secure =aaaaa","expected":"test=5","name":"Set cookie for Secure space equals"},{"cookie":"test=6; Secure= aaaaa","expected":"test=6","name":"Set cookie for Secure equals space"},{"cookie":"test=7; Secure","expected":"test=7","name":"Set cookie for spaced Secure"},{"cookie":"test=8; Secure ;","expected":"test=8","name":"Set cookie for space Secure with ;"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/attributes_secure_non_secure.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1; Secure","expected":"","name":"(non-secure) Ignore cookie for Secure attribute"},{"cookie":"test=2; seCURe","expected":"","name":"(non-secure) Ignore cookie for seCURe attribute"},{"cookie":"test=3; Secure=","expected":"","name":"(non-secure) Ignore cookie for for Secure= attribute"},{"cookie":"test=4; Secure=aaaa","expected":"","name":"(non-secure) Ignore cookie for Secure=aaaa"},{"cookie":"test=5; Secure =aaaaa","expected":"","name":"(non-secure) Ignore cookie for Secure space equals"},{"cookie":"test=6; Secure= aaaaa","expected":"","name":"(non-secure) Ignore cookie for Secure equals space"},{"cookie":"test=7; Secure","expected":"","name":"(non-secure) Ignore cookie for spaced Secure"},{"cookie":"test=8; Secure ;","expected":"","name":"(non-secure) Ignore cookie for space Secure with ;"},{"cookie":"__Secure-test=9; Secure","expected":"","name":"(non-secure) Ignore cookie with __Secure- prefix and Secure"},{"cookie":"__Secure-test=10","expected":"","name":"(non-secure) Ignore cookie with __Secure- prefix and without Secure"},{"cookie":"__%53ecure-test=11","expected":"__%53ecure-test=11","name":"(non-secure) Cookie returned with __%53ecure- prefix and without Secure"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/encoding_charset.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test=1春节回家路·春运完全手册","expected":"test=1春节回家路·春运完全手册","name":"ASCII name and utf-8 value"},{"cookie":"тест=2","expected":"тест=2","name":"utf-8 name and ASCII value"},{"cookie":"test=\"3春节回家路·春运完全手册\"","expected":"test=\"3春节回家路·春运完全手册\"","name":"ASCII name and quoted utf-8 value"},{"cookie":"春节回=4家路·春运完全手册","expected":"春节回=4家路·春运完全手册","name":"utf-8 name and value"},{"cookie":"\"春节回=5家路·春运完全手册\"","expected":"\"春节回=5家路·春运完全手册\"","name":"quoted utf-8 name and value"},{"cookie":"春节回=6家路·春运; 完全手册","expected":"春节回=6家路·春运","name":"utf-8 name and value, with (invalid) utf-8 attribute"}] 2 | -------------------------------------------------------------------------------- /test/wpt/cookies/name.json: -------------------------------------------------------------------------------- 1 | [{"cookie":"test1=; path = /","expected":"test1=","name":"Set valueless cookie to its name with empty value","defaultPath":false},{"cookie":"=test=2","expected":"test=2","name":"Set a nameless cookie (that has an = in its value)"},{"cookie":"===test=2b","expected":"==test=2b","name":"Set a nameless cookie (that has multiple ='s in its value)"},{"cookie":"=test2c","expected":"test2c","name":"Set a nameless cookie"},{"cookie":"test =3","expected":"test=3","name":"Remove trailing WSP characters from the name string"},{"cookie":" test=4","expected":"test=4","name":"Remove leading WSP characters from the name string"},{"cookie":["\"test=5\"=test","\"test=5"],"expected":"\"test=5","name":"Only return the new cookie (with the same name)"},{"cookie":"test6;cool=dude","expected":"test6","name":"Ignore invalid attributes after nameless cookie"},{"cookie":"$Version=1; test=7","expected":"$Version=1","name":"Ignore invalid attributes after valid name (that looks like Cookie2 Version attribute)"},{"cookie":"test test=8","expected":"test test=8","name":"Set a cookie that has whitespace in its name"},{"cookie":"\"test9;test\"=9","expected":"\"test9","name":"Set a nameless cookie ignoring characters after first ;"},{"cookie":"\"test\"10;baz\"=qux","expected":"\"test\"10","name":"Set a nameless cookie ignoring characters after first ; (2)"},{"cookie":["=test=11","test11"],"expected":"test11","name":"Return the most recent nameless cookie"},{"cookie":["test11","test11a"],"expected":"test11a","name":"Return the most recent nameless cookie, without leading ="},{"cookie":["test11","test11a","=test11b"],"expected":"test11b","name":"Return the most recent nameless cookie, even if preceded by ="},{"cookie":["test11","test11a","=test11b","test=11c"],"expected":"test11b; test=11c","name":"Return the most recent nameless cookie, even if preceded by =, in addition to other valid cookie"},{"cookie":["test12=11","test12=12"],"expected":"test12=12","name":"Use last value for cookies with identical names"},{"cookie":["testA=13","testB=13"],"expected":"testA=13; testB=13","name":"Keep first-in, first-out name order"},{"cookie":["a=test14","z=test14"],"expected":"a=test14; z=test14","name":"Keep first-in, first-out single-char name order"},{"cookie":["z=test15","a=test15"],"expected":"z=test15; a=test15","name":"Keep non-alphabetic first-in, first-out name order"},{"cookie":"z=test16, a=test16","expected":"z=test16, a=test16","name":"Keep first-in, first-out order if comma-separated"},{"cookie":["testA=16","=test16","testB=16"],"expected":"testA=16; test16; testB=16","name":"Set nameless cookie, given `Set-Cookie: =test16`"},{"cookie":["test17a","test17b"],"expected":"test17b","name":"Overwrite nameless cookie"},{"cookie":"=","expected":"","name":"Ignore cookie with empty name and empty value"},{"cookie":"","expected":"","name":"Ignore cookie with no name or value"},{"cookie":"%74%65%73%74=20","expected":"%74%65%73%74=20","name":"URL-encoded cookie name is not decoded"}] 2 | -------------------------------------------------------------------------------- /test/ws_autobahn_SUITE_data/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "ws://localhost:33080", 3 | 4 | "cases": ["*"], 5 | "exclude-cases": [], 6 | "exclude-agent-cases": {} 7 | } 8 | --------------------------------------------------------------------------------