5

https://pub.dartlang.org/packages/shelf_web_socket shows this example

import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_web_socket/shelf_web_socket.dart';

void main() {
  var handler = webSocketHandler((webSocket) {
    webSocket.listen((message) {
      webSocket.add("echo $message");
    });
  });

  shelf_io.serve(handler, 'localhost', 8080).then((server) {
    print('Serving at ws://${server.address.host}:${server.port}');
  });
}

I would like to know how to combine this with my HTTP server initialization

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as sIo;
import 'package:shelf_auth/shelf_auth.dart' as sAuth;
import 'package:shelf_auth/src/authentication.dart' as sAuth2;
import 'package:option/option.dart';
import 'package:shelf_web_socket/shelf_web_socket.dart' as sWs;

...

var authMiddleware = sAuth.authenticate(
    [new MyAuthenticator()],
    sessionHandler: new sAuth.JwtSessionHandler('bla', 'blub', new UserLookup()),
    allowHttp: true,
    allowAnonymousAccess: false);

var handler = const shelf.Pipeline()
    .addMiddleware(shelf.logRequests())
    .addMiddleware(authMiddleware)
    .addHandler(_handleHttpRequest);

// var wsHandler = sWs.webSocketHandler(_handleWebSocketConnect);

sIo.serve(handler, '0.0.0.0', servePort).then((server) {
  _log.finest('Serving at http://${server.address.host}:${server.port}');
});

What needs to be done so that wsHandler gets called for WebSocket connects and handler keeps handling HTTP requests (if possible on the same port) and if possible uses the configured authentication and session management.

I tried it on a different port but with the authentication/session middleware (no idea if this is supposed to be used together)

   var authMiddleware = sAuth.authenticate(
        [new MyAuthenticator()],
        sessionHandler: new sAuth.JwtSessionHandler('bla', 'blub', new UserLookup()),
        allowHttp: true,
        allowAnonymousAccess: false);

    var handler = const shelf.Pipeline()
        .addMiddleware(shelf.logRequests())
        .addMiddleware(authMiddleware)
        .addHandler(_handleHttpRequest);

    sIo.serve(handler, '0.0.0.0', servePort).then((server) {
      _log.finest('Serving at http://${server.address.host}:${server.port}');
    });


    var wsHandler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addMiddleware(authMiddleware)
      .addHandler(sWs.webSocketHandler(_handleWebSocketConnect));

    sIo.serve(wsHandler, '0.0.0.0', servePort + 1).then((server) {
      _log.finest('Serving at ws://${server.address.host}:${server.port}');
    });

and got

Illegal argument(s): webSocketHandler may only be used with a server that supports request hijacking.
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567

1 Answers1

8

At the moment your root handler is the http handler. You'll need to set up a handler that conditionally sends requests to the ws handler or another handler for your http requests. Eg

/ws -> your ws handler

/rest -> your other handler

The easiest way to do that is to use a router like shelf_route.

However someone recently tried this and hit a bug in shelf that stopped this working. Which as you noted below is fixed but not merged.

Once the issue is fixed you should be able to do

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_route/shelf_route.dart' as route;
import 'package:shelf_web_socket/shelf_web_socket.dart' as sWs;
import 'package:shelf_auth/shelf_auth.dart' as sAuth;
import 'dart:async';
import 'package:option/option.dart';
import 'package:shelf_exception_response/exception_response.dart';

void main(List<String> arguments) {

  var authMiddleware = sAuth.authenticate(
        [new MyAuthenticator()],
        sessionHandler: new sAuth.JwtSessionHandler('bla', 'blub', new UserLookup()),
        allowHttp: true,
        allowAnonymousAccess: false);


  var router = (route.router()
      ..get('/rest', _handleHttpRequest)
      ..get('/ws', sWs.webSocketHandler(_handleWebSocketConnect)));

  var handler = const shelf.Pipeline()
      .addMiddleware(exceptionResponse())
      .addMiddleware(shelf.logRequests())
      .addMiddleware(authMiddleware)
      .addHandler(router.handler);

  route.printRoutes(router);

  io.serve(handler, '127.0.0.1', 8080).then((server) {
    print('Serving at http://${server.address.host}:${server.port}');
  });
}

Until the issue is fixed you can replace the router.handler with

var hackyRouterHandler = (shelf.Request request) {
  var path = request.url.path;
  if (path.startsWith('/rest')) {
    return _handleHttpRequest(request);
  }
  else if (path.startsWith('/ws')) {
    return sWs.webSocketHandler(_handleWebSocketConnect)(request);
  }
  else {
    throw new NotFoundException();
  }
};
Anders
  • 1,337
  • 1
  • 8
  • 17
  • 1
    In the meantime you can look at https://groups.google.com/a/dartlang.org/forum/m/#!searchin/misc/shelf/misc/PrkSD9iVISs It has such an example. Unfortunately it looks like the shelf issue is not fixed yet. It's a pity it's not on gh as it's such a small change – Anders Oct 03 '14 at 07:29
  • This is ready for code review since 3 days https://code.google.com/p/dart/issues/detail?id=21043 – Günter Zöchbauer Oct 03 '14 at 07:35
  • 1
    If only we could approve, merge and publish it ;-) – Anders Oct 03 '14 at 07:40
  • Great! I wanted to try shelf for quite a while but couldn't find the time. I started using it because of the session handler in your `shelf_auth` package. – Günter Zöchbauer Oct 03 '14 at 08:10
  • 1
    Btw, I have the fix in my Git repository here till a new version is published: https://github.com/Fox32/shelf – Fox32 Oct 03 '14 at 08:27