phantom_proxy 1.2.17 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 54b0f0d023b215fda611a4fa487a2e7610ad3b29
4
+ data.tar.gz: 314bcd2a4a6551e9f741bb852dd57b108c87dd1e
5
+ SHA512:
6
+ metadata.gz: 4d98501212d94d5b96f304150b57c255a521af55855c63234d24faa11f5e2057b29196f97f485ae9aa652c7c96ac85d70ebc3f0e98997d1e1da8dcc70a144090
7
+ data.tar.gz: 245b3c3b6e7ef879309824b3dff8537d22262bb68bfc9e4b93eedd0cbcfd882b89dc24128e3975cab8a12c1d1f3879dcfbbbaa247b59593922bfdf2bd0ddf128
data/.gitignore CHANGED
@@ -1,12 +1,14 @@
1
- phantom_proxy-*.gem
2
- .rvmrc
3
- log/
4
-
5
- #IDE
6
- .redcar
7
- .project
8
- .rbenv-gemsets
9
-
10
- doc
11
- !.keep
12
- Gemfile.lock
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rbenv-gemsets ADDED
@@ -0,0 +1 @@
1
+ phantom_proxy2
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ree-1.8.7-2012.02
1
+ 2.0.0-p481
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
+ # Specify your gem's dependencies in phantom_proxy2.gemspec
3
4
  gemspec
4
-
5
- gem 'rack'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Suddani
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/phantom_proxy CHANGED
@@ -1,60 +1,53 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'thin'
4
-
5
- require 'fileutils'
6
- require 'timeout'
7
- require 'stringio'
8
- require 'time'
9
- require 'forwardable'
10
- require 'rack'
11
- require 'daemons'
12
-
13
- module PhantomJSProxy
14
- CONFIG = File.expand_path(File.dirname(__FILE__))+"/../lib/phantom_proxy/config.ru"
15
- end
16
-
17
- require 'phantom_proxy'
18
-
19
- # Become a daemon
20
- options = {
21
- :app_name => "phantom_proxy",
22
- :backtrace => true,
23
- :ontop => true,
24
- :log_output => true
25
- }
26
- #Daemons.daemonize(options)
3
+ require "goliath/runner"
4
+ require "phantom_proxy"
5
+ require 'optparse'
6
+
7
+ module PhantomProxy
8
+ def self.load_config
9
+ options = {}
10
+ optparse = OptionParser.new do|opts|
11
+ opts.banner = "Usage: "
12
+ opts.on( '-l [FILE]', '--logfile [FILE]', 'Write log to FILE' ) do |file|
13
+ # PhantomProxy2.logger = Logger.new(file)
14
+ options[:log_file] = file
15
+ end
16
+ opts.on('--hmac [STRING]', 'Use a hmac key to secure the connection' ) do |hmac|
17
+ # PhantomProxy2.hmac_key = hmac
18
+ options[:hmac_key] = hmac
19
+ end
20
+ end
21
+ args = ARGV.dup
22
+ remaining = []
23
+ while !args.empty?
24
+ begin
25
+ head = args.shift
26
+ remaining.concat(optparse.parse([head, args].flatten))
27
+ rescue OptionParser::InvalidOption
28
+ remaining << head
29
+ retry
30
+ end
31
+ end
32
+ PhantomProxy.logger = Logger.new(options[:log_file]) if options[:log_file]
33
+ PhantomProxy.hmac_key = options[:hmac_key] if options[:hmac_key]
34
+ remaining
35
+ end
36
+ def self.run_phantom_proxy(args)
37
+ puts "Run with #{args}"
38
+ runner = Goliath::Runner.new(args, nil)
39
+ runner.logger=PhantomProxy.logger
40
+ runner.port = PhantomProxy.port if PhantomProxy.respond_to?(:port)
41
+ runner.address = PhantomProxy.address if PhantomProxy.respond_to?(:address)
27
42
 
28
- PARAMETERS = Array.new
43
+ Goliath.env = PhantomProxy.env if PhantomProxy.respond_to?(:env)
29
44
 
30
- hmac_key = nil
31
- phantom = false
32
- last_arg = nil
33
- ARGV.each { |arg|
34
- if !/-hmac/.match(arg) && !/-hmac/.match(last_arg) && !/-self/.match(arg)
35
- PARAMETERS << arg
36
- end
37
- phantom = true if /-self/.match(arg)
38
- hmac_key = arg if /-hmac/.match(last_arg)
39
- last_arg = arg
40
- }
45
+ runner.api = PhantomProxy::Service.new
46
+ runner.app = Goliath::Rack::Builder.build(PhantomProxy::Service, runner.api)
41
47
 
42
- if hmac_key
43
- if !File.directory?("/tmp/phantom_proxy")
44
- Dir.mkdir("/tmp/phantom_proxy")
45
- end
46
-
47
- File.open("/tmp/phantom_proxy/key", 'w+') {|f| f.write(hmac_key) }
48
- else
49
- begin
50
- File.delete("/tmp/phantom_proxy/key")
51
- rescue
48
+ puts "Now starting PhantomProxy #{PhantomProxy::VERSION}...."
49
+ runner.run
52
50
  end
53
51
  end
54
52
 
55
- if !phantom
56
- startoptions = ["start", "-R", PhantomJSProxy::CONFIG, "-P", "/tmp/pids/phantom_proxy.pid", "--tag", "phantom_proxy"]+PARAMETERS
57
- runner = Thin::Runner.new(startoptions).run!
58
- else
59
- Thin::Server.start(PhantomJSProxy::PhantomJSServer.new, ARGV[0], ARGV[1], ARGV[2])
60
- end
53
+ PhantomProxy.run_phantom_proxy PhantomProxy.load_config
@@ -0,0 +1,46 @@
1
+ module PhantomProxy
2
+ module Http
3
+ def self.renderer
4
+ @@renderer||=TemplateRenderer.create(binding)
5
+ end
6
+ def self.error_object_binding(obj)
7
+ obj||={}
8
+ def obj.get_binding
9
+ binding
10
+ end
11
+ def obj.title
12
+ self[:title]
13
+ end
14
+ def obj.content
15
+ self[:content]
16
+ end
17
+ def obj.error_code
18
+ self[:error_code]
19
+ end
20
+ obj.get_binding
21
+ end
22
+
23
+ def self.error_massages
24
+ @error_massages||={"Not Found" => 404, "Server Error" => 500, "Not Allowed" => 403, "Not Authorized" => 401}
25
+ end
26
+
27
+ error_massages.each do |error_name, error_code|
28
+ define_singleton_method error_name.gsub(" ", "").to_sym do |data=error_name, content_type="text/html"|
29
+ Response(error_code, renderer.render("error_page", error_object_binding(:title=>error_name, :content=>data, :error_code => 404)), content_type)
30
+ end
31
+ end
32
+
33
+ def self.NextApi(data="Not Found", content_type="text/plain")
34
+ Response(600, data, content_type)
35
+ end
36
+ def self.OK(data="", content_type="text/plain")
37
+ Response(200, data, content_type)
38
+ end
39
+ def self.Response(status_code=200, data="", content_type="text/plain")
40
+ [status_code, {"Content-Type" => content_type}, data]
41
+ end
42
+ def self.OK_no_data(data="", content_type="text/plain")
43
+ [204, {}, ""]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ module PhantomProxy
2
+ module Jsonizer
3
+ def json_var(*var_names)
4
+ @json_vars = (@json_vars||[])+var_names.flatten
5
+ end
6
+
7
+ def json_vars
8
+ @json_vars||= []
9
+ end
10
+
11
+ module Methods
12
+ def render_json(obj=nil)
13
+ Http.OK (obj||self).to_json, "application/json"
14
+ end
15
+
16
+ def render_xml(obj=nil)
17
+ obj = Nokogiri::XML::Builder.new do |xml|
18
+ yield xml
19
+ end if block_given?
20
+ Http.OK (obj||self).to_xml, "application/xml"
21
+ end
22
+
23
+ def to_json
24
+ stuff = Hash.new
25
+ self.class.json_vars.each{|var_name|
26
+ stuff[var_name.to_sym]=send(var_name)# if respond_to?(var_name)
27
+ }
28
+ puts stuff
29
+ stuff.to_json
30
+ end
31
+
32
+ def to_xml()
33
+ xml = Nokogiri::XML::Builder.new do |xml|
34
+ xml.PhantomProxyStatus() {
35
+ self.class.json_vars.each{|var_name|
36
+ var = send(var_name)
37
+ var = var.to_xml if var.respond_to?(:to_xml)
38
+ xml.send(var_name, var)# if respond_to?(var_name)
39
+ }
40
+ }
41
+ end
42
+ xml.to_xml
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ module PhantomProxy
2
+
3
+ class PhantomProxyLogger
4
+ def initialize logger, req_id
5
+ @logger=logger
6
+ @req_id=req_id
7
+ end
8
+ def info msg
9
+ @logger.info "[#{@req_id}] -> #{msg}"
10
+ end
11
+ def warn msg
12
+ @logger.warn "[#{@req_id}] -> #{msg}"
13
+ end
14
+ def error msg
15
+ @logger.error "[#{@req_id}] -> #{msg}"
16
+ end
17
+ def debug msg
18
+ @logger.debug "[#{@req_id}] -> #{msg}"
19
+ end
20
+ end
21
+
22
+ module Logable
23
+ def logger
24
+ @logger ||= PhantomProxy.logger
25
+ end
26
+
27
+ def logger=(_logger)
28
+ @logger=_logger
29
+ end
30
+
31
+ def self.next_id
32
+ @req_id ||= 0
33
+ @req_id+=1
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ module PhantomProxy
2
+ module StatusInfo
3
+ def self.boot_up
4
+ @start_time=Time.now
5
+ end
6
+ boot_up
7
+
8
+ def self.uptime
9
+ Time.now-@start_time
10
+ end
11
+ def self.cache_hit
12
+ @cache_hit||=0
13
+ end
14
+ def self.cache_hit=(obj)
15
+ @cache_hit=obj
16
+ end
17
+ def self.cache_miss
18
+ @cache_miss||=0
19
+ end
20
+ def self.cache_miss=(obj)
21
+ @cache_miss=obj
22
+ end
23
+ def self.cache_error
24
+ @cache_error||=0
25
+ end
26
+ def self.cache_error=(obj)
27
+ @cache_error=obj
28
+ end
29
+ def self.cache_access
30
+ @cache_access||=0
31
+ end
32
+ def self.cache_access=(obj)
33
+ @cache_access=obj
34
+ end
35
+ def self.connections
36
+ @connections||=0
37
+ end
38
+ def self.connections=(obj)
39
+ @connections=obj
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ module PhantomProxy
2
+ class ReloadTemplateRenderer
3
+ def initialize(binding_)
4
+ @binding = binding_
5
+ end
6
+
7
+ def render(template_name, bind=nil)
8
+ TemplateRenderer.new(bind||@binding).render(template_name)
9
+ end
10
+ end
11
+
12
+ class TemplateRenderer
13
+ def self.create(binding_)
14
+ (PhantomProxy.respond_to?(:env) && PhantomProxy.env == :production) ? TemplateRenderer.new(binding_) : ReloadTemplateRenderer.new(binding_)
15
+ end
16
+
17
+ def initialize(controller_binding_)
18
+ @controller_binding=controller_binding_
19
+ end
20
+
21
+ def render(template_name, bind=nil)
22
+ template(template_name).result(bind||controller_binding)
23
+ end
24
+
25
+ private
26
+
27
+ def controller_binding
28
+ @controller_binding
29
+ end
30
+
31
+ def template(template_name)
32
+ begin
33
+ templates[template_name]||=::ERB.new(File.read(PhantomProxy.root.join("views", "#{template_name}.erb")))
34
+ rescue Errno::ENOENT => e
35
+ templates[template_name]||=::ERB.new(File.read(PhantomProxy.root_gem.join("templates/views", "#{template_name}.erb")))
36
+ end
37
+ end
38
+
39
+ def templates
40
+ @templates||={}
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,105 @@
1
+ require 'tempfile'
2
+
3
+ module PhantomProxy
4
+ class PhantomJS
5
+ attr_accessor :dom
6
+ attr_accessor :image
7
+ attr_accessor :ready
8
+
9
+ include ::PhantomProxy::Logable
10
+
11
+ def initialize()
12
+ @ready = 503
13
+ end
14
+
15
+ def getUrl(url, pictureOnly=true, loadIFrames=true)
16
+ logger.info("PhantomJS: "+url)
17
+ @ready = 503
18
+
19
+ pictureFile = nil
20
+ picture = "none"
21
+
22
+ loadFrames = "false"
23
+
24
+ if loadIFrames
25
+ loadFrames = "true"
26
+ end
27
+
28
+ if pictureOnly
29
+ pictureFile = Tempfile.new(["phantom_proxy_page", ".png"])
30
+ picture = pictureFile.path
31
+ end
32
+
33
+ url_args = ""
34
+ url_args_ = []
35
+
36
+ if /\?/.match(url)
37
+ url_args = url.split('?')[1]
38
+ url = url.split('?')[0]
39
+
40
+ if url_args
41
+ url_args_ = url_args.split('&')
42
+ url_args = url_args_.join(' ')
43
+ end
44
+ end
45
+
46
+ @dom = invokePhantomJS(PhantomProxy.script_path, [picture, loadFrames, "\""+url+"\"", url_args_.length, url_args])
47
+
48
+ logger.info("Opened page: "+ /Open page: (.*?) END/.match(@dom)[1])
49
+
50
+ @ready = 503
51
+ dom_text = "Failed to load page"
52
+
53
+ if /DONE_LOADING_URL/.match(@dom)
54
+ logger.info("LOAD_DOM_TEXT")
55
+ dom_text = getDOMText @dom
56
+ logger.info("LOAD_DOM_TEXT_DONE")
57
+ if pictureOnly && File.exist?(picture)
58
+ logger.info("File is there")
59
+ @image = File.open(picture, "rb")# {|f| f.read }
60
+ pictureFile.close!
61
+ else
62
+ logger.info("No file to load at: "+picture)
63
+ @image = ""
64
+ end
65
+ @ready = 200
66
+ end
67
+ if /URL_ERROR_CODE/.match(@dom)
68
+ logger.info("LOAD_ERROR_CODE")
69
+ @ready = getHTTPCode @dom
70
+ logger.info("LOAD_ERROR_CODE_DONE: #{@ready}")
71
+ end
72
+ @dom = dom_text
73
+ return @dom
74
+ end
75
+
76
+ def getDOMText data
77
+ tmp = data.split('PHANTOMJS_DOMDATA_WRITE:')[1];
78
+ tmp = tmp.split('PHANTOMJS_DOMDATA_END')[0]
79
+ tmp
80
+ end
81
+
82
+ def getHTTPCode data
83
+ tmp = data.split('URL_ERROR_CODE: ')[1];
84
+ tmp = tmp.split('URL_ERROR_CODE_END')[0]
85
+ #tmp = /FAILED_LOADING_URL: (.*?)FAILED_LOADING_URL_END/.match(data)[1]
86
+ tmp.to_i
87
+ end
88
+
89
+ def getAsImageResponse(type='png')
90
+ return "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: image/"+type+"\r\n\r\n"+@image;
91
+ end
92
+
93
+ def invokePhantomJS(script, args)
94
+ argString = " "+args.join(" ")
95
+ logger.info("Call phantomJS with: "+argString)
96
+ out = ""
97
+
98
+ IO.popen(PhantomProxy.phantomjs_bin+" --ignore-ssl-errors=yes --web-security=false "+script+argString) {|io|
99
+ out = io.readlines.join
100
+ }
101
+ #logger.info("PHANTOMJS_OUT: "+out)
102
+ return out
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,86 @@
1
+ module PhantomProxy
2
+ class ProxyApi < AppRouterBase
3
+ get "/*path", :handle_proxy_request
4
+ get "/", :handle_proxy_request
5
+
6
+ put "*any", :next_api
7
+ delete "*any", :next_api
8
+ head "*any", :next_api
9
+ post "*any", :next_api
10
+
11
+ private
12
+ def handle_proxy_request
13
+ return Http.NotAuthorized unless !PhantomProxy.hmac_key || check_request_security
14
+ phJS = PhantomJS.new
15
+ PhantomProxy.wait_for(lambda {
16
+ phJS.getUrl(canonical_url, as_image?, iframes?)
17
+ })
18
+ return image_response(phJS) if as_image?
19
+ html_response(phJS)
20
+ end
21
+
22
+ def html_response(phJS)
23
+ [phJS.ready > 0 ? phJS.ready : 404 , {'Content-Type' => 'text/html'}, phJS.dom]
24
+ end
25
+
26
+ def image_response(phJS)
27
+ [200 , {'Content-Type' => 'image/png'}, phJS.image]
28
+ end
29
+
30
+ def host
31
+ env['SERVER_NAME']
32
+ end
33
+
34
+ def path
35
+ env[nil] ? env[nil][:path] : ""
36
+ end
37
+
38
+ def iframes?
39
+ env["HTTP_GET_PAGE_WITH_IFRAMES"] == "true" || PhantomProxy.always_iframe?
40
+ end
41
+
42
+ def as_image?
43
+ env["HTTP_GET_PAGE_AS_IMAGE"] == "true" || PhantomProxy.always_image?
44
+ end
45
+
46
+ def https?
47
+ env["SERVER_PORT"] == "443"
48
+ end
49
+
50
+ def protocoll
51
+ https? ? "https" : "http"
52
+ end
53
+
54
+ def canonical_path
55
+ ["#{protocoll}://#{host}",path].join("/")
56
+ end
57
+
58
+ def canonical_url
59
+ [canonical_path, env["params"] ? env["params"].map{|k,v| "#{k}=#{v}"}.join('&') : nil].join("?")
60
+ end
61
+
62
+ def check_request_security
63
+ if !hmac_hash || !hmac_time
64
+ return false
65
+ end
66
+
67
+ client_time= Time.parse(hmac_time)
68
+ remote_time= Time.now
69
+ remote_key = PhantomProxy.hmac_key.update(env['REQUEST_URI']+env['HTTP_HMAC_TIME']).hexdigest
70
+
71
+ if (hmac_hash != remote_key || (remote_time-client_time).abs > 120)
72
+ # control_panel.add_special_request "@did not pass security check"
73
+ logger.info "@did not pass security check"
74
+ return false
75
+ end
76
+ return true
77
+ end
78
+
79
+ def hmac_hash
80
+ env['HTTP_HMAC_KEY']
81
+ end
82
+ def hmac_time
83
+ env['HTTP_HMAC_TIME']
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,72 @@
1
+ module PhantomProxy
2
+ class AppRouterBase
3
+
4
+ extend Jsonizer
5
+ include Jsonizer::Methods
6
+ include Logable
7
+
8
+ attr_accessor :env
9
+
10
+ def initialize(env)
11
+ @env = env
12
+ end
13
+
14
+ def next_api
15
+ Http.NextApi
16
+ end
17
+
18
+ def renderer
19
+ @@renderer||=TemplateRenderer.create(binding)
20
+ end
21
+
22
+ def render(template_name, status_code=200, bind=nil)
23
+ begin
24
+ Http.Response(status_code, renderer.render(template_name, bind||binding), "html")
25
+ rescue Errno::ENOENT => e
26
+ Http.NotFound
27
+ end
28
+ end
29
+
30
+ def self.options(opt)
31
+ opt = {:function => opt.to_sym} if opt.class != Hash
32
+ {:controller => self.name, :function => :call}.merge(opt)
33
+ end
34
+
35
+ def self.http_verbs
36
+ @http_verbs||=["GET", "POST", "PUT", "DELETE", "HEAD"]
37
+ end
38
+
39
+ http_verbs.each do |method|
40
+ define_singleton_method method.downcase.to_sym do |path, opt = {}|
41
+ path = Journey::Path::Pattern.new path
42
+ router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => method}, {})
43
+ end
44
+ end
45
+
46
+ def self.call(env)
47
+ PhantomProxy::StatusInfo.connections+=1
48
+ print_ram_usage("RAM USAGE Before")
49
+ result=router.call(env)
50
+ print_ram_usage("RAM USAGE After")
51
+ PhantomProxy::StatusInfo.connections-=1
52
+ result
53
+ end
54
+
55
+ def self.print_ram_usage(text)
56
+ PhantomProxy.logger.info "#{text}[#{Process.pid}]: " + `pmap #{Process.pid} | tail -1`[10,40].strip
57
+ end
58
+
59
+ def self.call_controller(options, env)
60
+ options[:controller].respond_to?(options[:function]) ? options[:controller].send(options[:function], env) : PhantomProxy.const_get(options[:controller]).new(env).send(options[:function])
61
+ end
62
+
63
+ private
64
+ def self.routes()
65
+ (@@routes ||= {})[self.name] ||= Journey::Routes.new
66
+ end
67
+
68
+ def self.router()
69
+ (@@router ||= {})[self.name] ||= Journey::Router.new routes, {}
70
+ end
71
+ end
72
+ end