I am making my first steps with websockets in my application.
My frontend is using vue.js while my backend uses flask.
In my component I wrote this.
created() {
console.log('Starting connection to WebSocket Server');
// this.connection = new WebSocket('wss://echo.websocket.org');
this.connection = new WebSocket('wss://192.168.0.22:5000');
this.connection.onmessage = function (event) {
console.log(event);
};
this.connection.onopen = function (event) {
console.log(event);
console.log('Successfully connected to the echo websocket server...');
};
},
In my flask app.py besides other stuff I have this
app = Flask(__name__)
socketio = SocketIO(app)
CORS(app)
"""Socket.IO decorator to create a websocket event handler"""
#socketio.on('my event')
def handle_my_custom_event(json, methods=['GET', 'POST']):
print('received my event: ' + str(json))
socketio.emit('my response', json, callback=messageReceived)
def messageReceived(methods=['GET', 'POST']):
print('message was received!!!')
if __name__ == "__main__":
socketio.run(app, debug=True)
In my browser I get the error that firefox could not make a connection to wss://192.168.0.22:5050. I already tried the frontend with the websocket from a tutorial which is commented out now.
I am not sure, which url I should use for my own backend or what I have to add there.
Sorry if this is obvious but I am a complete beginnern.
Thanks in advance!
EDIT:
In chrome the error I receive is "WebSocket connection to 'wss://192.168.0.38:5000/' failed: WebSocket opening handshake timed out"
Also as I saw this error when trying out stuff, maybe this question could be relevant? vue socket.io connection attempt returning "No 'Access-Control-Allow-Origin' header is present" error even when origins have been set
so the part for the socket which i ended up using for the client/component:
import io from 'socket.io-client';
created() {
// test websocket connection
const socket = io.connect('http://192.168.0.38:5000');
// getting data from server
// eslint-disable-next-line
socket.on('connect', function () {
console.error('connected to webSocket');
//sending to server
socket.emit('my event', { data: 'I\'m connected!' });
});
// we have to use the arrow function to bind this in the function
// so that we can access Vue & its methods
socket.on('update_on_layouts', (data) => {
this.getAllLayouts();
console.log(data);
});
},
The Flask server code stayed as shown above. Additionally here is an example from my flask server to emit the update_on_layouts socketio.emit('update_on_layouts', 'success')
Related
I'm creating a React Native/Expo app that uses Deno on the backend. I've created an API in the backend that I can run locally on localhost:4000. When I try to use fetch to call the API in the Expo app, however, I keep getting an error
[Unhandled promise rejection: TypeError: Network request failed] at node_modules/whatwg-fetch/dist/fetch.umd.js:535:17 in setTimeout$argument_0
Here is how I set up the backend
import { Application } from "https://deno.land/x/oak/mod.ts";
import { oakCors } from "https://deno.land/x/cors/mod.ts";
import { APP_HOST, APP_PORT } from "./config.ts";
import router from "./routes.ts";
import _404 from "./controllers/404.ts";
import errorHandler from "./controllers/errorHandler.ts";
const app = new Application();
app.use(oakCors());
app.use(errorHandler);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(_404);
console.log(`Listening on port:${APP_PORT}...`);
And how I use fetch to call the API
const App = () => {
const getData = async () => {
const response = await fetch("http://localhost:4000/something");
const data = await response.json();
console.log(data);
};
useEffect(() => {
getData();
}, []);
return (
...
);
};
Note
Some answers on StackOverflow suggest fetching http://[IP_ADDRESS]:4000/something instead of localhost. I've tried that with no luck.
I've verified that the API is working. I can call it successfully in VSCode's Thunder Client and I can also see the data by going to http://localhost:4000 in the browser.
I found a solution to this issue. I'm running my Expo app on a physical device while my server is running on my computer, on localhost. It makes sense that I'm unable to make requests to localhost on my device, because localhost is not running there.
I fixed this issue by using ngrok, a tool that forwards localhost to a cloud URL, and fetching that URL in the app.
Use the local IP assign to your device, like: http://192.168.20.109:port/api/x
find the local IP using the command "ipconfig" in windows or ifconfig in linux
I am new to vue and maybe I miss some fundamental concept here but I am not able to force my vue app to call the backend from the frontend server origin (ip:port). It seems that the API calls arriving from the client IPs instead - cause an issue that the backend CORS control cannot work.
I try to create a simple hello-world app and host a separate backend and frontend server in a docker container on a NAS running at local network. The ip of the NAS on the local network is 192.168.0.150. I mapped the port: 5000 to the backend and 6080 to the frontend.
I use python fastAPI as a backend and vuejs as a frontend. everyting works fine if I allow all the origins in the fastapi.middleware.cors setting :
Backend - fastapi.middleware.cors settings :
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
#app.get("/")
def home(request:Request):
client_host = request.client.host
client_port= request.client.port
return {"message": "Hello, World!","client_host": client_host,"client_port":client_port }
'''
frontend/src/main.js - axios setting
axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'http://192.168.0.150:5000/'; // the FastAPI backend
frontend/view - api call:
<template>
<div>
<p>{{ msg }}</p>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Ping',
data() {
return {
msg: '',
};
},
methods: {
getMessage() {
axios.get('/')
.then((res) => {
this.msg = res.data;
})
.catch((error) => {
console.error(error);
this.msg = error.msg;
});
},
},
created() {
this.getMessage();
},
};
</script>
regardless of how I host the Vue app (with dev server -npm run serve- or after a build with a HTTP server) the message above in the tmeplate shows the ip of the devices on the network from where the page is opened and not the frontend (192.168.0.150:6080) as I would expect.
So that if I set the fastapi middleware to restrict the origins then the API call fails:
allow_origins=["http://192.168.0.150:6080/"]
I also tried to set up a proxy, based on this: https://techformist.com/vuejs-proxy-error-for-backend-connection/ but keep the same behavior.
would you please help with what I miss here?
is my understanding wrong that the API should feel that it is called from the frontend host:port rather than the clients?
I am working on a React Native mobile chat application utilizing laravel-echo library for socket connections.
I have a function based EchoServer component, rendered from a custom header component on every page. It calls handleNewMessage through useEffect hook and sets a new Echo object for listening to sockets.
let echo = new Echo({
broadcaster: 'socket.io',
client: socketio,
authEndpoint: constants.echoServer + '/broadcasting/auth',
host: constants.echoServer + ':' + constants.echoServerPort,
auth: {
headers: {
Accept: "application/json",
Authorization: `Bearer ${params.access_token}`,
}
},
});
echo
.private(channel)
.notification((notification) => {
handleNewMessage(notification);
});
console.log('Join "online" channel');
echo.join('online')
.here(users => {
if(users.find(data => data.id === params.user.id)) {
setState({
icon: ICONS.online,
color: COLORS.online
});
}
});
echo.connector.socket.on('subscription_error', (channel, data) => {
console.log('channel subscription error: ' + channel, data);
});
I do not have any issues with listening notifications from the socket connection. However, since this component is rendered in a header component as mentioned above, it also creates a new connection every time the header is rendered. That causes same notifications to come multiple times increasingly (At first I receive 1 notification, then 2 and 3 etc.)
I solved that problem by using a useContext hook for the echo object. I basically used the useContext hook as a global variable.
After,
const [ socket, setSocket ] = useContext(SocketContext);
I check if socket already has a value or not, before creating a "new Echo" object.
if (socket){
return;
}
However, with this solution I had to add another context in my parent App.js file such that:
<AuthContext.Provider value={authContext}>
<SocketContext.Provider value={[socket, setSocket]}>
<MessageStore>
<NavigationContainer>
{ user.access_token?
<MenuStack/>
:
<AuthStack/>
}
</NavigationContainer>
</MessageStore>
</SocketContext.Provider>
</AuthContext.Provider>
I can see that nested contexts might be an issue in future (if they're not already) as I already have 3. What I'd like to ask is, is there a better solution in order to check if socket connection has been established without using useContext hook?
I was also thinking maybe useContext is not the best solution for this.
Also establishing the socket connection right after the authentication process only once might be a better solution, instead of checking it every time in the header.
I am looking for suggestions about the best practices for this case.
Thank you for your helps in advance.
I created service in Visual Studio with Conveyor extension to make it accessible in local network. I installed certificate on my Android device so there is green padlock when calling it in browser and service works fine.
However when calling it from React Native app with axios then i get Network error.
That's my code:
const fetchData = async () => {
console.log('url', `${Settings.API_URL}/location/GetDataByMyIp`);
try {
const result = await axios(
`${Settings.API_URL}/location/GetDataByMyIp`,
);
console.log('result', result);
ipData = {
city: result.data.city,
longitude: result.data.longitude,
latitude: result.data.latitude,
};
} catch (e) {
console.log('error', e);
}
};
await fetchData();
In console i see:
url https://192.168.1.14:45455/api/location/GetDataByMyIp
error Error: Network Error
at createError (path_to_app\node_modules\axios\lib\core\createError.js:16)
at EventTarget.handleError (path_to_app\node_modules\axios\lib\adapters\xhr.js:83)
at EventTarget.dispatchEvent (path_to_app\node_modules\event-target-shim\dist\event-target-shim.js:818)
at EventTarget.setReadyState (path_to_app\node_modules\react-native\Libraries\Network\XMLHttpRequest.js:575)
at EventTarget.__didCompleteResponse (path_to_app\node_modules\react-native\Libraries\Network\XMLHttpRequest.js:389)
at path_to_app\node_modules\react-native\Libraries\Network\XMLHttpRequest.js:502
at RCTDeviceEventEmitter.emit (path_to_app\node_modules\react-native\Libraries\vendor\emitter\EventEmitter.js:189)
at MessageQueue.__callFunction (path_to_app\node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:425)
at path_to_app\node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112
at MessageQueue.__guard (path_to_app\node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:373)
I tried to add this line to AndroidManifest.xml application tag but still have the error
android:usesCleartextTraffic="true"
Some solutions say about adding as 2nd parameter to axios, but it doesn't work with React Native.
new https.Agent({
rejectUnauthorized: false,
});
I tried to call some other service from the internet and then error was gone.
Is there some solution to this? I haven't found anything more. My last idea is to host service on Azure so I'll have properly signed SSL, but i guess it has to be a way to run it locally.
Edit: It works through http
I have an node / express js app that was generated using the yoman full stack generator. I have swapped out mongo / mongoose for cloudant db (which is just a paid for version of couchdb). I have a written a wrapper for the Cloudant node.js library which handles cookie auth with my instance via an init() method wrapped in a promise. I have refactored my application to not start the express server until the connection to the db has been established as per snippet below taken from my app.js
myDb.init(config).then(function (db) {
logger.write(1001, '','Connection to Cloudant Established');
// Start server
server.listen(config.port, config.ip, function () {
logger.write(1001, "",'Express server listening on '+config.port+', in '+app.get('env')+' mode');
});
});
On my express routes I have introduced a new middleware which attaches the db object to the request for use across the middleware chain as per below. This gets the db connection object before setting the two collections to use.
exports.beforeAll = function (req, res, next) {
req.my = {};
// Adding my-db
req.my.db = {};
req.my.db.connection = myDb.getDbConnection();
req.my.db.orders = req.my.db.connection.use(dbOrders);
req.my.db.dbRefData = req.my.db.connection.use(dbRefData);
next();
};
This mechanism works when i manually drive my apis through POSTman as the express server won't start until after the promise from the db connection has been resolved. However when running my automated tests the first few tests are now always failing because the application has not finished initialising with the db before jasmine starts to run my tests against the APIs. I can see in my logs the requests on the coming through and myDb.getDbConnection(); in the middleware returning undefined. I am using supertest and node-jasmine to run my tests. For example
'use strict';
var app = require('../../app');
var request = require('supertest');
describe('GET /api/content', function () {
it('should respond with JSON object', function (done) {
request(app)
.get('/api/content')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) return done(err);
expect(res.body).toEqual(jasmine.any(Object));
done();
});
});
});
So, my question is how can I prevent supertest from making the requests until the server.listen() step has been completed as a result of the myDb.init() call being resolved? OR perhaps there is some kind of jasmine beforeAll that I can use to stop it running the describes until the promise has been resolved?
You could make you app return an EventEmitter which emits a "ready" event when it has completed its initialisation.
Then your test code, in a before clause, can wait until the "ready" event arrives from the app before proceeding with the tests.