i want to use the java websocket API in a Spring Boot application.
I created the following class as described here:
https://www.baeldung.com/java-websockets
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
#ServerEndpoint(value = "/test")
public class FrontendEndpoint {
#OnOpen
public void onOpen(Session session) throws IOException {
session.getBasicRemote().sendText("Test");
}
#OnMessage
public void onMessage(Session session, String message) throws IOException {
}
#OnClose
public void onClose(Session session) throws IOException {
}
#OnError
public void onError(Session session, Throwable throwable) {
}
}
I try to connect to that websocket but nothing happens. I saw a lot of articles in the internet but nothing helped me. I dont know how to get it to work.
When i try to connect to the websocket nothing happend.
Spring Boot version: 2.0.3
Websocket-API version: 1.1
I also don't see an open port for the websocket.
Thanks
BR
I once created a sample application with Spring Boot and Websocket API
https://github.com/simasch/spring-boot-websocket
but the problem is that the ServerEndpoint is not managed by Spring but by the Websocket implementation. So this is maybe not the way you should use websockets with Spring Boot.
I would recommend to have a look how Spring think that Websockets should be used:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket
It's a bit late, but in case someone will come here for a solution.
It is not covered in Spring documentation how to use Java WebSocket API (JSR-356) in Spring and I didn't manage to find any tutorial describing how to achieve it despite I've spent several hours googling it.
In order to use Java WebSocket API with Spring you should use class org.springframework.web.socket.server.standard.SpringConfigurator:
https://docs.spring.io/spring/docs/current/javadoc-api/index.html?org/springframework/web/socket/server/standard/SpringConfigurator.html
from
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>...</version>
</dependency>
Just add it to your
#ServerEndpoint(... configurator = SpringConfigurator.class)
and it is done. Your annotated class becomes a usual Spring-managed component legal for #Autowired injection and so on.
Hope it helps someone :)
UPD.
It should be mentioned, that JSR 356 WebSocket initialization through the SpringConfigurator is only supported in .war projects, that are to be deployed as usual artifacts in servlet container.
It will not work as expected when project is packaged in executable Spring Boot .jar or Spring Boot executable .war and when it is deployed it standalone mode via java -jar {app}
I didn't manage to find a way to initialize JSR 356 WebSocket with #Autowired Spring components in it in Spring Boot environment in executable archive.
It is possible to deploy JSR 356 WebSocket via ServerEndpointExporter.setAnnotatedEndpointClasses(...), but every class provided must have a no-arg constructor otherwise exception will be thrown. #Autowired field will not be processed by Spring in this case.
If you want to use java websocket API in a Spring Boot application you have to add this to spring configuration
#Configuration
public class WebSocketConfig {
#Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
Here is a full article.
Related
I have to save some values in a reactive way using spring Webflux. But when I send the request then 404 status is returned as a response.
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
EmpController class
#RestController
#RequestMapping("/emp")
public class EmpController {
private EmployeeRepository empRepo;
#Autowired
public EmpController(EmployeeRepository empRepo)
{
this.empRepo=empRepo;
}
#PostMapping("/save")
#Consumes({MediaType.APPLICATION_JSON})
public void saveEmp(#RequestBody Mono<Employee> emp)
{
emp.subscribe(e-> {
e.setDate(new Date());
empRepo.saveEmp(e);
});
}
}
When I send the request via PostMan then 404(not found) is returned.
JAX-RS is a specification within Java EE of how to code REST api's. Several libraries have then implemented said specification, Like Jersey or restEasy. WHen building a Java EE application, you needed one of these libraries to be able to build a rest api.
Spring built their own way of building rest apis spring-web for non reactive applications, and spring-webflux for reactive applications.
Jersey and restEasy (to my knowledge) only works if you are building a non-reactive application.
In order to make your code work you need to remove:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
Jersey is a Java EE implementation of JAX-RS. It is used in Java EE to build Java EE styled rest apis. You are building a reactive application, using Spring Webflux which has its own way of building REST api's.
Spring is not a Java EE application. When you added that dependency, spring assumed that you wanted to build a Spring Application but not use the Spring built in REST api functions and annotations etc, so it didn't register your code, that you have written with Springs way of building rest apis.
It assumed you were going to write a REST api the "jersey way" and if you are using jersey you need to register your api classes manually. And (to my knowledge) Jersey only works with non-webflux applications.
This is all mainly basic knowledge, and if you dont understand why i suggest you read up and build a regular spring boot application, before trying out webflux.
I suggest you read the following parts:
Reactive programming, Reactor getting started
Baeldung Webflux
Building a reactive Webflux Application
Spring boot Webflux
It is Strange but when I removed jersey dependency and it Worked. Still not sure about the reason behind it.
raised pull request you the merge the same to take the changes I have done
https://github.com/Benzeman97/reactive-async-app/pull/1
You need to remove below Jersey dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
The reason behind is that the spring-boot-starter-jersey is a starter for building Restful web applications using JAX-RS and Jersey. Since you have used it in your project the spring does not use in built spring functions for rest api's like #GetMapping, #PostMapping.
If you want to use jersey to create the rest api then use #GET annotation for Get api and the #Produces to defined the mapping as below.
Eg.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.springframework.stereotype.Service;
#Service
#Path("/hello")
public class HelloService {
#GET
#Produces("text/plain")
public String hello() {
return "Hello from Spring";
}
}
Also you have to register this class in JerseyConfig.
import com.zetcode.endpoint.HelloService;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;
#Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(HelloService.class);
}
}
And if you want to go ahead with the spring build in functions and use reactive just remove the jersey dependency and use webflux dependency to create your rest api's.
I had the same problem.
The solution:
go to application.properties,
remove server.servlet.context-path
and add spring.webflux.base-path
I have been trying to figure out an issue with spring boot and as i am new to spring I thought of getting some help here.
I have a spring boot based java application which runs as a daemon and makes some GET request to a remote server. (Acts only as a client).
But my spring boot application internally starts an embedded tomcat container.
My understanding is that if the java app acts as a server, it would need tomcat. But my application being only a consumer of remote machine's GET APIs, why would it need an embedded tomcat ?
In my pom file I have specified spring-boot-starter-web,
on assumption that it is needed for even making GET calls.
But after doing some research on disabling embedded tomcat, I found a solution.
To make following changes,
#SpringBootApplication(exclude = {EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class})
&
in application.yml
spring:
main:
web-environment: false
With the application.yml changes, my jar is not even getting started, aborts directly, without even logging anything in logback logs.
Now, if i remove the application.yml change, my jar starts (only with first change in #SpringBootApplication anno.) but goes into some exception.
[main] o.s.boot.SpringApplication : Application startup failed
org.springframework.context.ApplicationContextException: Unable to start embedded container; nested exception is org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
My Doubts here are,
1) Is tomcat, be it standalone or embedded, really needed for a application which just makes GET API calls to remote machine ?
2) How do i overcome this exception and safely remove the embedded tomcat and still perform the GET API calls ?
You seem to be on completely the wrong track here, starting from a web application template and then trying to turn off the web application aspect.
Far better to start from a regular commandline client template and go from there, as detailed in the relevant Spring Guide.
Basically the application reduces to
#SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String args[]) {
SpringApplication.run(Application.class);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
Quote quote = restTemplate.getForObject(
"http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
log.info(quote.toString());
};
}
}
And the pom to
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
I had this problem. All I wanted was to have a Client making REST requests. Unfortunately I had a dependency which was embedding Jetty, and Jetty was always started.
In order to disable Jetty all I needed to do was to add in applications.properties the following entry:
spring.main.web-application-type=none
That fixed it.
Here is the most simple solution for me, make spring boot application just a restful api consumer.
Replace the dependence
implementation("org.springframework.boot:spring-boot-starter-web")
with
implementation("org.springframework.boot:spring-boot-starter-json")
RestTemplate and jackson are available in the project without embedded tomcat.
Answering your questions:
1) embedded by defaut - not needed for clients HTTP requests;
2) You can use CommandLineRunner for spring boot applications without any web:
#SpringBootApplication
public class SpringBootConsoleApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Override
public void run(String... args) {
// TODO: start you thread here to execute client HTTP REST requests (or any other job you need)
}
}
This will disable web completelly - no issues with manual miss-configuration.
Here is some docs:
http://www.baeldung.com/spring-boot-console-app
You also need replase spring-boot-starter-web dependency with spring-boot-starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
From your question, i assume you want your application to keep running in background and it makes some get calls in it's life cycle. If that's the case, then
answering your first question, yes, you need an embedded tomcat or
jetty or need to deploy your application to an external application
server.
Second, to get rid of the exception your facing, don't exclude
EmbeddedServletContainerAutoConfiguration and
WebMvcAutoConfiguration class as it's needed for default embedded
tomcat auto configuration.
I have been experimenting with Apache Camel for creating a REST based micro services. Based on my experiments this is how I created an echo service with Jetty:
Maven
Included the following dependency in the Maven build:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty</artifactId>
</dependency>
Java
Created a small route in Java which gets a request, converts it to a string, saves it to a file and then sends it back to the client:
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
public class EchoHttpServer {
public static void main(String[] args) throws Exception {
CamelContext context = new DefaultCamelContext();
context.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("jetty://http://localhost:8099/echo?httpMethodRestrict=POST")
.convertBodyTo(String.class)
.to("file://D:/work")
.process(exchange -> exchange.getOut().setBody(exchange.getIn().getBody()));
}
});
context.start();
}
}
Integration Test
This can then be tested using the Chrome extension Postman with which you can send a POST request which is then echoed by the reply:
Is this the recommended way to create REST based services with Apache Camel? Which are the potential problems of the approach taken above?
There are several ways how to expose a REST services with Camel.
Pure servet - this is what you already did. Seems to be quick and simple way (maybe too simple).
REST Camel endpoints (thanks noMad) http://camel.apache.org/restlet.html
In a container with a framework. My favourite is Apache Karaf with Camel-CXF . It takes more time to start, more memory to run, but frameworks allow additional features, such as authentication, auhtorization, logging, ..
If someone finds another way, please add an answer or comment, I am keen to find new options as well
We've generated a basic Spring Boot application to test some features.
I've prepared it to be deployed on an embedded server and on Java EE servers (Tomcat 7 and JBoss EAP 6.2) without applying any changes.
I've included server.context-parameters.* property on application.properties file.
If I deploy the application on an embedded server using java -jar or mvn spring-boot:run, it's working without problems. But, If I deploy the same application on Tomcat 7 or JBoss EAP 6.2, I'm not able to load the context-params correctly.
Which is the correct way to define context parameters on Spring Boot application deployed on Java EE container without use web.xml file?
You could see all debugging information related with this Spring Boot issue here
Finally, I found the following solution thanks to Stéphane Nicoll
server.context-parameters.* only works for embedded servers, so to configure context-parameters on Java EE server is necessary to include a #Bean of type ServletContextInitializer like the following:
#Bean
public ServletContextInitializer contextInitializer() {
return new ServletContextInitializer() {
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
servletContext.setInitParameter("dummy.type","on-context-parameters");
}
};
}
So I'm looking to upgrade my projects from spring boot 1.1.9.RELEASE to 1.2.1.RELEASE.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>${spring.boot.version}</version>
</dependency>
However, on startup, I gained:
Exception in thread "Thread-0" org.springframework.context.ApplicationContextException: Failed to start bean 'subProtocolWebSocketHandler'; nested exception is java.lang.IllegalArgumentException: No handlers
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:176)
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:51)
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:346)
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:149)
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:112)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:770)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:483)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:95)
at com.springagain.Application.run(Application.java:17)
Caused by: java.lang.IllegalArgumentException: No handlers
at org.springframework.util.Assert.isTrue(Assert.java:65)
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.start(SubProtocolWebSocketHandler.java:234)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:173)
... 8 more
Here's how my websocket configuration looks
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfiguration extends
AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/", "/topic/");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// TODO Auto-generated method stub
}
}
Switching back to 1.1.9.RELEASE of only the spring-boot-starter-websocket dependency (and keeping all other spring boot dependencies at 1.2.1.RELEASE and spring core at 4.1.4), the exception disappears.
Looks like a bug but can someone confirm?
UPDATE: More context - this is from a backend server code - no websocket clients connect to it. Intention is to publish 'interesting' events over RabbitMQ, which are then available to clients from front end servers that expose a websocket endpoint. Code on my front end servers add the endpoint with Socksjs support:
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/push").withSockJS();
}
From that standpoint, either my understanding is fundamentally flawed :), OR Spring has become overzealous in checking that there should be a websocket endpoint always.
The root of the problem is that you haven't configured any endpoints in registerStompEndpoints. An application that's trying to use STOMP, but has not configured any STOMP endpoints, won't work correctly.
When you're using Spring Boot 1.1.9.RELEASE you'll have some Spring Framework 4.0.x jars on your classpath. Spring Framework 4.0.x's WebSocket support doesn't notice the misconfiguration and allows your app to start even though it won't work. Spring Framework 4.1's WebSocket support notices this misconfiguration and throws an exception, thereby alerting you to the problem.