runtime config, client VNC config, proxy max-in-memory-size, fixes REST proxy, limpeza do log no login.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package com.litoralregas.backend_gateway;
|
package com.litoralregas.backend_gateway;
|
||||||
|
|
||||||
import com.litoralregas.backend_gateway.gateway.ProxyProperties;
|
import com.litoralregas.backend_gateway.gateway.ProxyProperties;
|
||||||
|
import com.litoralregas.backend_gateway.runtime.RuntimeConfigProperties;
|
||||||
import com.litoralregas.backend_gateway.security.JwtProperties;
|
import com.litoralregas.backend_gateway.security.JwtProperties;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
@@ -9,6 +10,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableConfigurationProperties({
|
@EnableConfigurationProperties({
|
||||||
ProxyProperties.class,
|
ProxyProperties.class,
|
||||||
|
RuntimeConfigProperties.class,
|
||||||
JwtProperties.class
|
JwtProperties.class
|
||||||
})
|
})
|
||||||
public class BackendGatewayApplication {
|
public class BackendGatewayApplication {
|
||||||
@@ -16,4 +18,4 @@ public class BackendGatewayApplication {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(BackendGatewayApplication.class, args);
|
SpringApplication.run(BackendGatewayApplication.class, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.litoralregas.backend_gateway.auth;
|
|||||||
|
|
||||||
import com.litoralregas.backend_gateway.auth.dto.LoginRequest;
|
import com.litoralregas.backend_gateway.auth.dto.LoginRequest;
|
||||||
import com.litoralregas.backend_gateway.auth.dto.LoginResponse;
|
import com.litoralregas.backend_gateway.auth.dto.LoginResponse;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -10,19 +9,13 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
public AuthController(
|
public AuthController(AuthService authService) {
|
||||||
AuthService authService,
|
|
||||||
PasswordEncoder passwordEncoder
|
|
||||||
) {
|
|
||||||
this.authService = authService;
|
this.authService = authService;
|
||||||
this.passwordEncoder = passwordEncoder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public LoginResponse login(@RequestBody LoginRequest request) {
|
public LoginResponse login(@RequestBody LoginRequest request) {
|
||||||
System.out.println(passwordEncoder.encode("admin123"));
|
|
||||||
return authService.login(request);
|
return authService.login(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ public class ClientEntity {
|
|||||||
@Column(name = "backend_base_url", nullable = false)
|
@Column(name = "backend_base_url", nullable = false)
|
||||||
private String backendBaseUrl;
|
private String backendBaseUrl;
|
||||||
|
|
||||||
|
@Column(name = "default_vnc_host", nullable = false)
|
||||||
|
private String defaultVncHost;
|
||||||
|
|
||||||
|
@Column(name = "default_vnc_port", nullable = false)
|
||||||
|
private int defaultVncPort = 5900;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|
||||||
@@ -34,6 +40,14 @@ public class ClientEntity {
|
|||||||
return backendBaseUrl;
|
return backendBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefaultVncHost() {
|
||||||
|
return defaultVncHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultVncPort() {
|
||||||
|
return defaultVncPort;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
@@ -50,7 +64,15 @@ public class ClientEntity {
|
|||||||
this.backendBaseUrl = backendBaseUrl;
|
this.backendBaseUrl = backendBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDefaultVncHost(String defaultVncHost) {
|
||||||
|
this.defaultVncHost = defaultVncHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultVncPort(int defaultVncPort) {
|
||||||
|
this.defaultVncPort = defaultVncPort;
|
||||||
|
}
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class ClientService {
|
public class ClientService {
|
||||||
|
|
||||||
|
private static final String DEFAULT_VNC_HOST = "198.19.0.176";
|
||||||
|
private static final int DEFAULT_VNC_PORT = 5900;
|
||||||
|
|
||||||
private final ClientRepository clientRepository;
|
private final ClientRepository clientRepository;
|
||||||
|
|
||||||
public ClientService(ClientRepository clientRepository) {
|
public ClientService(ClientRepository clientRepository) {
|
||||||
@@ -19,6 +22,16 @@ public class ClientService {
|
|||||||
ClientEntity client = new ClientEntity();
|
ClientEntity client = new ClientEntity();
|
||||||
client.setName(request.name());
|
client.setName(request.name());
|
||||||
client.setBackendBaseUrl(request.backendBaseUrl());
|
client.setBackendBaseUrl(request.backendBaseUrl());
|
||||||
|
client.setDefaultVncHost(
|
||||||
|
request.defaultVncHost() == null || request.defaultVncHost().isBlank()
|
||||||
|
? DEFAULT_VNC_HOST
|
||||||
|
: request.defaultVncHost()
|
||||||
|
);
|
||||||
|
client.setDefaultVncPort(
|
||||||
|
request.defaultVncPort() == null
|
||||||
|
? DEFAULT_VNC_PORT
|
||||||
|
: request.defaultVncPort()
|
||||||
|
);
|
||||||
client.setEnabled(true);
|
client.setEnabled(true);
|
||||||
|
|
||||||
ClientEntity saved = clientRepository.save(client);
|
ClientEntity saved = clientRepository.save(client);
|
||||||
@@ -38,8 +51,10 @@ public class ClientService {
|
|||||||
client.getId(),
|
client.getId(),
|
||||||
client.getName(),
|
client.getName(),
|
||||||
client.getBackendBaseUrl(),
|
client.getBackendBaseUrl(),
|
||||||
|
client.getDefaultVncHost(),
|
||||||
|
client.getDefaultVncPort(),
|
||||||
client.isEnabled(),
|
client.isEnabled(),
|
||||||
client.getCreatedAt()
|
client.getCreatedAt()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ public record ClientResponse(
|
|||||||
Long id,
|
Long id,
|
||||||
String name,
|
String name,
|
||||||
String backendBaseUrl,
|
String backendBaseUrl,
|
||||||
|
String defaultVncHost,
|
||||||
|
int defaultVncPort,
|
||||||
boolean enabled,
|
boolean enabled,
|
||||||
String createdAt
|
String createdAt
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.litoralregas.backend_gateway.client.dto;
|
|||||||
|
|
||||||
public record CreateClientRequest(
|
public record CreateClientRequest(
|
||||||
String name,
|
String name,
|
||||||
String backendBaseUrl
|
String backendBaseUrl,
|
||||||
|
String defaultVncHost,
|
||||||
|
Integer defaultVncPort
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.litoralregas.backend_gateway.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "app.cors")
|
||||||
|
public class CorsProperties {
|
||||||
|
|
||||||
|
private List<String> allowedOrigins = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<String> getAllowedOrigins() {
|
||||||
|
return allowedOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedOrigins(List<String> allowedOrigins) {
|
||||||
|
this.allowedOrigins = allowedOrigins;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,53 @@
|
|||||||
package com.litoralregas.backend_gateway.config;
|
package com.litoralregas.backend_gateway.config;
|
||||||
|
|
||||||
import com.litoralregas.backend_gateway.security.JwtAuthenticationFilter;
|
import com.litoralregas.backend_gateway.security.JwtAuthenticationFilter;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(CorsProperties.class)
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
|
||||||
|
|
||||||
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
private final CorsProperties corsProperties;
|
||||||
|
|
||||||
|
public SecurityConfig(
|
||||||
|
JwtAuthenticationFilter jwtAuthenticationFilter,
|
||||||
|
CorsProperties corsProperties
|
||||||
|
) {
|
||||||
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||||
|
this.corsProperties = corsProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
return http
|
return http
|
||||||
|
.cors(Customizer.withDefaults())
|
||||||
.csrf(csrf -> csrf.disable())
|
.csrf(csrf -> csrf.disable())
|
||||||
.httpBasic(httpBasic -> httpBasic.disable())
|
.httpBasic(httpBasic -> httpBasic.disable())
|
||||||
.formLogin(formLogin -> formLogin.disable())
|
.formLogin(formLogin -> formLogin.disable())
|
||||||
.logout(logout -> logout.disable())
|
.logout(logout -> logout.disable())
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||||
.requestMatchers("/auth/**").permitAll()
|
.requestMatchers("/auth/**").permitAll()
|
||||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
.requestMatchers("/ws/backend/vnc").permitAll()
|
.requestMatchers("/api/backend/ws").permitAll()
|
||||||
|
.requestMatchers("/api/backend/ws/vnc").permitAll()
|
||||||
|
.requestMatchers("/api/runtime/**").authenticated()
|
||||||
.requestMatchers("/api/backend/**").authenticated()
|
.requestMatchers("/api/backend/**").authenticated()
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
)
|
)
|
||||||
@@ -34,8 +55,42 @@ public class SecurityConfig {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
|
||||||
|
config.setAllowedOrigins(corsProperties.getAllowedOrigins());
|
||||||
|
config.setAllowedMethods(List.of(
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"PATCH",
|
||||||
|
"DELETE",
|
||||||
|
"OPTIONS"
|
||||||
|
));
|
||||||
|
config.setAllowedHeaders(List.of(
|
||||||
|
HttpHeaders.AUTHORIZATION,
|
||||||
|
HttpHeaders.CONTENT_TYPE,
|
||||||
|
HttpHeaders.ACCEPT,
|
||||||
|
"X-Requested-With",
|
||||||
|
"Origin"
|
||||||
|
));
|
||||||
|
|
||||||
|
config.setExposedHeaders(List.of(
|
||||||
|
HttpHeaders.AUTHORIZATION
|
||||||
|
));
|
||||||
|
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
config.setMaxAge(3600L);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io.netty.channel.ChannelOption;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
|
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import reactor.netty.http.client.HttpClient;
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
|
||||||
@@ -18,8 +19,15 @@ public class WebClientConfig {
|
|||||||
Math.toIntExact(proxyProperties.getConnectTimeout().toMillis()))
|
Math.toIntExact(proxyProperties.getConnectTimeout().toMillis()))
|
||||||
.responseTimeout(proxyProperties.getResponseTimeout());
|
.responseTimeout(proxyProperties.getResponseTimeout());
|
||||||
|
|
||||||
|
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
|
||||||
|
.codecs(configurer -> configurer
|
||||||
|
.defaultCodecs()
|
||||||
|
.maxInMemorySize(Math.toIntExact(proxyProperties.getMaxInMemorySize().toBytes())))
|
||||||
|
.build();
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||||
|
.exchangeStrategies(exchangeStrategies)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class BackendProxyService {
|
public class BackendProxyService {
|
||||||
|
|
||||||
@@ -38,9 +40,11 @@ public class BackendProxyService {
|
|||||||
+ (query != null ? "?" + query : "");
|
+ (query != null ? "?" + query : "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
HttpMethod method = HttpMethod.valueOf(request.getMethod());
|
||||||
|
|
||||||
WebClient.RequestBodySpec requestSpec = webClient
|
WebClient.RequestBodySpec requestSpec = webClient
|
||||||
.method(HttpMethod.valueOf(request.getMethod()))
|
.method(method)
|
||||||
.uri(targetUrl);
|
.uri(URI.create(targetUrl));
|
||||||
|
|
||||||
String contentType = request.getContentType();
|
String contentType = request.getContentType();
|
||||||
|
|
||||||
@@ -54,8 +58,12 @@ public class BackendProxyService {
|
|||||||
requestSpec.header("Accept", accept);
|
requestSpec.header("Accept", accept);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResponseEntity<String> response = requestSpec
|
WebClient.RequestHeadersSpec<?> outboundRequest =
|
||||||
.bodyValue(body != null ? body : "")
|
supportsRequestBody(method)
|
||||||
|
? requestSpec.bodyValue(body != null ? body : "")
|
||||||
|
: requestSpec;
|
||||||
|
|
||||||
|
ResponseEntity<String> response = outboundRequest
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.toEntity(String.class)
|
.toEntity(String.class)
|
||||||
.block();
|
.block();
|
||||||
@@ -82,4 +90,10 @@ public class BackendProxyService {
|
|||||||
.resolveCurrentClient()
|
.resolveCurrentClient()
|
||||||
.getBackendBaseUrl();
|
.getBackendBaseUrl();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private boolean supportsRequestBody(HttpMethod method) {
|
||||||
|
return HttpMethod.POST.equals(method)
|
||||||
|
|| HttpMethod.PUT.equals(method)
|
||||||
|
|| HttpMethod.PATCH.equals(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ public class GatewayController {
|
|||||||
return backendProxyService.getHealth();
|
return backendProxyService.getHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/api/backend/**")
|
@RequestMapping(value = "/api/backend/**", headers = "Upgrade!=websocket")
|
||||||
public ResponseEntity<String> proxy(
|
public ResponseEntity<String> proxy(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
@RequestBody(required = false) String body
|
@RequestBody(required = false) String body
|
||||||
) {
|
) {
|
||||||
return backendProxyService.proxy(request, body);
|
return backendProxyService.proxy(request, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.litoralregas.backend_gateway.gateway;
|
package com.litoralregas.backend_gateway.gateway;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.util.unit.DataSize;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ public class ProxyProperties {
|
|||||||
private String backendBaseUrl;
|
private String backendBaseUrl;
|
||||||
private Duration connectTimeout = Duration.ofSeconds(3);
|
private Duration connectTimeout = Duration.ofSeconds(3);
|
||||||
private Duration responseTimeout = Duration.ofSeconds(10);
|
private Duration responseTimeout = Duration.ofSeconds(10);
|
||||||
|
private DataSize maxInMemorySize = DataSize.ofMegabytes(64);
|
||||||
|
|
||||||
public String getBackendBaseUrl() {
|
public String getBackendBaseUrl() {
|
||||||
return backendBaseUrl;
|
return backendBaseUrl;
|
||||||
@@ -34,4 +36,12 @@ public class ProxyProperties {
|
|||||||
public void setResponseTimeout(Duration responseTimeout) {
|
public void setResponseTimeout(Duration responseTimeout) {
|
||||||
this.responseTimeout = responseTimeout;
|
this.responseTimeout = responseTimeout;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public DataSize getMaxInMemorySize() {
|
||||||
|
return maxInMemorySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxInMemorySize(DataSize maxInMemorySize) {
|
||||||
|
this.maxInMemorySize = maxInMemorySize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+11
-3
@@ -10,14 +10,22 @@ import org.springframework.web.socket.server.standard.ServletServerContainerFact
|
|||||||
public class BackendWebSocketConfig implements WebSocketConfigurer {
|
public class BackendWebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
private final VncGatewayWebSocketHandler vncGatewayWebSocketHandler;
|
private final VncGatewayWebSocketHandler vncGatewayWebSocketHandler;
|
||||||
|
private final StompGatewayWebSocketHandler stompGatewayWebSocketHandler;
|
||||||
|
|
||||||
public BackendWebSocketConfig(VncGatewayWebSocketHandler vncGatewayWebSocketHandler) {
|
public BackendWebSocketConfig(
|
||||||
|
VncGatewayWebSocketHandler vncGatewayWebSocketHandler,
|
||||||
|
StompGatewayWebSocketHandler stompGatewayWebSocketHandler
|
||||||
|
) {
|
||||||
this.vncGatewayWebSocketHandler = vncGatewayWebSocketHandler;
|
this.vncGatewayWebSocketHandler = vncGatewayWebSocketHandler;
|
||||||
|
this.stompGatewayWebSocketHandler = stompGatewayWebSocketHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
registry.addHandler(vncGatewayWebSocketHandler, "/ws/backend/vnc")
|
registry.addHandler(stompGatewayWebSocketHandler, "/api/backend/ws")
|
||||||
|
.setAllowedOrigins("*");
|
||||||
|
|
||||||
|
registry.addHandler(vncGatewayWebSocketHandler, "/api/backend/ws/vnc")
|
||||||
.setAllowedOrigins("*");
|
.setAllowedOrigins("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,4 +40,4 @@ public class BackendWebSocketConfig implements WebSocketConfigurer {
|
|||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+503
@@ -0,0 +1,503 @@
|
|||||||
|
package com.litoralregas.backend_gateway.gateway.websocket;
|
||||||
|
|
||||||
|
import com.litoralregas.backend_gateway.client.ClientEntity;
|
||||||
|
import com.litoralregas.backend_gateway.client.ClientRepository;
|
||||||
|
import com.litoralregas.backend_gateway.security.JwtService;
|
||||||
|
import jakarta.websocket.ContainerProvider;
|
||||||
|
import jakarta.websocket.WebSocketContainer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.PingMessage;
|
||||||
|
import org.springframework.web.socket.PongMessage;
|
||||||
|
import org.springframework.web.socket.SubProtocolCapable;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||||
|
import org.springframework.web.socket.WebSocketMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||||
|
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class StompGatewayWebSocketHandler extends AbstractWebSocketHandler implements SubProtocolCapable {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(StompGatewayWebSocketHandler.class);
|
||||||
|
|
||||||
|
private static final int MAX_BINARY_MESSAGE_BUFFER_SIZE = 4 * 1024 * 1024;
|
||||||
|
private static final int MAX_TEXT_MESSAGE_BUFFER_SIZE = 1024 * 1024;
|
||||||
|
private static final List<String> STOMP_SUB_PROTOCOLS = List.of(
|
||||||
|
"v10.stomp",
|
||||||
|
"v11.stomp",
|
||||||
|
"v12.stomp"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final JwtService jwtService;
|
||||||
|
private final ClientRepository clientRepository;
|
||||||
|
private final StandardWebSocketClient webSocketClient;
|
||||||
|
|
||||||
|
private final Map<String, WebSocketSession> backendSessions = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Queue<WebSocketMessage<?>>> pendingMessages = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public StompGatewayWebSocketHandler(
|
||||||
|
JwtService jwtService,
|
||||||
|
ClientRepository clientRepository
|
||||||
|
) {
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
this.clientRepository = clientRepository;
|
||||||
|
|
||||||
|
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
|
||||||
|
container.setDefaultMaxBinaryMessageBufferSize(MAX_BINARY_MESSAGE_BUFFER_SIZE);
|
||||||
|
container.setDefaultMaxTextMessageBufferSize(MAX_TEXT_MESSAGE_BUFFER_SIZE);
|
||||||
|
|
||||||
|
this.webSocketClient = new StandardWebSocketClient(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(WebSocketSession frontendSession) {
|
||||||
|
log.info(
|
||||||
|
"STOMP frontend websocket connected. frontendSession={}, uri={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
frontendSession.getUri()
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String token = extractAccessToken(frontendSession);
|
||||||
|
|
||||||
|
if (token == null || token.isBlank()) {
|
||||||
|
log.warn("STOMP frontend websocket rejected. reason=missing_token frontendSession={}", frontendSession.getId());
|
||||||
|
sendError(frontendSession, "Invalid or missing access token");
|
||||||
|
closeSafe(frontendSession, "missing token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jwtService.isValid(token)) {
|
||||||
|
log.warn("STOMP frontend websocket rejected. reason=invalid_token frontendSession={}", frontendSession.getId());
|
||||||
|
sendError(frontendSession, "Invalid or missing access token");
|
||||||
|
closeSafe(frontendSession, "invalid token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = jwtService.extractUserId(token);
|
||||||
|
Long clientId = jwtService.extractClientId(token);
|
||||||
|
String username = jwtService.extractUsername(token);
|
||||||
|
String role = jwtService.extractRole(token);
|
||||||
|
|
||||||
|
ClientEntity client = clientRepository.findByIdAndEnabledTrue(clientId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Client not found or disabled"));
|
||||||
|
|
||||||
|
String backendWebSocketUrl = toBackendStompWebSocketUrl(client.getBackendBaseUrl());
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"Opening STOMP backend websocket. frontendSession={}, userId={}, username={}, role={}, clientId={}, clientName={}, backendUrl={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
userId,
|
||||||
|
username,
|
||||||
|
role,
|
||||||
|
client.getId(),
|
||||||
|
client.getName(),
|
||||||
|
backendWebSocketUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
WebSocketHttpHeaders headers = backendHeaders(frontendSession);
|
||||||
|
|
||||||
|
webSocketClient.execute(
|
||||||
|
new BackendStompBridgeHandler(frontendSession),
|
||||||
|
headers,
|
||||||
|
URI.create(backendWebSocketUrl)
|
||||||
|
).whenComplete((backendSession, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
log.error(
|
||||||
|
"STOMP backend websocket connection failed. frontendSession={}, clientId={}, backendUrl={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
client.getId(),
|
||||||
|
backendWebSocketUrl,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
sendError(frontendSession, "Could not connect to backend STOMP websocket");
|
||||||
|
closeSafe(frontendSession, "backend websocket connection failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
backendSessions.put(frontendSession.getId(), backendSession);
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"STOMP backend websocket connected. frontendSession={}, backendSession={}, backendUri={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
backendSession.getId(),
|
||||||
|
backendSession.getUri()
|
||||||
|
);
|
||||||
|
|
||||||
|
flushPendingMessages(frontendSession, backendSession);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Exception error) {
|
||||||
|
log.error(
|
||||||
|
"STOMP frontend websocket setup failed. frontendSession={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
sendError(frontendSession, error.getMessage());
|
||||||
|
closeSafe(frontendSession, "setup exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getSubProtocols() {
|
||||||
|
return STOMP_SUB_PROTOCOLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(
|
||||||
|
WebSocketSession frontendSession,
|
||||||
|
WebSocketMessage<?> message
|
||||||
|
) {
|
||||||
|
log.debug(
|
||||||
|
"STOMP frontend websocket message received. frontendSession={}, type={}, size={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
messageType(message),
|
||||||
|
payloadLength(message)
|
||||||
|
);
|
||||||
|
|
||||||
|
forwardOrQueue(frontendSession, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionClosed(
|
||||||
|
WebSocketSession frontendSession,
|
||||||
|
CloseStatus status
|
||||||
|
) {
|
||||||
|
log.info(
|
||||||
|
"STOMP frontend websocket closed. frontendSession={}, status={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
status
|
||||||
|
);
|
||||||
|
|
||||||
|
pendingMessages.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
WebSocketSession backendSession = backendSessions.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
if (backendSession != null) {
|
||||||
|
closeSafe(backendSession, "frontend closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleTransportError(
|
||||||
|
WebSocketSession frontendSession,
|
||||||
|
Throwable exception
|
||||||
|
) {
|
||||||
|
log.error(
|
||||||
|
"STOMP frontend websocket transport error. frontendSession={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
exception
|
||||||
|
);
|
||||||
|
|
||||||
|
pendingMessages.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
WebSocketSession backendSession = backendSessions.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
closeSafe(backendSession, "frontend transport error");
|
||||||
|
closeSafe(frontendSession, "frontend transport error");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forwardOrQueue(
|
||||||
|
WebSocketSession frontendSession,
|
||||||
|
WebSocketMessage<?> message
|
||||||
|
) {
|
||||||
|
WebSocketSession backendSession = backendSessions.get(frontendSession.getId());
|
||||||
|
|
||||||
|
if (backendSession != null && backendSession.isOpen()) {
|
||||||
|
log.debug(
|
||||||
|
"Forwarding STOMP message frontend -> backend. frontendSession={}, backendSession={}, type={}, size={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
backendSession.getId(),
|
||||||
|
messageType(message),
|
||||||
|
payloadLength(message)
|
||||||
|
);
|
||||||
|
|
||||||
|
sendSafe(backendSession, message, "frontend -> backend");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue<WebSocketMessage<?>> queue = pendingMessages
|
||||||
|
.computeIfAbsent(frontendSession.getId(), id -> new ConcurrentLinkedQueue<>());
|
||||||
|
|
||||||
|
queue.add(message);
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Queued STOMP frontend message because backend is not ready. frontendSession={}, queueSize={}, type={}, size={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
queue.size(),
|
||||||
|
messageType(message),
|
||||||
|
payloadLength(message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushPendingMessages(
|
||||||
|
WebSocketSession frontendSession,
|
||||||
|
WebSocketSession backendSession
|
||||||
|
) {
|
||||||
|
Queue<WebSocketMessage<?>> queue = pendingMessages.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
if (queue == null || queue.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
"Flushing queued STOMP frontend messages. frontendSession={}, backendSession={}, count={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
backendSession.getId(),
|
||||||
|
queue.size()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (WebSocketMessage<?> pendingMessage : queue) {
|
||||||
|
sendSafe(backendSession, pendingMessage, "flush queued frontend -> backend");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractAccessToken(WebSocketSession session) {
|
||||||
|
URI uri = session.getUri();
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String query = uri.getQuery();
|
||||||
|
|
||||||
|
if (query == null || query.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] params = query.split("&");
|
||||||
|
|
||||||
|
for (String param : params) {
|
||||||
|
String[] parts = param.split("=", 2);
|
||||||
|
|
||||||
|
if (parts.length == 2 && parts[0].equals("access_token")) {
|
||||||
|
return URLDecoder.decode(parts[1], StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toBackendStompWebSocketUrl(String backendBaseUrl) {
|
||||||
|
String normalizedBaseUrl = backendBaseUrl.endsWith("/")
|
||||||
|
? backendBaseUrl.substring(0, backendBaseUrl.length() - 1)
|
||||||
|
: backendBaseUrl;
|
||||||
|
|
||||||
|
String wsBaseUrl = normalizedBaseUrl
|
||||||
|
.replaceFirst("^http://", "ws://")
|
||||||
|
.replaceFirst("^https://", "wss://");
|
||||||
|
|
||||||
|
return wsBaseUrl + "/ws";
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebSocketHttpHeaders backendHeaders(WebSocketSession frontendSession) {
|
||||||
|
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
|
||||||
|
String acceptedProtocol = frontendSession.getAcceptedProtocol();
|
||||||
|
|
||||||
|
if (acceptedProtocol != null && !acceptedProtocol.isBlank()) {
|
||||||
|
headers.setSecWebSocketProtocol(acceptedProtocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendError(WebSocketSession session, String message) {
|
||||||
|
if (session == null || !session.isOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String payload =
|
||||||
|
"{\"type\":\"error\",\"message\":\"" +
|
||||||
|
safeJson(message == null ? "Unknown websocket error" : message) +
|
||||||
|
"\"}";
|
||||||
|
|
||||||
|
sendSafe(session, new TextMessage(payload), "send error");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeJson(String value) {
|
||||||
|
return value
|
||||||
|
.replace("\\", "\\\\")
|
||||||
|
.replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSafe(
|
||||||
|
WebSocketSession session,
|
||||||
|
WebSocketMessage<?> message,
|
||||||
|
String direction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (session == null) {
|
||||||
|
log.debug("STOMP websocket send skipped because session is null. direction={}", direction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session.isOpen()) {
|
||||||
|
log.debug(
|
||||||
|
"STOMP websocket send skipped because session is closed. direction={}, session={}",
|
||||||
|
direction,
|
||||||
|
session.getId()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (session) {
|
||||||
|
session.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"STOMP websocket message sent. direction={}, session={}, type={}, size={}",
|
||||||
|
direction,
|
||||||
|
session.getId(),
|
||||||
|
messageType(message),
|
||||||
|
payloadLength(message)
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (IllegalStateException closed) {
|
||||||
|
log.debug("STOMP websocket send skipped because session was already closed. direction={}", direction);
|
||||||
|
} catch (Exception error) {
|
||||||
|
log.warn(
|
||||||
|
"STOMP websocket send failed. direction={}, session={}",
|
||||||
|
direction,
|
||||||
|
session == null ? "null" : session.getId(),
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSafe(WebSocketSession session, String reason) {
|
||||||
|
try {
|
||||||
|
if (session == null || !session.isOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Closing STOMP websocket session. session={}, reason={}",
|
||||||
|
session.getId(),
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
|
||||||
|
} catch (Exception error) {
|
||||||
|
log.warn(
|
||||||
|
"Failed to close STOMP websocket session. reason={}, session={}",
|
||||||
|
reason,
|
||||||
|
session == null ? "null" : session.getId(),
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String messageType(WebSocketMessage<?> message) {
|
||||||
|
if (message instanceof TextMessage) {
|
||||||
|
return "TEXT";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message instanceof PingMessage) {
|
||||||
|
return "PING";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message instanceof PongMessage) {
|
||||||
|
return "PONG";
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int payloadLength(WebSocketMessage<?> message) {
|
||||||
|
try {
|
||||||
|
return message.getPayloadLength();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BackendStompBridgeHandler extends AbstractWebSocketHandler {
|
||||||
|
|
||||||
|
private final WebSocketSession frontendSession;
|
||||||
|
|
||||||
|
private BackendStompBridgeHandler(WebSocketSession frontendSession) {
|
||||||
|
this.frontendSession = frontendSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(WebSocketSession backendSession) {
|
||||||
|
log.debug(
|
||||||
|
"STOMP backend bridge handler established. frontendSession={}, backendSession={}, backendUri={}",
|
||||||
|
frontendSession.getId(),
|
||||||
|
backendSession.getId(),
|
||||||
|
backendSession.getUri()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(
|
||||||
|
WebSocketSession backendSession,
|
||||||
|
WebSocketMessage<?> message
|
||||||
|
) {
|
||||||
|
log.debug(
|
||||||
|
"STOMP backend websocket message received. backendSession={}, frontendSession={}, type={}, size={}",
|
||||||
|
backendSession.getId(),
|
||||||
|
frontendSession.getId(),
|
||||||
|
messageType(message),
|
||||||
|
payloadLength(message)
|
||||||
|
);
|
||||||
|
|
||||||
|
sendSafe(frontendSession, message, "backend -> frontend");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionClosed(
|
||||||
|
WebSocketSession backendSession,
|
||||||
|
CloseStatus status
|
||||||
|
) {
|
||||||
|
log.info(
|
||||||
|
"STOMP backend websocket closed. backendSession={}, frontendSession={}, status={}",
|
||||||
|
backendSession.getId(),
|
||||||
|
frontendSession.getId(),
|
||||||
|
status
|
||||||
|
);
|
||||||
|
|
||||||
|
backendSessions.remove(frontendSession.getId());
|
||||||
|
pendingMessages.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
closeSafe(frontendSession, "backend closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleTransportError(
|
||||||
|
WebSocketSession backendSession,
|
||||||
|
Throwable exception
|
||||||
|
) {
|
||||||
|
log.error(
|
||||||
|
"STOMP backend websocket transport error. backendSession={}, frontendSession={}",
|
||||||
|
backendSession.getId(),
|
||||||
|
frontendSession.getId(),
|
||||||
|
exception
|
||||||
|
);
|
||||||
|
|
||||||
|
sendError(frontendSession, "Backend websocket transport error");
|
||||||
|
|
||||||
|
backendSessions.remove(frontendSession.getId());
|
||||||
|
pendingMessages.remove(frontendSession.getId());
|
||||||
|
|
||||||
|
closeSafe(frontendSession, "backend transport error");
|
||||||
|
closeSafe(backendSession, "backend transport error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.litoralregas.backend_gateway.runtime;
|
||||||
|
|
||||||
|
public record ClientRuntimeConfig(
|
||||||
|
Long id,
|
||||||
|
String name
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.litoralregas.backend_gateway.runtime;
|
||||||
|
|
||||||
|
public record GatewayRuntimeConfig(
|
||||||
|
String backendApiBasePath,
|
||||||
|
String stompWebSocketPath,
|
||||||
|
String vncWebSocketPath
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.litoralregas.backend_gateway.runtime;
|
||||||
|
|
||||||
|
import com.litoralregas.backend_gateway.client.ClientEntity;
|
||||||
|
import com.litoralregas.backend_gateway.client.ClientResolver;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class RuntimeConfigController {
|
||||||
|
|
||||||
|
private final ClientResolver clientResolver;
|
||||||
|
private final RuntimeConfigProperties properties;
|
||||||
|
|
||||||
|
public RuntimeConfigController(
|
||||||
|
ClientResolver clientResolver,
|
||||||
|
RuntimeConfigProperties properties
|
||||||
|
) {
|
||||||
|
this.clientResolver = clientResolver;
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/runtime/config")
|
||||||
|
public RuntimeConfigResponse getRuntimeConfig() {
|
||||||
|
ClientEntity client = clientResolver.resolveCurrentClient();
|
||||||
|
|
||||||
|
return new RuntimeConfigResponse(
|
||||||
|
properties.getMode(),
|
||||||
|
new ClientRuntimeConfig(
|
||||||
|
client.getId(),
|
||||||
|
client.getName()
|
||||||
|
),
|
||||||
|
new GatewayRuntimeConfig(
|
||||||
|
properties.getBackendApiBasePath(),
|
||||||
|
properties.getStompWebSocketPath(),
|
||||||
|
properties.getVncWebSocketPath()
|
||||||
|
),
|
||||||
|
new VncRuntimeConfig(
|
||||||
|
client.getDefaultVncHost(),
|
||||||
|
client.getDefaultVncPort()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.litoralregas.backend_gateway.runtime;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "app.runtime")
|
||||||
|
public class RuntimeConfigProperties {
|
||||||
|
|
||||||
|
private String mode = "development";
|
||||||
|
private String backendApiBasePath = "/api/backend";
|
||||||
|
private String stompWebSocketPath = "/api/backend/ws";
|
||||||
|
private String vncWebSocketPath = "/api/backend/ws/vnc";
|
||||||
|
|
||||||
|
public String getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(String mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackendApiBasePath() {
|
||||||
|
return backendApiBasePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackendApiBasePath(String backendApiBasePath) {
|
||||||
|
this.backendApiBasePath = backendApiBasePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStompWebSocketPath() {
|
||||||
|
return stompWebSocketPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStompWebSocketPath(String stompWebSocketPath) {
|
||||||
|
this.stompWebSocketPath = stompWebSocketPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVncWebSocketPath() {
|
||||||
|
return vncWebSocketPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVncWebSocketPath(String vncWebSocketPath) {
|
||||||
|
this.vncWebSocketPath = vncWebSocketPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.litoralregas.backend_gateway.runtime;
|
||||||
|
|
||||||
|
public record RuntimeConfigResponse(
|
||||||
|
String mode,
|
||||||
|
ClientRuntimeConfig client,
|
||||||
|
GatewayRuntimeConfig gateway,
|
||||||
|
VncRuntimeConfig vnc
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.litoralregas.backend_gateway.runtime;
|
||||||
|
|
||||||
|
public record VncRuntimeConfig(
|
||||||
|
String defaultHost,
|
||||||
|
int defaultPort
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -24,7 +24,17 @@ gateway:
|
|||||||
backend-base-url: http://10.100.1.2:18450
|
backend-base-url: http://10.100.1.2:18450
|
||||||
connect-timeout: 3s
|
connect-timeout: 3s
|
||||||
response-timeout: 10s
|
response-timeout: 10s
|
||||||
|
max-in-memory-size: 64MB
|
||||||
|
|
||||||
|
app:
|
||||||
|
cors:
|
||||||
|
allowed-origins: ${APP_CORS_ALLOWED_ORIGINS:http://localhost:1420,http://127.0.0.1:1420}
|
||||||
|
runtime:
|
||||||
|
mode: ${APP_RUNTIME_MODE:development}
|
||||||
|
backend-api-base-path: ${APP_BACKEND_API_BASE_PATH:/api/backend}
|
||||||
|
stomp-web-socket-path: ${APP_STOMP_WEB_SOCKET_PATH:/api/backend/ws}
|
||||||
|
vnc-web-socket-path: ${APP_VNC_WEB_SOCKET_PATH:/api/backend/ws/vnc}
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: ${JWT_SECRET:backend-gateway-local-development-secret-2026-super-long}
|
secret: ${JWT_SECRET:backend-gateway-local-development-secret-2026-super-long}
|
||||||
expiration-minutes: ${JWT_EXPIRATION_MINUTES:1440}
|
expiration-minutes: ${JWT_EXPIRATION_MINUTES:1440}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE clients
|
||||||
|
ADD COLUMN default_vnc_host TEXT NOT NULL DEFAULT '198.19.0.176';
|
||||||
|
|
||||||
|
ALTER TABLE clients
|
||||||
|
ADD COLUMN default_vnc_port INTEGER NOT NULL DEFAULT 5900;
|
||||||
Reference in New Issue
Block a user