Dart shelf_web_socket with shelf_router gives Hijack exception

557 views Asked by At

I am trying to implement a basic server which serves both websockets and http requests.

Code is this;

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

void main(List<String> args) async {
  var app = Router();

  var wsHandle = webSocketHandler((webSocket) {
    webSocket.stream.listen((message) {
      print(message);
      webSocket.sink.add("echo $message");
    });
  });

  app.get('/', (Request r) {
    return Response.ok('hello-world');
  });

  app.get("/ws", wsHandle);

  var server = await io.serve(app, 'localhost', 8080);
  print("Server is on at ${server.address.host} ${server.port}");
}

When I try to connect the ws url I get Hijack Error.

Exception has occurred.
HijackException (A shelf request's underlying data stream was hijacked.
This exception is used for control flow and should only be handled by a Shelf adapter.)

I could not find a workaround. This solution does not work for me.

2

There are 2 answers

0
Steven On

Your code works just switch app.get("/ws") and app.get("/").

0
ab36245 On

I am having the exact same problem in 2023. It seems odd that this is an issue now when it was clearly described and noted as almost fixed in 2014. I also agree that the workaround given there doesn't seem to work.

Re-ordering the .gets (as answered previously) doesn't seem to have any effect for me.

But, the 2014 workaround can be made to work if you do three things:

  • Build your non-ws routes as usual (i.e. you can use shelf_router etc if you want) but do not include the ws route handling
  • Use the 2014 workaround code but notice that the url.path property of the request passed to the handler does not have a leading slash.
  • Change the address you listen on to InternetAddess.loopbackIPV4.

Here is a modified version of your code which I got to work

import 'dart:async';
import 'dart:io';

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

void main(List<String> args) async {
  var app = Router();

  app.get('/', (Request r) {
    return Response.ok('hello-world');
  });

  // app.get("/ws", wsHandle);

  final restHandle = app;
  // final restHandle = Pipeline().addMiddleware(logRequests()).addHandler(app);

  var wsHandle = webSocketHandler((webSocket) {
    webSocket.stream.listen((message) {
      print(message);
      webSocket.sink.add("echo $message");
    });
  });

  FutureOr<Response> handler(Request request) {
    var path = request.url.path;
    print('path is >>$path<<');
    if (path.startsWith('ws')) {
      return wsHandle(request);
    }
    return restHandle(request);
  }

  var server = await io.serve(handler, InternetAddress.loopbackIPv4, 8080);
  print("Server is on at ${server.address.host} ${server.port}");
}

Notice that the path test in the handler function excludes the leading slash.

Also notice that the address is InternetAddress.loopbackIPv4 rather than 'localhost'. If I use 'localhost' the request path always comes out empty. No idea why!

This is still a workaround at best!