{"id":19651055,"url":"https://github.com/mnutt/rails-threaded-proxy","last_synced_at":"2026-06-13T11:31:14.251Z","repository":{"id":258565754,"uuid":"872573386","full_name":"mnutt/rails-threaded-proxy","owner":"mnutt","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-18T18:04:54.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-09T23:53:53.139Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mnutt.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2024-10-14T17:11:28.000Z","updated_at":"2024-10-18T18:04:58.000Z","dependencies_parsed_at":"2024-10-19T15:39:44.821Z","dependency_job_id":null,"html_url":"https://github.com/mnutt/rails-threaded-proxy","commit_stats":null,"previous_names":["mnutt/rails-threaded-proxy"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnutt%2Frails-threaded-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnutt%2Frails-threaded-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnutt%2Frails-threaded-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnutt%2Frails-threaded-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mnutt","download_url":"https://codeload.github.com/mnutt/rails-threaded-proxy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240959788,"owners_count":19885024,"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":[],"created_at":"2024-11-11T15:04:59.355Z","updated_at":"2026-06-13T11:31:09.208Z","avatar_url":"https://github.com/mnutt.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rails-threaded-proxy\n\nAsynchronous high throughput reverse proxy for rails\n\n*Warning: experimental. Use at your own risk.*\n\n## About\n\nRails concurrency is often limited to running many processes, which can be memory-intensive. Even for servers that support threads, it can be difficult running dozens or hundreds of threads. But you may have backend services that are slow to respond, and/or return very large responses. It is useful to put these services behind rails for authentication, but slow responses can tie up your rails workers preventing them from serving other clients.\n\n`rails-threaded-proxy` disconnects the proxying from the rack request/response cycle, freeing up workers to serve other clients. It does this by running the origin request in a thread. But running in a thread is not enough: we need to be able to respond to the rails request, but rack owns the socket. So it hijacks the request: rack completes immediately but dissociates from the socket. Then we're free to manage the socket ourselves. Copying between sockets, we can achieve high throughput (100MB/s+) with minimal CPU and memory overhead.\n\n## Usage\n\n```ruby\nclass MyController\n  include ThreadedProxy::Controller\n\n  def my_backend\n    proxy_fetch \"http://backend.service/path/to/endpoint\", method: :post do |config|\n      config.on_headers do |client_response|\n        # override some response headers coming from the backend\n        client_response['content-security-policy'] = \"sandbox;\"\n      end\n    end\n  end\nend\n```\n\n## Requirements\n\nTested with Rails 7, but probably works in Rails 6+. Needs an application server that supports `rack.hijack`. (only tested on [https://puma.io/](Puma) so far)\n\n## Caveats\n\n* There isn't currently a way to limit concurrency. It is possible to run your server out of file descriptors, memory, etc.\n* Since the proxying happens in a thread, callbacks are also run inside of the thread. Don't do anything non-threadsafe in callbacks.\n* There is currently probably not sufficient error handling for edge cases. This is experimental.\n\n## Attribution\n\nInspired by [https://github.com/axsuul/rails-reverse-proxy](rails-reverse-proxy), and tries to use similar API structure where possible. If you don't care about the specific benefits of `rails-threaded-proxy`, you should consider using `rails-reverse-proxy` instead.\n\n## License\n\nSee LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmnutt%2Frails-threaded-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmnutt%2Frails-threaded-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmnutt%2Frails-threaded-proxy/lists"}