Connect to Web-Socket-Server at runtime from back-end java class - java

I have implemented the following Web-Socket-Server using spring. We don't want to use STOMP and JSocks.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketServerHandler(), "/websocket/user/{userId}");
}
}
#Service
public class WebSocketServerHandler extends TextWebSocketHandler {
private List<WebSocketSession> sessions = new ArrayList<WebSocketSession>();
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
session.sendMessage(message);
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions = null;
}
}
Now I can successfully connect to that Web-Socket-Server from front-end client (browser). Now I want to connect to that Web-Socket-Server from some of my java class at Run-Time and then want to send messages to that Web-Socket-Server. Anyone have any idea how I can I do that?
I have added a test Rest Controller as below:
#RequestMapping(value = "/web-socket/message/{message}")
public void sendMessage(#PathVariable("message") final String message) throws Exception {
final String WS_URI = "ws://localhost:8080/web-socket/user/23";
final CountDownLatch latch = new CountDownLatch(1);
WebSocketClientHandler handler = new WebSocketClientHandler(latch);
WebSocketClient client = new StandardWebSocketClient();
WebSocketSession session = client.doHandshake(handler, WS_URI).get();
session.sendMessage(new TextMessage(message));
latch.await(5, TimeUnit.SECONDS);
session.close();
}
I am successfully able to send message to the websocket server, only for the 1st time by calling this Test RestController using google rest client. If I need to send message again, then I have to restart the tomcat server. Is there anything wrong what I am doing here?

spring-websocket has WebSocket client support. The basic contract is the WebSocketClient. Spring doesn't implement the entire WebSocket support, but offers an abstraction layer over other implementations. The common Spring client abstraction is the StandardWebSocketClient. A simple example look something like
public static void main(String... args) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
EchoHandler handler = new EchoHandler(latch);
WebSocketClient client = new StandardWebSocketClient();
WebSocketSession session = client.doHandshake(handler, ECHO_URL).get();
session.sendMessage(new TextMessage("Hello World"));
latch.await(5000, TimeUnit.SECONDS);
session.close();
}
Where EchoHandler is a client side WebSocketHandler (same as server side).
public class EchoHandler extends TextWebSocketHandler {
private final CountDownLatch latch;
public EchoHandler(CountDownLatch latch) {
this.latch = latch;
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
System.out.println("------- received client message ------");
System.out.println(message.getPayload());
System.out.println("--------- end client message ---------");
latch.countDown();
}
}
You can also wrap the WebSocketClient in a WebSocketConnectionManager if you are running the client in a String environment. This will give you some lifecycle helpers if you need it. See example in Spring Boot sample.
As far the the dependencies, like I said, Spring doesn't implement the entire WebSocket client support, so you need an implementation. If you are running the client in a server that supports WebSocket, then you will not need to add anything. The support implementation should already be on the classpath from the server. The main supported WebSocket implementations are Jetty, Tomcat, Undertow (mainly Wildfly or standalone Undertow), Tyrus (i.e. Glassfish, WebLogic).
If you are running the client in a standalone app, then you will need to add a WebSocket implementation. Unfortunately, from what I tested, none of the implementation provide a complete workable "client-only" jar. They either require or already pull in the complete (server included) implementation. So using just the client, will still require pulling in a bunch of server jars. Here's what I came up with from testing
Common for all
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>${websocket.version}</version>
</dependency>
Tomcat
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
Undertow
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<version>${undertow.version}</version>
</dependency>
Tyrus
<!-- tyrus-client is pulled in by this. -->
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-server</artifactId>
<version>${tyrus.version}</version>
</dependency>
Jetty
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
<version>${jetty.version}</version>
</dependency>
With Jetty, it does't use the standard Java WebSocket API, so it is not used in the StandardWebSocketClient. You will need to do instead
JettyWebSocketClient client = new JettyWebSocketClient();
client.start();
Everything else above is the same though.
I just looked at the Spring source to see all the different dependencies they used. You can check it out and play around with the dependencies. Maybe there are different more efficient (lighter) combination of dependencies that will still work. The above is just what I tested with and was able to get to work.

Related

How to create an "https" connection in an MSF4J microservice?

I'm new to MSF4J micorservices. I was trying out a hello-world MSF4J example as given here: https://javahelps.com/wso2-msf4j-hello-world. Everything works as expected, but when I try to access the service using an https url https://localhost:8080/service in the Chrome browser, I am getting an error as This site can’t provide a secure connection. ERR_SSL_PROTOCOL_ERROR.
I'm using Java 8 and my code is this (only two Java classes):
import javax.ws.rs.*;
#Path("/service")
public class HelloService {
#GET
#Path("/")
public String get() {
System.out.println("GET invoked");
return "Hello world!";
}
#POST
#Path("/")
public void post() {
System.out.println("POST invoked");
}
}
public class Main {
public static void main(String[] args) {
new MicroservicesRunner(8080).
deploy(new HelloService())
.start();
}
}
Dependencies in pom.xml:
<dependencies>
<dependency>
<groupId>org.wso2.msf4j</groupId>
<artifactId>msf4j-core</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
Is it possible to enable https connection with this service? if yes, then how can it be done?
I found some https configuration here: https://github.com/wso2/msf4j/blob/master/samples/spring-helloworld/README.md#2-configuring-https-transport . But I am not sure if this configuration method should be used here, as it says "Configuring MSF4J through Spring" in its title and I am not using any Spring libraries.
Any help would be greatly appreciated.

How to decode JSon response with custom Feign client?

In my application, I have to know, from a list a server addresses, which are up. The solution I found is to call health endpoint from Spring-Boot Actuator, for each of them. The JSon reponse is:
{
"status": "UP"
}
In other parts of the application, I use Feign clients from Spring-Cloud defined with the #FeignClient annotation, which works perfectly:
#FeignClient(
name = "tokenProxy",
url = "${host}:${port}"
)
Unfortunately this kind of configuration doesn't allow to re-use the same client to call the same endpoint on different addresses. So I have to define my own custom client (If there is another solution, do not hesitate to tell me! ):
#GetMapping(
value = "/servers"
)
public Server discover() {
MyClient myClient = Feign.builder()
.target(
Target.EmptyTarget.create(
MyClient.class
)
);
return myClient.internalPing(URI.create("http://localhost:8090"));
}
interface MyClient {
#RequestLine("GET /actuator/health")
Server internalPing(URI baseUrl);
}
class Server {
private final String status;
#JsonCreator
public Server(#JsonProperty("status") String status) {
this.status = status;
}
public String getStatus() {
return status;
}
}
When I call the endpoint /servers, I get the following error, indicating that my custom Feign client isn't confgured with the appropriate decoder:
feign.codec.DecodeException: class com.xxx.web.Server is not a type supported by this decoder.
at feign.codec.StringDecoder.decode(StringDecoder.java:34) ~[feign-core-10.10.1.jar:na]
at feign.codec.Decoder$Default.decode(Decoder.java:92) ~[feign-core-10.10.1.jar:na]
at feign.AsyncResponseHandler.decode(AsyncResponseHandler.java:115) ~[feign-core-10.10.1.jar:na]
at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:87) ~[feign-core-10.10.1.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-10.10.1.jar:na]
I guess i should use JacksonDecoder, but I cannot find it in my dependencies from Spring-Cloud Hoxton.SR5:
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
Could someone help me, either with a better solution for my need or a explanation on how to properly configure custom Feign clients?
Thanks in advance
In fact, the library which includes Jackson decoder and encoder was not loaded by default when using spring-cloud dependencies. To fix the issue I simply had to add the following to my pom.xml file:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
</dependency>
Another way could be annotating the class with #Import(FeignClientsConfiguration.class) which is the default configuration provided by Spring Cloud Netflix.
Then it becomes easy to inject both Encoder and Decoder when creating the Feign client:
#Import(FeignClientsConfiguration.class)
#Configuration
public class MyConfiguration {
(...)
Myclient myClient (Decoder feignDecoder, Encoder feignEncoder) {
return Feign.builder()
.decoder( feignDecoder )
.encoder( feignEncoder )
.target(
Target.EmptyTarget.create(
MyClient.class
)
);
}
There are two different defined encoders in the configuration class (pageable or not), so pay attention to clearly identify which you want, either by its name or a qualifier.

How can we start a mockserver from Java to receive incoming traffic?

I am trying to start a mock a server from Java and keep it running to receive incoming requests from other sources (Postman, CURL, etc).
I have tried the Junit approach, but when the unit tests finishes, the server is shutdown.
On the other hand, running the standalone version
http://www.mock-server.com/mock_server/running_mock_server.html#running_from_command_line
keeps the mock server running.
I would like to achieve the same thing, but from the Java code.
The question is, how may I make it run and stay running?
Thanks
So you need an HTTP server for non-testing purposes? I'd try with Spring, something like:
#RestController
public class CatchAllController {
#RequestMapping("/**")
public String catchAll(HttpServletRequest request) {
return request.getRequestURI();
}
}
There is an example on that same page (paragraph "Client API - starting and stopping"). This code works for me:
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import org.mockserver.integration.ClientAndServer;
public class Checker {
public static void main(String[] args) {
ClientAndServer mockServer = startClientAndServer(1080);
}
}
You have to call
mockServer.stop();
later to stop it.
You will need the following maven dependency:
<!-- mockserver -->
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.5.1</version>
</dependency>

SpringMVC Websockets Messaging User Authentication with Spring Security

I have seen a couple of threads about this issue, but none of them seem to really answer the question directly.
Background, I have spring security installed, working, and running smoothly in other parts of the application. My username is "developer".
Running on Java 7, Glassfish 4, Spring 4, and using Angular + StompJS
Let's get some code here:
package com.myapp.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {
public final static String userDestinationPrefix = "/user/";
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp").withSockJS().setSessionCookieNeeded(true);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
//registry.enableStompBrokerRelay("/topic,/user");
registry.enableSimpleBroker("/topic", "/user");
registry.setUserDestinationPrefix(userDestinationPrefix);
}
}
Ok, now here is a controller, to send out stuff every 3 seconds:
import org.springframework.messaging.simp.SimpMessagingTemplate;
…
#Autowired
private SimpMessagingTemplate messagingTemplate;
…
#Scheduled(fixedDelay = 3000)
public void sendStuff ()
{
Map<String, Object> map = new HashMap<>();
map.put(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
System.out.print("Sending data! " + System.currentTimeMillis());
//messagingTemplate.convertAndSend("/topic/notify", "Public: " + System.currentTimeMillis());
messagingTemplate.convertAndSendToUser("developer", "/notify", "User: " + System.currentTimeMillis());
messagingTemplate.convertAndSendToUser("notYou", "/notify", "Mr Developer Should Not See This: " + System.currentTimeMillis());
}
And finally the JavaScript using SockJS
var client = new SockJS('/stomp');
var stomp = Stomp.over(client);
stomp.connect({}, function(s) {
//This should work
stomp.subscribe('/user/' + s.headers['user-name'] + '/notify', console.debug);
//This SHOULD NOT
stomp.subscribe('/user/notYou/notify', console.debug);
});
client.onclose = $scope.reconnect;
And finally, for kicks, the pom.xml
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.6.RELEASE</version>
</dependency>
Here is what does work:
I can produce wonderfully communication back and forth between the client and the server
It's fast
messagingTemplate.convertAndSend and messagingTemplate.convertAndSendToUser
This is the problem (noted above): Anyone can subscribe to other users feeds.
Now, there are a few other versions of this floating around, I will list them below, and explain why the answers are all wrong:
What are the security issues around an open websocket connection?
Spring websocket with stomp security - every user can subscribe to any other users queue?
Websocket: How To Push A Message To A Target User
Here's the problem:
Look at messagingTemplate.convertAndSendToUser - All that does is add the "user prefix" and then the username provided and then use messagingTemplate.convertAndSend which does not apply security.
Then people say that "you need to use spring security just like everywhere else" - the problem here is A) that I am SENDING data to the client asynchronously, so B) I will be using this code completely outside of the user's session, possibly from a different user (say to send a notification to another logged in user).
Let me know if this is too closely related to a different post, but I this is a big problem for me and I wanted to do this justice.
I can get more details though if anyone needs more details.
New Spring Security 4x now fully support Web Socket, you can refer the link Preview Spring Security WebSocket Support
Or SpringSecuritySupportWebSocket.html in case you need a complete example,
I think you must make these changes:
1) You must not enableSimpleBroker for "/user" because it's a special queue handled automatically by the broker
2) if the server uses for example the annotation "#SendToUser("/queue/private")" the client must subscribe to the queue "/user/queue/private" : you must not prepend the username in the queue because it's a transparent operation handled by the broker
I'm sure this works correctly because I'm using it in my setup.
I've not tried with the convertAndSendToUser() method but since its semantic should be the same of the annotation, it should work too.
You can override configureInbound method in a JavaConfig class extending AbstractSecurityWebSocketMessageBrokerConfigurer.
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated() 1
.simpSubscribeDestMatchers("/user/queue/errors").permitAll() 2
.simpDestMatchers("/app/**").hasRole("USER") 3
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") 4
.simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() 5
.anyMessage().denyAll(); 6
}
}
There, you can configure credentials to subscribe a channel, send messages or several other things, as mentioned in Spring WebSocket documntation https://docs.spring.io/spring-security/site/docs/current/reference/html/websocket.html#websocket-authorization

How do I register the Jackson provider with the Wink client?

I'm trying to set up a toy application (which may turn in to a real application someday). I'm running into a problem with Wink and Jackson. I've got two applications: one runs wink-server on jetty and seems to be providing some JSON data just fine; one runs wink-client on jetty and receives the JSON data just fine. The problem lies in automagically deserializing the JSON data back into my Java bean.
Here's the code I use in my wink client action:
RestClient client = new RestClient();
Resource resource = client.resource("http://localhost:8081/helloworld");
User user = resource.accept(MediaType.APPLICATION_JSON).get(User.class);
Here's the error I receive when I try to run the Struts action:
java.lang.RuntimeException: No javax.ws.rs.ext.MessageBodyReader found for type class my.package.structure.User and media type application/json. Verify that all entity providers are correctly registered.
org.apache.wink.client.internal.handlers.ClientResponseImpl.readEntity(ClientResponseImpl.java:123)
org.apache.wink.client.internal.handlers.ClientResponseImpl.getEntity(ClientResponseImpl.java:65)
org.apache.wink.client.internal.handlers.ClientResponseImpl.getEntity(ClientResponseImpl.java:52)
org.apache.wink.client.internal.ResourceImpl.invoke(ResourceImpl.java:186)
org.apache.wink.client.internal.ResourceImpl.get(ResourceImpl.java:294)
my.package.structure.action.HelloWorldAction.execute(HelloWorldAction.java:29)
...
If I replace the last line in the first code snippet with the following line, everything works fine and dandy.
String message = resource.accept(MediaType.APPLICATION_JSON).get(String.class);
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(message, User.class);
It's clear that the data is getting across just fine, but the problem seems to lie with the fact that the JacksonJsonProvider class is not registered with Wink client. I've seen a lot of ways to register the provider with the Wink server, but not the Wink client.
Is it possible to do make the first code snippet operate properly? If so, how?
(As an aside, the other problem may be that I'm missing annotations on my User class. Right now there aren't any. Maybe I need some...)
Step 1: Create a class that extends javax.ws.rs.core.Application that allows you to set singletons.
import java.util.Collections;
import java.util.Set;
import javax.ws.rs.core.Application;
public class ClientApplication extends Application {
private Set<Object> singletons = Collections.emptySet();
#Override
public Set<Object> getSingletons() {
return singletons;
}
public void setSingletons(final Set<Object> singletons) {
this.singletons = singletons;
}
}
Step 2: In your action, create a org.apache.wink.client.ClientConfig for your org.apache.wink.client.RestClient. This allows you add the org.codehaus.jackson.jaxrs.JacksonJsonProvider to your providers list.
ClientApplication clientApplication = new ClientApplication();
Set<Object> s = new HashSet<Object>();
s.add(new JacksonJsonProvider());
clientApplication.setSingletons(s);
ClientConfig clientConfig = new ClientConfig().applications(clientApplication);
RestClient restClient = new RestClient(clientConfig);
Step 3: Create the org.apache.wink.client.Resource, use the get(Class<T> responseEntity) method and everything will now work as expected.
Resource resource = client.resource("http://localhost:8081/helloworld");
User user = resource.accept(MediaType.APPLICATION_JSON).get(User.class);
If you want to be really slick about it, you can use Spring to set up a ClientConfig bean and inject it in to your actions. Then, you can just call new RestClient(clientConfig) every time and not worry about replicating the entire setup.
i ran into this issue when trying to write some integration tests that POST an object for my rest plugin.
Rather then spinning out a new class you can provide the Jackson provider with an inline class.
#Before
public void setup(){
javax.ws.rs.core.Application app = new javax.ws.rs.core.Application() {
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(JacksonJaxbJsonProvider.class);
return classes;
}
};
//create auth handler
clientConfig = new ClientConfig();
clientConfig.applications(app);
BasicAuthSecurityHandler basicAuthSecurityHandler = new BasicAuthSecurityHandler();
basicAuthSecurityHandler.setUserName(USERNAME);
basicAuthSecurityHandler.setPassword(PASSWORD);
clientConfig.handlers(basicAuthSecurityHandler);
//create client usin auth and provider
client = new RestClient(clientConfig);
}
Then you can post and consume your annotated objects.
#Test
public void aReadWriteTokenCanBeCreatedAsRequested(){
ClientResponse response = client.resource(resourceUrlToken).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(readAndWriteToken);
assertEquals("Could not create token needed for test",200,response.getStatusCode());
readAndWriteToken = response.getEntity(TokenResource.class);
assertNotNull("Returned token does not have UUID",readAndWriteToken.getUuid());
}
If you're using maven you can make sure Jackson is on the test classpath (check for compatible versions):
<!-- TEST DEPENDENCIES -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
I wish I could help with registration; but with respect to annotations, I don't think you should need any for Jackson to try to deserialize value. If you are missing something you need you would get different kind of exception.

Categories