5

Goal of the project is, is to have a JVM (Kotlin) server and a Python client communicating with each other by a websocket. The server must send updates to the client and the client will process these updates.

The server is a Spring Boot application, running the Spring Boot websocket server. The client is for now just a Python script, running websocket-client to connect to the server.

What is working:

  • List item
  • the client can connect to the server
  • the client can connect to a topic on the server (using STOMP)
  • the client can send a message to the server and the server receives and processes this message
  • the server can send a message on the websocket

What is not working:

  • the message from the server isn't received by the client

I've tried the receiving part of the client by connecting to the echo server of websocket.org (ws://echo.websocket.org) and the client receives the echoed messages from the server. So it seems to me that the problem isn't at the client side.


Code time.

Kotlin server: Code for creating the websocket server:

package nl.sajansen.screenchangenotifierserver

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.simp.SimpMessagingTemplate
import org.springframework.messaging.simp.config.MessageBrokerRegistry
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Controller
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer
import java.time.Instant
import java.time.format.DateTimeFormatter


@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig : WebSocketMessageBrokerConfigurer {
    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/ws").setAllowedOrigins("*")
    }

    override fun configureMessageBroker(config: MessageBrokerRegistry) {
        config.enableSimpleBroker("/topic", "/queue")
        config.setApplicationDestinationPrefixes("/app")
    }
}

@Controller
class WebsocketController @Autowired constructor(val template: SimpMessagingTemplate) {
    @Scheduled(fixedDelayString = "1000")
    fun blastToClientsHostReport() {
        println("Sending something on the websocket")
        template.convertAndSend("/topic/greeting", "Hello World");
    }

    @MessageMapping("/greeting")
    fun handle(message: String): String {
        println("Received message: $message")
        template.convertAndSend("/topic/greeting", message)
        return "[" + getTimestamp() + ": " + message
    }
}

fun getTimestamp(): String = DateTimeFormatter.ISO_INSTANT.format(Instant.now())

Gradle dependencies etc.:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.1.9.RELEASE"
    id("io.spring.dependency-management") version "1.0.8.RELEASE"
    kotlin("jvm") version "1.2.71"
    kotlin("plugin.spring") version "1.2.71"
    kotlin("plugin.jpa") version "1.2.71"
    kotlin("plugin.allopen") version "1.2.71"       // For JPA lazy fetching
}

allOpen {
    annotation("javax.persistence.Entity")
    annotation("javax.persistence.Embeddable")
    annotation("javax.persistence.MappedSuperclass")
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("com.h2database:h2:1.4.197") // Fixed version as a workaround for https://github.com/h2database/h2database/issues/1841
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test") {
        exclude(module = "junit")
        exclude(module = "mockito-core")
    }
    testImplementation("org.junit.jupiter:junit-jupiter-api")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
    testImplementation("com.ninja-squad:springmockk:1.1.2")
    compile("org.springframework.boot:spring-boot-starter-websocket")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "1.8"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Python client:

import random

import stomper as stomper
import websocket


def main():
  websocket.enableTrace(True)

  # Connecting to websocket
  ws = websocket.create_connection("ws://localhost:8080/ws")

  # Subscribing to topic
  client_id = str(random.randint(0, 1000))
  sub = stomper.subscribe("/topic/greeting", client_id, ack='auto')
  ws.send(sub)

  # Sending some message
  ws.send(stomper.send("/app/greeting", "Hello there"))

  while True:
    print("Receiving data: ")
    d = ws.recv()
    print(d)


if __name__ == '__main__':
  main()

Pip dependencies:

opencv-python==4.1.1.26
websocket-client==0.56.0
stomper==0.4.3

Console output

Now, the console output of the server is this. One can see that when the client isn't connected, there are no subscribers to send the scheduled message. Than the client connects successfully and the scheduled message is broadcasted to 1 subscriber.

Sending something on the websocket
2019-10-17 12:45:09.425 DEBUG 32285 --- [MessageBroker-3] org.springframework.web.SimpLogging      : Processing MESSAGE destination=/topic/greeting session=null payload=Hello World
Sending something on the websocket
2019-10-17 12:45:10.426 DEBUG 32285 --- [MessageBroker-3] org.springframework.web.SimpLogging      : Processing MESSAGE destination=/topic/greeting session=null payload=Hello World
2019-10-17 12:45:10.849  INFO 32285 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-10-17 12:45:10.850  INFO 32285 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-10-17 12:45:10.850 DEBUG 32285 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Detected StandardServletMultipartResolver
2019-10-17 12:45:10.855 DEBUG 32285 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : enableLoggingRequestDetails='true': request parameters and headers will be shown which may lead to unsafe logging of potentially sensitive data
2019-10-17 12:45:10.855  INFO 32285 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
2019-10-17 12:45:10.861 DEBUG 32285 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : GET "/ws", parameters={}
2019-10-17 12:45:10.865 DEBUG 32285 --- [nio-8080-exec-1] o.s.w.s.s.s.WebSocketHandlerMapping      : Mapped to org.springframework.web.socket.server.support.WebSocketHttpRequestHandler@27a9f025
2019-10-17 12:45:10.872 DEBUG 32285 --- [nio-8080-exec-1] o.s.w.s.s.s.WebSocketHttpRequestHandler  : GET /ws
2019-10-17 12:45:10.885 DEBUG 32285 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 101 SWITCHING_PROTOCOLS
2019-10-17 12:45:10.901 DEBUG 32285 --- [nio-8080-exec-1] s.w.s.h.LoggingWebSocketHandlerDecorator : New StandardWebSocketSession[id=393fc3cd-9ca3-1749-1ea8-541def6592e0, uri=ws://localhost:8080/ws]
2019-10-17 12:45:10.912 DEBUG 32285 --- [nboundChannel-2] org.springframework.web.SimpLogging      : Processing SUBSCRIBE /topic/greeting id=216 session=393fc3cd-9ca3-1749-1ea8-541def6592e0
2019-10-17 12:45:10.914 DEBUG 32285 --- [nboundChannel-7] .WebSocketAnnotationMethodMessageHandler : Searching methods to handle SEND /app/greeting session=393fc3cd-9ca3-1749-1ea8-541def6592e0 text/plain payload=Hello there, lookupDestination='/greeting'
2019-10-17 12:45:10.915 DEBUG 32285 --- [nboundChannel-7] .WebSocketAnnotationMethodMessageHandler : Invoking nl.sajansen.screenchangenotifierserver.WebsocketController#handle[1 args]
Received message: Hello there
2019-10-17 12:45:10.916 DEBUG 32285 --- [nboundChannel-7] org.springframework.web.SimpLogging      : Processing MESSAGE destination=/topic/greeting session=null payload=Hello there
2019-10-17 12:45:10.916 DEBUG 32285 --- [nboundChannel-7] org.springframework.web.SimpLogging      : Broadcasting to 1 sessions.
2019-10-17 12:45:10.919 DEBUG 32285 --- [nboundChannel-7] org.springframework.web.SimpLogging      : Processing MESSAGE destination=/topic/greeting session=393fc3cd-9ca3-1749-1ea8-541def6592e0 payload=[2019-10-17T10:45:10.917Z: Hello there
2019-10-17 12:45:10.919 DEBUG 32285 --- [nboundChannel-7] org.springframework.web.SimpLogging      : Broadcasting to 1 sessions.
Sending something on the websocket
2019-10-17 12:45:11.427 DEBUG 32285 --- [MessageBroker-3] org.springframework.web.SimpLogging      : Processing MESSAGE destination=/topic/greeting session=null payload=Hello World

The output of the client is this, but just waits until the end of the world for a message to receive:

--- request header ---
GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8080
Origin: http://localhost:8080
Sec-WebSocket-Key: 8ihHk0C5C0nji4s7u4atZQ==
Sec-WebSocket-Version: 13


-----------------------
--- response header ---
HTTP/1.1 101
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: Drq8+/vJkXkvToB3/AuCGMdYwuo=
Date: Thu, 17 Oct 2019 10:45:10 GMT
-----------------------
send: b'\x81\xb9\x88k\xa6j\xdb>\xe49\xcb9\xef(\xcda\xcf\x0e\xb2Y\x97\\\x82\x0f\xc3\x19\xfc\x02\xc8\x0b\xfc\x02\xc9\x04\xb2D\xd2\x05\xf8\x02\xc5E\xef\x19\xc3\x0f\xfc\x02\xc8\r\x82\n\xc5\x01\xb2\n\xd3\x1e\xe7a\xacj\x82'
send: b'\x81\xc5\xd0\x8dE6\x83\xc8\x0br\xda\xe9 E\xa4\xe4+W\xa4\xe4*X\xea\xa2$F\xa0\xa2"D\xb5\xe81_\xbe\xeaOU\xbf\xe31S\xbe\xf9hB\xa9\xfd \x0c\xa4\xe8=B\xff\xfd)W\xb9\xe3O<\x98\xe8)Z\xbf\xad1^\xb5\xff 6\xda'
Receiving data:

Some more background information in case you've better ideas: The updates will contain a image file (probably encoded to base64). The updates must be send close to realtime (allowed lag not more than 1 second). Interval of these updates can range from a couple of minutes to half a second. Client and server are two different machines, in the same network, but that network is limited on throughput.


So, who can spot what is going wrong?

I've read a good part of this document about websockets, but I can't see what is going wrong: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket

This answer also got me far, but the example itself isn't working to begin with (after correcting the while not True to while True).

Update 18 Oct 2019: I looked for a Python SocketJS client, because I got the Kotlin server part working with SocketJS and a JavaScript client. But I can't find any Python SocketJS client implementations.. I wonder if the only solution left is to start the websocket server in the Python applicaion (client side) and let the client send it's websocket server details to the Kotlin server, which then will connect to the client's websocket server. It's not a pretty solution, but I wonder if it will work. I'll keep you updated.

Update 1 Feb 2021: I did not spend any more time to this problem. But I will let this post be in case someone figures this out and can help other people with the solution.

1 Answer 1

1

I was facing the same problem last month. I assume that you are using Stomp over Web Socket for Python. Referring to Websocket Client not receiving any messages, I think you forget to initiate the connection.

Therefore, you should change your python-client code from this

...
 # Connecting to websocket
  ws = websocket.create_connection("ws://localhost:8080/ws")

  # Subscribing to topic
  client_id = str(random.randint(0, 1000))
  sub = stomper.subscribe("/topic/greeting", client_id, ack='auto')
  ws.send(sub)
...

into this

 # Connecting to websocket
 ws = websocket.create_connection("ws://localhost:8080/ws")
  
 # Initate Stomp connection!
 ws.send("CONNECT\naccept-version:1.0,1.1,2.0\n\n\x00\n")

  # Subscribing to topic
  client_id = str(random.randint(0, 1000))
  sub = stomper.subscribe("/topic/greeting", client_id, ack='auto')
  ws.send(sub)

I Hope its worked.

Although, I solve this problem by Implementing the WebSocketApp class from import websocket library, instead of doing procedural steps like your code.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.