Skip to content

Instantly share code, notes, and snippets.

@takeru
Created May 27, 2010 10:27
Show Gist options
  • Select an option

  • Save takeru/415668 to your computer and use it in GitHub Desktop.

Select an option

Save takeru/415668 to your computer and use it in GitHub Desktop.
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.ServletException
import java.util.Date
import java.util.ArrayList
import java.util.TreeMap
import java.net.URL
import java.net.MalformedURLException
import java.io.IOException
import com.google.appengine.api.urlfetch.URLFetchService
import com.google.appengine.api.urlfetch.FetchOptions
import "FetchOptionsBuilder", "com.google.appengine.api.urlfetch.FetchOptions$Builder"
import com.google.appengine.api.urlfetch.HTTPHeader
import com.google.appengine.api.urlfetch.HTTPRequest
import com.google.appengine.api.urlfetch.HTTPResponse
import com.google.appengine.api.urlfetch.URLFetchServiceFactory
import com.google.appengine.api.urlfetch.HTTPMethod
import com.google.apphosting.api.ApiProxy
import "ApiProxyLogRecord", "com.google.apphosting.api.ApiProxy$LogRecord"
import "ApiProxyLogRecordLevel", "com.google.apphosting.api.ApiProxy$LogRecord$Level"
class SafeSpinupFilter; implements Filter
def initialize
end
def init(conf)
{:return=>void}
@status = 1
@counter = 0
@load_counter = 0
@spinup_path = "/__spinup"
@sorry_path = "/sorry.html"
@status_enter_time = Date[4]
@status_enter_time[1] = Date.new
log("SafeSpinupFilter:init ==========================================")
end
def doFilter(_request, _response, chain)
{:return=>void}
request = HttpServletRequest(_request)
response = HttpServletResponse(_response)
doFilter_0(request, response, chain)
end
# syncronized
def doFilter_0(request:HttpServletRequest,
response:HttpServletResponse,
chain:FilterChain)
throws IOException, ServletException
{:return=>void}
log("SafeSpinupFilter:begin ==========================================")
status_time_limit = 0
kick_count = 0
default_kick_ttl = 0
default_app_ttl = 0
@counter += 1
log("@counter=#{@counter} @status=#{@status} RequestURL=#{request.getRequestURL}?#{request.getQueryString}")
if @status==3
# [Status=3]: Rails loaded. Ready to process app.
doFilter_app(request, response, chain)
nil
elsif @status==1
# [Status=1]: Java loaded(only 1sec.). Need to load JRuby.
status_time_limit = 60
kick_count = 1
default_kick_ttl = 5
default_app_ttl = 2
doFilter_load(request, response, chain,
status_time_limit,
kick_count,
default_kick_ttl,
default_app_ttl)
nil
elsif @status==2
# [Status=2]: JRuby loaded. Need to load Rails.
status_time_limit = 60
kick_count = 1
default_kick_ttl = 5
default_app_ttl = 2
doFilter_load(request, response, chain,
status_time_limit,
kick_count,
default_kick_ttl,
default_app_ttl)
nil
else
raise "invalid status=#{@status}"
nil
end
log("SafeSpinupFilter:end ==========================================")
log("")
log("")
nil
end
def doFilter_load(request:HttpServletRequest,
response:HttpServletResponse,
chain:FilterChain,
status_time_limit:int,
kick_count:int,
default_kick_ttl:int,
default_app_ttl:int)
throws IOException, ServletException
{:return=>void}
if is_app_url(request) && status_sec(@status) < status_time_limit
kick_count.times do
kick_spinup_url(request, default_kick_ttl)
end
redirect_to_self_or_sorry(request, response, default_app_ttl)
nil
else
# load JRuby or Rails
start_ms = System.currentTimeMillis
chain.doFilter(request, response)
@status += 1
@status_enter_time[@status] = Date.new
if @status==2
log("SafeSpinupFilter: *** JRuby loaded. #{(System.currentTimeMillis-start_ms)/100/10.0}sec. ***")
elsif @status==3
log("SafeSpinupFilter: *** Rails loaded. #{(System.currentTimeMillis-start_ms)/100/10.0}sec. ***")
else
log("SafeSpinupFilter: *** Nothing to load. #{(System.currentTimeMillis-start_ms)/100/10.0}sec. ***")
end
nil
end
end
def doFilter_app(request:HttpServletRequest,
response:HttpServletResponse,
chain:FilterChain)
throws IOException, ServletException
{:return=>void}
if is_app_url(request)
chain.doFilter(request, response)
else
kick_spinup_url(request, -1)
end
end
def destroy
{:return=>void}
log("destroy")
end
def is_spinup_url(request:HttpServletRequest)
# log "@spinup_path: #{@spinup_path}"
# log "PathInfo : #{request.getPathInfo || '(null)'}"
# log "ContextPath : #{request.getContextPath || '(null)'}"
log "is_spinup_url:ServletPath=#{request.getServletPath || '(null)'}"
return @spinup_path.equals(request.getServletPath)
end
def is_app_url(request:HttpServletRequest)
if is_spinup_url(request)
false
else
true
end
end
def redirect_to_self_or_sorry(request:HttpServletRequest,
response:HttpServletResponse,
default_app_ttl:int)
{:return=>void}
redir_url = ""
ttl = Integer.parseInt(request.getParameter("_app_redir_ttl_") || "#{default_app_ttl}")
if "GET".equals(request.getMethod) && 0<ttl
u = request.getRequestURL
u.append("?")
q = request.getQueryString
if q
u.append(q).append("&")
end
u.append("_app_redir_ttl_=").append(ttl)
app_redir_key = request.getParameter("_app_redir_key_") || "#{System.currentTimeMillis}"
u.append("&_app_redir_key_=").append(app_redir_key)
redir_url = u.toString
else
redir_url = @sorry_path
end
log "redirect_to_self_or_sorry: redir_url=#{redir_url}"
response.setStatus(302)
response.setHeader("Location", redir_url)
end
def kick_spinup_url(request:HttpServletRequest, ttl:int)
throws MalformedURLException
if ttl < 0
ttl = Integer.parseInt(request.getParameter("_kick_ttl_")) + ttl
end
kick_key = request.getParameter("_kick_key_") || "#{System.currentTimeMillis}"
if 0<ttl
fs = URLFetchServiceFactory.getURLFetchService
url = "http://"+request.getServerName
url += ":#{request.getServerPort}" if 80 != request.getServerPort
url += @spinup_path
url += "?_kick_key_=#{kick_key}&_kick_ttl_=#{ttl}&_kick_time_=#{System.currentTimeMillis}"
log "kick_spinup_url: #{url}"
fs.fetchAsync(URL.new(url)) # Future
else
nil
end
end
def status_sec(status:int)
(Date.new.getTime - @status_enter_time[status].getTime) / 1000
end
def log(message:String)
{:return=>void}
# puts(Date.new.toString + " : " + message)
record = ApiProxyLogRecord.new(ApiProxyLogRecordLevel.info,
System.currentTimeMillis()*1000,
Date.new.toString + " : " + message)
ApiProxy.log(record)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment