File: user_guide.md

package info (click to toggle)
ruby-middleware 0.1.0-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 132 kB
  • sloc: ruby: 291; makefile: 2
file content (189 lines) | stat: -rw-r--r-- 5,343 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# Middleware User Guide

`middleware` is a library which provides a generalized implementation
of the middleware pattern for Ruby. 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.

## Installing

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

    gem install 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.

```ruby
# Basic middleware that just prints the inbound and
# outbound steps.
class Trace
  def initialize(app, value)
    @app   = app
    @value = value
  end

  def call(env)
    puts "--> #{@value}"
    @app.call(env)
    puts "<-- #{@value}"
  end
end

# Build the actual middleware stack which runs a sequence
# of slightly different versions of our middleware.
stack = Middleware::Builder.new do
  use Trace, "A"
  use Trace, "B"
  use Trace, "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:

    class MiddlewareExample
      def initialize(app); end
      def call(env); end
    end

Therefore, a basic middleware example follows:

    class Trace
      def initialize(app)
        @app = app
      end

      def call(env)
        puts "Trace up"
        @app.call(env)
        puts "Trace down"
      end
    end

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

  * **initialize(app)** - This is a constructor. It can take additional arguments
    but 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.

### 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. A basic example, in the context of a web request:

    lambda { |env| puts "You requested: #{env["http.request_url"]}" }

## 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.new do
      use Trace
      use lambda { |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:

    stack.call

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

### 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 a new item after the Trace middleware
    stack.insert_after(Trace, SomeOtherMiddleware)

    # Replace the lambda
    stack.replace(1, SomeOtherMiddleware)

    # Delete the lambda
    stack.delete(1)

### Passing Additional Constructor Arguments

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

    class Echo
      def initialize(app, message)
        @app = app
        @message = message
      end

      def call(env)
        puts @message
        @app.call(env)
      end
    end

We can initialize `Echo` with a proper message as follows:

    Middleware::Builder.new do
      use Echo, "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.