Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use shelf for middleware #1

Open
vicb opened this issue Jun 23, 2014 · 2 comments
Open

Use shelf for middleware #1

vicb opened this issue Jun 23, 2014 · 2 comments
Labels

Comments

@vicb
Copy link
Contributor

vicb commented Jun 23, 2014

Any plan to use shelf for middleware ?

Shelf is part of the Dart sdk.

Middleware allow re-using code across frameworks but this would not work if every framework comes with its own middleware impl.

@Scorpiion
Copy link
Owner

Hi Victor and thanks for your feedback.

I'm well aware of Shelf (I check the community and the mailing list multiple times per day), and I guess it's worth noting that Vane and Vane's middleware system was developed before Shelf even existed, even thought it was made public at a later point. If Shelf would have existed earlier Vane might have been designed differently, but it did not and truth be told I think Shelf sometime feels like a handful to use, but I might not have used it enough yet and be a bit biased... It's also a matter of taste as well I guess. To give an example, declaring that a handler should have one middleware execute before itself in Shelf and Vane:

Shelf

var pipeline = const shelf.Pipeline().addMiddleware(shelf.logRequests())
  .addHandler(_echoRequest);

Vane

var pipeline = [logRequests];

To write something to the body of a response:

Shelf

shelf.Response writeBody(shelf.Request request) {
  var body = new StringBuffer();
  body.write("Writing some data...");

  return new shelf.Response.ok(body);
}

Vane

class Test extends Vane {
  Future main() {
    write("Writing some data...");
    return close();
  }
}

These are quite simple examples, but if we take something a bit more advanced, to run 2 middlewares in sync, for example 1 does logging, 1 does an auth check, either one of them can end the request by calling close() if they want. Then we have 3 async handler middlewares that run at the same time and maybe process some different things concurrently or what ever your app might need, then we want to wait for all those 3 async middleware handlers to finish, then run the main handler, doing the stuff we wanna do, then just for the kicks of it run two more synchronous middleware handlers after than, maybe add some standard stuff to the response or more logging the response or what ever the developer, employer or customer would want to get done. I don't know how to do this with Shelf's middleware system, or if it's even possible, but with Vane it's quite easy:

class MiddleFest extends Vane {
  var pipeline = [SyncMiddle, SyncMiddle, AsyncMiddle, AsyncMiddle, AsyncMiddle, This, SyncMiddle, SyncMiddle];

  @Route("/middlefest")
  Future party() {
    new Timer(new Duration(seconds: 1), () {
      print("In main handler... (pipeline index = $pIndex)");
      writeln("In main handler... (pipeline index = $pIndex)");
      next();
    });

    return end;
  }
}

class AsyncMiddle extends Vane {
  var async = true;

  Future main() {
    new Timer(new Duration(seconds: 1), () {
      print("In async (pipeline index = $pIndex)");
      writeln("In async (pipeline index = $pIndex)");
      next();
    });

    return end;
  }
}

class SyncMiddle extends Vane {
  Future main() {
    new Timer(new Duration(seconds: 1), () {
      print("In sync (pipeline index = $pIndex)");
      writeln("In sync (pipeline index = $pIndex)");
      next();
    });

    return end;
  }
}

If you run this and check the console output you will see this, with a 1 second delay between the synchronous handlers and the asynchronous handlers printing their output at the same time:

In sync (pipeline index = 0)
In sync (pipeline index = 1)
In async (pipeline index = 2)
In async (pipeline index = 3)
In async (pipeline index = 4)
In main handler... (pipeline index = 5)
In sync (pipeline index = 6)
In sync (pipeline index = 7)

Further you can send data between the different middleware handlers with the help of the "Tube" that Vane provides, I left that out of this example to not make it too big, but it's quite easy to use. I used a Timer here to show that the handlers actually run in the order specified. It's actually a very good "sample load" because it's an async structure and a lot of things in Dart is async and hence most "loads", may it be reading from a disk, a database or doing a HttpRequest, can be simulated with a Timer for these kinds of tests. The async nature of Dart is the reason why Vane handlers return a Future, otherwise it's not possible to control their execution flow.

You could say that Vane provide a bit of a run time environment optimized for server side programming, and the middleware system is one part of that. Whereas Shelfs middleware system is more of a standalone system that at the same time is integrated with Shelf but not in the same way. The separation between handlers are a bit different I think. Simply put comparing Vane and Shelf is not an apples to apples comparison. The term "middleware" is also very ambiguous, I have considered other names but it seems to be the termed chosen and used by the industry at large or so to speak, it's not a great term but the best fit.

On the subject of being part of the SDK, I guess that is a mater of definition. Shelf is not part of the Dart SDK you download from dartlang.org but it is a package that is developed and maintained by developers on the Dart team. To use it you have to depend on it in your pubspec just like with any other package. Behind the scene Vane's "serve()" function is using the HttpServer from the http_server package (also a package maintained and developed by the Dart team), but I get your point.

Vane is primarily developed by myself and I work more than full time at DartVoid, Vane is one of my main responsibilities. While I understand the power about Shelf being supported and developed by developers that are part of the Dart team I think it worth saying that Vane is also very well supported, it's not some random hobby project. DartVoid is the company behind the DartVoid PaaS platform for Dart and it's very much in our interest to make sure Vane is as good as possible and that it works well. It is used in production in our management platform and a lot of design and work have been put in to make Vane easy to use and to make the best use of the features that the Dart language provide. We plan to both write more examples using Vane and to write more middleware handlers that will be bundled with Vane in the near future.

I neither think it has to be an either or question, developers will always have somewhat different tastes and rather than having the primary goal of only having one framework or middleware system to "rule them all", I think it's more important to make sure that said frameworks can coexists and that you can reuse components from different ones. A little bit like we see with web components where you can use JavaScript components in Dart and wise verse (at least that's what I have understand is the goal or already possible, I think of this repo in particular: https://github.com/dart-lang/core-elements )

With all that said, Shelf and Shelf middleware's is pretty much already supported, it could be integrated more but I'm not sure how stable Shelf's API/interface is at this point and it's quite easy to use as it is. I would like to make it clear that even if I personally think Vane's middleware system is more powerful and easier to use I have no reason not to support Shelf middleware at the same time. In fact, combining Vane's routing with Shelf handlers might actually be on of the easiest ways to use Shelf today.

Here are four handler examples:

  • A single shelf handler
  • A shelf handler pipeline
  • A handler that makes use of Vane's @route annotation (based on the rfc6570 URI Template standard) a little more to pass in an argument from the url to the handler
  • A handler that uses the externalsShelf middleware, shelf_static to host static files

These can all coexist with the other handler types; Vane, Podo and Func. Since the Shelf handlers are just func handles with "io.handleRequest" inside. Shelf handlers could in the same way be put on Podo classes (since they are normal classes with Func handlers inside) making it possible to group multiple Shelf functions in one class, also something that Shelf don't provide on it's own.

import 'dart:io';
import 'package:vane/vane.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_static/shelf_static.dart';

var shelfPipeline = const shelf.Pipeline().addMiddleware(shelf.logRequests())
  .addHandler(_echoRequest);

shelf.Response _echoRequest(shelf.Request request) {
  return new shelf.Response.ok('Request for "${request.url}"');
}

@Route("/shelf1")
void shelfWithoutMiddleware(HttpRequest request) {
  io.handleRequest(request, _echoRequest);
}

@Route("/shelf2")
void shelfWithMiddleware(HttpRequest request) {
  io.handleRequest(request, shelfPipeline);
}

@Route("/hello/{user}")
void shelfWithParamter(HttpRequest request, String user) {
  io.handleRequest(request, (request) {
    return new shelf.Response.ok('Hello $user');
  });
}

@Route("/web")
void shelfStatic(HttpRequest request) {
  io.handleRequest(request, createStaticHandler('/home/robert/Workspace/Hello/client',
                                                defaultDocument: 'index.html'));
}

void main() => serve();

So to sum up, you can use Shelf handlers and existing Shelf middlewares with Vane and some reasons why to combine them:

  • You get request routing
  • You can pass parameters from the request url as arguments to your shelf handlers
  • You can use a class to group shelf handlers and get them routed as well
  • You can combine that with existing or new handlers written with Vane or normal Func or Podo handlers

I'm not sure how to answer your question "Any plan to use shelf for middleware?", it's a bit too wide I think. I think it could be possible to make a more closer integration, and I have been thinking about different ways to do so, but as I have shown it is already possible to use Shelf handlers and middleware.

If the question would have been:

"Do you plan to rip out Vane's middleware system and use "Shelf style" middleware instead?"

Then the answer is no and to be honest it's not really an apples to apples comparison at this point either. If the two would have been more alike it would have been more likely.

If the question would have been:

"Is it possible to use Shelf with Vane in the same program today?"

Then the answer would have been yes, it's possible and works quite good, in fact it's one of the easiest ways to use Shelf, by combining it with Vane since Shelf does not have any built in routing nor can you send parameters from the url as arguments and you can use Podo classes to group several Shelf handlers together.

If the question would have been:

"Are there any plans to make tighter integration with Shelf, such as having a Vane middleware and a Shelf middleware working on the same request?"

Then the answer would have been yes, there are ideas of how that can be done but I'm not sure at this point what type of integration is best. Pretty much all you need to start a Vane handler/middleware or a Shelf handler/middleware is a HttpRequest, so you can start either one but you need to get access to that underlying HttpRequest again to start another one, and you need the first handler/middleware to let go of said HttpRequest. In Vane you can not do that right now and as far as I know not in a Shelf handler either (I have looked at the hijack stuff in Shelf but Vane need a dart:io HttpRequest not a Shelf Request or stream).

I have thought a lot about this particular issue of interoperability and I have been thinking of adding a way to get access to that underlying HttpRequest. I have been thinking of adding a getter called "zRequest" that returns the underlying HttpRequest. In part because the letter "z" is used for depths in some areas and because using "z" would put it at the end of the code completion list during development. I have been thinking to propose that so that more frameworks might have the same "zRequest" getter to get access to their underlying HttpRequest and hence making it easier to combine code written for different frameworks but I have not gotten around to do so just yet.

Do you think this explanation answered your question? If I add these Shelf examples in a new topic on Shelf to the README and docs, would that satisfy this issue do you think? I think it would be better to do more specific issues later when we know more exactly how such integration's that I mentioned should look like.

@Scorpiion Scorpiion changed the title use shelf for middleware Use shelf for middleware Jun 25, 2014
@vicb
Copy link
Contributor Author

vicb commented Jun 25, 2014

Robert,

I have asked a vague question to get your opinion. I wasn't expected such a detailed answer, thank you very much for all the details.

What about making a wiki page of your answer ? The information is very valuable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants