Skip to content

Adding middleware #85

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

Merged
merged 9 commits into from
Jun 22, 2017
Merged

Conversation

donny-dont
Copy link

Currently a work in progress to see if this is on the right track. Will add documentation during the review process.

@@ -28,6 +29,9 @@ abstract class Client {
/// [BrowserClient] if `dart:html` is available.
factory Client() => new IOClient();

factory Client.handler(Handler handler, {CloseHandler onClose})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't like using function typedefs in parameters, because it's much harder to quickly figure out what they are. Handler is okay since it's such a fundamental type, but I'd write void onClose().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

import 'response.dart';

typedef Future<Response> Handler(Request request);
typedef void CloseHandler();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I wouldn't make this a public type.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Would be totally rid of it but no types on the right 😞

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually if I can target 1.24 then we can get rid of the typedef since there's now a syntax for it. Let me know if its ok to do that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sounds fine—1.24 will probably be out before this is ready for release.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

closeHandler();

inner.close();
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a small thing, but it's probably more efficient to directly pass in inner.close if closeHandler() isn't passed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be but this ensures that close goes down through the pipeline.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside into performance would it be better to just have

Future<Request> _defaultRequest(Request request) async => request;
Future<Response> _defaultResponse(Response response) async => response;

etc

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be but this ensures that close goes down through the pipeline.

I'm not sure what you mean by "goes down the pipeline".

As an aside into performance would it be better to just have

Future<Request> _defaultRequest(Request request) async => request;
Future<Response> _defaultResponse(Response response) async => response;

etc

Nah, it would be trivial for implementations to hoist these if it's actually relevant to performance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onClose is a void () function. Within the middleware it will be unable to call inner.close because the Client is not passed to it. Also this makes things safer because the inner.close is always called and someone writing a middleware can't forget to call close.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suggesting you write:

onClose: closeHandler == null
    ? inner.close
    : () {
        closeHandler();
        inner.close();
      }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Middleware createMiddleware({
RequestHandler requestHandler,
ResponseHandler responseHandler,
CloseHandler closeHandler,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call this onClose, for consistency with new Client.handler() and other Dart APIs. It's also not really a handler, since it's not invoked for a request.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want this to be

onRequest, onResponse, onClose?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think handle is good for the first two.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


Middleware createMiddleware({
RequestHandler requestHandler,
ResponseHandler responseHandler,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably a good idea to add an errorHandler here that works the same as shelf's.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want the same HijackException functionality?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not right now. We may find a compelling use-case at some point, but I think it's a lot less crucial on the client since you aren't trying to mount a WebSocket handler as part of an existing app or whatever.

responseHandler ??= (response) async => response;
closeHandler ??= () {};

return (Client inner) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't type anonymous function parameters.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

(Request request) async {
request = await requestHandler(request);

final response = await inner.send(request);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

import 'request.dart';
import 'response.dart';

typedef Future<Response> Handler(Request request);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put this in its own library. I know it seems silly when it's just one line of code, but having one file per type makes them a lot easier to track down in the source.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


Middleware createMiddleware({
RequestHandler requestHandler,
ResponseHandler responseHandler,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not right now. We may find a compelling use-case at some point, but I think it's a lot less crucial on the client since you aren't trying to mount a WebSocket handler as part of an existing app or whatever.

Middleware createMiddleware({
RequestHandler requestHandler,
ResponseHandler responseHandler,
CloseHandler closeHandler,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think handle is good for the first two.

closeHandler();

inner.close();
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be but this ensures that close goes down through the pipeline.

I'm not sure what you mean by "goes down the pipeline".

As an aside into performance would it be better to just have

Future<Request> _defaultRequest(Request request) async => request;
Future<Response> _defaultResponse(Response response) async => response;

etc

Nah, it would be trivial for implementations to hoist these if it's actually relevant to performance.

@donny-dont
Copy link
Author

@nex3 let me know if everything other than the error handler bits are looking good. If so I can start adding some documentation to it all as well.

Copy link
Member

@nex3 nex3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything other than the error handler looks good so far.

import 'response.dart';

typedef Future<Response> Handler(Request request);
typedef void CloseHandler();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sounds fine—1.24 will probably be out before this is ready for release.

closeHandler();

inner.close();
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suggesting you write:

onClose: closeHandler == null
    ? inner.close
    : () {
        closeHandler();
        inner.close();
      }

@donny-dont
Copy link
Author

@nex3 I added some error handling any particular issues with it? It follows along with what I saw in shelf

Copy link
Member

@nex3 nex3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling looks good.

I'm wondering if the onClose parameter to new HandlerClient should be optional. I can imagine handlers that don't want to do anything when they're closed—forwarding directly to an in-process shelf handler, for example.

@donny-dont
Copy link
Author

@nex3 HandlerClient isn't exposed. Its only available through the factory constructor in Client. If you would rather expose it directly then we can do that.

@nex3
Copy link
Member

nex3 commented May 31, 2017

Oh, I forgot. In that case, it's fine as-is.

@donny-dont
Copy link
Author

I'll go ahead and add some documentation then this should be good to go 👍

@donny-dont
Copy link
Author

@nex3 docs look alright?

@@ -30,6 +30,13 @@ abstract class Client {
/// [BrowserClient] if `dart:html` is available.
factory Client() => new IOClient();

/// Creates a new client handler.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "Creates a new Client from a [handler] callback." The client itself isn't a handler in the type-system sense, so we should avoid implying that in the docs.

///
/// The [handler] is a function that receives a [Request] and returns a
/// [Future<Response>]. It will be called when [Client.send] is invoked. This
/// allows for composition of a [Client] within a larger application.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last sentence is confusing. It's not clear to me why this constructor in particular makes composition easier.

/// [Future<Response>].
///
/// A [Handler] may receive a request directly for a HTTP resource or it may be
/// composed as part of a larger application.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear what it means to receive a request "directly". Maybe, "A [Handler] may call an underlying HTTP implementation, or it may wrap another [Handler] or a [Client]."

@@ -9,15 +9,26 @@ import 'handler.dart';
import 'request.dart';
import 'response.dart';

/// A [Handler] based HTTP client.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "[Handler] based" -> "[Handler]-based"

HandlerClient(this._handler, void onClose())
: _close = onClose;

/// Sends an HTTP request and asynchronously returns the response.
///
/// The [Handler] function will be called when this method is invoked.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this line is necessary, especially since you can't directly refer to the values. Same for the similar line in close().

@nex3 nex3 merged commit 5466bac into dart-lang:master Jun 22, 2017
@nex3
Copy link
Member

nex3 commented Jun 22, 2017

Thanks!!

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

Successfully merging this pull request may close these issues.

3 participants