[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: a network client

From: Catonano
Subject: Re: a network client
Date: Thu, 13 Dec 2018 14:11:48 +0100

Il giorno gio 13 dic 2018 alle ore 11:08 Mark H Weaver <address@hidden> ha

> I'm sorry, but there's not enough information in your email for me to
> understand what's happening.
> It would be most helpful if you could provide a small, self-contained
> example of the server and client code, so that we can try to reproduce
> the problem on our systems.
>      Regards,
>        Mark

Thank you Mark

Discussing this issue on the fediverse, I have been suggested a solution

Now, for the sake of the completeness of this thread, I'm still reporting
here, this could be useful to someone else !

Ok, here's my server (it requires Fibers)

(define-module (lsp-server)
  #:use-module (rnrs bytevectors)
  #:use-module (fibers)
  #:use-module (ice-9 textual-ports)
  #:use-module (ice-9 rdelim)
  #:use-module (ice-9 match)
  #:use-module (json)

  #:export (go

(define (make-default-socket family addr port)
  (let ((sock (socket PF_INET SOCK_STREAM 0)))
    (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
    (fcntl sock F_SETFD FD_CLOEXEC)
    (bind sock family addr port)
    (fcntl sock F_SETFL (logior O_NONBLOCK (fcntl sock F_GETFL)))

(define (client-loop port addr store)
  (setvbuf port 'block 1024)
  ;; Disable Nagle's algorithm.  We buffer ourselves.
  (setsockopt port IPPROTO_TCP TCP_NODELAY 1)
  (let loop ()
    ;; TODO: Restrict read-line to 512 chars.
    (let ((line (read-line port)))
       ((eof-object? line)
        (close-port port))
        ;; TODO this is where the server is gonna cook up its replies
    (display line)
    (put-string port "Content-Length: 121\r\n")
    (put-string port "\r\n")

;;    (scm->json
;;      (list
;;       (cons "jsonrpc" "2.0")
;;       (cons "id" 1)
;;       (cons "method" "textDocument/didOpen"))  port #:pretty #t )
        (put-string port "{" )
        (put-string port "\"jsonrpc\" : \"2.0\",")
        (put-string port "\"id\" : 1,")
        (put-string port "\"method\" : \"textDocument/didOpen\"")
        (put-string port "}")
        (force-output port)


(define (socket-loop socket store)
  (let loop ()
    (match (accept socket SOCK_NONBLOCK)
      ((client . addr)
       (spawn-fiber (lambda () (client-loop client addr store))
                    #:parallel? #t)

(define* (run-lsp-server #:key
                          (host #f)
                          (family AF_INET)
                          (addr (if host
                                    (inet-pton family host)
                          (port 11211)
                          (socket (make-default-socket family addr port)))
  (listen socket 1024)
  (sigaction SIGPIPE SIG_IGN)
  (socket-loop socket (make-hash-table)))

(define* (go #:key (port 11211))
  (lambda () (run-lsp-server #:host "" #:port port))

(run-fibers (go))

and this is my client

(define-module (lsp-client)
  #:use-module (rnrs bytevectors)
  ;;#:use-module (fibers)
  ;;#:use-module (fibers channels)
  #:use-module (ice-9 binary-ports)
  #:use-module (ice-9 textual-ports)
  #:use-module (ice-9 rdelim)
  #:use-module (ice-9 match)
  #:use-module (ice-9 textual-ports)
  #:use-module (ice-9 rdelim)
  #:use-module (ice-9 match)
  ;; #:use-module (json)

(define (connect-to-server addrinfo)
  (let ((port (socket (addrinfo:fam addrinfo)
                      (addrinfo:socktype addrinfo)
                      (addrinfo:protocol addrinfo))))
    ;; Disable Nagle's algorithm.  We buffer ourselves.
    (setsockopt port IPPROTO_TCP TCP_NODELAY 1)
    (fcntl port F_SETFL (logior O_NONBLOCK (fcntl port F_GETFL)))
    (setvbuf port 'block 1024)
    (connect port (addrinfo:addr addrinfo))

(define* (client  addrinfo #:key (request-port 11211))
  (let ((port (connect-to-server addrinfo)))
    (pk port)
    ;; TODO a proper request
    (put-string port "ciao")
    (put-char port #\newline)
    (force-output port)
    (display "server called")
    ;; TODO displaying the response
    (let loop ((store ""))
      (let ((line (read-line port)))
     ((eof-object? line)
        (close-port port)
        (display "Closing port")
        (display store)
        (force-output (current-output-port))))
      (display (string-append store line))
      (force-output (current-output-port))
      (loop (string-append store line))))))))

(define (run-ping-test port)
  (let ((addrinfo (car (getaddrinfo "" (number->string port)))))
    (client addrinfo #:request-port port)))

In order to test these 2:

1) in a terminal run

guile ./lsp-server.scm

2) in another terminal run

scheme@(guile-user)> ,m (lsp-client)
scheme@(lsp-client)> (run-ping-test 11211)

this will run the client and the client will call the server over the

What you will see on the client terminal is, roughly:

;;; (#<input-output: socket 14>)
server called
Content-Length: 121
Content-Length: 121

but if you call the same server with nc, like this:

$ nc localhost 11211
Content-Length: 121

{"jsonrpc" : "2.0","id" : 1,"method" : "textDocument/didOpen"}

as you see, you get a part of the reply that's a Json formatted string (a
json object or however you want to call it)

So the client in Guile Scheme is not receiving or is not reporting a part
of the server reply

It proceses 2 lines properly and then it hangs

Why is that ?

That is because this part of the server's reply
{"jsonrpc" : "2.0","id" : 1,"method" : "textDocument/didOpen"}

is NOT terminated with a \n character

So, on the client side, the "read-line" waits for the line to be terminated
Because that's supposed to read lines, after all

Adding a termination on the server's reply will solve the client's confusion

Now the next step should be dealing with line lengths

Lines that are too long should probably be refused, or truncated, somehow

but that's for a different thread

Thanks again

reply via email to

[Prev in Thread] Current Thread [Next in Thread]