Writing non blocking command line client

Open
#165 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
20/100
Issue type
Bug
Clarity
Needs clarification
Activity status
Stale
Tech stack
python
Domain
cli, networking

Research direction

Start with cli.py and the websocket.py trace, then compare the gevent 1.0.1 and ws4py 0.3.4 behavior around WebSocketClient.connect() and stdin handling. Reproduce the blocked terminal input with the supplied example; the issue has no stated acceptance criteria beyond explaining or resolving why input is not transmitted.

Written by the indexing model from the issue text.

Description

I am trying to create command line client for websocket message. User can type the message and mean if any message is received it should be printed to terminal.

Here is a example using gevent.

# -*- coding: utf-8 -*-
# cli.py

import sys
import os
import fcntl

import json
import urllib
import urlparse

import gevent
from gevent.socket import wait_read
from gevent.event import Event

import select

from ws4py.client.geventclient import WebSocketClient
from ws4py.exc import HandshakeError


class StdioPipedWebSocketHelper:
    def received_message(self, websocket, m):
        if m.is_binary:
            print("Binary data receieved. Skipping")
        else:
            sys.stdout.write(m.data)
        sys.stdout.write("\n")
        sys.stdout.flush()

    def opened(self, websocket):
        def connect_stdin():
            sys.stdout.write("\n")
            fcntl.fcntl(sys.stdin, fcntl.F_SETFL, os.O_NONBLOCK)
            while True:
                wait_read(sys.stdin.fileno())
                print(">")
                buf = sys.stdin.readline()
                if len(buf) == 0:
                    break
                print("[sending payload of length %d]" % (len(buf)))
                websocket.send_msg(buf)

        # XXX: We wait for the socket to open before reading stdin so that we
        # support behaviour like: echo foo | wssh -l ...
        gevent.spawn(connect_stdin)
        print("done")

    def closed(self):
        print("Connection closed by peer.")
        print("Shutting down...")
        exit(0)


class Client(WebSocketClient):
    def __init__(self, credentials):
        url = credentials.pop('url')
        qs = urllib.urlencode(credentials)
        full_url = url + "?" + qs
        print("Connecting: {}".format(full_url))
        WebSocketClient.__init__(self, full_url)

        self.shutdown_cond = Event()
        self.io = StdioPipedWebSocketHelper()

    def opened(self):
        self.io.opened(self)

    def closed(self, closed, reason):
        self.io.closed()
        self.shutdown_cond.set()

    def received_message(self, message):
        msg_obj = parse_msg(message)
        self.io.received_message(message=msg_obj)

    def send_msg(message):
        msg = json.dumps({'post': message})
        self.send(msg, False)

    def connect_and_wait(self):
        self.connect()
        self.shutdown_cond.wait()


def start(credentials):
    """Connect to the serve and keep running.

    :param credentials: `dict` containing details about server and tokens.
    """
    client = Client(credentials=credentials)
    try:
        client.connect_and_wait()
    except (IOError, HandshakeError), e:
        print >> sys.stderr, e



if __name__ == "__main__":
    credentials = {'url': 'ws://echo.websocket.org', 'foo': 'bar'}
    start(credentials=credentials)

Most of the code is borrowed from @progrium's work on wssh. wssh repo no more work because of old version of ws4py.

(ws4py_tests)➜  ws4py_test  python cli.py
Connecting: ws://echo.websocket.org?foo=bar
done
foo
foo
bar
^CConnection closed by peer.
Shutting down...

After connecting to server, code is blocked inside websocket.once. Input from terminal is not read and transmitted.

Trace
(ws4py_tests)➜  ws4py_test  python -m trace --trace cli.py
...
 --- modulename: greenlet, funcname: start
greenlet.py(186):         if self._start_event is None:
greenlet.py(187):             self._start_event = self.parent.loop.run_callback(self.switch)
greenlet.py(203):         return g
cli.py(46):         print("done")
done
websocket.py(426):                 while not self.terminated:
 --- modulename: websocket, funcname: terminated
websocket.py(194):         return self.client_terminated is True and self.server_terminated is True
websocket.py(427):                     if not self.once():
 --- modulename: websocket, funcname: once
websocket.py(295):         if self.terminated:
 --- modulename: websocket, funcname: terminated
websocket.py(194):         return self.client_terminated is True and self.server_terminated is True
websocket.py(299):         try:
websocket.py(300):             b = self.sock.recv(self.reading_buffer_size)
Venv
(ws4py_tests)➜  ws4py_test  pip freeze
gevent==1.0.1
greenlet==0.4.7
ws4py==0.3.4

Any thing I am doing dumb here ?

Dominant language
Python
Stars
1.1k
Forks
285
PR merge metrics
No merged PRs in 30d

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Lawouach/WebSocket-for-Python

All issues in Lawouach/WebSocket-for-Python

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.