My imaginary Rack replacement

Last updated 4 months ago

One day I was really frustrated the ruby package rack, so I imagined what a better rack would look like.

require "webstack"
require "webstack-throttle"
require "webstack-protection"
require "webstack-tempfile_manager"
require "webstack-deserializer"
require "webstack-dispatcher"
require "webstack-content_length_setter"
require "webstack-content_type_setter"
require "webstack-accept_setter"
require "webstack-serializer"
# Each cycle calls this server block.
# The request and response objects are semi-mutable, see: webstack2.rb.
Webstack::Server.new do |stack, request, response|
Throttle::Middleware.new(request)
Protection::Middleware.new(request)
TempfileManager::Middleware.new(request)
Deserializer::Middleware.new(request)
Dispatcher.new(stack)
ContentLengthSetter::Middleware.new(response)
ContentTypeSetter::Middleware.new(response)
AcceptSetter::Middleware.new(response)
Serializer::Middleware.new(response)
end

Each middleware piece is handed the appropriate part, allowed to mutate sub-sections, and then is garbage collected. If a middleware encounters a problem it can do one of two things:

  • Halt the stack with a status code (4XX, 5XX), see: throttle & protection

  • Add an error body instead of a mutated raw, see: deserializer & tempfile_manager

    This allowes the stack designer to handle errors in the desired

    way.

Pros:

  • Each middleware is given a clear designation on the cycle

  • Raw data isn't mutated, ever, allowing access to known untainted information

  • The stack is clean and clear cut, defining boundries for middleware

  • The middleware is capable of being immutable

  • Error handling is no longer up to the middleware developer, no more

    json error responses in your XML endpoint

  • Middleware can now share behavior

  • It's efficient to debug the process, as things happen once and get

    memoized.

  • Middleware can't fuck with other middleware.

Cons:

  • No more #call affinity, so no more Procs or Lambdas.

  • Might be bigger in memory with the multiple states, keys, etc.

# The content of response
{
# raw is immutable
"raw" => {
"request" => "GET /accounts?limit=10",
"header" => {
"Content-Type" => "application/json",
"Accept" => "application/json"
},
"body" => #<StringIO#f2edasd>
},
# modified is writable but no overwrites or deletes
"modified" => {
"webstack" => {
"verb" => :get,
"path" => "/accounts",
"query" => {
"limit" => 10
},
"header" => {
"Content-Type" => "application/json",
"Accept" => "application/json"
}
"body" => #<StringIO#f2edasd>
},
"throttle::middleware" => {
# ... throttles mutated version of raw ...
}
# ... Other middleware's versions of raw ...
}
}
# At the end all of modified's headers & query are merged and last body, verb, path, status are used.
# The content of request
{
"webstack" => {
"status" => 200,
"header" => {},
"body" => nil
},
"content_length_setter::middleware" => {
"header" => {
"Content-Length" => 300
},
"body" => nil
}
# ... Other middleware's structs ...
}
# At the end all headers are merged and last body & status are used.