Groovy Question

Spring-websocket to stream results of a command back to browser

I'm trying to stream back to browser the results of a ping command as they happen... I'd really like that the user could see the command running instead of just seeing the final result. Here's what I have (it's Groovy BTW, not that it matters) :)

@Controller
class IntranetWebsocketController {

@MessageMapping("/ping")
@SendToUser(destinations = "/topic/ping", broadcast = false)
static ResponseEntity (Map<String, String> address) {
def builder = new ProcessBuilder("/bin/ping", "-c", "10", "-s", "1400", "-W", "200", "-i", "0.2", "-D", "-O", address.get("ip"))
builder.redirectErrorStream(true)
def process = builder.start()
ResponseEntity.ok().body(new InputStreamResource(process.inputStream))
}
}


But then I get this:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.lang.UNIXProcess$ProcessPipeInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.http.ResponseEntity["body"]->org.springframework.core.io.InputStreamResource["inputStream"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:275) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1109) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1134) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:2484) ~[jackson-databind-2.8.1.jar:2.8.1]
at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertToInternal(MappingJackson2MessageConverter.java:240) ~[spring-messaging-4.3.2.RELEASE.jar:4.3.2.RELEASE]
... 17 common frames omitted


Any ideas on how can I solve this? Obviously it's a mapping problem but I have no clue on how I can deal with this. Is there a way to tell the mapper to just send everything as a String maybe?

[EDIT 1]

Based on what benjamin.d said I changed the code a bit to the following:

@Controller
class IntranetWebsocketController {
@Autowired
SimpMessagingTemplate template

@MessageMapping("/ping")
void pingSend (Map<String, String> ipaddress, Principal principal) {
def builder = new ProcessBuilder("/bin/ping", "-c", "10", "-s", "1400", "-W", "200", "-i", "0.2", "-D", "-O", ipaddress.get("ip"))
builder.redirectErrorStream(true)
def process = builder.start()

def inputReader = new InputStreamReader(process.inputStream)
def bufferedReader = new BufferedReader(inputReader)

def line
while ((line = bufferedReader.readLine()) != null) {
template.convertAndSendToUser(principal.name, "/topic/ping", line)
}
}
}


The only problem now is that if I have multiple tabs opened listening to this websocket, they all will react when the data is received since I removed the @SendToUser annotation. But that is a different matter.

Answer

Jackson will look for getters and annotated methods for serialization. In your case, it cannot find anything, and by default it will fail for empty beans.

You can disable this feature using:

mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

This code however will still not work, as Jackson will not be aware that new data is available for serialization. Instead you will have to launch a thread that will read in the process input stream (using the read method on InputStream). The read method will block execution until new data is available. Once you get data from InputStream.read(), then you push it back to your websocket clients.