{"id":13508328,"url":"https://github.com/CrowdHailer/raxx","last_synced_at":"2025-03-30T11:31:41.132Z","repository":{"id":56473663,"uuid":"60898046","full_name":"CrowdHailer/raxx","owner":"CrowdHailer","description":"Interface for HTTP webservers, frameworks and clients","archived":false,"fork":false,"pushed_at":"2020-11-05T10:28:23.000Z","size":840,"stargazers_count":404,"open_issues_count":8,"forks_count":29,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-25T10:08:05.925Z","etag":null,"topics":["ace","backend","cowboy","elixir","elixir-webservers","framework","rack","umbrella","web","web-application-framework"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/raxx","language":"Elixir","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/CrowdHailer.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}},"created_at":"2016-06-11T08:29:44.000Z","updated_at":"2024-12-13T16:28:55.000Z","dependencies_parsed_at":"2022-08-15T19:30:33.853Z","dependency_job_id":null,"html_url":"https://github.com/CrowdHailer/raxx","commit_stats":null,"previous_names":[],"tags_count":76,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrowdHailer%2Fraxx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrowdHailer%2Fraxx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrowdHailer%2Fraxx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrowdHailer%2Fraxx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CrowdHailer","download_url":"https://codeload.github.com/CrowdHailer/raxx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246314011,"owners_count":20757450,"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":["ace","backend","cowboy","elixir","elixir-webservers","framework","rack","umbrella","web","web-application-framework"],"created_at":"2024-08-01T02:00:51.515Z","updated_at":"2025-03-30T11:31:40.513Z","avatar_url":"https://github.com/CrowdHailer.png","language":"Elixir","funding_links":[],"categories":["HTTP"],"sub_categories":[],"readme":"# Raxx\n\n**Interface for HTTP webservers, frameworks and clients.**\n\n[![Hex pm](http://img.shields.io/hexpm/v/raxx.svg?style=flat)](https://hex.pm/packages/raxx)\n[![Build Status](https://secure.travis-ci.org/CrowdHailer/raxx.svg?branch=master\n\"Build Status\")](https://travis-ci.org/CrowdHailer/raxx)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n\n- [Install from hex.pm](https://hex.pm/packages/raxx)\n- [Documentation available on hexdoc](https://hexdocs.pm/raxx)\n- [Discuss on slack](https://elixir-lang.slack.com/messages/C56H3TBH8/)\n\nSee [Raxx.Kit](https://github.com/CrowdHailer/raxx_kit) for a project generator that helps you set up\na web project based on [Raxx](https://github.com/CrowdHailer/raxx)/[Ace](https://github.com/CrowdHailer/Ace).\n\n## Simple server\n\n#### 1. Defining a server\n\n```elixir\ndefmodule MyServer do\n  use Raxx.SimpleServer\n\n  @impl Raxx.SimpleServer\n  def handle_request(%{method: :GET, path: []}, _state) do\n    response(:ok)\n    |\u003e set_header(\"content-type\", \"text/plain\")\n    |\u003e set_body(\"Hello, World!\")\n  end\n\n  def handle_request(%{method: :GET, path: _}, _state) do\n    response(:not_found)\n    |\u003e set_header(\"content-type\", \"text/plain\")\n    |\u003e set_body(\"Oops! Nothing here.\")\n  end\nend\n```\n\n- *A request's path is split into segments.\n  A request to `GET /` has path `[]`.*\n\n#### 2. Running a server\n\nTo start a Raxx server a compatible HTTP server is needed.\nThis example uses [Ace](https://github.com/crowdhailer/ace) that can serve both HTTP/1 and HTTP/2.\n\n```elixir\nraxx_server = {MyServer, nil}\nhttp_options = [port: 8080, cleartext: true]\n\n{:ok, pid} = Ace.HTTP.Service.start_link(raxx_server, http_options)\n```\n\n- *The second element in the Raxx server tuple is passed as the second argument to the `handle_request/2` callback.\n  In this example it is unused and so set to nil.*\n\nStart your project and visit [http://localhost:8080](http://localhost:8080).\n\n## HTTP streaming\n\nAn HTTP exchange involves a client sending data to a server receiving a response.\nA simple view is to model this as a single message sent in each direction.\n*Working with this model corresponds to `Raxx.SimpleServer` callbacks.*\n\n```txt\n           request --\u003e\nClient ============================================ Server\n                                   \u003c-- response\n```\n\nWhen the simple model is insufficient Raxx exposes a lower model.\nThis consists of a series of messages in each direction.\n*Working with this model corresponds to `Raxx.Server` callbacks.*\n\n```txt\n           tail | data(1+) | head(request) --\u003e\nClient ============================================ Server\n           \u003c-- head(response) | data(1+) | tail\n```\n\n- *The body of a request or a response, is the combination of all data parts sent.*\n\n#### Stateful server\n\nThe `LongPoll` server is stateful.\nAfter receiving a complete request this server has to wait for extra input before sending a response to the client.\n\n```elixir\ndefmodule LongPoll do\n  use Raxx.Server\n\n  @impl Raxx.Server\n  def handle_head(%{method: :GET, path: [\"slow\"]}, state) do\n    Process.send_after(self(), :reply, 30_000)\n\n    {[], state}\n  end\n\n  @impl Raxx.Server\n  def handle_info(:reply, _state) do\n    response(:ok)\n    |\u003e set_header(\"content-type\", \"text/plain\")\n    |\u003e set_body(\"Hello, Thanks for waiting.\")\n  end\nend\n```\n- *A long lived server needs to return two things; the message parts to send, in this case nothing `[]`;\n  and the new state of the server, in this case no change `state`.*\n- *The `initial_state` is configured when the server is started.*\n\n#### Server streaming\n\nThe `SubscribeToMessages` server streams its response.\nThe server will send the head of the response upon receiving the request.\nData is sent to the client, as part of the body, when it becomes available.\nThe response is completed when the chatroom sends a `:closed` message.\n\n```elixir\ndefmodule SubscribeToMessages do\n  use Raxx.Server\n\n  @impl Raxx.Server\n  def handle_head(%{method: :GET, path: [\"messages\"]}, state) do\n    {:ok, _} = ChatRoom.join()\n    outbound = response(:ok)\n    |\u003e set_header(\"content-type\", \"text/plain\")\n    |\u003e set_body(true)\n\n    {[outbound], state}\n  end\n\n  @impl Raxx.Server\n  def handle_info({ChatRoom, :closed}, state) do\n    outbound = tail()\n\n    {[outbound], state}\n  end\n\n  def handle_info({ChatRoom, data}, state) do\n    outbound = data(data)\n\n    {[outbound], state}\n  end\nend\n```\n- *Using `set_body(true)` marks that the response has a body that it is not yet known.*\n- *A stream must have a tail to complete, metadata added here will be sent as trailers.*\n\n#### Client streaming\n\nThe `Upload` server writes data to a file as it is received.\nOnly once the complete request has been received is a response sent.\n\n```elixir\ndefmodule Upload do\n  use Raxx.Server\n\n  @impl Raxx.Server\n  def handle_head(%{method: :PUT, path: [\"upload\"] body: true}, _state) do\n    {:ok, io_device} = File.open(\"my/path\")\n    {[], {:file, device}}\n  end\n\n  @impl Raxx.Server\n  def handle_data(data, state = {:file, device}) do\n    IO.write(device, data)\n    {[], state}\n  end\n\n  @impl Raxx.Server\n  def handle_tail(_trailers, state) do\n    response(:see_other)\n    |\u003e set_header(\"location\", \"/\")\n  end\nend\n```\n- *A body may arrive split by packets, chunks or frames.\n  `handle_data` will be invoked as each part arrives.\n  An application should never assume how a body will be broken into data parts.*\n\n#### Request/Response flow\n\nIt is worth noting what guarantees are given on the request parts passed to the\nServer's `handle_*` functions. It depends on the Server type,\n`Raxx.Server` vs `Raxx.SimpleServer`:\n\n\u003c!-- NOTE: diagram svg files contain the source diagram and can be edited using draw.io --\u003e\n![request flow](assets/request_flow.svg)\n\nSo, for example, after a `%Raxx.Request{body: false}` is passed to a Server's `c:Raxx.Server.handle_head/2`\ncallback, no further request parts will be passed to to the server (`c:Raxx.Server.handle_info/2`\nmessages might be, though).\n\nSimilarly, these are the valid sequences of the response parts returned from the Servers:\n\n\u003c!-- NOTE: diagram svg files contain the source diagram and can be edited using draw.io --\u003e\n![response flow](assets/response_flow.svg)\n\nAny `Raxx.Middleware`s should follow the same logic.\n\n#### Router\n\nThe `Raxx.Router` can be used to match requests to specific server modules.\n\n```elixir\ndefmodule MyApp do\n  use Raxx.Server\n\n  use Raxx.Router, [\n    {%{method: :GET, path: []}, HomePage},\n    {%{method: :GET, path: [\"slow\"]}, LongPoll},\n    {%{method: :GET, path: [\"messages\"]}, SubscribeToMessages},\n    {%{method: :PUT, path: [\"upload\"]}, Upload},\n    {_, NotFoundPage}\n  ]\nend\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrowdHailer%2Fraxx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCrowdHailer%2Fraxx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCrowdHailer%2Fraxx/lists"}