soveran / syro

Simple router for web applications

Home Page:http://soveran.github.io/syro/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Could/should we defer request verb matches to the Rack Request?

pote opened this issue · comments

commented

I'm reading the code to get a feel of this, and wanted to ask about the reasoning behind the post?, get? and other http verb boolean matchers.

In short: is there a specific reason those are implement in Syro instead of just encouraging the usage of req.post?, req.get? and all the equivalent Rack request methods? Seems like we could avoid some unnecessary code that way, specifically:

  # Request methods
  GET = 'GET'.freeze
  POST = 'POST'.freeze
  PATCH = 'PATCH'.freeze
  DELETE = 'DELETE'.freeze

#...
METHODS = [GET, POST, PATCH, DELETE].freeze
#...
    def get?
      @syro_method == 0
    end

    def post?
      @syro_method == 1
    end

    def patch?
      @syro_method == 2
    end

    def delete?
      @syro_method == 3
    end

Running a benchmark shows that - as you would expect - there is maybe a very small percentage gain over reimplementing the method (in the rehearsal section), but doing something like this appeals to me more from the point of view of removing unnecessary code as well as reducing the pollution of the namespace with extra methods, a patch for this would delete the code mentioned above and replace those methods with their Rack response equivalent where needed.

What do you think @soveran ? Am I missing anything?

The reasoning for all that started with the idea that maybe only a small subset of request methods are needed for most applications, and then (arbitrarily) I selected get, post, patch and delete. I left put out because having both put and patch seems redundant. Then @ehq noted OPTIONS should probably be included too, so there's room to revisit that decision. With the current implementation, if you want to do something similar but with OPTIONS, you need to use root { on(req.options?) { ... } }, and the same goes for any other method. I'm very open to discuss this a lot, it looks like there's room for improvement.

For not using req.post?, I was thinking it could be better to avoid string comparisons each time a match to the request method is made. It is fairly normal to have something like this:

get {
  # ...
}

patch {
  #...
}

delete {
  #...
}

In the example above we are comparing strings three times, and I wanted to avoid that. But with the current implementation, the result is more or less the same: in one case we do the string comparison before, in the other we do it at the end. At the start of that decision, I was thinking we could pass the cached method to sub-apps, and then the performance gain would be bigger. But given the way all requests terminate with method matchers it is the case that there's probably no performance gain at all. In short, I think you are right and unless we find very good benchmarks for the current approach, or unless we find a way to profit from that pseudo-caching, we can replace the implementation. Another initial thought was that maybe we could call get? and then get in application code, and saving an extra string comparison was tempting. But we have to think if that's really a use case, because right now it looks artificial.

commented

Ah, yeah, I thought it was probably because of the unfrozen string comparisons, makes sense.

I'm looking at Rack's code though, and they are doing pretty much the same we're doing here:

  GET     = 'GET'.freeze
  POST    = 'POST'.freeze
  PUT     = 'PUT'.freeze
  PATCH   = 'PATCH'.freeze
  DELETE  = 'DELETE'.freeze
  HEAD    = 'HEAD'.freeze
  OPTIONS = 'OPTIONS'.freeze
  LINK    = 'LINK'.freeze
  UNLINK  = 'UNLINK'.freeze
  TRACE   = 'TRACE'.freeze

#... In Rack::Request

    # Checks the HTTP request method (or verb) to see if it was of type DELETE
    def delete?;  request_method == DELETE  end

    # Checks the HTTP request method (or verb) to see if it was of type GET
    def get?;     request_method == GET       end

    # Checks the HTTP request method (or verb) to see if it was of type HEAD
    def head?;    request_method == HEAD      end

    # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
    def options?; request_method == OPTIONS end

    # Checks the HTTP request method (or verb) to see if it was of type LINK
    def link?;    request_method == LINK    end

    # Checks the HTTP request method (or verb) to see if it was of type PATCH
    def patch?;   request_method == PATCH   end

    # Checks the HTTP request method (or verb) to see if it was of type POST
    def post?;    request_method == POST    end

    # Checks the HTTP request method (or verb) to see if it was of type PUT
    def put?;     request_method == PUT     end

    # Checks the HTTP request method (or verb) to see if it was of type TRACE
    def trace?;   request_method == TRACE   end

    # Checks the HTTP request method (or verb) to see if it was of type UNLINK
    def unlink?;  request_method == UNLINK  end

Rack Source:

I get wanting to stick with an int comparison as opposed to a string one, I think it might be one of those things that end up not making a huge difference in performance though, but we should probably run more exhaustive benchmarks to find out for sure.

This approach has the added benefit of already including OPTIONS and a few others, I also like that we're scoping these things inside the req object instead of cramming them into the top-level namespace, feels a bit cleaner and makes sense to me logically, but that is very subjective.

Do check my reasoning though, because I'm very prone to jumping to conclusions hastily, but I think we can get away with removing that code without much of a change, I'll send a Pull Request for it, and of course feel free to not merge it, I just want to write the changes before I forget about all this. :)

BTW: beautiful work as always! Thanks for sharing it! \o/

commented

(I also think we probably want to provide both put and patch helpers, as well as options and all that, but that is probably another discussion entirely)

Insta-merged.