Skip to content

Repository files navigation

Code ClimateTest CoverageBuild Status

Middleware

Join the chat at https://gitter.im/Ibsciss/ruby-middleware

Middleware is a library which provides a generalized implementation of the chain of responsibility pattern for Ruby.

This pattern is used in Rack::Builder or ActionDispatch::MiddlewareStack to manage a stack of middlewares. This gem is a generic implementation for any Ruby project.

The middleware pattern is a useful abstraction tool in various cases, but is specifically useful for splitting large sequential chunks of logic into small pieces.

This is an updated version of the original Mitchell Hashimoto's library: https://github.com/mitchellh/middleware

Installing

Middleware is distributed as a RubyGem, so simply gem install:

$ gem install ibsciss-middleware

Or, in your Gemfile:

gem 'ibsciss-middleware', '~> 0.4.2'

Then, you can add it to your project:

require'middleware'

A Basic Example

Below is a basic example of the library in use. If you don't understand what middleware is, please read below. This example is simply meant to give you a quick idea of what the library looks like.

# Basic middleware that just prints the inbound and# outbound steps.classTracedefinitialize(app,value)@app=app@value=valueenddefcall(env)puts"--> #{@value}"@app.call(env)puts"<-- #{@value}"endend# Build the actual middleware stack which runs a sequence# of slightly different versions of our middleware.stack=Middleware::Builder.newdo |b|
b.useTrace,"A"b.useTrace,"B"b.useTrace,"C"end# Run it!stack.call(nil)

And the output:

--> A
--> B
--> C
<-- C
<-- B
<-- A

Middleware

What is it?

Middleware is a reusable chunk of logic that is called to perform some action. The middleware itself is responsible for calling up the next item in the middleware chain using a recursive-like call. This allows middleware to perform logic both before and after something is done.

The canonical middleware example is in web request processing, and middleware is used heavily by both Rack and Rails. In web processing, the first middleware is called with some information about the web request, such as HTTP headers, request URL, etc. The middleware is responsible for calling the next middleware, and may modify the request along the way. When the middlewares begin returning, the state now has the HTTP response, so that the middlewares can then modify the response.

Cool? Yeah! And this pattern is generally usable in a wide variety of problems.

Middleware Classes

One method of creating middleware, and by far the most common, is to define a class that duck types to the following interface:

classMiddlewareExampledefinitialize(app);enddefcall(env);endend

Therefore, a basic middleware example follows:

classTracedefinitialize(app)@app=appenddefcall(env)puts"Before next middleware execution"@app.call(env)puts"After next middleware execution"endend

A basic description of the two methods that a middleware must implement:

  • initialize(app) - The first argument sent will always be the next middleware to call, called app for historical reasons. This should be stored away for later.

  • call(env) - This is what is actually invoked to do work. env is just some state sent in (defined by the caller, but usually a Hash). This call should also call app.call(env) at some point to move on.

This architecture offers the biggest advantage of letting you enhance the env variable before passing it to the next middleware, and giving you the ability to change the returned data, as follows:

classGreetingdefinitialize(app,datas=nil)@app=app@datas=datasenddefcall(env)env="#{@datas}#{env}"result=@app(env)"#{result} !"endendMiddleware::Builder.new{ |b|
b.useGreeting,'Hello'}.call('John')#return "Hello John !"

Middleware Lambdas

A middleware can also be a simple lambda. The downside of using a lambda is that it only has access to the state on the initial call, there is no "post" step for lambdas:

Middleware::Builder.new{ |b|
b.use->(env){env + 3}b.use->(env){env * 2}}.call(1)#return 8

Middleware Stacks

Middlewares on their own are useful as small chunks of logic, but their real power comes from building them up into a stack. A stack of middlewares are executed in the order given.

Basic Building and Running

The middleware library comes with a Builder class which provides a nice DSL for building a stack of middlewares:

stack=Middleware::Builder.newdo |d|
d.useTraced.use->(env){puts"LAMBDA!"}end

This stack variable itself is now a valid middleware and has the same interface, so to execute the stack, just call call on it, so can compose middleware stack between them:

Middleware::Builder.newdo |d|
d.usestackend.call()

The call method takes an optional parameter which is the state to pass into the initial middleware.

You can optionally set a name, that will be displayed in inspect and for logging purpose:

Middleware::Builder.new(name: 'MyPersonalMiddleware')

Manipulating a Stack

Stacks also provide a set of methods for manipulating the middleware stack. This lets you insert, replace, and delete middleware after a stack has already been created. Given the stack variable created above, we can manipulate it as follows. Please imagine that each example runs with the original stack variable, so that the order of the examples doesn't actually matter:

Insert before

# Insert a new item before the Trace middlewarestack.insert_beforeTrace,SomeOtherMiddleware# Insert a new item at the top of the middleware stackstack.insert_before0,SomeOtherMiddleware

Insert after

# Insert a new item after the Trace middlewarestack.insert_after(Trace,SomeOtherMiddleware)# Insert a new item after the first middlewarestack.insert_after(0,SomeOtherMiddleware)

Insert after each

logger=->(env){penv}# Insert the middleware (can be also a middleware object) after each existing middlewarestack.insert_after_eachlogger

Insert before each

logger=->(env){penv}# Insert the middleware (can be also a middleware object) before each existing middlewarestack.insert_before_eachlogger

Replace

# Replace the second middlewarestack.replace(1,SomeOtherMiddleware)# Replace the Trace middlewarestack.replace(Trace,SomeOtherMiddleware)

Delete

# Delete the second middlewarestack.delete(1)# Delete the Trace middlewarestack.delete(Trace)

Passing Additional Constructor Arguments

When using middleware in a stack, you can also pass in additional constructor arguments. Given the following middleware:

classEchodefinitialize(app,message)@app=app@message=messageenddefcall(env)puts@message@app.call(env)endend

We can initialize Echo with a proper message as follows:

Middleware::Builder.newdouseEcho,"Hello, World!"end

Then when the stack is called, it will output "Hello, World!"

Note that you can also pass blocks in using the use method.

Lambda

Lambda work the same, with additional arguments:

Middleware::Builder.new{ |b|
# arrow syntax for lambda constructionb.use->(env,msg){putsmsg},'some message'}.call(1)#will print "some message"

Debug

You can see the content of a given stack using the inspect method

Middleware::Builder.new{ |b|
b.useTraceb.useEcho,"Hello, World!"}.inspect

It will output:

Middleware[Trace(),Echo("Hello, World!")]

If you have set a name, it will be displayed instead of Middleware.

Logging

A built-in logging mechanism is provided, it will output for each provider of the stack:

  • The provided arguments
  • The returned values (the first 255 chars) and the time (in milliseconds) elapsed in the call method

To initialize the logging you must provide a valid logger instance to #inject_logger.

It is also recommended to give a name to your middleware stack.

require'logger'classUpperCaseMiddlewaredefinitializeapp@app=appenddefcallenvsleep(1)env.upcaseendend# Build the middleware:Middleware::Builder.new(name: 'MyMiddleware'){ |b|
b.useUpperCaseMiddleware}.inject_logger(Logger.new(STDOUT)).call('a message')

It will output something like:

INFO -- MyMiddleware: UpperCaseMiddleware has been called with: "a message"
INFO -- MyMiddleware: UpperCaseMiddleware finished in 1001 ms and returned: "A MESSAGE"

Note: the provided logger instance must respond to #call(level severity, message, app name)

About

Middleware is a library which provides a generalized implementation of the chain of responsibility pattern for Ruby to build generic middleware stack

Topics

Resources

Stars

74 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages