> At this point the ewgi_examples is mixing the "ewgi gateway
> server"'s startup logic and the example middleware.
> I suggest that this middleware be moved to the ewgi project so that
> new projects depend solely on ewgi (and not ewgi_examples).
>
> The smak framework contains lots of useful pieces for actually
> creating something using ewgi. With that in mind I'd suggest that
> some of the modules also get moved to the ewgi project.
>
I agree. I thought for a long time that these projects should be
separate for ideological reasons, but now it seems those reasons just
make projects impractical.
Perhaps when you have finished reorganising your fork, we can merge
the changes into the parent ewgi project.
Best,
Hunter
> Now, fist of all, I must say this is wow 0_o :) I love it :)
>
> There's always a "however" lurking somewhere, of course :)
>
> I disagree with definitions for middleware and applications:
>
> Current version:
>
> Ewgi Middleware:
> - any function that receives an ewgi_context() and does
> something with it. That's it. No further rules apply. :\
>
> Ewgi Application:
> - a 2 parameter function that receives an ewgi_context()
> as its first argument and a parameter list as a second argument.
> This convention/restriction from the generic "ewgi middleware"
> described above defines a stable interface that developers can
> count on when integrating 3rd party middleware.
>
> In my opinion, the two are actually equivalent :) Since the latter
> is just the former, only with options. I think the real power in
> applications is chaining them. And there's currently no chaining
> method specified.
>
That's not strictly true. The ewgi specification tries to closely
follow the idea behind Python's WSGI and Ruby's Rack in that there is
no syntactic distinction between a middleware function and an
application function. Are you speaking more generally about the
example middleware and applications provided elsewhere?
> Right now middleware can only be chained sequentially like this:
>
> Ctx1 = mw1:run(Ctx, Opts1),
> Ctx2 = mw2:run(Ctx1, Opts2),
> Ctx3 = mw3:run(Ctx3, Opts2)...
>
> So, it's impossible to introduce a component that works on both the
> request and the response (like caching, conditional get, ETags etc.)
I may be completely confused here, so bear with me. What about the
following scenario (a middleware which routes to a particular
application based on the value of an X-Boolean header):
conditional_middleware(AppTrue, AppFalse) ->
F = fun(Ctx) ->
% x_boolean_value/1 just returns true or false depending on
the request header
case x_boolean_value(Ctx) of
true ->
AppTrue(Ctx);
false ->
AppFalse(Ctx)
end
end,
F.
When setting up the application, you would do:
Mw = conditional_middleware(App1, App2).
> And yeah, I'm trying to stick my own solutions as high up as
> possible and wave them in front of everybody's face, but still :))
>
> The basic idea is outlined here:
> http://groups.google.com/group/ewgi/browse_thread/thread/f9042018cb27baa3
>
> We could extend the existing proposal for middleware like this:
>
> Ewgi Applicaton:
> A parametrized middleware module that is initialized with a single
> parameter, App. If the module modifies the request, it should pass
> the modified request to this App. If the module modifies the
> response, it should get the response from the app and modify it:
>
> -module(example_mw, [App]).
>
> run(Ctx, Options) ->
> Ctx1 = modify_request(Ctx), %% optional
> Ctx2 = App(Ctx1),
> Ctx3 = modify_response(Ctx2), %%optional
> Ctx3.
I like the idea of being able to break down middleware pieces into
smaller pieces of functionality that modify only the request or the
response. Note that middleware can elect to modify both where
necessary.
How does the above "conditional_middleware" example fit into this
scheme? Also, are we talking about changing the specification here or
simply creating some common idioms provided with the implementation(s)
of the spec?
Best,
Hunter
On 22 Oct 2009, at 09:59, Dmitrii Dimandt wrote:
Now, fist of all, I must say this is wow 0_o :) I love it :)
There's always a "however" lurking somewhere, of course :)
I disagree with definitions for middleware and applications:
Current version:
Ewgi Middleware:
- any function that receives an ewgi_context() and does
something with it. That's it. No further rules apply. :\
Ewgi Application:
- a 2 parameter function that receives an ewgi_context()
as its first argument and a parameter list as a second argument.
This convention/restriction from the generic "ewgi middleware"
described above defines a stable interface that developers can
count on when integrating 3rd party middleware.
In my opinion, the two are actually equivalent :) Since the latter is just the former, only with options. I think the real power in applications is chaining them. And there's currently no chaining method specified.
That's not strictly true. The ewgi specification tries to closely follow the idea behind Python's WSGI and Ruby's Rack in that there is no syntactic distinction between a middleware function and an application function. Are you speaking more generally about the example middleware and applications provided elsewhere?
Right now middleware can only be chained sequentially like this:
Ctx1 = mw1:run(Ctx, Opts1),
Ctx2 = mw2:run(Ctx1, Opts2),
Ctx3 = mw3:run(Ctx3, Opts2)...
So, it's impossible to introduce a component that works on both the request and the response (like caching, conditional get, ETags etc.)
I may be completely confused here, so bear with me. What about the following scenario (a middleware which routes to a particular application based on the value of an X-Boolean header):
conditional_middleware(AppTrue, AppFalse) ->
F = fun(Ctx) ->
% x_boolean_value/1 just returns true or false depending on the request header
case x_boolean_value(Ctx) of
true ->
AppTrue(Ctx);
false ->
AppFalse(Ctx)
end
end,
F.
When setting up the application, you would do:
Mw = conditional_middleware(App1, App2).
And yeah, I'm trying to stick my own solutions as high up as possible and wave them in front of everybody's face, but still :))
The basic idea is outlined here:
http://groups.google.com/group/ewgi/browse_thread/thread/f9042018cb27baa3
We could extend the existing proposal for middleware like this:
Ewgi Applicaton:
A parametrized middleware module that is initialized with a single parameter, App. If the module modifies the request, it should pass the modified request to this App. If the module modifies the response, it should get the response from the app and modify it:
-module(example_mw, [App]).
run(Ctx, Options) ->
Ctx1 = modify_request(Ctx), %% optional
Ctx2 = App(Ctx1),
Ctx3 = modify_response(Ctx2), %%optional
Ctx3.
I like the idea of being able to break down middleware pieces into smaller pieces of functionality that modify only the request or the response.
Note that middleware can elect to modify both where necessary.
How does the above "conditional_middleware" example fit into this scheme? Also, are we talking about changing the specification here or simply creating some common idioms provided with the implementation(s) of the spec?
Best,
Hunter
Ok, my apologies. I was confused as to whether you were referring to
the spec or the example pieces of middleware. I see your point now.
So this kind of middleware may just be a separate idiom. Your example
below (with modify_request/1 and modify_response/1) is probably a more
common type of middleware. The specific problem I was thinking about
was regarding routing. You may want a piece of middleware to choose
an application based on the path, headers, etc.
>> Note that middleware can elect to modify both where necessary.
>>
>
> Under the current scheme it can't :) Unless you specifically pass a
> reference to the next middleware/app in the chain. And you would
> have to do that for every component down the chain :)
>
>
> Let's say, you have a caching middleware and a session middleware:
>
> request ->
> |
> cache (if hit, return, if miss pass down)
> |
> session (get session cookie, retrieve data based on session, pass
> down)
> |
> web_app (spiderpig does what spiderpig does :)), pass up)
> |
> session (set cookie based on session data etc., pass up)
> |
> cache (caclulate CRC, place in cache etc., pass up)
> |
> response
>
>
> How would you proceed in this case?
I think I see your point. You'd want something like this:
cache(App) ->
F = fun(Ctx) ->
case get_cached(Ctx) of
Ctx1 ->
Ctx1;
{error, not_found} ->
CtxToCache = App(Ctx),
add_to_cache(CtxToCache),
CtxToCache
end
end,
F.
And similarly for the session middleware? Then you would set up your
application as:
App = cache(session(web_app(Opts))).
Does that make any sense?
>> How does the above "conditional_middleware" example fit into this
>> scheme? Also, are we talking about changing the specification here
>> or simply creating some common idioms provided with the
>> implementation(s) of the spec?
>>
>
>
> Nope, just providing common idioms. I like the spec as is :) I'd
> only port SimplBridge's multipart to ewgi:
> http://github.com/rklophaus/SimpleBridge/blob/master/src/simple_bridge_multipart.erl
> :)
That sounds great.
Oh yeah, routing :) Mmmm.... Something like urlconf for Django.... :)
This setup doesn't allow either cache or session get to the request
data ;) Since web_app gets called first, so both cache and session
will only get the response data
>
>>> How does the above "conditional_middleware" example fit into this
>>> scheme? Also, are we talking about changing the specification
>>> here or simply creating some common idioms provided with the
>>> implementation(s) of the spec?
>>>
>>
>>
>> Nope, just providing common idioms. I like the spec as is :) I'd
>> only port SimplBridge's multipart to ewgi:
>> http://github.com/rklophaus/SimpleBridge/blob/master/src/simple_bridge_multipart.erl
>> :)
>
> That sounds great.
Hmmm... I didn't mean to say Id port it, but I guess I now have no
choice :)))) But I'll definitely look into it, since I was going to
anyway
> Oh yeah, routing :) Mmmm.... Something like urlconf for Django.... :)
Yep! If we're going to convince anybody to use ewgi for web framework
projects (like webmachine, beepbeep, etc), they need to have some kind
of URL routing!
>> I think I see your point. You'd want something like this:
>>
>> cache(App) ->
>> F = fun(Ctx) ->
>> case get_cached(Ctx) of
>> Ctx1 ->
>> Ctx1;
>> {error, not_found} ->
>> CtxToCache = App(Ctx),
>> add_to_cache(CtxToCache),
>> CtxToCache
>> end
>> end,
>> F.
>>
>> And similarly for the session middleware? Then you would set up
>> your application as:
>>
>> App = cache(session(web_app(Opts))).
>>
>> Does that make any sense?
>
>
> This setup doesn't allow either cache or session get to the request
> data ;) Since web_app gets called first, so both cache and session
> will only get the response data
In my example, web_app/1 is simply a setup function that *returns* an
ewgi application. The same applies to session/1 and cache/1. Because
they are each returning a function which then calls one of its
arguments (except for web_app which does what spiderpig does), they
are called in left-to-right order. If I simplify things vastly:
-module(test).
-export([example/0]).
f(App) ->
io:format("Setting up f...~n"),
fun(Ctx) ->
io:format("f is running (calling App)~n"),
Ctx1 = App(Ctx),
io:format("f is finished~n"),
Ctx1
end.
g(App) ->
io:format("Setting up g...~n"),
fun(Ctx) ->
io:format("g is running (calling App)~n"),
Ctx1 = App(Ctx),
io:format("g is finished~n"),
Ctx1
end.
spiderpig(Opts) ->
io:format("Setting up spiderpig...~n"),
fun(Ctx) ->
io:format("Running spiderpig with opts: ~p~n", [Opts]),
Ctx
end.
example() ->
App = f(g(spiderpig([a,b,c]))),
App(dummy_context).
It gives this output:
(emacs@tempe)2> test:example().
Setting up spiderpig...
Setting up g...
Setting up f...
f is running (calling App)
g is running (calling App)
Running spiderpig with opts: [a,b,c]
g is finished
f is finished
dummy_context
These middleware functions I've defined simply return closures which
are used later in the app. If your middleware components are too
complex to be in the body of an
anonymous function (or you wanted to make use of hot code reloading):
-module(test).
-export([example/0]).
f(App) ->
fun(Ctx) -> f_mod:run(Ctx, App) end.
g(App) ->
fun(Ctx) -> g_mod:run(Ctx, App) end.
spiderpig(Opts) ->
fun(Ctx) -> spiderpig:run(Ctx, Opts) end.
And, finally (this post is getting long!), you can split up the
middleware as you mentioned:
f(App) ->
fun(Ctx) -> f_mod:rsp(App(f_mod:req(Ctx))) end.
Or, another example:
f(App, Foo, Bar) ->
fun(Ctx) -> f_mod:rsp(App(f_mod:req(Ctx, Foo)), Bar) end.
req(Ctx, baz) ->
do_something(Ctx);
req(Ctx, ok) ->
do_something_else(Ctx).
rsp(Ctx, _Ignore) ->
Ctx.
I realise those are just toy examples, but does that clear anything up?
> Hmmm... I didn't mean to say Id port it, but I guess I now have no
> choice :)))) But I'll definitely look into it, since I was going to
> anyway
Excellent. I haven't had a chance to take a close look at
SimpleBridge, but I will hopefully be able to check it out at the
weekend.
~H
It does... However, it seems somehow more complex from a middleware
developer's point of view. Much more flexible, yes, but also more
complex :) Or may be I'm not seeing it yet (I'm working on something
like half a dozen of PHP files right now, so my vision is very blurry).
I don't see it right now, but it looks like this could be automated
somehow. In the end I want to specify middleware to be run something
like in http://docs.djangoproject.com/en/dev/topics/http/middleware/ —
just throw in a list and let it handle itself :)
>> Hmmm... I didn't mean to say Id port it, but I guess I now have no
>> choice :)))) But I'll definitely look into it, since I was going to
>> anyway
>
> Excellent. I haven't had a chance to take a close look at
> SimpleBridge, but I will hopefully be able to check it out at the
> weekend.
>
> ~H
Same here :)
App = cache(session(web_app(Opts))).Is just great! :)
conditional_middleware(AppTrue, AppFalse) ->
F = fun(Ctx) ->
% x_boolean_value/1 just returns true or false depending on
the request header
case x_boolean_value(Ctx) of
true ->
AppTrue(Ctx);
false ->
AppFalse(Ctx)
end
end,
F.
conditional_middleware(Ctx, [AppTrue, AppFalse]) ->
% x_boolean_value/1 just returns true or false depending on the request header
case x_boolean_value(Ctx) of
true ->
AppTrue(Ctx);
false ->
AppFalse(Ctx)
end.
F = ewgi_application:mfa_mw(?EMAIL_MODULE, conditional_middleware, [AppTrue, AppFalse]).
my_web_stack([...]) ->
CacheSaveApp = ewgi_application:mfa_mw(cache, save, CacheOpts),
SessionSaveApp = ewgi_application:mfa_mw(session, save, [SessionOpts, CacheSaveApp]),
SessionedApp = ewgi_application:module_mw(web_app, [WebAppOtps, SessionSaveApp]),
SessionFailApp = ...
CacheMissApp = ewgi_application:mfa_mw(session, load, [SessionOpts, SessionedApp, SessionFailApp]),
CacheHitApp = ...
MyWebStack = ewgi_application:mfa_mw(cache, load, [CacheOpts, CacheHitApp, CacheMissApp]),
MyWebStack.
-- cache module --
load(Ctx, [CacheOpts, CacheHitApp, CacheMissApp]) ->
case get_cached(Ctx) of
{ok, Ctx1} ->
CacheHitApp(Ctx1);
{error, not_found} ->
CacheMissApp(Ctx)
end.
save(Ctx, [CacheOpts]) ->
! cache_save_ok?
register error
Ctx.
-- session module --
load(Ctx, [SessionOpts, SessionedApp, SessionFailApp]) ->
session_load_ok?
SessionedApp(Ctx1)
else
SessionFailApp(Ctx1).
save(Ctx, [SessionOpts, NextApp]) ->
! session_save_ok?
register failure somewhere
NextApp(Ctx1).
-- webapp module --
run(Ctx, [WebAppOpts, NextApp]) ->
spiderpig doing its thing.
cache(App) ->
F = fun(Ctx) ->
case get_cached(Ctx) of
Ctx1 ->
Ctx1;
{error, not_found} ->
CtxToCache = App(Ctx),
add_to_cache(CtxToCache),
CtxToCache
end
end,
F.
> Ahoy! Hope I'm not too late to the party. :)
You're never too late!
> I believe Dmitrii's quest for an easy way to chain middleware is a
> worthy one and that Hunter's suggestion fits the bill quite nicelly.
> And now for the "lurking however" ;) -> that chain can only be built
> when we're dealing with relatively simple middleware. It's hard to
> keep away from middleware that does execution flow control across
> Apps and when we get to that there's no simple solution in sight
> (from here at least).
I think perhaps it would be useful to create a list of desired
middleware components that we could use:
http://wiki.github.com/skarab/ewgi
As we build up a toolkit of middleware components, we will be able to
see where to make useful higher level abstractions. I agree that
chaining middleware components is extremely verbose at the moment.
It might be useful to statically define a middleware stack with a list
of tuples and fold over that, chaining them together. Something like
this:
[{dispatcher, [{"/", [{more_middleware, ...}]},
{"/foo", [{other_middleware, ...}]}]
You could even define the same code Davide pasted above and fold over
the list, building up the chain until you reach MyWebStack.
> I now disagree with the definition old-Davide proposed about ewgi's
> "middleware" and "application". :)
>
> Today (2009-10-22) I argue that:
> • it makes more sense to call "Application" to the 1-arity
> functions that receives the ewgi_context() and return another
> ewgi_context() AND can be freely chained with other Apps like
> Hunter's example showed.
> So many outstanding features make it worthy of a name. :)
> • the "middleware" name can be left to any code that operates on
> ewgi_context();
>
> • module:run(EwgiCtx, OptionList) can be just an unnamed
> recommendation for defining a middleware function that is directly
> callable AND that can safely be wrapped by module ewgi_application's
> functions when we want to turn it into an "application" for
> combining it with other apps
> In old-Davide's defense: it was late and he didn't know what he was
> writting about. :)
That makes more sense to me, too. Generally, all middleware are
applications, but not vice-versa. The crucial difference seems to be
that middleware eventually rely on some other component to either
modify the request or response (depending on where in the chain).
Meanwhile, I'm going to start merging Davide's structural changes into
my branches and do some other cleanup unless anybody else has any
objections. I think the server implementations could use some
refactoring.
Best,
Hunter
I think perhaps it would be useful to create a list of desired middleware components that we could use:
http://wiki.github.com/skarab/ewgi
That makes more sense to me, too. Generally, all middleware are applications, but not vice-versa. The crucial difference seems to be that middleware eventually rely on some other component to either modify the request or response (depending on where in the chain).I now disagree with the definition old-Davide proposed about ewgi's "middleware" and "application". :)
Today (2009-10-22) I argue that:
• it makes more sense to call "Application" to the 1-arity functions that receives the ewgi_context() and return another ewgi_context() AND can be freely chained with other Apps like Hunter's example showed.
So many outstanding features make it worthy of a name. :)
• the "middleware" name can be left to any code that operates on ewgi_context();
• module:run(EwgiCtx, OptionList) can be just an unnamed recommendation for defining a middleware function that is directly callable AND that can safely be wrapped by module ewgi_application's functions when we want to turn it into an "application" for combining it with other apps
In old-Davide's defense: it was late and he didn't know what he was writting about. :)
Meanwhile, I'm going to start merging Davide's structural changes into my branches and do some other cleanup unless anybody else has any objections. I think the server implementations could use some refactoring.