{"id":13747291,"url":"https://github.com/janko/down","last_synced_at":"2025-09-29T08:05:46.061Z","repository":{"id":37879338,"uuid":"43178783","full_name":"janko/down","owner":"janko","description":"Streaming downloads using Net::HTTP, http.rb or HTTPX","archived":false,"fork":false,"pushed_at":"2025-02-01T22:26:26.000Z","size":514,"stargazers_count":1052,"open_issues_count":7,"forks_count":51,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-07-27T14:47:18.077Z","etag":null,"topics":["download","http","partial-responses","ruby","streaming","tempfile"],"latest_commit_sha":null,"homepage":"","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/janko.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2015-09-25T22:40:32.000Z","updated_at":"2025-07-10T13:22:05.000Z","dependencies_parsed_at":"2023-10-04T06:15:32.819Z","dependency_job_id":"3cf42aff-d460-4c89-8cac-e9ff85f52983","html_url":"https://github.com/janko/down","commit_stats":{"total_commits":333,"total_committers":23,"mean_commits":"14.478260869565217","dds":0.0780780780780781,"last_synced_commit":"558d3335d4af0edb1d3c1bcc6016c6f4dc23315b"},"previous_names":[],"tags_count":59,"template":false,"template_full_name":null,"purl":"pkg:github/janko/down","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janko%2Fdown","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janko%2Fdown/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janko%2Fdown/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janko%2Fdown/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/janko","download_url":"https://codeload.github.com/janko/down/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janko%2Fdown/sbom","scorecard":{"id":505298,"data":{"date":"2025-08-11","repo":{"name":"github.com/janko/down","commit":"1d0bacd5ed7944412e2959161e2da0cfec90e270"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.6,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":2,"reason":"Found 7/30 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/janko/down/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:37: update your workflow using https://app.stepsecurity.io/secureworkflow/janko/down/ci.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T23:08:23.229Z","repository_id":37879338,"created_at":"2025-08-19T23:08:23.230Z","updated_at":"2025-08-19T23:08:23.230Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277483339,"owners_count":25825564,"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","status":"online","status_checked_at":"2025-09-29T02:00:09.175Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["download","http","partial-responses","ruby","streaming","tempfile"],"created_at":"2024-08-03T06:01:24.057Z","updated_at":"2025-09-29T08:05:46.043Z","avatar_url":"https://github.com/janko.png","language":"Ruby","readme":"# Down\n\nDown is a utility tool for streaming, flexible and safe downloading of remote\nfiles. It can use [open-uri] + `Net::HTTP`, [http.rb] or [HTTPX] as the backend\nHTTP library.\n\n## Installation\n\n```rb\ngem \"down\", \"~\u003e 5.0\"\n```\n\n## Downloading\n\nThe primary method is `Down.download`, which downloads the remote file into a\n`Tempfile`:\n\n```rb\nrequire \"down\"\n\ntempfile = Down.download(\"http://example.com/nature.jpg\")\ntempfile #=\u003e #\u003cTempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg\u003e\n```\n\n### Metadata\n\nThe returned `Tempfile` has some additional attributes extracted from the\nresponse data:\n\n```rb\ntempfile.content_type      #=\u003e \"text/plain\"\ntempfile.original_filename #=\u003e \"document.txt\"\ntempfile.charset           #=\u003e \"utf-8\"\n```\n\n### Maximum size\n\nWhen you're accepting URLs from an outside source, it's a good idea to limit\nthe filesize (because attackers want to give a lot of work to your servers).\nDown allows you to pass a `:max_size` option:\n\n```rb\nDown.download(\"http://example.com/image.jpg\", max_size: 5 * 1024 * 1024) # 5 MB\n# Down::TooLarge: file is too large (max is 5MB)\n```\n\nWhat is the advantage over simply checking size after downloading? Well, Down\nterminates the download very early, as soon as it gets the `Content-Length`\nheader. And if the `Content-Length` header is missing, Down will terminate the\ndownload as soon as the downloaded content surpasses the maximum size.\n\n### Destination\n\nBy default the remote file will be downloaded into a temporary location and\nreturned as a `Tempfile`. If you would like the file to be downloaded to a\nspecific location on disk, you can specify the `:destination` option:\n\n```rb\nDown.download(\"http://example.com/image.jpg\", destination: \"/path/to/destination\")\n#=\u003e nil\n```\n\nIn this case `Down.download` won't have any return value, so if you need a File\nobject you'll have to create it manually.\n\nYou can also keep the tempfile, but override the extension:\n\n```rb\ntempfile = Down.download(\"http://example.com/some/file\", extension: \"txt\")\nFile.extname(tempfile.path) #=\u003e \".txt\"\n```\n\nYou can also override the default tempfile prefix:\n\n```rb\ntempfile = Down.download(\"http://example.com/image.jpg\", tempfile_name: \"custom-prefix\")\nFile.basename(tempfile.path) #=\u003e \"custom-prefix20150925-55456-z7vxqz.jpg\"\n```\n\n### Basic authentication\n\n`Down.download` and `Down.open` will automatically detect and apply HTTP basic\nauthentication from the URL:\n\n```rb\nDown.download(\"http://user:password@example.org\")\nDown.open(\"http://user:password@example.org\")\n```\n\n### Progress\n\n`Down.download` supports `:content_length_proc`, which gets called with the\nvalue of the `Content-Length` header as soon as it's received, and\n`:progress_proc`, which gets called with current filesize whenever a new chunk\nis downloaded.\n\n```rb\nDown.download \"http://example.com/movie.mp4\",\n  content_length_proc: -\u003e (content_length) { ... },\n  progress_proc:       -\u003e (progress)       { ... }\n```\n\n## Streaming\n\nDown has the ability to retrieve content of the remote file *as it is being\ndownloaded*. The `Down.open` method returns a `Down::ChunkedIO` object which\nrepresents the remote file on the given URL. When you read from it, Down\ninternally downloads chunks of the remote file, but only how much is needed.\n\n```rb\nremote_file = Down.open(\"http://example.com/image.jpg\")\nremote_file.size # read from the \"Content-Length\" header\n\nremote_file.read(1024) # downloads and returns first 1 KB\nremote_file.read(1024) # downloads and returns next 1 KB\n\nremote_file.eof? #=\u003e false\nremote_file.read # downloads and returns the rest of the file content\nremote_file.eof? #=\u003e true\n\nremote_file.close # closes the HTTP connection and deletes the internal Tempfile\n```\n\nThe following IO methods are implemented:\n\n* `#read` \u0026 `#readpartial`\n* `#gets`\n* `#seek`\n* `#pos` \u0026 `#tell`\n* `#eof?`\n* `#rewind`\n* `#close`\n\n### Caching\n\nBy default the downloaded content is internally cached into a `Tempfile`, so\nthat when you rewind the `Down::ChunkedIO`, it continues reading the cached\ncontent that it had already retrieved.\n\n```rb\nremote_file = Down.open(\"http://example.com/image.jpg\")\nremote_file.read(1*1024*1024) # downloads, caches, and returns first 1MB\nremote_file.rewind\nremote_file.read(1*1024*1024) # reads the cached content\nremote_file.read(1*1024*1024) # downloads the next 1MB\n```\n\nIf you want to save on IO calls and on disk usage, and don't need to be able to\nrewind the `Down::ChunkedIO`, you can disable caching downloaded content:\n\n```rb\nDown.open(\"http://example.com/image.jpg\", rewindable: false)\n```\n\n### Yielding chunks\n\nYou can also yield chunks directly as they're downloaded via `#each_chunk`, in\nwhich case the downloaded content is not cached into a file regardless of the\n`:rewindable` option.\n\n```rb\nremote_file = Down.open(\"http://example.com/image.jpg\")\nremote_file.each_chunk { |chunk| ... }\nremote_file.close\n```\n\n### Data\n\nYou can access the response status and headers of the HTTP request that was made:\n\n```rb\nremote_file = Down.open(\"http://example.com/image.jpg\")\nremote_file.data[:status]   #=\u003e 200\nremote_file.data[:headers]  #=\u003e { \"Content-Type\" =\u003e \"image/jpeg\", ... } (header names are normalized)\nremote_file.data[:response] # returns the response object\n```\n\nNote that a `Down::ResponseError` exception will automatically be raised if\nresponse status was 4xx or 5xx.\n\n### Down::ChunkedIO\n\nThe `Down.open` performs HTTP logic and returns an instance of\n`Down::ChunkedIO`. However, `Down::ChunkedIO` is a generic class that can wrap\nany kind of streaming. It accepts an `Enumerator` that yields chunks of\ncontent, and provides IO-like interface over that enumerator, calling it\nwhenever more content is needed.\n\n```rb\nrequire \"down/chunked_io\"\n\nDown::ChunkedIO.new(...)\n```\n\n* `:chunks` – `Enumerator` that yields chunks of content\n* `:size` – size of the file if it's known (returned by `#size`)\n* `:on_close` – called when streaming finishes or IO is closed\n* `:data` - custom data that you want to store (returned by `#data`)\n* `:rewindable` - whether to cache retrieved data into a file (defaults to `true`)\n* `:encoding` - force content to be returned in specified encoding (defaults to `Encoding::BINARY`)\n\nHere is an example of creating a streaming IO of a MongoDB GridFS file:\n\n```rb\nrequire \"down/chunked_io\"\n\nmongo = Mongo::Client.new(...)\nbucket = mongo.database.fs\n\ncontent_length = bucket.find(_id: id).first[:length]\nstream = bucket.open_download_stream(id)\n\nio = Down::ChunkedIO.new(\n  size: content_length,\n  chunks: stream.enum_for(:each),\n  on_close: -\u003e { stream.close },\n)\n```\n\n### Exceptions\n\nDown tries to recognize various types of exceptions and re-raise them as one of\nthe `Down::Error` subclasses. This is Down's exception hierarchy:\n\n* `Down::Error`\n  * `Down::TooLarge`\n  * `Down::InvalidUrl`\n  * `Down::TooManyRedirects`\n  * `Down::NotModified`\n  * `Down::ResponseError`\n    * `Down::ClientError`\n      * `Down::NotFound`\n    * `Down::ServerError`\n  * `Down::ConnectionError`\n  * `Down::TimeoutError`\n  * `Down::SSLError`\n\n## Backends\n\nThe following backends are available:\n\n* [Down::NetHttp](#downnethttp) (default)\n* [Down::Http](#downhttp)\n* [Down::Httpx](#downhttpx)\n\nYou can use the backend directly:\n\n```rb\nrequire \"down/net_http\"\n\nDown::NetHttp.download(\"...\")\nDown::NetHttp.open(\"...\")\n```\n\nOr you can set the backend globally (default is `:net_http`):\n\n```rb\nrequire \"down\"\n\nDown.backend :http # use the Down::Http backend\n\nDown.download(\"...\")\nDown.open(\"...\")\n```\n\n### Down::NetHttp\n\nThe `Down::NetHttp` backend implements downloads using [open-uri] and\n[Net::HTTP] standard libraries.\n\n```rb\ngem \"down\", \"~\u003e 5.0\"\n```\n```rb\nrequire \"down/net_http\"\n\ntempfile = Down::NetHttp.download(\"http://nature.com/forest.jpg\")\ntempfile #=\u003e #\u003cTempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg\u003e\n\nio = Down::NetHttp.open(\"http://nature.com/forest.jpg\")\nio #=\u003e #\u003cDown::ChunkedIO ...\u003e\n```\n\n`Down::NetHttp.download` is implemented as a wrapper around open-uri, and fixes\nsome of open-uri's undesired behaviours:\n\n* uses `URI::HTTP#open` or `URI::HTTPS#open` directly for [security](https://sakurity.com/blog/2015/02/28/openuri.html)\n* always returns a `Tempfile` object, whereas open-uri returns `StringIO`\n  when file is smaller than 10KB\n* gives the extension to the `Tempfile` object from the URL\n* allows you to limit maximum number of redirects\n\nOn the other hand `Down::NetHttp.open` is implemented using Net::HTTP directly,\nas open-uri doesn't support downloading on-demand.\n\n#### Redirects\n\n`Down::NetHttp#download` turns off open-uri's following redirects, as open-uri\ndoesn't have a way to limit the maximum number of hops, and implements its own.\nBy default maximum of 2 redirects will be followed, but you can change it via\nthe `:max_redirects` option:\n\n```rb\nDown::NetHttp.download(\"http://example.com/image.jpg\")                   # 2 redirects allowed\nDown::NetHttp.download(\"http://example.com/image.jpg\", max_redirects: 5) # 5 redirects allowed\nDown::NetHttp.download(\"http://example.com/image.jpg\", max_redirects: 0) # 0 redirects allowed\n\nDown::NetHttp.open(\"http://example.com/image.jpg\")                       # 2 redirects allowed\nDown::NetHttp.open(\"http://example.com/image.jpg\", max_redirects: 5)     # 5 redirects allowed\nDown::NetHttp.open(\"http://example.com/image.jpg\", max_redirects: 0)     # 0 redirects allowed\n```\n\n#### Proxy\n\nAn HTTP proxy can be specified via the `:proxy` option:\n\n```rb\nDown::NetHttp.download(\"http://example.com/image.jpg\", proxy: \"http://proxy.org\")\nDown::NetHttp.open(\"http://example.com/image.jpg\", proxy: \"http://user:password@proxy.org\")\n```\n\n#### Timeouts\n\nTimeouts can be configured via the `:open_timeout` and `:read_timeout` options:\n\n```rb\nDown::NetHttp.download(\"http://example.com/image.jpg\", open_timeout: 5)\nDown::NetHttp.open(\"http://example.com/image.jpg\", read_timeout: 10)\n```\n\n#### Headers\n\nRequest headers can be added via the `:headers` option:\n\n```rb\nDown::NetHttp.download(\"http://example.com/image.jpg\", headers: { \"Header\" =\u003e \"Value\" })\nDown::NetHttp.open(\"http://example.com/image.jpg\", headers: { \"Header\" =\u003e \"Value\" })\n```\n\n#### SSL options\n\nThe `:ssl_ca_cert` and `:ssl_verify_mode` options are supported, and they have\nthe same semantics as in `open-uri`:\n\n```rb\nDown::NetHttp.open(\"http://example.com/image.jpg\",\n  ssl_ca_cert:     \"/path/to/cert\",\n  ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)\n```\n\n#### URI normalization\n\nIf the URL isn't parseable by `URI.parse`, `Down::NetHttp` will\nattempt to normalize the URL using [Addressable::URI], URI-escaping\nany potentially unescaped characters. You can change the normalizer\nvia the `:uri_normalizer` option:\n\n```rb\n# this skips URL normalization\nDown::NetHttp.download(\"http://example.com/image.jpg\", uri_normalizer: -\u003e (url) { url })\n```\n\n#### Additional options\n\nAny additional options passed to `Down.download` will be forwarded to\n[open-uri], so you can for example add basic authentication or a timeout:\n\n```rb\nDown::NetHttp.download \"http://example.com/image.jpg\",\n  http_basic_authentication: ['john', 'secret'],\n  read_timeout: 5\n```\n\nYou can also initialize the backend with default options:\n\n```rb\nnet_http = Down::NetHttp.new(open_timeout: 3)\n\nnet_http.download(\"http://example.com/image.jpg\")\nnet_http.open(\"http://example.com/image.jpg\")\n```\n\n### Down::Http\n\nThe `Down::Http` backend implements downloads using the [http.rb] gem.\n\n```rb\ngem \"down\", \"~\u003e 5.0\"\ngem \"http\", \"~\u003e 5.0\"\n```\n```rb\nrequire \"down/http\"\n\ntempfile = Down::Http.download(\"http://nature.com/forest.jpg\")\ntempfile #=\u003e #\u003cTempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg\u003e\n\nio = Down::Http.open(\"http://nature.com/forest.jpg\")\nio #=\u003e #\u003cDown::ChunkedIO ...\u003e\n```\n\nSome features that give the http.rb backend an advantage over `open-uri` and\n`Net::HTTP` include:\n\n* Low memory usage (**10x less** than `open-uri`/`Net::HTTP`)\n* Proper SSL support\n* Support for persistent connections\n* Global timeouts (limiting how long the whole request can take)\n* Chainable builder API for setting default options\n\n#### Additional options\n\nAll additional options will be forwarded to `HTTP::Client#request`:\n\n```rb\nDown::Http.download(\"http://example.org/image.jpg\", headers: { \"Foo\" =\u003e \"Bar\" })\nDown::Http.open(\"http://example.org/image.jpg\", follow: { max_hops: 0 })\n```\n\nHowever, it's recommended to configure request options using http.rb's\nchainable API, as it's more convenient than passing raw options.\n\n```rb\nDown::Http.open(\"http://example.org/image.jpg\") do |client|\n  client.timeout(connect: 3, read: 3)\nend\n```\n\nYou can also initialize the backend with default options:\n\n```rb\nhttp = Down::Http.new(headers: { \"Foo\" =\u003e \"Bar\" })\n# or\nhttp = Down::Http.new { |client| client.timeout(connect: 3) }\n\nhttp.download(\"http://example.com/image.jpg\")\nhttp.open(\"http://example.com/image.jpg\")\n```\n\n#### Request method\n\nBy default `Down::Http` makes a `GET` request to the specified endpoint, but you\ncan specify a different request method using the `:method` option:\n\n```rb\nDown::Http.download(\"http://example.org/image.jpg\", method: :post)\nDown::Http.open(\"http://example.org/image.jpg\", method: :post)\n\ndown = Down::Http.new(method: :post)\ndown.download(\"http://example.org/image.jpg\")\n```\n\n### Down::Httpx\n\nThe `Down::Httpx` backend implements downloads using the [HTTPX] gem, which\nsupports the HTTP/2 protocol, in addition to many other features.\n\n```rb\ngem \"down\", \"~\u003e 5.0\"\ngem \"httpx\", \"~\u003e 1.0\"\n```\n```rb\nrequire \"down/httpx\"\n\ntempfile = Down::Httpx.download(\"http://nature.com/forest.jpg\")\ntempfile #=\u003e #\u003cTempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg\u003e\n\nio = Down::Httpx.open(\"http://nature.com/forest.jpg\")\nio #=\u003e #\u003cDown::ChunkedIO ...\u003e\n```\n\nIt's implemented in much of the same way as `Down::Http`, so be sure to check\nits docs for ways to pass additional options.\n\n## Development\n\nTests require that a [httpbin] server is running locally, which you can do via Docker:\n\n```sh\n$ docker pull kennethreitz/httpbin\n$ docker run -p 80:80 kennethreitz/httpbin\n```\n\nThen you can run tests:\n\n```\n$ bundle exec rake test\n```\n\n## License\n\n[MIT](LICENSE.txt)\n\n[open-uri]: http://ruby-doc.org/stdlib-2.3.0/libdoc/open-uri/rdoc/OpenURI.html\n[Net::HTTP]: https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html\n[http.rb]: https://github.com/httprb/http\n[HTTPX]: https://github.com/HoneyryderChuck/httpx\n[Addressable::URI]: https://github.com/sporkmonger/addressable\n[httpbin]: https://github.com/postmanlabs/httpbin\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanko%2Fdown","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanko%2Fdown","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanko%2Fdown/lists"}