# frozen_string_literal: true # we need this for command line Thread.current[:lux] ||= {} Thread.current[:lux][:cache] ||= {} class Lux::Page attr_accessor :headers, :cookies, :session, :files_in_use attr_reader :params, :request, :nav BeforeAndAfter.define self, :before, :after class << self # expects Rack env object but can acceept url # we need this for testing def prepare env env = Rack::MockRequest.env_for(env) if env.class == String request = Rack::Request.new env page = Lux::Page.new request # reset page cache Thread.current[:lux] = { cache:{}, page:page } page end # default rack env call def call env=nil prepare(env).render rescue Lux::Error.log $! raise $! if Lux.config(:show_server_errors) [500, {}, ['Lux server error']] end end ### def initialize(request) @render_start = Time.now @files_in_use = [] @request = request @headers = {} @cookies = {} @session = {} for cookie in request.env['HTTP_COOKIE'].to_s.split(/;\s*/).map{ |el| el.split('=',2) } @cookies[cookie[0]] = cookie[1] end @session = @cookies['__luxs'] ? (JSON.parse Crypt.decrypt @cookies['__luxs'] rescue {}) : {} # check for session if Lux.dev? && request.env['HTTP_REFERER'] && request.env['HTTP_REFERER'].index(request.host) && @session.keys.length == 0 puts "ERROR: There is no session set!".red end @session = HashWithIndifferentAccess.new(@session) @session['_junk'] = Crypt.uid @params = request.params.h_wia Lux::Page::EncryptParams.decrypt @params @nav = Lux::Controller::Nav.new request end def status num=nil Lux.log caller if num == 500 return @status if @status if num if num.is_numeric? @status ||= num.to_i else @status = case num.to_s when 'StandardError'; 400 when 'BadRequestError'; 400 when 'UnauthorizedError'; 401 when 'ForbidenError'; 403 when 'NotFoundError'; 404 when 'RateLimitError'; 503 else; 500 end end end @status end def body what=nil @body ||= what if @body && block_given? @body = yield @body Lux.error 'Lux.page.body is not a string (bad page.body filter)' unless @body.is_a?(String) end @body end def body= what body(what) end def body! what @body = what end def flash message=nil @flash ||= Flash.new(@session[:lux_flash]) message ? @flash.error(message) : @flash end def base_domain host = Lux.page.request.host.split('.') host_country = host.pop host_name = host.pop host_name ? "#{host_name}.#{host_country}" : host_country end def var Thread.current[:lux][:var] ||= Hashie::Mash.new end def host "#{request.env['rack.url_scheme']}://#{request.host}:#{request.port}".sub(':80','') rescue 'http://locahost:3000' end def no_cache? force=nil return false if !force && Lux.prod? Lux.page.request.env['HTTP_CACHE_CONTROL'] == 'no-cache' ? true : false rescue false end def redirect(where=nil, opts={}) return @headers['location'] unless where @status = opts.delete(:status) || 302 opts.map { |k,v| flash.send(k, v) } @headers['location'] = where.index('//') ? where : "#{Lux.page.host}#{where}" @body = %[redirecting to #{@headers['location']}\n\n#{opts.values.join("\n")}] end def permanent_redirect(where) redirect(where, status:301) end def content_type(type=nil) return @content_type unless type # can be set only once return @content_type if @content_type type = 'application/json' if type == :json type = 'text/plain' if type == :text type = 'text/html' if type == :html @content_type = type end def content_type=(type) content_type(type) end def etag(*args) @headers['etag'] ||= %[W/"#{Lux.cache.generate_key(args)}"] if Lux.page.request.env['HTTP_IF_NONE_MATCH'] == @headers['etag'] if Lux.config(:perform_caching) Lux.page.status(304) Lux.page.body 'not-modified' return true else print ' 304>' if Lux.page.status != 304 && Lux.verbose? end end false end def once(id, data=nil) @once_hash ||= {} return if @once_hash[id] @once_hash[id] = true data || yield end def process_body # if @body is not set, this is error now unless @body @status = 500 @body = 'Page BODY not defined. Maybe you did not call cell action but method instad.' end # respond as JSON if we recive hash if @body.kind_of?(Hash) @body = Lux.dev? ? JSON.pretty_generate(@body) : JSON.generate(@body) if Lux.page.request.params[:callback] @body = "#{@request.params[:callback]}(#{ret})" @content_type ||= 'text/javascript' else @content_type ||= 'application/json' end @body += "\n" else # if somebody sets @content_type, respect that @body = @body.to_s unless @body.kind_of?(String) @content_type ||= 'text/plain' if @body[0,1] != '<' @content_type ||= 'text/html' end # show out of process errors if enables and if is html page # becasue for eaxmple we dont want to show error page instead of image # we want user to see it if Lux.config(:show_server_errors) && @content_type == 'text/html' && File.exist?(Lux::Error::OUT_OF_PROCESS_ERROR_PATH) data = File.read(Lux::Error::OUT_OF_PROCESS_ERROR_PATH) File.unlink(Lux::Error::OUT_OF_PROCESS_ERROR_PATH) @body = Lux::Error::render data end if request.host =~ %r{^[\d\.]+$} # if request is IP domain = request.host else domain = base_domain domain = domain.index('.') ? ".#{domain}" : domain end @session[:lux_flash] = flash.to_h # skip adding of cookies and time to strong etag parameters if !@headers['etag'] || @headers['etag'].index('/') @headers['set-cookie'] = "__luxs=#{Crypt.encrypt(@session.to_json)}; Expires=#{(Time.now+1.month).utc}; Path=/; Domain=#{domain};" @headers['x-lux-speed'] = "#{((Time.now-@render_start)*1000).round(1)}ms" end @headers['content-type'] ||= "#{@content_type}; charset=utf-8" # if "no-store" is present then HTTP_IF_NONE_MATCH is not sent from browser @headers['cache-control'] ||= 'must-revalidate, %s, max-age=0' % Lux.page.var.user ? :private : :public Lux.page.etag(@body) if Lux.page.request.request_method == 'GET' @status ||= 200 Lux.log " #{@status}, #{@headers['x-lux-speed']}" if ENV['LUX_PRINT_ROUTES'] print '* Finished route print ' puts @status == 404 ? 'without a match'.red : 'with a match'.green exit end [@status, @headers, [@body]] end def render Lux.log "\n#{Lux.page.request.request_method.white} #{Lux.page.request.path.white}" Lux::Config.live_require_check! if Lux.config(:auto_code_reload) BeforeAndAfter.execute(self, :before) Lux::Controller.new.rescued_main BeforeAndAfter.execute(self, :after) process_body end end