Use Retrofit to consume Server Sent Events - java

I am trying to consume a rest api [1] that sends server sent events to the client.
I am currently using retrofit from square to consume this but I am not sure how to do it.
Can some one who has prior experience working with retrofit help ?
If not retrofit please suggest other Java libraries that can do that.
[1] https://mesosphere.github.io/marathon/docs/rest-api.html#get-v2-events

Try this library : oksee.
OkSse is an extension library for OkHttp to create a Server-Sent Event (SSE) client
As I experienced the same problem,from my research it is the best option for now, as Retrofit doesn't support it.
https://github.com/heremaps/oksse

I know that it is old question. But I didn't find a whole example and now try to provide it by my code. We use only retrofit and coroutines
1.In retrofit API interface need to add code. Pay attention what we use #Streaming and return type Call<ResponseBody>
#POST("/v1/calc/group-prices")
#Streaming
fun calculateGroupPrices(#Body listOptions: List<GroupCalculatorOptions>): Call<ResponseBody>
2.In your repository class need to add this code. Pay attention what we use flow and read a stream. To understand that a message with a payload has arrived, it must begin with "data:"
fun loadGroupDeliveryRateInfos(listOptions: List<GroupCalculatorOptions>) = flow {
coroutineScope {
val response = restApi.calculateGroupPrices(listOptions).execute()
if (response.isSuccessful) {
val input = response.body()?.byteStream()?.bufferedReader() ?: throw Exception()
try {
while (isActive) {
val line = input.readLine() ?: continue
if (line.startsWith("data:")) {
try {
val groupDeliveryRateInfo = gson.fromJson(
line.substring(5).trim(),
GroupDeliveryRateInfo::class.java
)
emit(groupDeliveryRateInfo)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
} catch (e: IOException) {
throw Exception(e)
} finally {
input.close()
}
} else {
throw HttpException(response)
}
}
}
3.Final step we need to collect our data in ViewModel. We just need to call the method from repository
repository.loadGroupDeliveryRateInfos(it.values.toList())
.collect { info ->
handleGroupDeliveryRateInfo(info)
}
And that's all, no additional libraries are needed.

There's no real need to conflate Retrofit and SSE. Use retrofit to get an inputstream, then find (or write) an inputstream parser that chunks up the SSE events.
In retrofit I have this:
public interface NotificationAPI {
#GET("notifications/sse")
Call<InputStream> getNotificationsStream(#retrofit2.http.Query("Last-Event-ID") String lastEventId);
}
I wrote a quick converter factory for InputStream:
public class InputStreamConverterFactory extends Converter.Factory {
private static class InputStreamConverter implements Converter<ResponseBody, InputStream> {
#Nullable
#Override
public InputStream convert(ResponseBody value) throws IOException {
return value.byteStream();
}
}
#Override
public #Nullable
Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (type.equals(InputStream.class)) {
return new InputStreamConverter();
}
return null;
}
}
My client code looks like this:
var cinputStream = api.getNotificationsStream(null);
var inputStream = cinputStream.execute().body();
try(var sseStream = new MySSEStreamParser(inputStream)) {
//handle the stream here...
}
There is an OkHttp SSE parser, which you probably could use. However:
The OkHttp SSE code comes with threads. Chances are you'd want to bring your own threading model.
The actual OkHttp SSE parser is an internal package. Which doesn't make it a great candidate for lifting.

Related

How extract response to String and save it to variable

Being new to Java/JSON/REST Assured topics, I would like to extract a parameter of "token": from a JSON response body as a String and store it as variable which I could take to some other classes and use there. However, I have tried it and have not found a way. Below is part of a code which I have created at the beginning in a same manner as other requests stored in this class, but this is the first one from which I need something from the response:
public FakeTokenVO fakeToken() {
String payload = "payloadthere";
return given(specBuilder.fakeTokenRequestSpecification()) .
body(payload)
.log().all()
.when()
.post(RestApiRoutes.FAKE_URI)
.then()
.log().all()
.extract()
.response()
.as(FakeTokenVO.class);
}
Don't mind about the payload and those VO classes as it is stored as data model somewhere else.
Response from the request made looks like this:
{
"createTokenResponse": {
"createTokenSuccess": {
"token": "token_with_somewhere_about_700_characters"
}
}
}
Here is how I have tried to modify it to get the part of response which I need later (the token to authorize other requests):
#Test
public void fakeToken()
{
String payload = "payloadthere";
String token = given(specBuilder.fakeTokenRequestSpecification())
.body(payload)
.log().all()
.when()
.post(RestApiRoutes.FAKE_URI)
.then()
.log().all()
.extract()
.response()
.body().path("createTokenResponse.createTokenSuccess.token");
System.out.print(token);
}
This test returns me a value which I needed, but I do not know how to implement it as a method instead of test. Please help how should I approach it? What am I missing there? I tried to search for answers, but I haven't found a solution yet or do not know how to implement it in my part of the code.
I assume that you can get your response as a String. So all you need to do is to parse your Json String. For that you can use any available Json parser. The most popular ones are Json-Jackson (also known as Faster XML) or Gson (by Google). Both are very well known and popular. (My personal preference is Jackson, but it is a matter of opinion).
However, For simplistic cases like this I wrote my own utility (a thin wrapper over Jackson library) that allows you to parse Json String very simply without learning relatively complex libraries. With my utility your code may look like this:
try {
Map<String, Object> map = JsonUtils.readObjectFromJsonString(jsonStr, Map.class);
Map<String, Object> innerMap = map.get("createTokenResponse");
Map<String, Object> innerMap2 = map.get("createTokenSuccess");
String token = innerMap.get("token");
} catch (IOException e) {
e.printStacktrace();
}
Or you can create your own classes such as
public class TokenResult {
String token;
//getter and setter
}
public class TokenHolder {
private TokenResult createTokenSuccess;
//setter and getter
}
public class TokenResponse {
private TokenHolder createTokenResponse;
//setter and getter
}
And than your code may look like this:
try {
TokenResponse response = JsonUtils.readObjectFromJsonString(jsonStr, TokenResponse .class);
String token = response.getCreateTokenResponse().getCreateTokenSuccess().getToken();
} catch (IOException e) {
e.printStacktrace();
}
Here is a Javadoc for JsonUtils class. This Utility comes as part of Open Source MgntUtils library written and maintained by me. You can get the library as maven artifact on Maven Central here or on the github (including source code and javadoc)

How to publish message to 2 kafka topics based on condition - spring cloud stream

Currently i have a spring clound funtion which consumes a topic and publish in to another topic. But for particular condition i need to publish message to another topic. Basically need to publish message to multiple topic from spring cloud function.
Current code snippets
#Bean
public Function<Message<InputMessage>, Message<OutputMessage>>
messageTransformer(){
return new KafkaTransformer();
}
public class KafkaTransformer
implements Function<
Message<InputMessage>, Message<OutputMessage>> {
#Override
public Message<OutputMessage> apply(
Message<InputMessage> inputMessage) {
try {
Message<OutputMessage> outputMessage = process(inputMessage);
return outputMessage;
} catch (Exception e) {
// need to send message to another topic ( which is other than dlq).
}
}
}
spring.cloud.stream.bindings.messageTransformer-in-0.destination=input.topic
spring.cloud.stream.bindings.messageTransformer-out-0.destination=output.topic
spring.cloud.function.definition=messageTransformer
Did you look into using StreamBridge API for this? Sounds like that should work for what you are looking for. Here are the docs.

Define generic Protobuf converter based on annotated method value type

Currently I use JSON for the messages in spring kafka and that is pretty easy and works with almost no coding:
#KafkaListener(topics = "foo.t",)
public void receive(Foo payload) {
LOG.info("received payload='{}'", payload);
doMagic(payload);
}
#KafkaListener(topics = "bar.t",)
public void receive(Bar payload) {
LOG.info("received payload='{}'", payload);
doMagic(payload);
}
and little config:
# Kafka Config
spring.kafka.bootstrap-servers=broker.kafka
spring.kafka.consumer.group-id=some-app
spring.kafka.consumer.properties.value.deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=com.example.someapp.dto
Which works (great) because the content/type information is encoded in the JSON and thus can be restored from the bytes alone. (There are issues with that as well, but it works)
However in protobuf I don't have these meta-information or at least I don't know where to find them.
Question:
Is there a way to declare a generic kafka MessageConverter that works for multiple types, without throwing all the nice abstraction/auto configuration from spring out of the window?
(I would also like to use this for JSON, as encoding the content/data type of the message i the message has both some security and compatibility issues)
I would like to avoid something like this solution: https://stackoverflow.com/a/46670801/4573065 .
Alternatives
Write a message converter/ Deserializer that tries all kafka classes.
#Override
public T deserialize(String topic, byte[] data) {
try {
return (T) Foo.parse(data);
} catch (Exception e) {
try {
return (T) Bar.parse(data);
} catch (Exception e1) {
throw e
}
}
}
However this will probably negate all performance gains I hope to get by using a binary format.
Another alternative would be to statically map topic->content type however this would still be something error prone or at least hard to make spring configure for you.
EDIT: My producer looks like this:
public void send(String message) {
kafkaTemplate.send("string.t", message);
}

auto connect in case of error

i am working on android app , I often get HTTP error 500 when i try to access the url due to bad connectivity which causes my app to fail . Response returned from URL is in JSON format so in order to Parse this json i used jackson api
JsonNode monthlyUrlNode = objectMapper.readValue(url, JsonNode.class);
In case of failure i want to reconnect to url with a delay of 30 seconds
i referred this Retry a connection on timeout in Java , but it is not of much use to me
Have you thought of using a Proxy object? Proxy let's you wrap an interface in a way that you can perform the same intervention independent of the method being called. Let's assume you've already created a client object for accessing interface SomeService. Then you can create a proxy class with a 30-second retry built in:
public class ServiceProxy implements InvocationHandler {
private final SomeService realClient;
public ServiceProxy(SomeService realClientObject) {
this.realClient = realClientObject;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(realClient, args);
if (result instanceof URL) {
JsonNode urlNode = objectMapper.readValue(url, JsonNode.class);
if (some condition on urlNode) {
// Wait and retry
Thread.sleep(30000);
result = method.invoke(realClient, args);
}
}
return result;
}
}
You create the proxy object by passing it the original interface, like:
public class ProxyFactory {
public static SomeService get(SomeService originalClient) {
return (SomeService)Proxy.newProxyInstance(SomeService.class.getClassLoader(),
new Class[]{SomeService.class},
new ServiceProxy(originalClient));
}
}
If you need this sort of fine control, do not pass a URL to Jackson. Use an appropriate HTTP library to read the content from the URL into memory, retrying as needed, and then feed it to Jackson.

Using Gson for Restlet to convert Post data (Representation) to an Object

I am trying to post a form to a Restlet ServerResource and read it into an object using Gson Restlet Extension.
There's no documentation on how to use it and nothing on StackOverflow.
What is the correct way of using gson restlet extension?
Following is what I have tried so far:
public class CustomerSegment {
private int visitsMin;
private int visitsMax;
// Getters, Setters and constructors
}
public class CampaignsResource extends ServerResource {
#Post
public Representation createCampaign(Representation entity) {
Form form = new Form(entity);
// Using form is the usual way, which works fine
// form: [[visitsMin=3], [visitsMax=6]]
CustomerSegment segment = null;
// Following hasn't worked
GsonConverter converter = new GsonConverter();
try {
segment = converter.toObject(entity, CustomerSegment.class, this);
//segment = null
} catch (IOException e1) {
e1.printStackTrace();
}
GsonRepresentation<CustomerSegment> gson
= new GsonRepresentation<CustomerSegment>(entity, CustomerSegment.class);
try {
segment = gson.getObject();
//NullPointerException
} catch (IOException e) {
e.printStackTrace();
}
return new EmptyRepresentation();
}
}
Form data that is being posted:
In fact, you can leverage the built-in converter support of Restlet without explicitly use the gson converter.
In fact, when you put the GSON extension within the classpath, the converter it contains is automatically registered within the Restlet engine itself. To check that you can simply use these lines when starting your application:
List<ConverterHelper> converters
= Engine.getInstance().getRegisteredConverters();
for (ConverterHelper converterHelper : converters) {
System.out.println("- " + converterHelper);
}
/* This will print this in your case:
- org.restlet.ext.gson.GsonConverter#2085ce5a
- org.restlet.engine.converter.DefaultConverter#30ae8764
- org.restlet.engine.converter.StatusInfoHtmlConverter#123acf34
*/
Then you can rely on beans within signatures of methods in your server resources instead of class Representation, as described below:
public class MyServerResource extends ServerResource {
#Post
public SomeOutputBean handleBean(SomeInputBean input) {
(...)
SomeOutputBean bean = new SomeOutputBean();
bean.setId(10);
bean.setName("some name");
return bean;
}
}
This works in both sides:
Deserialization of the request content into a bean that is provided as parameter of the handling method in the server resource.
Serialization into the response content of the returned bean.
You don't have anything more to do here.
For the client side, you can leverage the same mechanism. It's based on the annotated interfaces. For this, you need to create an interface defining what can be called on the resource. For our previous sample, it would be something like that:
public interface MyResource {
#Post
SomeOutputBean handleBean(SomeInputBean input);
}
Then you can use it with a client resource, as described below:
String url = "http://localhost:8182/test";
ClientResource cr = new ClientResource(url);
MyResource resource = cr.wrap(MyResource.class);
SomeInputBean input = new SomeInputBean();
SomeOutputBean output = resource.handleBean(input);
So in your case, I would refactor your code as described below:
public class CampaignsResource extends ServerResource {
private String getUri() {
Reference resourceRef = getRequest().getResourceRef();
return resourceRef.toString();
}
#Post
public void createCampaign(CustomerSegment segment) {
// Handle segment
(...)
// You can return something if the client expects
// to have something returned
// For creation on POST method, returning a 204 status
// code with a Location header is enough...
getResponse().setLocationRef(getUri() + addedSegmentId);
}
}
You can leverage for example the content type application/json to send data as JSON:
{
visitsMin: 2,
visitsMax: 11
}
If you want to use Gson, you should use this content type instead of the urlencoded one since the tool targets JSON conversion:
Gson is a Java library that can be used to convert Java Objects into
their JSON representation. It can also be used to convert a JSON string
to an equivalent Java object. Gson can work with arbitrary Java objects
including pre-existing objects that you do not have source-code of.
Hope it helps you,
Thierry

Categories