kemalcr / kemal

Fast, Effective, Simple Web Framework

Home Page:https://kemalcr.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

before_all can not intercept http processing use env.redirect "new_path"

zw963 opened this issue · comments

Description

Following is sample code:

before_all do |env|
  next if env.request.path == path.sign_in

  auth_token = env.session.string?("auth_token")
  
  current_user = User.get_user_by_auth_token(auth_token) # <============= this will return nil because not login

  if current_user.nil?
    puts "1"*100
    env.redirect path.sign_in                            # <============== what we expected is redirect to "/auth/login-in" instead
	puts "2"*100
    next
  else
    env.session.string("current_user_open_id", current_user.open_id)
  end
end

get "/" do |env|
  puts "3"*100
  open_id = env.session.string("current_user_open_id") # <================= but code come here, which cause exception.
  
  current_user = UserQuery.new.open_id(open_id).first

  render_use_layout "home.ecr"
end

get "/auth/sign-in" do
  errors = [] of String
  render_use_layout "auth/sign_in.ecr"
end

Expected behavior: [What you expect to happen]

When i visit 127.0.0.1:3000, because current_user = User.get_user_by_auth_token(auth_token) return nil,
i expect kemal redirect to "/auth/login-in" env.redirect path.sign_in; next

Actual behavior: [What actually happens]

please check log like this:

[development] Kemal is ready to lead at http://0.0.0.0:3000
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
Exception: Missing hash key: "93080ff27482881cd7f518835e52f16b" (KeyError)
  from /usr/share/crystal/src/hash.cr:1077:11 in '[]'
  from lib/kemal-session/src/kemal-session/engines/memory.cr:144:7 in 'string'
  from lib/kemal-session/src/kemal-session/engine.cr:82:5 in 'string'
  from src/test_project.cr:40:3 in '->'
  from lib/kemal/src/kemal/route.cr:12:26 in '->'
  from lib/kemal/src/kemal/route_handler.cr:49:39 in 'process_request'
  from lib/kemal/src/kemal/route_handler.cr:17:7 in 'call'
  from /usr/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/websocket_handler.cr:13:14 in 'call'
  from /usr/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/filter_handler.cr:21:7 in 'call'
  from /usr/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/static_file_handler.cr:5:14 in 'call'
  from /usr/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/exception_handler.cr:8:7 in 'call'
  from /usr/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/log_handler.cr:8:37 in 'call'
  from /usr/share/crystal/src/http/server/handler.cr:28:7 in 'call_next'
  from lib/kemal/src/kemal/init_handler.cr:12:7 in 'call'
  from /usr/share/crystal/src/http/server/request_processor.cr:51:11 in 'process'
  from /usr/share/crystal/src/http/server.cr:515:5 in 'handle_client'
  from /usr/share/crystal/src/http/server.cr:468:13 in '->'
  from /usr/share/crystal/src/fiber.cr:146:11 in 'run'
  from /usr/share/crystal/src/fiber.cr:98:34 in '->'
  from ???

the code in "/" still running, the exception raise because open_id = env.session.string("current_user_open_id")
visit not-exists key.

But, the issue is, this should not happen, it should redirect to "/auth/login-in" instead, right?

Versions

Crystal 1.50dev, kemal master latest.

I think it's because of the next in the following, it breaks from if block and runs into the else condition

if current_user.nil?
    puts "1"*100
    env.redirect path.sign_in                            # <============== what we expected is redirect to "/auth/login-in" instead
	puts "2"*100
    next
  else

can you remove the nextand try again?

can you remove the nextand try again?

Sorry, remove next not work. the log is same when i visit "/"

[development] Kemal is ready to lead at http://0.0.0.0:3000
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
Exception: Missing hash key: "current_user_open_id" (KeyError)

Hi, @sdogruyol , i quite sure, env.redirect not work in before_all do block.

anyway, i need one place to all request to ensure current_user is exists, if this feature
not work, it quite hard to write DRY code.

Hi, i found why this issue happen.

Following is my before_all code

before_all do |env|
  next if env.request.path == path.sign_in

  current_user = User.get_user_by_auth_token(env.session.string?("auth_token"))

  if current_user.nil?
      env.redirect path.sign_in
    next
  else
      env.set "current_user", current_user
  end
end

Following is my router block

    get path.license_index do |env|
      current_user = env.get("current_user").as(User)

      licenses = LicenseQuery.new
    
      render_with_current_user("license/index.ecr")
    end

When i request path.license_index, at first, will retrieve current_user in before_all block.

We assume current case, current_user is nil, so, code env.redirect path.sign_in will run.

The really wired things is, before env.redirect really happen, the request handle process still wil go into
router block, env.get("current_user").as(User) will raise Missing hash key: "current_user" (KeyError) in
this case, because i never set it in before_all.

so, why this issue is happen is discovered, how to fix that? maybe i do things wrong way?

Anyway, i don't think we should do env.get?("current_user") check in my request block is useful.
because if i need check it, i really don't need before_all!

What i expected just is, redirect early in before_all call.

But, for now, before_all just is a place do some common things, but still continue.

I guess it caused by lazy? as describe by following

The Filter middleware is lazily added as soon as a call to after_X or before_X is made. 
It will not even be instantiated unless a call to after_X or before_X is mad

@zw963 I think you would want to close the response immediately after redirection.

if current_user.nil?
    puts "1"*100
    env.redirect path.sign_in                            # <============== what we expected is redirect to "/auth/login-in" instead
    env.response.close
    puts "2"*100
    next
  else
    env.session.string("current_user_open_id", current_user.open_id)
  end

@sdogruyol I think it makes sense to close the response inside of the HTTP::Server::Context#redirect method, better to make it an optional parameter:

    def redirect(url : String, status_code : Int32 = 302, *, body : String? = nil, close : Bool = true)
      @response.headers.add "Location", url
      @response.status_code = status_code
      @response.print(body) if body
      @response.close if close
    end

Even with this, it would still go through all before filters, it only stops at the beginning of Kemal::RouteHandler#process_request because it short circuits if the response is already closed.

Maybe we can add something similar to HTTP::StaticFileHandler's fallthrough option to filters.

@zw963 I think you would want to close the response immediately after redirection.

Yes, you are right.

Cool, my issue is solved! quite perfect.

before this, there is no easy way to use setted env in request handler.

e.g.

get path.home_index do |env|
  current_user = env.get("current_user").as(User)
  # ...
end

When i try to visit path.home_index directly without login in, will raise exception
because no "current_user" key get set before.

even, after i change to current_user = env.get?("current_user").as(User), it still not work.
because raise exception: " Exception: cast from Nil to User failed".

I really don't want add a if/else again for make current_user always work, even, i have done it in before_all.

Now, it works like a charm!!

Thanks for @sdogruyol , @cyangle , and all.

Do we consider release 1.1.3?

@zw963 I'll release 1.2.0 with Crystal 1.5.0 👍

I'll release 1.2.0 with Crystal 1.5.0

Okay, that soulds reasonable, anyway, some default behavior in before_filter was changed.

@zw963 I'll release 1.2.0 with Crystal 1.5.0 +1

Forget to release 1.2.0 ? 😄

Haha no @zw963 thanks for the ping 👍 I'll cut a release soon 🎉