We are using jetty version 9.3.11 Version jetty container for deploying our application
we are using tyrus client for connecting client to websocket server that we have deployed.
when i connect using the following code:
WebSocket ws = null;
try {
ws = new WebSocketFactory().createSocket(server)
.addListener(new NotifierAdapter(userId, channel))
.addExtension(WebSocketExtension.PERMESSAGE_DEFLATE).connect();
} catch (WebSocketException we) {
connect(server, userId, channel);
}catch(Exception e)
{
e.printStackTrace();
}return ws;
Around 8000 connections my machine was taking 40% Memory
But with the sight modification of code(removing deflate extension)
try {
ws = new WebSocketFactory().createSocket(server)
.addListener(new NotifierAdapter(userId, channel))
.connect();
} catch (WebSocketException we) {
connect(server, userId, channel);
}catch(Exception e)
{
e.printStackTrace();
}return ws;
Was able to connect to 15000 connections with only 15% Memory
Is there any leak with respective to deflaters even in latest version of Jetty server..
Is there any way to disable PERMESSAGE_DEFLATE in server side of jetty..
Also we are using spring websocket passing jettyrequestupgrade strategy
Spring config code is as follows:
#Configuration
#EnableWebMvc
#EnableAspectJAutoProxy
#EnableWebSocket
public class WebMVCConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
private static final String NOTIFIER_ENDPOINT_URL = "/notificationHandler";
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(socketHandler(), NOTIFIER_ENDPOINT_URL).setAllowedOrigins("*");
}
#Bean
public WebSocketHandler socketHandler() {
return new NotificationSocketHandler();
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
WebSocketServerFactory ws=new WebSocketServerFactory(policy);
ws.getExtensionFactory().unregister("permessage-deflate");
return new DefaultHandshakeHandler(new JettyRequestUpgradeStrategy(ws));
}
}
Although i did un register of permessage-deflate when i connect from browser in response header i can see that flag still enabled any other changes should be made in containter for this??
Or is there any problem with spring websocket configuration:
Also response header is as follows:
Connection:Upgrade
Sec-WebSocket-Accept:xbbUnu7pDWs9Q0st4T1LzsIfqao=
Sec-WebSocket-Extensions:permessage-deflate
Upgrade:WebSocket
I think the bug below havent been resolved yet.
https://github.com/eclipse/jetty.project/issues/293
Is there any workaround for this(Explicit configuration in jetty server to disable PERMESSAGE_DEFLATE in jetty server container or spring config for Jetty
After searching i couldnt find any solution on server container/code configuration with spring websocket was not working ..
As a workaround i wrote a interceptor which will remove the parameter Sec Websocket entension from the header..so that server will send response without this extension
The reason for the memory leak is a JVM bug with Deflate implementations in the JVM Classpath.
Setting up an ObjectPool just delays the inevitable, it will just slow down the memory leak.
If you have access to the WebSocketServletFactory then ...
factory.getExtensionFactory().unregister("permessage-deflate");
If you only have access via JSR-356, then implement a custom Configurator and strip the permessage-deflate extension out during the extension negotiation.
public class StripExtensionsConfigurator extends ServerEndpointConfig.Configurator
{
#Override
public List<Extension> getNegotiatedExtensions(List<Extension> installed,
List<Extension> requested)
{
return Collections.emptyList();
}
}
Related
I have a spring boot application. I am trying to add the websocket piece to it. The problem is my angular client can't connect to it. I used smart websocket client google plugin, but still not able to connect. Here is the setup.
I am using Intellij Idea on localhost. the spring boot application is running on localhost:8080. I can see the WebSocketSession is runnign from intellij idea console.
Here is the setup:
#Slf4j
#RestController
public class WebsocketController {
#MessageMapping("/ws-on/hello")
#SendTo("/ws-on/greetings")
public UserStateIndicator greeting(UserStateIndicator indicator) throws Exception {
Thread.sleep(1000); // simulated delay
log.debug("websocket " + indicator.toString());
return indicator;
}
}
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/ws-on");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-on")
.setAllowedOrigins("http://localhost:4200")
.withSockJS();
}
}
my angular is running on localhost:4200.
I used ws://localhost:8080/ws-on as the url from StompJS to connect.
My question is how do I find the websocket url to connect, and how do I know the websocket is running on the spring boot server?
finally I figured it out. Because I am using SockJS on both angular and spring boot, so, the URL is actaully http not ws. the correct url to connect is then http://localost:8080/ws-on
Trying to set connection to my spring boot server in flutter app
final socketUrl = 'https://10.0.0.6:8443/notifications';
if (stompClient == null) {
stompClient = StompClient(
config: StompConfig.SockJS(
url: socketUrl,
onConnect: onConnect,
onWebSocketError: (dynamic error) => print(error.toString()),
));
print(stompClient.config.url);
stompClient.activate();
}
but function onConnect is never called and i dont get any error message neither. I have tried change url to wss://10.0.0.6:8443/notifications and remove SockJs with same result. Also tried to connect to wss://echo.websocket.org without any success.
Web server is config as follow and it has valid certs
#Configuration
#EnableWebSocketMessageBroker
public class webSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/notification/item");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notifications");
registry.addEndpoint("/notifications").withSockJS();
}
}
Calling socket from JS web app on same server is working fine.
I have tried other flutter libs (Stomp, Socket_IO, WebSocketChannel) to connect but without any success. Is there something I am missing in configuration ?
You are probably running into problems with CORS. Please try only this line:
registry.addEndpoint("/notifications").setAllowedOrigins("*").withSockJS();
while keeping the client as it is posted here.
We are using SpringBoot and SpringFox using #EnableSwagger2 to expose the swagger-ui.html API documentation (we don't need it to automate client code, just as documentation and test ui).
Is it possible to run all swagger related endpoints under a different port (for example the spring boot management/monitoring port) than the main application?
I researched a bit, but did not find a way in swagger's/springfox' configuration to do it. Is there a spring way to do this?
Yes, there is a Spring way of doing this:
Step 1. Adding an additional Tomcat connector
To add a port to the embedded server an additional connector needs to be configured.
We will do it by providing custom WebServerFactoryCustomizer:
#Component
public class TomcatContainerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${swagger.port}")
private int swaggerPort;
#Override
public void customize(TomcatServletWebServerFactory factory) {
Connector swaggerConnector = new Connector();
swaggerConnector.setPort(swaggerPort);
factory.addAdditionalTomcatConnectors(swaggerConnector);
}
}
Now Tomcat listens on two ports but it serves the same content on both of them. We need to filter it.
Step 2. Adding a filter
Adding a servlet filter is pretty straightforward with a FilterRegistrationBean.
It can be created anywhere, I added it directly to the TomcatContainerCustomizer.
#Component
public class TomcatContainerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${swagger.port}")
private int swaggerPort;
#Value("${swagger.paths}")
private List<String> swaggerPaths;
#Override
public void customize(TomcatServletWebServerFactory factory) {
Connector swaggerConnector = new Connector();
swaggerConnector.setPort(swaggerPort);
factory.addAdditionalTomcatConnectors(swaggerConnector);
}
#Bean
public FilterRegistrationBean<SwaggerFilter> swaggerFilterRegistrationBean() {
FilterRegistrationBean<SwaggerFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new SwaggerFilter());
filterRegistrationBean.setOrder(-100);
filterRegistrationBean.setName("SwaggerFilter");
return filterRegistrationBean;
}
private class SwaggerFilter extends OncePerRequestFilter {
private AntPathMatcher pathMatcher = new AntPathMatcher();
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
boolean isSwaggerPath = swaggerPaths.stream()
.anyMatch(path -> pathMatcher.match(path, httpServletRequest.getServletPath()));
boolean isSwaggerPort = httpServletRequest.getLocalPort() == swaggerPort;
if(isSwaggerPath == isSwaggerPort) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
httpServletResponse.sendError(404);
}
}
}
}
The properties swagger.port and swagger.paths are configured in the application.yaml:
server.port: 8080
swagger:
port: 8088
paths: |
/swagger-ui.html,
/webjars/springfox-swagger-ui/**/*,
/swagger-resources,
/swagger-resources/**/*,
/v2/api-docs
So far so good: the swagger-ui is served on the port 8088, our api on the 8080.
But there is a problem: when we try to connect to the api from the swagger-ui,
the requests are sent to the 8088 instead of 8080.
Step 3. Adjusting SpringFox config.
Swagger assumes that the api runs on the same port as the swagger-ui.
We need to explicitly specify the port:
#Value("${server.port}")
private int serverPort;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.host("localhost:" + serverPort);
}
And the last problem: as the ui runs on a different port than the api,
the requests are considered cross-origin. We need to unblock them.
It can be done globally:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**/*").allowedOrigins("http://localhost:" + swaggerPort);
}
};
}
or by adding annotations to the controllers:
#CrossOrigin(origins = "http://localhost:${swagger.port}")
Versions used: SpringBoot 2.2.2.RELEASE, springfox-swagger2 2.9.2
For a working example see https://github.com/mafor/swagger-ui-port
I don't think so. When you're setting the Spring Boot management port (management.server.port), a second application server gets started to serve the actuator stuff. As far as I know there is no possibility (apart from custom actuator endpoints) to publish something on that server.
What is your use case exactly? Do you want to prevent access to Swagger in production or for non-authenticated users?
I have an application with WebSockets using spring-boot application as backend and Stomp/SockJS in the client side, the spring-boot application consume JMS queue messages and notify the changes to the right user. What is the problem? Sometimes works and sometimes doesn't work, same code and users could work or not.
The client side code is a bit more difficult to copy here because it's integrate over react/redux application but basically is a subscription to two different channels, both defined in the configuration of Spring. The sessions are created correctly according to debug information but just sometimes the message is processed to send it to connected sessions.
This is the configuration class for Spring.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/stomp")
.setAllowedOrigins("*")
.withSockJS();
}
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry
.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/xxxx/yyyy", "/ccccc");
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Object raw = message
.getHeaders()
.get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
Object name = ((Map<?,?>) raw).get("email");
if (name instanceof LinkedList) {
String user = ((LinkedList<?>) name).get(0).toString();
accessor.setUser(new User(user));
}
}
}
return message;
}
});
}
}
This is the JMS listener to process queue message and send it to specific user.
#Component
public class UserEventListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final SimpMessagingTemplate template;
#Autowired
public UserEventListener(SimpMessagingTemplate pTemplate) {
this.template = pTemplate;
}
#JmsListener(destination="user/events")
public void onStatusChange(Map<String, Object> props) {
if (props.containsKey("userEmail")) {
logger.debug("Event for user received: {}", props.get("userEmail"));
template.convertAndSendToUser((String)props.get("userEmail"), "/ccccc", props);
}
}
}
Edit 1:
After more debugging the times when doesn't work the "session" for WebSocket seems to be lost by Spring configuration. I don't see any log information about "Disconnected" messages or something similar, besides if I debug remotely the server when this happens the problem doesn't appears during debugging session. Some idea? The class from Spring where session disappear is DefaultSimpUserRegistry.
After more research I found a question with the same problem and the solution here. Basically the conclusion is this:
Channel interceptor is not the right place to authenticate user, we need to change it with a custom handshake handler.
How I can easily configure the embedded tomcat server to redirect all http traffic to https? I have Spring Boot running on an ec2 instance that is behind an elastic load balancer. I have configured the ELB to handle ssl for me (which is awesome) and it sets the X-FORWARDED-PROTO header to "https". I want to detect when that isn't set, and redirect the user to force them to use https if they aren't already.
So far, I have tried adding the following to my application.properties file with no luck:
server.tomcat.protocol-header=x-forwarded-proto
security.require-ssl=true
My answer is a little late but I just recently had this problem and want to post a solution which worked for me.
Originally, I thought that setting tomcat up to use the X-Forwarded headers would suffice but the RemoteIPValve from Tomcat, which should normally handle this case, didnt work for me.
My solution was to add an EmbeddedServletContainerCustomizer and add a ConnectorCustomizer:
(note that I am using Tomcat 8 here)
#Component
public class TomcatContainerCustomizer implements EmbeddedServletContainerCustomizer {
private static final Logger LOGGER = LoggerFactory.getLogger(TomcatContainerCustomizer.class);
#Override
public void customize(final ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
final TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(connector -> {
connector.setScheme("https");
connector.setProxyPort(443);
});
LOGGER.info("Enabled secure scheme (https).");
} else {
LOGGER.warn("Could not change protocol scheme because Tomcat is not used as servlet container.");
}
}
}
The important thing is that you not only set the Scheme to https but also the ProxyPort without which all internal redirects from Spring Boot were routed to port 80.
The configuration property security.require-ssl doesn't work when basic authentication is disabled (at least on old versions of Spring Boot). So you probably need to secure all the requests manually with code similar to this one:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject private SecurityProperties securityProperties;
#Override
protected void configure(HttpSecurity http) throws Exception {
if (securityProperties.isRequireSsl()) http.requiresChannel().anyRequest().requiresSecure();
}
}
You can check my full answer here: Spring Boot redirect HTTP to HTTPS
You will need a keystore file and few config classes.
The below link explains it in detail.
Https on embedded tomcat
Spring Boot 2.0 redirection of http to https:
Add the following to the #Configuration
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(redirectConnector());
return tomcat;
}
private Connector redirectConnector() {
Connector connector = new Connector(
TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}