Query ghhops-server over local network?

I’m digging into Hops lately and so far loving it! But although I read quite a lot about it, I’m still asking myself:
Is it possible to run a ghhops-server instance on one computer and query the available endpoints from another computer in the local network?

@stevebaer could you give me a hint if/how this can be done? Or do I need to deploy a “real” compute server for this?

Yes, but you must allow incoming connections to your computer or wherever the Flask server is running from. This isn’t done in Python or Rhino, but rather in the network preferences of your OS.

If you set the host parameter of your Flask app to 0.0.0.0, the server will be accessible from <your ip>:5000 on any computer on your local network.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
  return "Hello world!" 

if __name__ == '__main__':
    app.debug = True
    app.run(host="0.0.0.0")  # <---
1 Like

That’s already helpful, thanks! Do you know by any chance if that’s also possible without using flask?
I need RhinoCommon functionality so I can’t go the flask route :frowning:

You mention Hops above. Hops uses Flask!

You can also run without flask, using the internal http server. For example when using rhino.inside with Hops.

Edit, forgot the link:

Ah yes, you’re right. If you have a valid rhino license on your computer, you should be able to use rhino.inside (if I’m not mistaken).

You can choose to use Rhino.Inside or not use Rhino.Inside with ghhops-server. It depends on if you need the functionality provided by have access to all of RhinoCommon and Grasshopper or if you can get by with the functionality provided by rhino3dm. In either case you can choose to use flask or not use flask. It doesn’t matter.

Hey Steve,
sorry to keep bothering, but from reading the code I can either register rhinoinside as a Middleware when initializing hops OR use flask as a Middleware.
I would actually be very happy if both together were possible, so if that’s the case could you provide an example how to do it?

Can you point me to the code that is giving you this false impression?

Yes, I also realize that the comment says it would not be strictly necessary. So it’s definitely not my goal to be some know-it-all, I just want to understand it and see if I can use it for a project with my students. That’s the reason I asked for an example before modifying the code or doing lots of trial and error.
Still would be very grateful for any further tips. :slight_smile:

# determine the correct middleware base on the source app being wrapped
        # when running standalone with no source apps
        if app is None:
            hlogger.debug("Using Hops default http server")
            params._init_rhino3dm()
            return hmw.HopsDefault()

        # if wrapping another app
        app_type = repr(app)
        # if app is Flask
        if app_type.startswith("<Flask"):
            hlogger.debug("Using Hops Flask middleware")
            params._init_rhino3dm()
            return hmw.HopsFlask(app, *args, **kwargs)

        # if wrapping rhinoinside
        # paractically this is not necessary. it is implemented this way
        # mostly to provide a level of consistency on how Hops is used
        elif app_type.startswith("<module 'rhinoinside'"):
            # detemine if running with rhino.inside.cpython
            # and init the param module accordingly
            if not Hops.is_inside():
                raise Exception("rhinoinside is not loaded yet")
            hlogger.debug("Using Hops default http server with rhinoinside")
            params._init_rhinoinside()
            return hmw.HopsDefault(*args, **kwargs)

        raise Exception("Unsupported app")

compute.rhino3d/src/ghhops-server-py/ghhops_server at master · mcneel/compute.rhino3d · GitHub</init.py

Interesting, I’m not sure why this code works this way. We’ll need to get some input from @eirannejad on this

I just tried it out by changing the code to use flask as well as rhinoinside, works out of the box.

I would argue that hops server should differentiate between server type (Hops HTTP or Flask) and app type (rhino3dm or rhinoinside). Throwing these into the same basket (like it is implemented at the moment) does not make sense to me and I would very much appreciate a fix/change to the official code @eirannejad @stevebaer . I’ll gladly contribute a suggestion via pull request but I don’t know if you would even consider, so I’m waiting for a sign :slight_smile:

EDIT:
Should anyone encounter the same problem, here is my workaround code that just subclasses the Hops class while introducing a force_rhinoinside option. I’m sure it can be solved more elegantly, but this is a quick WFM solution:

import ghhops_server as hs

class CustomHops(hs.Hops):
    """Custom Hops Middleware allowing Flask app to also run Rhino.Inside"""

    def __new__(cls,
                app=None,
                debug=False,
                force_rhinoinside=False,
                *args,
                **kwargs) -> hs.base.HopsBase:
        # set logger level
        hs.hlogger.setLevel(hs.logging.DEBUG if debug else hs.logging.INFO)

        # determine the correct middleware base on the source app being wrapped
        # when running standalone with no source apps
        if app is None:
            hs.hlogger.debug("Using Hops default http server")
            hs.params._init_rhino3dm()
            return hs.middlewares.HopsDefault()

        # if wrapping another app
        app_type = repr(app)
        # if app is Flask
        if app_type.startswith("<Flask"):
            if force_rhinoinside:
                hs.hlogger.debug("Using Hops Flask middleware and rhinoinside")
                hs.params._init_rhinoinside()
            else:
                hs.hlogger.debug("Using Hops Flask middleware and rhino3dm")
                hs.params._init_rhino3dm()
            return hs.middlewares.HopsFlask(app, *args, **kwargs)

        # if wrapping rhinoinside
        elif app_type.startswith("<module 'rhinoinside'"):
            # determine if running with rhino.inside.cpython
            # and init the param module accordingly
            if not CustomHops.is_inside():
                raise Exception("rhinoinside is not loaded yet")
            hs.hlogger.debug("Using Hops default http server with rhinoinside")
            hs.params._init_rhinoinside()
            return hs.middlewares.HopsDefault(*args, **kwargs)

        raise Exception("Unsupported app!")