[Maya-Python] Request-Response Inversion

94 views
Skip to first unread message

Marcus Ottosson

unread,
Mar 9, 2015, 4:44:22 AM3/9/15
to python_in...@googlegroups.com

Hi all,

I’ve made somewhat of a hack that I’d like to you by you to hear what you think of it.

In a nutshell, sending a request to a web-server is one way; you send, the server responds. It’s a “pull” relationthip. There’s no way to get the server to send any data to a client without it having asked for it, that’s why you’ve got things like socket.io and gevent. A “push” relationship.

The hack then, is a way around this and assumes that you have control over both server and client. It works like this.

Sending a request from server-to-client

  1. The client makes an initial, regular request
  2. The request is not responded to, but simply held on to; like a handle to the client
  3. When the server is looking to make a request to the client, it simply responds.

The handle can be held indefinitely, and may be re-established per response. Effectively allowing the server to make any amount of “requests” to the client.

Here’s an implementation.

"""Request-response inversion

Inverse the typical request-response pattern so as to allow a host to make
requests to the client.

Usage:
    # Terminal 1
    # The client
    >>> import server
    >>> import threading
    >>> t = threading.Thread(target=server.app.run, kwargs={"threaded": True})
    >>> t.start()

    # Terminal 2
    # The host
    $ curl -X POST http://127.0.0.1:5000/dispatch
    ... blocking

    # Terminal 1
    # Client sending "request"
    >>> server.queue.put("show")

    # Terminal 2
    # Host receiving "request"
    {"status": "ok", "result": "Showing.."}

Requests may be made *before* having been responded to. With that, we've
got a true bi-directional communication link going.

Usage:
    # Terminal 1
    # The client
    >>> import server
    >>> import threading
    >>> t = threading.Thread(target=server.app.run, kwargs={"threaded": True})
    >>> t.start()

    # Terminal 2
    # First request from host
    $ curl -X POST http://127.0.0.1:5000/dispatch
    ... blocking

    # Terminal 3
    # Second request from host
    $ curl -X GET  http://127.0.0.1:5000/dispatch
    {"status": "ok", "queue": []}

    # Terminal 1
    # Client sending "request"
    >>> server.queue.put("show")

    # Terminal 2
    # Host receiving "request".
    {"status": "ok", "result": "Showing.."}

"""

# Standard library
import Queue

# Third-party dependencies
import flask

app = flask.Flask(__name__)

# This queue is used for communication between threads.
# It'll be queried (and may be empty) by the client,
# and filled by the host. When filled, the blocking query
# is released and processed.
queue = Queue.Queue()

@app.route("/dispatch", methods=["GET", "POST"])
def dispatch():
    if flask.request.method == "GET":
        return flask.jsonify(status="ok", queue=list(queue.queue))
    else:
        cmd = queue.get()
        if cmd == "show":
            return flask.jsonify(status="ok", result="Showing..")
        return flask.jsonify(status="fail",
                             result="Command not recognised: %s" % cmd)

```

Technically, no requests are being made to the client. Practically on the other hand, any data can be sent, asynchronously, in both directions.

Thoughts?

Best,
Marcus

--
Marcus Ottosson
konstr...@gmail.com

Marcus Ottosson

unread,
Mar 9, 2015, 5:13:20 AM3/9/15
to python_in...@googlegroups.com

I’d like to you by you to hear what you think of it.

That’s some literacy excellence, right there. :)

Justin Israel

unread,
Mar 9, 2015, 5:37:47 AM3/9/15
to python_in...@googlegroups.com
Can you explain a concrete problem you are trying to solve? Without a concrete problem, I am not sure where this would be specifically useful over other options.

What this looks like at face-value is a long poll (http://en.wikipedia.org/wiki/Push_technology). The standard http communication you originally referred to as "push" pattern is actually a request-response pattern. The client issues an http request, and the server responds. What you have proposed now is more of a push pattern, because the client "subscribes" to a channel by hitting an endpoint and waiting with interest. When something of interest becomes available, the server will then push to a client. 

Now that being said, it is one option for an http client to subscribe to specific information and wait for it to be ready. But it isn't necessarily more flexible or robust than a websocket approach, which is actually true bi-directional. The server/client connection would be established, and either side is free to send or receive, according to some established protocol. In your example, the client is subscribing to a specific request, since it must hit an endpoint of a determined type to express interest in something. It cannot change its request at this point, and it expects to receive "something". You could easily decide to send structure you want though, and have the client take different actions, to make it a generic communication push system.  Basically, socket-io does all of these techniques combined, in order to provide a highly-compatible solution of fallback mechanisms. 

Then there is also the new Server Sent Events spec: http://www.w3.org/TR/eventsource/

Basically, these techniques exist. Are you trying to do something a bit different than these approaches to solve a different problem?

Justin



On Mon, Mar 9, 2015 at 10:13 PM Marcus Ottosson <konstr...@gmail.com> wrote:

I’d like to you by you to hear what you think of it.

That’s some literacy excellence, right there. :)

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/CAFRtmOCCmk5mrOcz0dXtGCvGaN7RqMZZgiTXmg3TPnzTZDu_0A%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Marcus Ottosson

unread,
Mar 9, 2015, 5:52:45 AM3/9/15
to python_in...@googlegroups.com
Thanks Justin, long-polling sounds like what this is.

The specific problem is having a client receive a single notification from the server, with as little effort as possible. There is no data involved, and the event will only happen once. Because of this, I'm looking for alternatives to more complete solutions, like socket.io, that fit the bill.

Justin Israel

unread,
Mar 9, 2015, 6:13:29 AM3/9/15
to python_in...@googlegroups.com

Then it sounds like the long polling solution should work just fine if you only need a one-off notification.


On Mon, 9 Mar 2015 10:52 PM Marcus Ottosson <konstr...@gmail.com> wrote:
Thanks Justin, long-polling sounds like what this is.

The specific problem is having a client receive a single notification from the server, with as little effort as possible. There is no data involved, and the event will only happen once. Because of this, I'm looking for alternatives to more complete solutions, like socket.io, that fit the bill.

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Marcus Ottosson

unread,
Mar 9, 2015, 6:30:22 AM3/9/15
to python_in...@googlegroups.com

Cool, thanks.

At the risk of going off topic, I was also looking into the signals standard library for this, as I’ve got a handle to the Popen instance of the client in this case.

Have you, or anyone, had any experience in passing user-defined events across processes using signals?​

Justin Israel

unread,
Mar 9, 2015, 6:35:23 AM3/9/15
to python_in...@googlegroups.com

Does this mean the communication is taking place all on the same machine? If that is the case, why not just use sockets? Why the whole http server/client?

I've used signals for specific things and you don't necessarily have to have launched or own the target process. In a certain daemon service I maintain, I use USR1 to signal a specific type of shutdown request as opposed to a SIGTERM.

If you own the child process, you can just use a pipe between them to communicate.


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Marcus Ottosson

unread,
Mar 9, 2015, 6:44:23 AM3/9/15
to python_in...@googlegroups.com

Ah, excellent. This is what I’ve been looking for​, I merely used this inverse request thing because I didn’t understand the alternatives well enough.

If you own the child process, you can just use a pipe between them to communicate.

I’ve seen this mentioned, read about it, attempted it, but never really understood it. Could you provide a small example?

Here’s how my situation looks.

>>> import subprocess
>>> proc = subprocess.Popen(["python"], creationflags=subprocess.CREATE_NEW_PROCESS)
>>> send_signal_somehow(proc)

It’ll open up a new interpreter, how can I handle the incoming signal in there, and how do I send it from the parent process?

In a certain daemon service I maintain, I use USR1 to signal a specific type of shutdown request as opposed to a SIGTERM.

That also sounds viable; in this case, it’s a request for a GUI in an external process to show, which I’d imagine being similar if not identical to a shutdown request.

Marcus Ottosson

unread,
Mar 9, 2015, 6:45:15 AM3/9/15
to python_in...@googlegroups.com

Sorry, that’s:

>>> import subprocess
>>> proc = subprocess.Popen(["python"], creationflags=subprocess.CREATE_NEW_CONSOLE)
>>> send_signal_somehow(proc)
--
Marcus Ottosson
konstr...@gmail.com

Justin Israel

unread,
Mar 9, 2015, 6:52:11 AM3/9/15
to python_in...@googlegroups.com

Unfortunately I can't give a custom example at this moment,  but you have a perfectly good stdin pipe that can be used in your subprocess Popen command. Just google how to use it in the meantime. You can write to stdin in the parent process and read from it in the child. It would be exactly like what you originally described with http.The child can block and wait on stdin until the parent sends something.


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Marcus Ottosson

unread,
Mar 9, 2015, 7:03:05 AM3/9/15
to python_in...@googlegroups.com

No problem, Justin.

Also, I missed this.

why not just use sockets?​

Sockets, as in starting another server within the client? Or are you referring to a more light-weight way of utilising sockets here? The weight of instantiating another server was one reason for not doing that initially.

Justin Israel

unread,
Mar 9, 2015, 2:15:10 PM3/9/15
to python_in...@googlegroups.com

My suggestion of sockets was a lighter weight suggestion than using flask to start an httpserver and route full requests between processes. Maybe I am missing something from that example. But flask is an extremely heavy handed approach to getting to processes talking on the same machine. Especially when you have a parent child process relationship. That was why I ended up suggesting just a pipe when you are launching the process. A pipe, say using stdin, is like a socket connection that just gets connected automatically, with one end have a read side and one having a write side.
That was why I had originally asked about the concrete problem, because an httpserver is completely reasonable under one circumstance a heavy in another. Same with a pipe being reasonable in one, and not feasible in another.


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Marcus Ottosson

unread,
Mar 9, 2015, 2:28:26 PM3/9/15
to python_in...@googlegroups.com

But flask is an extremely heavy handed approach to getting to processes talking on the same machine.

That’s exactly what I’ve got, and can understand it’s heavy-handed.

I did have a closer look at subprocess and pipes, but quickly remembered what my struggles were last time I tried. Maybe in time I’ll transition that way, because at the moment it’s above my head.

I have no experience with sockets either; I assumed it was the same as communicating via GET and POST using urllib2.

At the end of the day, I have two processes, on the same machine, in a parent/child relationship, passing plain-old-data between each other (one-way, mostly) using Flask and HTTP requests. If you find the time for an example of an alternative, that would be really great.

Best,
Marcus

Justin Israel

unread,
Mar 9, 2015, 4:13:00 PM3/9/15
to python_in...@googlegroups.com
Here is an example of communicating via stdin between the parent and child process:
https://gist.github.com/justinfx/b94fab9d1f056380cb28

You launch the parent, and it will launch the process, using a PIPE for stdin. The child then starts a thread and reads lines from stdin. The parent can communicate to it whenever it wants. 

As for the topic on sockets, that is what http is using under the hood, with http being the protocol that is used for the handshaking pattern and the req-reply message pattern (i.e. there is a header and a body and it happens this way and that way). Dropping down to sockets just means you make your own connection, to a port on the local machine in this particular case, and speak your own protocol, whether that is just writing lines, or sending json or pickle. And you don't need an external web framework dependency to do that. It has some similar caveats to the http approach as well. For instance, the default port for a web server is 80. A given user may or may not have permissions for an application they launch to bind to port 80, which means you have to pick a non-privileged port. There is also the similar situation where port 80 is already in use by a local web server and not available to you, so you have to pick a random one. And in this case, you have to make sure to communicate the right port number to the child, which you could do through an env key like "PARENT_PORT=12345".  

A pipe is like a phone call between two people. You aren't going to clash because each parent/child will have their "private line". Another caveat of your original solution is that you might run into a problem where you launch two instances of the parent. They both can't bind to the same port to serve the http endpoints. Also if there is more than one child somehow (not sure exactly how things get launched), they all would try and communicate to the same http server that was able to bind to the port, and each child would pull message items from the queue so they would split the messages like a work pipeline. 

All in all, the pipe approach would probably be easiest to manage. 



--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Tony Barbieri

unread,
Mar 9, 2015, 4:16:20 PM3/9/15
to python_in...@googlegroups.com
If you have zeromq already installed and available, it's another option...possibly also heavy-handed compared to a PIPE.


For more options, visit https://groups.google.com/d/optout.



--
Tony

Marcus Ottosson

unread,
Mar 9, 2015, 4:40:01 PM3/9/15
to python_in...@googlegroups.com
Thanks Justin, having a look.

Re zeromq: That would be good option, and also solve this problem before ever having it. I did run with it at first, but found distribution to be the killer here.

The reason, by the way, for using Flask here is because it's already running as I'm using it for more than a simple signal across processes. It works well for what it's doing, but I'd imagine PIPE, sockets or even signals do be equally suited and a potential replacement.

Marcus Ottosson

unread,
Mar 10, 2015, 7:32:57 AM3/10/15
to python_in...@googlegroups.com

The long-polling approach presented another challenge; how do you cancel an ongoing HTTP request?

As the request is ongoing until given a reply from the server, when the server finally dies, the client will be left without a response and throws an exception.

Is it safe to discard the exception, and force the termination?

I ended up using

os._exit(1)

Instead of

sys.exit()

Which works, but probably bypasses more than just clean-up of the sockets involved.

I noticed you’ve gotten a similar question before, Justin.
http://stackoverflow.com/questions/9389414/how-to-kill-a-requests-request-object-that-is-in-progress

Reply all
Reply to author
Forward
0 new messages