Cannot connect websocket over servlet.context.path - spring-boot

I have a spring boot application (2.0.5.RELEASE) which is running on port 8989. The application sends the log messages over websocket to the client.
i have created a javascript client that connects to the websocket and appends the messages to the html text area. This client is under the resource\static\js folder of the application.
It works fine, when i dont use servet.context.path.
Is there any way, i can work it out with context path.
Am i missing some configurational parameter?
I would really appreciate your help in this regard.
Application.properties
server.port=8989
server.servlet.contextPath=/DemoService/webresources/test/
The Websocket config is mentioned below:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
/**
* Register Stomp endpoints: the url to open the WebSocket connection.
*/
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Register the "/websocket" endpoint, enabling the SockJS protocol.
// SockJS is used (both client and server side) to allow alternative
// messaging options if WebSocket is not available.
registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
The client looks like this,
$(window).ready(function() {
connect();
});
function connect() {
// var socket = new SockJS('/websocket');
var socket = new SockJS('/DemoService/webresources/test/websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
stompClient.subscribe('/topic/pushlognotification', function(
notification) {
var txt = $('#textArea');
txt.val(txt.val() + "\n" + "\n" + notification);
txt.scrollTop(txt[0].scrollHeight);
});
});
}
HTML page is :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Messages</title>
</head>
<body>
<textarea type="text" id="textArea" placeholder="Messages..." rows="15" cols="60"></textarea>
<script src='js/jquery.min.js'></script>
<script src="js/index.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
</body>
</html>

found out the solution after examining the console output.
The HTML code could not find the registered files through src such as 'js/jquery.min.js' etc. because of the configured context paths.
The solution is either append the context path to all the src like below.
or give a "." sign
this worked for me.

Related

Could not resolve view with name 'index' in Spring Boot

Spring boot: 2.3.3.RELEASE
Java: 11
I use webflux + RouterFunction + Thymeleaf and encounter the error "Could not resolve view with name 'index'".
index.html is under "resources/templates".
I put some source code looks important.
Are we not able to use Thymeleaf if we use "RouterFunction"?
Please feel free to put a comment if you need more detail.
######## handler ###########
#Component
public class ItemHandler {
public RouterFunction<ServerResponse> routes = route()
.path("/item", builder -> builder.GET("/", this::index))
.build();
public Mono<ServerResponse> index(ServerRequest request) {
Map<String, Object> attributes = new HashMap<>();
attributes.put("items", "Hello");
return ServerResponse.ok().contentType(MediaType.TEXT_HTML)
.render("index", attributes);
}
}
######## index.html ###########
<!DOCTYPE html>
<html lang="ja"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<h1>FluxTest</h1>
</body>
</html>
######## entry point ###########
#SpringBootApplication
public class DemoWebfluxApplication {
public static void main(String[] args) {
SpringApplication.run(DemoWebfluxApplication.class, args);
}
}
The default property in charge of handling the location of static files is spring.resources.static-locations.
The default values are /META-INF/resources/, /resources/, /static/, /public/. You can either override the default values or put your index.html in one of these locations.

How to change swagger-ui.html default path

I wanna change my swagger-ui path from localhost:8080/swagger-ui.html to
localhost:8080/myapi/swagger-ui.html in springboot
redirect is helpless to me
In the application.properties of Spring Boot
springdoc.swagger-ui.path=/swagger-ui-custom.html
in your case it will be
springdoc.swagger-ui.path=/myapi/swagger-ui.html
if for some reason you don't want redirect to /swagger-ui.html you can set itself contents as home view, setting an index.html at resources/static/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to another awesome Microservice</title>
</head>
<body>
<script>
document.body.innerHTML = '<object type="text/html" data="/swagger-ui.html" style="overflow:hidden;overflow-x:hidden;overflow-y:hidden;height:100%;width:100%;position:absolute;top:0px;left:0px;right:0px;bottom:0px"></object>';
</script>
</body>
</html>
then accessing to your http://localhost:8080/ you will see your swagger docs.
finally you can customize path and .html file using:
registry.addViewController("/swagger").setViewName("forward:/index.html");
like suggests this answer
You can modify springfox properties in application.properties
For example, to edit the base-url
springfox.documentation.swagger-ui.base-url=documentation
For e.g. setting it to /documentation will put swagger-ui at /documentation/swagger-ui/index.html
If you want to add, for example, documentation prefix - You can do like this for path http://localhost:8080/documentation/swagger-ui.html:
kotlin
#Configuration
#EnableSwagger2
#ConfigurationPropertiesScan("your.package.config")
#Import(value = [BeanValidatorPluginsConfiguration::class])
class SwaggerConfiguration(
private val swaggerContactProp: SwaggerContactProp, private val swaggerProp: SwaggerProp
) : WebMvcConfigurationSupport() {
// https://springfox.github.io/springfox/docs/current/
#Bean
fun api(): Docket = Docket(DocumentationType.SWAGGER_2)
.groupName("Cards")
.apiInfo(getApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("your.controllers.folder"))
.paths(PathSelectors.any())
.build()
private fun getApiInfo(): ApiInfo {
val contact = Contact(swaggerContactProp.name, swaggerContactProp.url, swaggerContactProp.mail)
return ApiInfoBuilder()
.title(swaggerProp.title)
.description(swaggerProp.description)
.version(swaggerProp.version)
.contact(contact)
.build()
}
override fun addViewControllers(registry: ViewControllerRegistry) {
with(registry) {
addRedirectViewController("/documentation/v2/api-docs", "/v2/api-docs").setKeepQueryParams(true)
addRedirectViewController(
"/documentation/swagger-resources/configuration/ui", "/swagger-resources/configuration/ui"
)
addRedirectViewController(
"/documentation/swagger-resources/configuration/security", "/swagger-resources/configuration/security"
)
addRedirectViewController("/documentation/swagger-resources", "/swagger-resources")
}
}
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
registry.addResourceHandler("/documentation/**").addResourceLocations("classpath:/META-INF/resources/")
}
}
#ConfigurationProperties(prefix = "swagger")
#ConstructorBinding
data class SwaggerProp(val title: String, val description: String, val version: String)
#ConfigurationProperties(prefix = "swagger.contact")
#ConstructorBinding
data class SwaggerContactProp(val mail: String, val url: String, val name: String)
and in applicatiom.yml:
swagger:
title: Cards
version: 1.0
description: Documentation for API
contact:
mail: email#gmail.com
url: some-url.com
name: COLABA card
Also don't forget to add in build.gradle.kts:
implementation("io.springfox:springfox-swagger2:$swagger")
implementation("io.springfox:springfox-swagger-ui:$swagger")
implementation("io.springfox:springfox-bean-validators:$swagger")
I've found several possible solutions for myself. Maybe it will be helpful for somebody else.
Set springdoc.swagger-ui.path directly
The straightforward way is to set property springdoc.swagger-ui.path=/custom/path. It will work perfectly if you can hardcode swagger path in your application.
Override springdoc.swagger-ui.path property
You can change default swagger-ui path programmatically using ApplicationListener<ApplicationPreparedEvent>. The idea is simple - override springdoc.swagger-ui.path=/custom/path before your Spring Boot application starts.
#Component
public class SwaggerConfiguration implements ApplicationListener<ApplicationPreparedEvent> {
#Override
public void onApplicationEvent(final ApplicationPreparedEvent event) {
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
Properties props = new Properties();
props.put("springdoc.swagger-ui.path", swaggerPath());
environment.getPropertySources()
.addFirst(new PropertiesPropertySource("programmatically", props));
}
private String swaggerPath() {
return "/swagger/path"; //todo: implement your logic here.
}
}
In this case, you must register the listener before your application start:
#SpringBootApplication
#OpenAPIDefinition(info = #Info(title = "APIs", version = "0.0.1", description = "APIs v0.0.1"))
public class App {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(App.class);
application.addListeners(new SwaggerConfiguration());
application.run(args);
}
}
Redirect using controller
You can also register your own controller and make a simple redirect as suggested there.
Redirect code for Spring WebFlux applications:
#RestController
public class SwaggerEndpoint {
#GetMapping("/custom/path")
public Mono<Void> api(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("/swagger-ui.html"));
return response.setComplete();
}
}
The problem with such an approach - your server will still respond if you call it by address "/swagger-ui.html".
You can use this code, it worked for me
package com.swagger.api.redirect;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class SwaggerApiReDirector implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/documentation/v2/api-docs", "/v2/api-docs");
registry.addRedirectViewController("/documentation/configuration/ui", "/configuration/ui");
registry.addRedirectViewController("/documentation/configuration/security", "/configuration/security");
registry.addRedirectViewController("/documentation/swagger-resources", "/swagger-resources");
registry.addRedirectViewController("/documentation/swagger-resources/configuration/ui", "/swagger-resources/configuration/ui");
registry.addRedirectViewController("/documentation", "/documentation/swagger-ui.html");
registry.addRedirectViewController("/documentation/", "/documentation/swagger-ui.html");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/documentation/**").addResourceLocations("classpath:/META-INF/resources/");
}
}
If you are using spring boot then please update
application.properties file and write here
server.servlet.context-path=/myapi
it will redirect you as you want.

Spring Boot, static resources and mime type configuration

I'm facing a Spring Boot configuration issue I can't deal with...
I'm trying to build an HelloWorld example for HbbTV with Spring Boot, so I need to serve my "index.html" page with mime-type="application/vnd.hbbtv.xhtml+xml"
my index.html will be accessed as a static page, for instance http://myserver.com/index.html?param=value.
with the following code, no matter how hard I try, I get a text/html content type.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//HbbTV//1.1.1//EN" "http://www.hbbtv.org/dtd/HbbTV-1.1.1.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>MyApp HBBTV</title>
<meta http-equiv="content-type" content="Content-Type: application/vnd.hbbtv.xhtml+xml; charset=UTF-8" />
</head>
<body>
...
</body>
</html>
So I tried to add a "home()" endpoint into a #Controller to force the correct mime-type, and that works.
#RestController
public class HbbTVController {
#RequestMapping(value = "/hbbtv", produces = "application/vnd.hbbtv.xhtml+xml")
String home() {
return "someText";
}
...
}
"That works" mean the jetty server serves me a html file with the correct content-type containing the test someText.
My next try were to replace the #RestController by #Controller (same produce config), and replace "someText" by index.html
#Controller
public class HbbTVController {
#RequestMapping(value = "/hbbtv", produces = "application/vnd.hbbtv.xhtml+xml")
String home() {
return "index.html";
}
...
}
Well, it serves my index.html correctly, but the Content-Type is wrong : text/html instead of application/vnd.hbbtv.xhtml+xml.
Furthermore, I don't want to access to myserver.com/hbbtv to get index.html, but directly to myserver.com/index.html.
How could I do that ?
Thanks...
Well, finally, I found the "Spring boot compliant solution". It's the same as Jamie Birch suggested, but realized with Spring mechanisms.
Spring Boot 1:
#Configuration
public class HbbtvMimeMapping implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
mappings.add("html", "application/vnd.hbbtv.xhtml+xml; charset=utf-8");
mappings.add("xhtml", "application/vnd.hbbtv.xhtml+xml; charset=utf-8");
container.setMimeMappings(mappings);
}
}
Spring Boot 2:
#Configuration
public class HbbtvMimeMapping implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
#Override
public void customize(ConfigurableServletWebServerFactory factory) {
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
mappings.add("html", "application/vnd.hbbtv.xhtml+xml; charset=utf-8");
mappings.add("xhtml", "application/vnd.hbbtv.xhtml+xml; charset=utf-8");
factory.setMimeMappings(mappings);
}
}
I'll extend comment providen by #Cheloute
Sping boot have default mime types
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/MimeMappings.java
to override already setted mime type you should remove it first
Here is example what I used to override js and css
#Configuration
public class CustomServletConfiguration implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
#Override
public void customize(ConfigurableServletWebServerFactory factory) {
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
mappings.remove("js");
mappings.add("js", "application/javascript;charset=utf-8");
mappings.remove("css");
mappings.add("css", "text/css;charset=utf-8");
factory.setMimeMappings(mappings);
factory.setPort(9000);
}
}
Can't help with the Spring Boot side, but if you get no other responses, try these:
Set the file-type as .xhtml rather than .html.
Provide a mapping from .xhtml to MIME type application/vnd.hbbtv.xhtml+xml on your Jetty server's mime.properties file. A few more details on how to do that here.

SockJS + Stomp + SpringBoot = Issue

I have an application with a page that subscribe a websocket endpoint with this:
var socket = new SockJS("/hello");
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.subscribe('/topic/nextticket', function (ws_response) {
var data = JSON.parse(ws_response.body);
....
});
});
This app run on Raspberry and all works fine when the raspberry is connected on internet. But in a local network (no Internet) I have this
My backend conf is:
#Configuration
#PropertySource("file:/etc/app.properties")
#EnableWebSocketMessageBroker
class MessageBroker extends AbstractWebSocketMessageBrokerConfigurer {
#Autowired
Environment env
#Override
void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic")
config.setApplicationDestinationPrefixes("/app")
}
#Override
void registerStompEndpoints(StompEndpointRegistry registry) {
def ip = env.getProperty("kiosk.ip") ? env.getProperty("kiosk.ip") as String : "192.168.1.90"
registry.addEndpoint("/hello")
.setAllowedOrigins("*")
.withSockJS()
.setClientLibraryUrl("http://${ip}:8080/js/sockjs.js")
}
}
I don't understand why have a CORS (I suppose) problem.. And the page reload forever.
Thanks in advance
Luis
I had this CORS behaviour in my application whenever I used a specific IP.
application.properties
server.address: 192.168.1.90
After removing server.address from the application.properties the application worked as expected. If somebody knows the cause of this please let me know. I would have expected that
registry.setAllowedOrigins("*")
would remove all CORS behaviour as document here

Websocket - httpSession returns null

I would like to make the connection between a websocket handshake \ session to a HttpSession object.
I've used the following handshake modification:
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
{
#Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
HttpSession httpSession = (HttpSession)request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
As mentioned in this post:
Accessing HttpSession from HttpServletRequest in a Web Socket #ServerEndpoint
Now,
For some reason on the hand shake, the (HttpSession)request.getHttpSession() returns null all the time.
here is my client side code:
<!DOCTYPE html>
<html>
<head>
<title>Testing websockets</title>
</head>
<body>
<div>
<input type="submit" value="Start" onclick="start()" />
</div>
<div id="messages"></div>
<script type="text/javascript">
var webSocket =
new WebSocket('ws://localhost:8080/com-byteslounge-websockets/websocket');
webSocket.onerror = function(event) {
onError(event)
};
webSocket.onopen = function(event) {
onOpen(event)
};
webSocket.onmessage = function(event) {
onMessage(event)
};
function onMessage(event) {
document.getElementById('messages').innerHTML
+= '<br />' + event.data;
}
function onOpen(event) {
document.getElementById('messages').innerHTML
= 'Connection established';
}
function onError(event) {
alert(event.data);
}
function start() {
webSocket.send('hello');
return false;
}
</script>
</body>
</html>
Any ideas why no session is created ?
Thanks
This is intended behaviour, but I agree it might be confusing. From the HandshakeRequest.getHttpSession javadoc:
/**
* Return a reference to the HttpSession that the web socket handshake that
* started this conversation was part of, if the implementation
* is part of a Java EE web container.
*
* #return the http session or {#code null} if either the websocket
* implementation is not part of a Java EE web container, or there is
* no HttpSession associated with the opening handshake request.
*/
Problem is, that HttpSession was not yet created for your client connection and WebSocket API implementation just asks whether there is something created and if not, it does not create it. What you need to do is call httpServletRequest.getSession() sometime before WebSocket impl filter is invoked (doFilter(...) is called).
This can be achieved for example by calling mentioned method in ServletRequestListener#requestInitalized or in different filter, etc..
Here is an impl for Pavel Bucek's Answer, after adding it, i got my session
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
#WebListener
public class RequestListener implements ServletRequestListener {
#Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO Auto-generated method stub
}
#Override
public void requestInitialized(ServletRequestEvent sre) {
((HttpServletRequest) sre.getServletRequest()).getSession();
}
}
Building on #pavel-bucek 's answer, I wrote a simple HttpSessionInitializerFilter servlet filter.
Just download the jar from the "Releases" page and save it anywhere in the classpath, then add the following snippet to your web.xml descriptor (modify the url-pattern as needed):
<filter>
<filter-name>HttpSessionInitializerFilter</filter-name>
<filter-class>net.twentyonesolutions.servlet.filter.HttpSessionInitializerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpSessionInitializerFilter</filter-name>
<url-pattern>/ws/*</url-pattern>
</filter-mapping>
first: create a new class
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
#WebListener
public class RequestListener implements ServletRequestListener {
#Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
}
#Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
((HttpServletRequest)servletRequestEvent.getServletRequest()).getSession();
}
}
and then add the "ServletComponentScan" annotation on the App main:
#SpringBootApplication
#ServletComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Resources