module Tor

Constants

MOBILE_AGENT
USER_AGENT
VERSION

Public Class Methods

config() click to toggle source
# File lib/rest-tor.rb, line 25
def self.config
  @config ||= Configuration.new
end
logger() click to toggle source
# File lib/rest-tor.rb, line 9
def self.logger
  @logger ||= Logger.new(STDOUT).tap do |log|
    log.formatter = proc do |severity, datetime, progname, msg|
      "[#{datetime.strftime('%Y-%m-%d %H:%M:%S.%3N')}] [#{Process.pid}-#{Thread.current.object_id}] #{msg}\n"
    end
  end
end
logger=(log) click to toggle source
# File lib/rest-tor.rb, line 17
def self.logger=(log)
  @logger = log
end
setup(&block) click to toggle source
# File lib/rest-tor.rb, line 21
def self.setup(&block)
  instance_exec(&block)
end

Public Instance Methods

avaliable?() click to toggle source
# File lib/rest_tor/instance.rb, line 96
def avaliable?
  begin
    pid && Process.getpgid( pid ) && true
  rescue Errno::ESRCH
    false
  end
end
clear() click to toggle source
# File lib/rest_tor/tor.rb, line 161
def clear
  Dir.glob(config.dir.join("**/*.pid")).each do |path|
    begin
      Process.kill("KILL", File.read(path).chomp.to_i)
    rescue Errno::ESRCH
    end
  end
  FileUtils.rm_rf(config.dir)
  true
ensure
  store.clear
end
count() click to toggle source
# File lib/rest_tor/tor.rb, line 174
def count
  Dir.glob(config.dir.join("*")).count
end
dir(port) click to toggle source
# File lib/rest_tor/tor.rb, line 143
def dir(port)
  config.dir.join("#{port}").tap do |dir|
    FileUtils.mkpath(dir) if not Dir.exists?(dir)
  end
end
hold_tor(mode: :default, rest: false) { || ... } click to toggle source
# File lib/rest_tor/tor.rb, line 24
def hold_tor(mode: :default, rest: false, &block)
  return yield if rest
  if tor=Thread.current[:tor]
    port = tor.port
  else
    port, tor = Dispatcher.take(mode: mode)
  end

  Thread.current[:tor] = tor

  if block_given?
    yield port, tor
  end
ensure
  Thread.current[:tor] = nil
  tor && tor.release!
end
init() click to toggle source
# File lib/rest_tor/tor.rb, line 12
def init
  lock("tor:init", expires: 1.minutes) do
    threads = []
    config.count.times { |i| threads << Thread.new { listen(config.port + i + 1) }  }
    threads.map(&:join)
  end
end
listen(port) click to toggle source
# File lib/rest_tor/tor.rb, line 116
def listen(port)
  return if port.blank? || !port.to_s.match(/^\d+$/)

  logger.info "Open tor with port:#{port}"

  system config.command.call(port)

  if ip=test(port)
    store.insert(port, ip)
  else
    tor = Tor.store[port]
    raise DiedPortError if tor&.died?
    raise UnvaliablePort if !tor
  end
rescue Error, RestClient::Exception => e
  stop(port)
  logger.info "#{e.class}:#{e.message}"
  retry
end
request(options={}) { |res, req, headers| ... } click to toggle source
# File lib/rest_tor/tor.rb, line 42
def request(options={}, &block)
  url     = options[:url]
  mobile  = options[:mobile]
  proxy   = options[:proxy]
  proxy   = nil if proxy == true
  raw     = options[:raw].nil? ? true : false
  mode    = options[:mode]    || :default
  method  = options[:method]  || :get
  payload = options[:payload] || {}
  timeout = options[:timeout] || 10
  format  = options[:format]  || (raw ? :html : :string)
  headers = options[:headers] || options[:header] || {}
  default_header = { 'User-Agent' => mobile ? MOBILE_AGENT : USER_AGENT }
  time, body = Time.now, nil
  rest    = proxy != nil

  hold_tor(mode: mode, rest: rest) do |port, tor|
    Thread.current[:tor] = tor if tor.present?

    proxy  ||= "socks5://127.0.0.1:#{port}" if not rest

    logger.info "Started #{method.to_s.upcase} #{url.inspect} (proxy:#{proxy} | mode:#{mode})"

    params  = {
      method:   method,
      url:      url,
      payload:  payload,
      proxy:    proxy,
      timeout:  timeout,
      headers:  default_header.merge(headers)
    }

    begin
      response = RestClient::Request.execute(params) do |res, req, headers|
        if res.code == 302
          res.follow_redirection
        else
          yield(res, req, headers ) if block_given?
          res
        end
      end
      tor&.success!
      body = response.body
      logger.info "Completed #{response.try(:code)} OK in #{(Time.now-time).round(1)}s (size: #{Utils.number_to_human_size(body.bytesize)})"
    rescue Exception => e
      if tor
        tor.fail!(e)
        logger.info "#{e.class}: #{e.message}, <Tor#(success: #{tor.counter.success}, fail: #{tor.counter.fail}, port: #{tor.port})>"
      end
      raise e
    end
  end
  case format.to_s
    when "html"    then Nokogiri::HTML(Utils.encode_html(body))
    when "json"    then Utils.to_json(body)
    when "string"  then body
  else
    raise InvalidFormat, format.to_s
  end
end
restart(port) click to toggle source
# File lib/rest_tor/tor.rb, line 136
def restart(port)
  lock("tor:#{port}:restart", expires: 1.minutes) do
    stop(port)
    listen(port)
  end
end
stop(port) click to toggle source
# File lib/rest_tor/tor.rb, line 103
def stop(port)
  logger.info "Stop tor port:#{port}"
  instance = store[port]
  if instance && instance.pid
    Process.kill("KILL", instance.pid)
  end
  FileUtils.rm_rf dir(port)
rescue Exception
  
ensure
  store.delete(port)
end
store() click to toggle source
# File lib/rest_tor/tor.rb, line 20
def store
  @store ||= Redis::HashKey.new('tor', marshal: true).tap { |s| s.send(:extend, Builder) }
end
test(port) click to toggle source
# File lib/rest_tor/tor.rb, line 149
def test(port)
  logger.info  "Testing tor #{port}"

  req = RestClient::Request.execute({method: :get, url: config.ipApi, proxy: "socks5://127.0.0.1:#{port}"})
  config.ipParser.call(req.body).tap do |ip|
    logger.info "  IP: #{ip} "
  end
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SOCKSError, SOCKSError::TTLExpired, Errno::ECONNRESET => e
  logger.error "#{e.class}: #{e.message}"
  false
end
unused() click to toggle source
# File lib/rest_tor/tor.rb, line 178
def unused
  store.select {|_, options| !options.using? }
end