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);
}
}
Related
When I use the below code it's working fine and change the theme, but the problem is if one of the user logged and change the theme it effects to the every user in the system. what I want is to effect the theme only for the particular user but not for every one.
Web.xml
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>#{settingsController.userTheme}</param-value>
</context-param>
primeface (.xhtml)
<h:outputLabel for="userTheme" value="Theme Name *:" style="width: 300px"/>
<p:selectOneMenu id="userTheme" value="#{settingsController.userTheme}" style="width:200px"
required="true" requiredMessage="Theme Name is Required" >
<f:selectItems value="#{settingsController.themeMap}"/>
</p:selectOneMenu>
SettingsController.java class
#ManagedBean(name = "settingsController")
#SessionScoped
#Controller
public class SettingsController {
private String userTheme = "start" ;
private Map<String , String> themeMap ;
#PostConstruct
public void init (){
setThemeMapInit( );
}
public String getUserTheme() {
return userTheme;
}
public void setUserTheme(String userTheme) {
this.userTheme = userTheme;
}
public Map<String, String> getThemeMap() {
return themeMap;
}
public void setThemeMapInit() {
themeMap = new LinkedHashMap<String, String>();
themeMap.put("Aristo", "aristo");
themeMap.put("After-noon", "afternoon");
themeMap.put("After-Work", "afterwork");
themeMap.put("Black-Tie", "black-tie");
themeMap.put("Blitzer", "blitzer");
themeMap.put("Bluesky", "bluesky");
themeMap.put("Bootstrap", "bootstrap");
themeMap.put("Casablanca", "casablanca");
themeMap.put("Cupertino", "cupertino");
themeMap.put("Dark-Hive", "dark-hive");
themeMap.put("Delta", "delta");
themeMap.put("Excite-Bike", "excite-bike");
themeMap.put("Flick", "flick");
themeMap.put("Glass-X", "glass-x");
themeMap.put("Home", "home");
themeMap.put("Hot-Sneaks", "hot-sneaks");
themeMap.put("Humanity", "humanity");
themeMap.put("Overcast", "overcast");
themeMap.put("Pepper-Grinder", "pepper-grinder");
themeMap.put("Redmond", "redmond");
themeMap.put("Rocket", "rocket");
themeMap.put("Sam", "sam");
themeMap.put("Smoothness", "smoothness");
themeMap.put("South-Street", "south-street");
themeMap.put("Start", "start");
themeMap.put("Sunny", "sunny");
themeMap.put("Swanky-Purse", "swanky-purse");
themeMap.put("UI-Lightness", "ui-lightness");
}
public void setThemeMap(Map<String, String> themeMap) {
this.themeMap =themeMap;
}
public void sumbitUserSettings (){
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
try {
ec.redirect(((HttpServletRequest) ec.getRequest()).getRequestURI());
} catch (IOException ex) {
Logger.getLogger(SettingsController.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Every Spring bean is a Singletone by default, that's why all users are affected, despite of #SessionScoped.
You can't use #ManagedBean and #Controller at the same time, see why.
The best way to combine Spring and JSF in the same app, is to use Joinfaces.
With Joinfaces your bean will end up looking like this
#Named
#SessionScoped
public class SettingsController {
Related:
JSF vs. Spring MVC
I have front-end(script) and back-end(Spring-Boot) code.
In backend code:
#GetMapping("/calldata")
public Response call() {
...//Imagine this operations take more than 5 minutes.
}
In font-end code:
I just call this backend api and open socket and wait until data is ready in loading state.
Is there a way to say from backend to frontend; "Don't wait to me. I will notify to you when I am ready. And I will serve my data."?
You want you request to be handled asynchronously. You can use websockets which keeps a single persistent connection open between server and client.
https://www.baeldung.com/spring-websockets-sendtouser
I had the same problem and my solution include a combination of WebSocket and Async programming. The good thing about this approach is, you can still call your REST endpoint normally. I am using SpringBoot and Angular 9. Here is what I did:
Create an async service on BE
Create WebSocket on BE
Create WebSocket on FE
Create a common topic and let FB and BE listen to it, where BE will push the response and FE and read from it.
Create a void controller method and call the async service's method
a. By doing this, your FE will not wait for the server response and your async service can continue to process the request.
Once your service is done processing, push the response to a websocket topic
Listen to the topic on your FE, and once BE pushes the response you'll be able to handle it on FE.
Here is the sample code:
Index.html:
<script>
var global = global || window;
var Buffer = Buffer || [];
var process = process || {
env: { DEBUG: undefined },
version: []
};
</script>
FE WebSocket congif file:
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
export class WebSocketAPI {
// localWebSocketEndpoint = 'http://localhost:8080/ws';
webSocketEndpoint = '/ws';
topic = '/topic/greetings'; // this is the topic which will be used to exchagne data
stompClient: any;
constructor() { }
connect() {
let ws = new SockJS(this.webSocketEndpoint);
this.stompClient = Stomp.over(ws);
const that = this;
that.stompClient.connect({}, function (frame) {
that.stompClient.subscribe(that.topic, function (sdkEvent) {
that.onMessageReceived(sdkEvent);
})
})
}
disconnect() {
if (this.stompClient !== null) {
this.stompClient.disconnect();
}
}
// you don't need this
send(name) {
this.stompClient.send('/app/hello', {}, JSON.stringify({name: name}));
}
// this is where you will receive your data once Server is done process
onMessageReceived(message) {
console.log('received: ', message);
// this.app.handleMessage(message.body);
}
}
BE Controller method:
#GetMapping("/calldata")
#ResponseStatus(value = HttpStatus.OK)
#LogExecutionTime
public void call() {
asyncService.processAsync();
}
AsyncService:
#Service
public class AsyncService {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#LogExecutionTime
#Async("asyncExecutor")
public void processAsync() {
// do your processing and push the response to the topic
simpMessagingTemplate.convertAndSend("/topic/greetings", response);
}
}
WebSocketConfig:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
And finally AsyncConfig:
#Configuration
#EnableAsync
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
return executor;
}
}
Hope this will help you as well.
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.
We have a (working) SOAP web service based on Spring WS with DefaultWsdl11Definition.
This is basically what it looks like:
#Endpoint("name")
public class OurEndpoint {
#PayloadRoot(namespace = "somenamespace", localPart = "localpart")
public void onMessage(#RequestPayload SomePojo pojo) {
// do stuff
}
}
It is wired in Spring and it is correctly processing all of our SOAP requests. The only problem is that the method returns a 202 Accepted. This is not what the caller wants, he'd rather have us return 204 No Content (or if that is not possible an empty 200 OK).
Our other endpoints have a valid response object, and do return 200 OK. It seems void causes 202 when 204 might be more appropriate?
Is it possible to change the response code in Spring WS? We can't seem to find the correct way to do this.
Things we tried and didn't work:
Changing the return type to:
HttpStatus.NO_CONTENT
org.w3c.dom.Element <- not accepted
Adding #ResponseStatus <- this is for MVC, not WS
Any ideas?
Instead of what I wrote in the comments it is possibly the easiest to create a delegation kind of solution.
public class DelegatingMessageDispatcher extends MessageDispatcher {
private final WebServiceMessageReceiver delegate;
public DelegatingMessageDispatcher(WebServiceMessageReceiver delegate) {
this.delegate = delegate;
}
public void receive(MessageContext messageContext) throws Exception {
this.delegate.receive(messageContext);
if (!messageContext.hasResponse()) {
TransportContext tc = TransportContextHolder.getTransportContext();
if (tc != null && tc.getConnection() instanceof HttpServletConnection) {
((HttpServletConnection) tc.getConnection()).getHttpServletResponse().setStatus(200);
}
}
}
}
Then you need to configure a bean named messageDispatcher which would wrap the default SoapMessageDispatcher.
#Bean
public MessageDispatcher messageDispatcher() {
return new DelegatingMessageDispatcher(soapMessageDispatcher());
}
#Bean
public MessageDispatcher soapMessageDispatcher() {
return new SoapMessageDispatcher();
}
Something like that should do the trick. Now when response is created (In the case of a void return type), the status as you want is send back to the client.
When finding a proper solutions we've encountered some ugly problems:
Creating custom adapters/interceptors is problematic because the handleResponse method isn't called by Spring when you don't have a response (void)
Manually setting the status code doesn't work because HttpServletConnection keeps a boolean statusCodeSet which doesn't get updated
But luckily we managed to get it working with the following changes:
/**
* If a web service has no response, this handler returns: 204 No Content
*/
public class NoContentInterceptor extends EndpointInterceptorAdapter {
#Override
public void afterCompletion(MessageContext messageContext, Object o, Exception e) throws Exception {
if (!messageContext.hasResponse()) {
TransportContext tc = TransportContextHolder.getTransportContext();
if (tc != null && tc.getConnection() instanceof HttpServletConnection) {
HttpServletConnection connection = ((HttpServletConnection) tc.getConnection());
// First we force the 'statusCodeSet' boolean to true:
connection.setFaultCode(null);
// Next we can set our custom status code:
connection.getHttpServletResponse().setStatus(204);
}
}
}
}
Next we need to register this interceptor, this can be easily done using Spring's XML:
<sws:interceptors>
<bean class="com.something.NoContentInterceptor"/>
</sws:interceptors>
A big thanks to #m-deinum for pointing us in the right direction!
To override the afterCompletion method really helped me out in the exact same situation. And for those who use code based Spring configuration, here´s how one can add the interceptor for a specific endpoint.
Annotate the custom interceptor with #Component, next register the custom interceptor to a WsConfigurerAdapter like this:
#EnableWs
#Configuration
public class EndpointConfig extends WsConfigurerAdapter {
/**
* Add our own interceptor for the specified WS endpoint.
* #param interceptors
*/
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new PayloadRootSmartSoapEndpointInterceptor(
new NoContentInterceptor(),
"NAMESPACE",
"LOCAL_PART"
));
}
}
NAMESPACE and LOCAL_PART should correspond to the endpoint.
If someone ever wanted to set custom HTTP status when returning non-void response, here is solution:
Spring Boot WS-Server - Custom Http Status
So, I am working on CQ5. I would like to deploy a bundled component as a service to filter & modify the .inifinity.json output (from sling) to the CQ5.
I am able to build and deploy, and have both the component and bundle being active. However, when a page or call an infinity.json , I don't see the output in log. I suspect because the services not properly installed? or some other service return the call before running my service? not sure. and here is my code:
package com.my.test;
import javax.servlet.*;
import java.io.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.annotation.component.*;
#Component(
provide=Filter.class,
immediate=true
)
public class TestFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(TestFilter.class);
private FilterConfig filterConfig;
public void init (FilterConfig filterConfig) {
LOGGER.info ("INIT .");
this.setFilterConfig(filterConfig);
}
public void destroy() {
LOGGER.info ("Destroy me NOW!!...");
}
public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain){
try
{
LOGGER.info ("Within Simple Filter ... :) ");
LOGGER.info ("Filtering the Request ...");
chain.doFilter (request, response);
LOGGER.info ("Within Simple Filter ... ");
LOGGER.info ("Filtering the Response ...");
} catch (IOException io) {
LOGGER.info ("IOException raised in SimpleFilter");
} catch (ServletException se) {
LOGGER.info ("ServletException raised in SimpleFilter");
}
}
public FilterConfig getFilterConfig() {
return this.filterConfig;
}
public void setFilterConfig (FilterConfig filterConfig){
this.filterConfig = filterConfig;
}
}
Am I missing anything in the annotation? or anything that I should have done?
Looking the discussion threads here and here, it looks like you need to add annotations to set a sling.filter.scope #Property, and also to declare the #Service.
Something like this:
#Component(
provide=Filter.class,
immediate=true
)
#Service(javax.servlet.Filter.class)
#Properties({
#Property(name = "sling.filter.scope", value = "request")
})
The Sling integration test services source code include a few Filters at [1], that you can use as examples. As David says, you're probably just missing the #Service annotation.
http://svn.apache.org/repos/asf/sling/trunk/launchpad/test-services/src/main/java/org/apache/sling/launchpad/testservices/filters/