Thanks to visit codestin.com
Credit goes to github.com

Skip to content

alexfilatov/torex

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Torex

CircleCI Hex.pm Hex Docs Hex.pm Downloads License

Elixir HTTP client for making requests through the Tor network. Wraps HTTPoison with SOCKS5 proxy support for routing traffic through a local Tor node.

Requirements

  • Elixir 1.14+
  • A running Tor node

Installation

Add torex to your dependencies in mix.exs:

def deps do
  [{:torex, "~> 0.2.0"}]
end

Tor Setup

macOS

brew install tor
brew services start tor

Linux (Debian/Ubuntu)

sudo apt install tor
sudo systemctl start tor

Docker

docker run -d -p 9050:9050 dperson/torproxy

Tor runs on port 9050 by default.

Configuration

Add to your config/config.exs:

config :torex,
  tor_host: ~c"127.0.0.1",
  tor_port: 9050,
  # Optional: for circuit renewal (new exit node IP)
  control_port: 9051,
  control_password: "your_password"

Note: tor_host uses a charlist (~c"...") as required by the underlying :hackney library.

Usage

GET requests

{:ok, body} = Torex.get("http://example.onion")

case Torex.get("http://check.torproject.org") do
  {:ok, body} ->
    IO.puts("Response: #{body}")
  {:error, {:http_error, status, body}} ->
    IO.puts("HTTP #{status}: #{body}")
  {:error, %{reason: reason}} ->
    IO.puts("Request failed: #{reason}")
end

POST requests

POST requests automatically encode the body as JSON:

{:ok, response} = Torex.post("http://example.onion/api", %{
  username: "user",
  password: "secret"
})

Error Handling

Torex returns tagged tuples for all responses:

case Torex.get(url) do
  {:ok, body} ->
    # Success - HTTP 200
    process(body)

  {:error, {:http_error, status_code, body}} ->
    # Non-200 HTTP response
    Logger.warning("HTTP #{status_code}: #{body}")

  {:error, %{reason: :econnrefused}} ->
    # Tor not running or unreachable
    Logger.error("Cannot connect to Tor")

  {:error, %{reason: :timeout}} ->
    # Request timed out
    Logger.error("Request timed out")

  {:error, error} ->
    # Other errors
    Logger.error("Request failed: #{inspect(error)}")
end

Verifying Tor Connection

Test that your traffic is routing through Tor:

{:ok, body} = Torex.get("https://check.torproject.org/api/ip")
IO.inspect(Jason.decode!(body))
# => %{"IsTor" => true, "IP" => "..."}

Circuit Renewal (IP Rotation)

For scraping or when you need a fresh exit node IP, use renew_circuit/0:

# Get current IP
{:ok, body} = Torex.get("https://api.ipify.org")
IO.puts("Current IP: #{body}")

# Request new circuit (new exit node)
:ok = Torex.renew_circuit()

# Wait a moment for the new circuit
Process.sleep(1000)

# Verify new IP
{:ok, body} = Torex.get("https://api.ipify.org")
IO.puts("New IP: #{body}")

Tor Control Port Setup

Circuit renewal requires the Tor control port. Add to your torrc:

ControlPort 9051
HashedControlPassword <your_hashed_password>

Generate a hashed password:

tor --hash-password "your_password"

Scraping Example with IP Rotation

defmodule Scraper do
  @max_requests_per_ip 10

  def scrape(urls) do
    urls
    |> Enum.chunk_every(@max_requests_per_ip)
    |> Enum.flat_map(fn chunk ->
      results = Enum.map(chunk, &fetch/1)

      # Rotate IP after each batch
      Torex.renew_circuit()
      Process.sleep(1000)

      results
    end)
  end

  defp fetch(url) do
    case Torex.get(url) do
      {:ok, body} -> {:ok, url, body}
      {:error, reason} -> {:error, url, reason}
    end
  end
end

Note: Tor rate-limits circuit renewal to once per 10 seconds. Calling more frequently succeeds but won't change the circuit.

License

MIT

About

Make requests to Tor network with Elixir

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages