Created
May 27, 2010 10:27
-
-
Save takeru/415668 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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