Is there a way to support all paths in a Nestjs Gateway using the ws module? - websocket

I want my Gateway to be able to receive connections from any path as I'm going to receive user id as parameter. I'm using the ws module.
Example:
ws://localhost:3000/12345
another example:
ws://localhost:3000/54321
I put #WebSocketGateway({ path: '*' }) but it doesn't work. Here is my Gateway:
#WebSocketGateway({ path: '*' })
export class CacheGateway implements OnGatewayConnection {
private readonly logger: Logger = new Logger(CacheGateway.name);
private readonly clients: { [key: string]: WebSocket } = {};
public handleConnection(#ConnectedSocket() client: WebSocket): void {
const userID = client.url?.slice(1);
if (userID) {
this.clients[userID] = client;
this.logger.log(
`User Connected: ${userID}. At: ${new Date().toISOString()}`,
);
} else {
this.logger.log('Anonymous user connected');
}
}
}

Related

Using ConnectableFlux for hot stream REST endpoint

I'm trying to create a REST endpoint to subscribe to a hot stream using Reactor.
My test provider for the stream looks like this:
#Service
class TestProvider {
fun getStream(resourceId: String): ConnectableFlux<QData> {
return Flux.create<QData> {
for (i in 1..10) {
it.next(QData(LocalDateTime.now().toString(), "next"))
Thread.sleep(500L)
}
it.complete()
}.publish()
}
}
My service for the REST endpoint looks like this:
#Service
class DataService #Autowired constructor(
private val prv: TestProvider
) {
private val streams = mutableMapOf<String, ConnectableFlux<QData>>()
fun subscribe(resourceId: String): Flux<QData> {
val stream = getStream(resourceId)
return Flux.push { flux ->
stream.subscribe{
flux.next(it)
}
flux.complete()
}
}
private fun getStream(resourceId: String): ConnectableFlux<QData> {
if(streams.containsKey(resourceId).not()) {
streams.put(resourceId, createStream(resourceId))
}
return streams.get(resourceId)!!
}
private fun createStream(resourceId: String): ConnectableFlux<QData> {
val stream = prv.getStream(resourceId)
stream.connect()
return stream
}
}
The controller looks like this:
#RestController
class DataController #Autowired constructor(
private val dataService: DataService
): DataApi {
override fun subscribe(resourceId: String): Flux<QData> {
return dataService.subscribe(resourceId)
}
}
The API interface looks like this:
interface DataApi {
#ApiResponses(value = [
ApiResponse(responseCode = "202", description = "Client is subscribed", content = [
Content(mediaType = "application/json", array = ArraySchema(schema = Schema(implementation = QData::class)))
])
])
#GetMapping(path = ["/subscription/{resourceId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
fun subscribe(
#Parameter(description = "The resource id for which quality data is subscribed for", required = true, example = "example",allowEmptyValue = false)
#PathVariable("resourceId", required = true) #NotEmpty resourceId: String
): Flux<QData>
}
Unfortunately, my curl delivers an empty array.
Does anyone has an idea what the issue is? Thanks in advance!
I had to run connect() async in DataService:
CompletableFuture.runAsync {
stream.connect()
}

Spring Cloud Gateway : How to pass params to custom filter

I have a custom filter defined in my application.yml. I need to take some more parameters from that filter definition in YAML, so that I can perform a logic inside my custom filter. I have multiple such routes defined wherein the filter parameters differs. The problem I am facing is, not able to read the values specified in YAML file.
application.yml ---
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://api.rangon.pi
predicates:
- Path=/api/staticdata/rattlefeed*
filters:
- AddRequestHeader=X-Y-Host, rangon
- TestGatewayFilter=admin, XY8382, basic
//Is there any way to get "admin, XY8382, basic" in my custom filter class
My filter class
#Component
public class TestGatewayFilter implements
GatewayFilterFactory<TestGatewayFilter.Config> {
#Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
Route r = (Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
// ServerWebExchange route =
// exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
// List<GatewayFilter> list = r.getFilters();
GatewayFilter gwf = r.getFilters().get(r.getFilters().size() - 1);
Builder builder = exchange.getRequest().mutate();
// use builder to manipulate the request
return chain.filter(exchange.mutate().build());
};
}
public Config newConfig() {
Config c = new Config();
return c;
}
public static class Config {
// Put the configuration properties for your filter here
}
}
There are two ways to define params for your filters in your configuration file, in this example I'm going to use an application.yml
First of all, you can use the args keyword to define a key-value list of arguments:
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://myapi.com
filters:
- name: TestLoggingFilter
args:
value: ThisIsATest
And the second form, you can use inline args:
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://myapi.com
filters:
- TestLoggingFilter=ThisIsATest
If you want to use inline args make sure to override the shortcutFieldOrder method in your filter with an array containing the name of the params you want to receive, this array is also used to define the order of the params.
The following is an example of a simple filter that works with any of the previous definitions, as this example override shortcutFieldOrder as well:
package com.es.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
#Component
public class TestLoggingFilter implements GatewayFilterFactory<TestLoggingFilter.Config> {
private static final Logger LOG = LoggerFactory.getLogger(TestLoggingFilter.class);
private static final String VALUE = "value";
#Override
public Config newConfig() {
return new Config();
}
#Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(VALUE);
}
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
LOG.info("Filter enabled with value: " + config.value);
return chain.filter(exchange);
};
}
public static class Config {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

Implement properly list of Objects from rest api

I want to implement Angular example which gets list from rest API. I tried this:
SQL query:
#Override
public Iterable<Merchants> findAll() {
String hql = "select e from " + Merchants.class.getName() + " e";
TypedQuery<Merchants> query = entityManager.createQuery(hql, Merchants.class);
List<Merchants> merchants = query.getResultList();
return merchants;
}
Rest controller:
#RestController
#RequestMapping("/merchants")
public class MerchantController {
#GetMapping("/list")
public Iterable<Merchants> getMerchantsList() {
return merchantRepository
.findAll();
}
}
Service:
#Injectable({
providedIn: 'root'
})
export class MerchantService {
constructor(private http: HttpClient) {
}
getList() {
return this.http.get("...../api/merchants/list");
}
}
Component:
#Component({
selector: 'app-terminal',
templateUrl: './terminal.component.html',
styleUrls: ['./terminal.component.scss']
})
export class TerminalComponent implements OnInit {
merchants: Merchant[];
constructor(private merchantService: MerchantService,
private router: Router,
private route: ActivatedRoute) {
}
ngOnInit() {
this.merchantService.getList();
}
}
But when I lock the component via web page nothing happens. Can you give me some advice where I'm wrong?
Probably my typescript is somewhere incorrect?
You need to call subscribe on the Observable, otherwise it won't make the HTTP request
ngOnInit() {
this.merchantService.getList().subscribe(res => {
console.log("The response is", res);
});
}

axios can't accept response data sent by Spring Boot controller

I tried to integrate vue.js with Spring Boot. This is my vue.js code:
<template>
// ...
</template>
<script>
export default {
name: "Login",
data: function() {
return {
username: '',
password: '',
msg: ''
}
},
methods: {
// post data to Spring Boot
login() {
axios.post('/login',{
username: this.username,
password: this.password
})
.then(function(response) {
if(response.data.code === 200){
this.$store.dispatch('setCurrentUser',this.username);
// vue-route
this.$router.push('/course');
} else {
this.msg = response.message;
}
})
.catch(function(err) {
this.msg = 'error';
});
}
}
};
</script>
And this is my Spring Boot controller:
#RestController
#ResponseBody
public class LoginController {
#Autowired
private ResultGenerator resultGenerator;
#PostMapping("/login")
public RestResult login(String username, String password){
if(username.equals("123") && password.equals("123")){
return resultGenerator.getSuccessResult();
} else {
return resultGenerator.getFailResult("error");
}
}
}
The controller will return JSON data which looks like:{"code":200,"message":"success","data":null}. When the login method was called, controller could accept the username and password and controller sent response data too. But that was all and vue-router didn't work. All I saw in the brower was:
Can anyone help?
------------------ Addition -----------------------
This is vue-router config:
const routes = [
{
path: '/',
component: Login
},
{
path: '/signin',
component: Signin
},
{
path: '/course',
component: Course
}
];
const router = new VueRouter({
routes,
mode: "history"
});
The problem could be that you return resultGenerator.getSuccessResult(). Have you tried redirecting to the '/course' path inside Spring Boot Controller?
#PostMapping("/login")
public RestResult login(String username, String password){
if(username.equals("123") && password.equals("123")){
this.$router.push('/course');
} else {
return resultGenerator.getFailResult("error");
}
}
If the Vue.js and Spring boot are 2 different apps (like backend and frontend), this may help:
Try using #CrossOrigin (CORS) on your #controller or on the method that expose the rest, I had similar issues on an Ionic 3 proyect and thaty solved the problem.
EXAMPLE:
#CrossOrigin(origins = "http://localhost:9000")
#GetMapping("/greeting")
public Greeting greeting(#RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
It should look something like this:
#RestController
#ResponseBody
public class LoginController {
#Autowired
private ResultGenerator resultGenerator;
#CrossOrigin(origins = "http://localhost:9000") // The IP:PORT of the vue app origin
#PostMapping("/login")
public RestResult login(String username, String password){
if(username.equals("123") && password.equals("123")){
return resultGenerator.getSuccessResult();
} else {
return resultGenerator.getFailResult("error");
}
}
}
Source from spring.io Here! :D

MVC in Angular 2 - update component from service?

AIM: I send a http request from angular 2 ui to java server. While it is executing, server generates messages with progress status so I can update progress bar on ui.
I have 3 entities: AppComponent, AppService and WebsocketService.
Here is a simplified example of WebsocketService. It can subscribe to message topic and perform some actions on incoming each message.
export class WebsocketService {
private client: Client;
constructor() {
var service = this;
service.client = Stomp.client('ws://localhost:8080/stomp/websocket');
service.client.connect("user", "pass", function () {
service.client.subscribe("/progress", function (message) {
// some actions here
})
});
}
}
So, my question is: how to update AppComponent's property (field) value, which is binded to template, from AppService or even WebsocketService? Use a setter? Is it allright from the terms of MVC?
There is more than one way to do this but I would use a "Subject" stream.
Here is an example:
import {Injectable} from '#angular/core';
import {Http, Headers, RequestOptions, Response} from '#angular/http';
import {Observable} from 'rxjs/Rx';
import {Subject} from 'rxjs/Subject';
import {NextObserver} from 'rxjs/Observer';
export class WebsocketService {
public ProcessingMessages$: Observable<string>;
private client: Client;
private processingMessages: Subject<string>;
constructor() {
this.processingMessages = new Subject<string>();
this.ProcessingMessages$ = this.processingMessages.asObservable();
var service = this;
service.client = Stomp.client('ws://localhost:8080/stomp/websocket');
service.client.connect("user", "pass", function () {
service.client.subscribe("/progress", function (message) {
this.processingMessages.next(message);
})
});
}
}
// Sample Component
#Component({
selector: 'my-component',
template: 'WebsocketService Message: {{Message}}',
})
export class Sample Component implements OnInit {
public Message: string;
constructor(
private service: WebsocketService
) {
}
ngOnInit() {
this.service.ProcessingMessages$.subscribe((message: string) => {
this.Message = message;
});
}
}

Resources