Jaguar-dart / jaguar_hotreload

Hot reloader for Dart
BSD 3-Clause "New" or "Revised" License
9 stars 5 forks source link

Error: "connection not upgraded to websocket" #6

Open mindplay-dk opened 4 years ago

mindplay-dk commented 4 years ago

I've got a straight forward setup like this:

main(List<String> args) async {
  if (HotReloader.isHotReloadable) {
    print('Hot reloading enabled');
    final reloader = HotReloader();
    await reloader.addPackagePath(Uri(scheme: 'package', path: 'server_test/'));
    await reloader.go();
  }

  // ... launch a web server (using shelf) ...
}

When I launch my server.dart script, I get this output:

C:\workspace\dart\server-test>dart --enable-vm-service bin/server.dart
Observatory listening on http://127.0.0.1:8181/2vuzbj7xrus=/
Hot reloading enabled
Listening for file changes at C:\workspace\dart\server-test\lib\
Serving at http://localhost:8080

But the moment I change one of the files, it crashes with this stack trace:

Paths C:\workspace\dart\server-test\lib\src\images.dart changed!
Reloading the application...
Unhandled exception:
WebSocketException: Connection to 'http://localhost:8181/ws#' was not upgraded to websocket
#0      _WebSocketImpl.connect.<anonymous closure>.error (dart:_http/websocket_impl.dart:1052:9)
#1      _WebSocketImpl.connect.<anonymous closure> (dart:_http/websocket_impl.dart:1061:14)
#2      _RootZone.runUnary (dart:async/zone.dart:1379:54)
#3      _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#4      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#5      Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#6      Future._completeWithValue (dart:async/future_impl.dart:522:5)
#7      Future.wait.<anonymous closure> (dart:async/future.dart:400:22)
#8      _RootZone.runUnary (dart:async/zone.dart:1379:54)
#9      _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#10     Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#11     Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#12     Future._completeWithValue (dart:async/future_impl.dart:522:5)
#13     Future._asyncComplete.<anonymous closure> (dart:async/future_impl.dart:552:7)
#14     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
#15     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
#16     _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:116:13)
#17     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:173:5)

I'm on 64-bit Windows 10 with the following Dart version:

2.6.1 (Mon Nov 11 13:12:24 2019 +0100) on "windows_x64"

I noticed a mismatch in socket URLs between the startup message:

Observatory listening on http://127.0.0.1:8181/2vuzbj7xrus=/

And the error message:

Connection to 'http://localhost:8181/ws#' was not upgraded to websocket

The websocket URL displayed at startup contains a random key that changes with every launch, so a constant is probably not enough to make this work.

Likely related to #5, though the error message in that issue is different. (?)

Let me know if there's anything I can do to help resolve the issue.

mindplay-dk commented 4 years ago

Okay, a bit of progress - I was able to reference this library for a way to get the correct URL.

It looks like this:

import 'dart:developer' as dev;
import 'package:path/path.dart' as path;

main(List<String> args) async {
  if (HotReloader.isHotReloadable) {
    var info = await dev.Service.getInfo();
    var uri = info.serverUri;
    uri = uri.replace(path: path.join(uri.path, 'ws'));
    if (uri.scheme == 'https') {
      uri = uri.replace(scheme: 'wss');
    } else {
      uri = uri.replace(scheme: 'ws');
    }

    print('Hot reloading enabled');
    final reloader = HotReloader(vmServiceUrl: uri.toString());
    await reloader.addPackagePath(Uri(scheme: 'package', path: 'server_test/'));
    await reloader.go();
  }

  // ...
}

Something similar should probably replace the constant URL in the library?

(Let me know if you'd like a PR for that change.)

This gets me connected and running - I now see messages like these:

Paths C:\workspace\dart\server-test\lib\src\images.dart changed!
Reloading the application...

So far, so good.

It doesn't work though - changes made to that script don't have any effect.

I'm using shelf, and the thing I was hoping to reload is a Middleware inserted into a Pipeline at start-up. So this is effectively a function instance that persists in an object property somewhere.

I don't know the limitations or the precise workings of hot reloading, but would I need to do something else to get this working? (e.g. tear down and rebuild my HttpServer instance with new instances of the middleware and router etc.?)

mindplay-dk commented 4 years ago

I've also tried the basic example that ships with the package, and this doesn't work either.

For example, changing the count value, or changing the increment expression to count += 2 doesn't have any effect other than seeing the "paths changed" and "reloading" messages on the console.

mindplay-dk commented 4 years ago

For the record, someone on the Dart Gitter channel gave it a shot on Linux - he also had to copy the code I posted (to get the websocket path) to get it working, so that bit needs a PR either way.

He then confirmed that the example did work for him.

So the issue is likely Windows-specific.

We also found that the vm_service_lib package has been superseded by the vm_service package and should be upgraded - I tried switching that dependency, which was easy enough, just needs a simple update of the import paths, but it didn't address this issue.

tejainece commented 4 years ago

Please submit a PR with the fix.

mindplay-dk commented 4 years ago

I will open a separate issue to track the Windows problem.

jahan-addison commented 4 years ago

Thank you so much @mindplay-dk, your snippet worked for me on a macOS setup where I was having this issue as well.