kemalcr / kemal

Fast, Effective, Simple Web Framework
https://kemalcr.com
MIT License
3.65k stars 189 forks source link

Add a before_send middleware feature in development mode for better tooling integration #343

Open samueleaton opened 7 years ago

samueleaton commented 7 years ago

I'd like to be able to make some development tooling that integrates with Kemal. One needed feature is the ability to access the HTTP context right before it is sent to the client (after the route handler) so that the tools can do extra things (e.g. inject a special websocket script into the html body).

If we add a macro that checks for KEMAL_ENV at compile time we can add before_respond_handler checker.

This could be added to the process_request method in the route_handler.cr file like so:

private def process_request(context)
  raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_defined?
  route = context.route_lookup.payload.as(Route)
  content = route.handler.call(context)
ensure
  remove_tmpfiles(context)
  if Kemal.config.error_handlers.has_key?(context.response.status_code)
    raise Kemal::Exceptions::CustomException.new(context)
  end
  context.response.print(content)

  # before_send macro here
  {% unless env("KEMAL_ENV") == "production" %}
    # this is where you would check if there are any before_send middlewares set
    puts "allow before_respond handlers to modify context before sending: ", context
  {% end %}

  context
end

The point is to be able to create more tools for development mode. This will have absolutely zero effect on performance in production mode because the macro won't add any code in production.

The phoenix framework (elixir), for example, does a similar check in development mode for live code reloading. This would allow for more tooling to solve problems as in https://github.com/kemalcr/kemal/issues/335

Thank you!

sdogruyol commented 7 years ago

This sounds interesting @samueleaton, i'm OK for this if it's all compile time and no performance penalty for production apps 👍

samueleaton commented 7 years ago

@sdogruyol Sweet. I'll make a WIP branch and will discuss with you further how to we should go about adding a before_respond_handler or before_send_handler middleware for development.

sdogruyol commented 7 years ago

before_respond_handler sounds fine 👍

samueleaton commented 7 years ago

Its looking like the response is a write-only IO. So is it possible to read from it?

faustinoaq commented 7 years ago

Hi, this could be a good feature!

I created a Kemal-watcher plugin to use with Kemal in development environments, helping to reload the browser when a file change occur. This plugin is very basic, it just add a new handler and does context.response.print but works :sweat_smile:

crisward commented 7 years ago

As @samueleaton pointed out, the response is write-only io. It can't be read or modified because it's probably already been sent to the client. The only way to achieve this would be to buffer the output, to allow it to be modified before being added to response.

This is possible when the response is a return value from a route, but if it's being piped directory into the response, I'm not sure.

If the above restriction is ok, this could be added here https://github.com/kemalcr/kemal/blob/master/src/kemal/route.cr#L11