phantom_proxy 1.2.17 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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