{"id":13800617,"url":"https://github.com/inaka/shotgun","last_synced_at":"2025-05-16T05:07:11.796Z","repository":{"id":18741766,"uuid":"21953558","full_name":"inaka/shotgun","owner":"inaka","description":"For the times you need more than just a gun.","archived":false,"fork":false,"pushed_at":"2024-10-07T12:46:07.000Z","size":405,"stargazers_count":168,"open_issues_count":16,"forks_count":45,"subscribers_count":49,"default_branch":"master","last_synced_at":"2025-04-06T17:05:20.258Z","etag":null,"topics":["erlang","hacktoberfest","shotgun","sse"],"latest_commit_sha":null,"homepage":"http://inaka.github.io/shotgun/","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inaka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-07-17T18:48:17.000Z","updated_at":"2025-01-22T15:12:04.000Z","dependencies_parsed_at":"2024-12-13T02:03:08.211Z","dependency_job_id":"eb388d92-e58c-4435-a7ad-f5c9c03d81fd","html_url":"https://github.com/inaka/shotgun","commit_stats":{"total_commits":177,"total_committers":38,"mean_commits":4.657894736842105,"dds":0.728813559322034,"last_synced_commit":"007af89942d58b60c39295097a64746a5844a2b6"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fshotgun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fshotgun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fshotgun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fshotgun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inaka","download_url":"https://codeload.github.com/inaka/shotgun/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254471060,"owners_count":22076585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","hacktoberfest","shotgun","sse"],"created_at":"2024-08-04T00:01:14.353Z","updated_at":"2025-05-16T05:07:06.789Z","avatar_url":"https://github.com/inaka.png","language":"Erlang","readme":"shotgun\n=======\n\nFor the times you need more than just a [gun](https://github.com/ninenines/gun).\n\n![](http://lbsommer-author.yolasite.com/resources/Funny%20gun%20sign%2017.jpg)\n\n## Rationale\n\nAfter using the [gun](http://github.com/extend/gun) library on a project where\nwe needed to consume Server-sent Events (SSE) we found that it provided great\nflexibility, at the cost of having to handle each raw message and data,\nincluding the construction of the response body data.\nAlthough this is great for a lot of scenarios, it can get cumbersome and\nrepetitive after implementing it a couple of times. This is why we ended up\ncreating **shotgun**, an HTTP client that uses **gun** behind the curtains but\nprovides a simple API that has out-of-the-box support for SSE.\n\n## Usage\n\n*shotgun* uses maps and hence requires Erlang 17 to compile and run.\n\n*shotgun* is an OTP application, so before being able to use it, it has to\nbe started. Either add it as one of the applications in your\n[`.app`](http://www.erlang.org/doc/man/app.html) file or run the following\ncode:\n\n```erlang\napplication:ensure_all_started(shotgun).\n```\n\n### Regular Requests\n\nOnce the application is started a connection needs to be created in order to\nstart making requests:\n\n```erlang\n{ok, Conn} = shotgun:open(\"google.com\", 80),\n{ok, Response} = shotgun:get(Conn, \"/\"),\nio:format(\"~p~n\", [Response]),\nshotgun:close(Conn).\n```\n\nWhich results in:\n\n```erlang\n#{body =\u003e \u003c\u003c\"\u003cHTML\u003e\u003cHEAD\u003e\"...\u003e\u003e,\n  headers =\u003e [\n     {\u003c\u003c\"location\"\u003e\u003e,\u003c\u003c\"http://www.google.com/adfs\"\u003e\u003e},\n     {\u003c\u003c\"content-type\"\u003e\u003e,\u003c\u003c\"text/html; charset=UTF-8\"\u003e\u003e},\n     {\u003c\u003c\"x-content-type-options\"\u003e\u003e,\u003c\u003c\"nosniff\"\u003e\u003e},\n     {\u003c\u003c\"date\"\u003e\u003e,\u003c\u003c\"Fri, 17 Oct 2014 17:18:32 GMT\"\u003e\u003e},\n     {\u003c\u003c\"expires\"\u003e\u003e,\u003c\u003c\"Sun, 16 Nov 2014 17:18:32 GMT\"\u003e\u003e},\n     {\u003c\u003c\"cache-control\"\u003e\u003e,\u003c\u003c\"public, max-age=2592000\"\u003e\u003e},\n     {\u003c\u003c\"server\"\u003e\u003e,\u003c\u003c\"sffe\"\u003e\u003e},\n     {\u003c\u003c\"content-length\"\u003e\u003e,\u003c\u003c\"223\"\u003e\u003e},\n     {\u003c\u003c\"x-xss-protection\"\u003e\u003e,\u003c\u003c\"1; mode=block\"\u003e\u003e},\n     {\u003c\u003c\"alternate-protocol\"\u003e\u003e,\u003c\u003c\"80:quic,p=0.01\"\u003e\u003e}\n   ],\n   status_code =\u003e 302}\n}\n\n%= ok\n```\n\nImmediately after opening a connection we did a GET request, where we didn't\nspecify any headers or options. Every HTTP method has its own **shotgun**\nfunction that takes a connection, a uri (which needs to include the slash),\na headers map or a proplist containing the headers, and an options map. \nSome of the functions (`post/5`, `put/5` and `patch/5`) also take a body \nargument.\n\nAlternatively there's a generic `request/6` function in which the user can\nspecify the HTTP method as an argument in the form of an atom: `get`, `head`,\n`options`, `delete`, `post`, `put` or `patch`.\n\n**IMPORTANT:** When you are done using the shotgun connection remember to close\nit with `shotgun:close/1`.\n\n### HTTP Secure Requests\n\nIt is possible to tell shotgun to use SSL by providing the atom `https` as the\nthird argument when creating a connection with to the `open` function. Just\nlike when performing HTTP requests it is also necessary to specify\na port. HTTPS servers typically listen for connections on port 443 and this\nwill be the most likely value you'll need to use.\n\n### Basic Authentication\n\nIf you need to provide basic authentication credentials in your requests, it is\nas easy as specifying a `basic_auth` entry in the headers map:\n\n```erlang\n{ok, Conn} = shotgun:open(\"site.com\", 80),\n{ok, Response} = shotgun:get(Conn, \"/user\", #{basic_auth =\u003e {\"user\", \"password\"}}),\n, or\n{ok, Response} = shotgun:get(Conn, \"/user\", [{basic_auth, {\"user\", \"password\"}}]),\nshotgun:close(Conn).\n```\n\n### Specifying a Timeout\n\nThe `timeout` option can be used to specify a value for all types of requests:\n\n```erlang\n{ok, Conn} = shotgun:open(\"google.com\", 80).\n{error, Error} = shotgun:get(Conn, \"/\", #{}, #{timeout =\u003e 10}).\nio:format(\"~p~n\", [Error]).\n%%= {timeout,{gen_fsm,sync_send_event,[\u003c0.368.0\u003e,{get,{\"/\",[],[]}},10]}}\nshotgun:close(Conn).\n```\n\nThe default `timeout` value is 5000 if none is specified.\n\n### Consuming Server-sent Events\n\nTo use **shotgun** with endpoints that generate SSE the request must be\nconfigured using some values in the options map, which supports the following\nentries:\n\n- `async ::boolean()`: specifies if the request performed will return a chunked\nresponse. **It currently only works for GET requests.**. Default value is\n`false`.\n\n- `async_mode :: binary | sse`: when `async` is `true` the mode specifies\nhow the data received will be processed. `binary` mode treats each chunk received\nas raw binary. `sse` mode buffers each chunk, splitting the data received into\nSSE. Default value is `binary`.\n\n- `handle_event :: fun((fin | nofin, reference(), binary()) -\u003e any())`: this function\nwill be called each time either a chunk is received (`async_mode` = `binary`) or\nan event is parsed (`async_mode` = `sse`). If no handle_event function is\nprovided the data received is added to a queue, whose values can be obtained\ncalling the `shotgun:events/1`. Default value is `undefined`.\n\nThe following is an example of the usage of **shotgun** when consuming SSE.\n\n```erlang\n{ok, Conn} = shotgun:open(\"localhost\", 8080).\n%= {ok,\u003c0.6003.0\u003e}\n\nOptions = #{async =\u003e true, async_mode =\u003e sse},\n{ok, Ref} = shotgun:get(Conn, \"/events\", #{}, Options).\n%= {ok,#Ref\u003c0.0.1.186238\u003e}\n\n% Some event are generated on the server...\nEvents = shotgun:events(Conn).\n%= [{nofin, #Ref\u003c0.0.1.186238\u003e, \u003c\u003c\"data: pong\"\u003e\u003e}, {nofin, #Ref\u003c0.0.1.186238\u003e, \u003c\u003c\"data: pong\"\u003e\u003e}]\n\nshotgun:events(Conn).\n%= []\n```\n\nNotice how the second call to `shotgun:events/1` returns an empty list. This is\nbecause events are stored in a queue and each call to `events` returns all\nevents queued so far and then removes these from the queue. So it's important\nto understand that `shotgun:events/1` is a function with side-effects when using\nit.\n\nAdditionally **shotgun** provides a `parse_event/1` helper function that\nturns a server-sent event binary into a map:\n\n```erlang\nshotgun:parse_event(\u003c\u003c\"data: pong\\ndata: ping\\nid: 1\\nevent: pinging\"\u003e\u003e).\n%= #{data =\u003e [\u003c\u003c\"pong\"\u003e\u003e,\u003c\u003c\"ping\"\u003e\u003e],event =\u003e \u003c\u003c\"pinging\"\u003e\u003e,id =\u003e \u003c\u003c\"1\"\u003e\u003e}\n```\n\n## Building \u0026 Test-Driving\n\nTo build **shotgun** just run the following on your command shell:\n\n```sh\nrebar3 compile\n```\n\nTo start up a shell where you can try things out run the following (after\nbuilding the project as described above):\n\n```sh\nrebar3 shell\n```\n\n## Contact Us\nIf you find any **bugs** or have a **problem** while using this library, please\n[open an issue](https://github.com/inaka/shotgun/issues/new) in this repo\n(or a pull request :)).\n\nAnd you can check all of our open-source projects at\n[inaka.github.io](http://inaka.github.io)\n","funding_links":[],"categories":["HTTP"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finaka%2Fshotgun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finaka%2Fshotgun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finaka%2Fshotgun/lists"}