I'm having a problem trying to get a service URL discover by eureka.
I'm using eureka-js-client to connect to Eureka and for testing purposes I've created two microservices, I've called it: ms1 and ms2.
What I've tried is:
Start Eureka server to allow services register into it
Start ms1 and register into Eureka
Start ms2, register into Eureka and get ms1 URL.
To accomplish this I've launched eureka server as a Spring Boot app using #EnableEurekaServer. This part works fine, I can access http://localhost:8761/ and see the dashboard.
Then, in my microservices I've this configuration
this._client = new Eureka({
instance: {
app: 'ms1',
instanceId: 'ms1',
hostName: 'localhost',
ipAddr: '127.0.0.1',
statusPageUrl: `http://localhost:${port ? port : this._port}`,
healthCheckUrl: `http://localhost:${port? port : this._port}/health`,
port: {
'$': port? port: this._port,
'#enabled': true,
},
vipAddress: 'myvip',
dataCenterInfo: {
'#class': 'com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo',
name: 'MyOwn',
},
},
eureka: {
host: 'localhost',
port: 8761,
servicePath: '/eureka/apps/'
},
})
And the same for ms2 changing the name.
When I run the project it output registered with eureka: ms1/ms1 and services seems to be registered in eureka correctly:
But now the problem is trying to get the URL of one of the two services. From either of the two services, if I try to get the Eureka instances I always get an empty list.
I have this code:
let instances: any = this.getClient().getInstancesByAppId(microserviceName);
let instance = null;
let url = ''
if (instances != null && instances.length > 0) {
instance = instances[0];
let protocol = instance.securePort["#enabled"] == "true" ? "https" : "http";
url = `${protocol}//${instance.ipAddr}:${instance.port.$}/`
}
Where in "microserviceName" variable I've tried:
"ms1"
"MS1"
"ms1/ms1"
But the response is always an empty array with this output:
Unable to retrieve instances for appId: ms1
So, what's the problem? Have I missed something? I think the flow is correct:
Start Eureka server.
Register services into server.
Look for instances in the server.
Thanks in advance.
Finally I solved my own issue. All was working good, the ms2 was able to find ms1 using the code I posted, so the problem was:
My ms2 file was like this:
EurekaService.getClient().start()
EurekaService.getUrl('ms1')
EurekaService.getClient()?.stop()
And it seems like EurekaService.getClient().start() does not block until it ends (or is available or whatever), so the client is not up and can't get the instance ms1.
Note that the method getUrl() has the code provided in the OP:
let instances: any = this.getClient().getInstancesByAppId(microserviceName);
let instance = null;
...
So I've changed the code like this:
start()
async function start(){
EurekaService.getClient().start()
await new Promise(f => setTimeout(f, 1000));
const url = EurekaService.getUrl('ms1')
console.log("url = ",url)
EurekaService.getClient()?.stop()
}
And works perfectly, the output log is:
registered with eureka: ms2/ms2
url = http//127.0.0.1:8002/
de-registered with eureka: ms2/ms2
So, start method is not async so I can't use await or .then(), I have to set a timeout and wait to complete.
I don't know if there is a better way to do this or by the nature of the architecture can't be controlled when is available.
By the way, for me, 1 second timeout is enough.
Related
I'm using Hapi.
I'm also using NES client to connect to another node instance. In order to access this socket from the routes, I attach it to the server variable like so:
exports = async () => {
//api
var server = new Hapi.Server({
host: process.env.WEB_HOST || '127.0.0.1',
port: process.env.WEB_PORT || '8080'
});
// register plugins
await server.register(plugins);
// add routes
await routes(server);
server.socket = new Socket(identifier); // uses NES
return server;
}
I want to access the socket from a library file. How do I do this without having to pass it the server variable each time? When I try to module.exports the server, it never gives me a reference, only a version of the variable at the time it was exported.
Have you tried server.decorate. If I understood correctly, you want to access your socket variable from routes. Why not just create basic plugin and create your socket instance and pass that into server object. For example;
exports.plugin = {
async register(server, options) {
server.decorate('server', 'socketConn', new Socket(identifier));
},
name: 'socket-connection'
};
and in your routes you can access this variable as request.server.socketConn.
I can't figure out how to retrieve query parameters on the server side for socket.io
1.2.1
Here's my client side code
var socket = io('http://localhost:3000/',{_query:"sid=" + $('#sid').attr('data-sid') + "&serial=" + $('#serial_tracker').text()});
and the server side:
io.use(function(socket,next){ //find out if user is logged in
var handshake = socket.request;
console.log(socket.request._query);
handshake.sid = handshake.query.sid;
}
socket.request._query is:
{ EIO: '3', transport: 'polling', t: '1419909065555-0' }
Does anyone know how query parameters work in socket io 1.2.1?
Thanks for any help and if you need any more information, just ask me.
When sending handshake query data to socket.io, use the following property name in the object:
{
query: 'token=12345'
}
I see above you used _query for a property name instead.
You should be able to access the query information at socket.request._query at that point. I'm not sure if there is a better way to get a hold of that data? I'm guessing yes, since they put an underscore in front of it, but I haven't found a better way yet.
Here's the full example of a connect query that is working for me (forgive the formatting, I'm copy/pasting this out of different node modules into an inline solution).
Server (using socket 1.2.1 nodejs):
var restify = require('restify');
var api = restify.createServer();
var socketio = require('socket.io');
var io = socketio.listen(api.server); // api is an instance of restify, listening on localhost:3000
io.use(function(socket, next) {
// socket.request._query.token is accessible here, for me, and will be '12345'
next();
});
api.listen(3000, function() {
console.log('%s listening at %s', api.name, api.url);
});
Client (chrome browser using the client library located at https://cdn.socket.io/socket.io-1.2.1.js):
var socket = io.connect('http://localhost:3000/', { query: 'token=12345' });
I'm seeing strange behavior when trying to add pdf file generation.
The following code, on the if statement, throws:
both\routes.js
Router.onBeforeAction(function () { if (!Meteor.user() || Meteor.loggingIn()) {
this.redirect('welcome.view'); } else {
Meteor.call("userFileDirectory", function (error, result) {
if (error)
throw error;
else
console.log(result);
});
this.next(); } }, { except: ['welcome.view'] });
Error: Meteor.userId can only be invoked in method calls. Use
this.userId in publish functions. at Object.Meteor.userId
(packages/accounts-base/accounts_server.js:19:1) at Object.Meteor.user
(packages/accounts-base/accounts_server.js:24:1) at [object
Object].Router.onBeforeAction.except
(app/both/3-router/routes.js:10:15) at
packages/iron:router/lib/router.js:277:1 at [object
Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1)
at [object Object].hookWithOptions
(packages/iron:router/lib/router.js:276:1) at boundNext
(packages/iron:middleware-stack/lib/middleware_stack.js:251:1) at
runWithEnvironment (packages/meteor/dynamics_nodejs.js:108:1) at
packages/meteor/dynamics_nodejs.js:121:1 at [object Object].dispatch
(packages/iron:middleware-stack/lib/middleware_stack.js:275:1)
Only when I add this code into the file, and the /pdf route is taken:
Router.route('/pdf', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {
where: 'server'
});
The above code works fine; the pdf is rendered to the screen and no exception is thrown, when I take out the onBeforeAction code.
The opposite is also true, if I take out the server route, there is no route that causes an exception.
This occurs because the route you're using is a server side route. The technique Meteor uses to authenticate a user is done via the DDP protocol, over websockets.
When your browser makes a GET/POST request to the server it doesn't have any information regarding the user's authentication state.
You use Meteor.user() in your Route.onBeforeAction but it has no access to this information.
The solution to this is find an alternative way to authenticate the user. One such method is to use cookie's.
This is known issue with Meteor's authentication system, see: https://github.com/EventedMind/iron-router/issues/649
A better way than cookies could be a named collection of Meteor that stores userId and some sessionId:
You can store current userId on the client side before the call to the server:
var sessionId = Random.id();
col = new Mongo.Collection('session');
col.insert({
sessionId: sid,
userId: Meteor.userId(),
issued: new Date()
});
And then pass sessionId to the server through a GET/POST request and read it on the server:
var sid = this.request.query.sid;
var user = col.findOne({sessionId: sid}); // returns an object
Using a separate parameter is better than using userId itself because you can revoke this sessionId after some time or immediately after the server call.
Proper allow/deny permissions are required to prevent anyone from updating the collection. Also, please note that you can't trust new Date() on the client's side.
I'm trying to access (CRUD) Google Drive from a Firefox extension. Extensions are coded in Javascript, but neither of the two existing javascript SDKs seem to fit; the client-side SDK expects "window" to be available, which isn't the case in extensions, and the server-side SDK seems to rely on Node-specific facilities, as a script that works in node no longer does when I load it in chrome after running it through browserify. Am I stuck using raw REST calls? The Node script that works looks like this:
var google = require('googleapis');
var readlineSync = require('readline-sync');
var CLIENT_ID = '....',
CLIENT_SECRET = '....',
REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob',
SCOPE = 'https://www.googleapis.com/auth/drive.file';
var oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
var url = oauth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: SCOPE // If you only need one scope you can pass it as string
});
var code = readlineSync.question('Auth code? :');
oauth2Client.getToken(code, function(err, tokens) {
console.log('authenticated?');
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
console.log('authenticated');
oauth2Client.setCredentials(tokens);
} else {
console.log('not authenticated');
}
});
I wrap the node GDrive SDK using browserify on this script:
var Google = new function(){
this.api = require('googleapis');
this.clientID = '....';
this.clientSecret = '....';
this.redirectURL = 'urn:ietf:wg:oauth:2.0:oob';
this.scope = 'https://www.googleapis.com/auth/drive.file';
this.client = new this.api.auth.OAuth2(this.clientID, this.clientSecret, this.redirectURL);
}
}
which is then called using after clicking a button (if the text field has no code it launches the browser to get one):
function authorize() {
var code = document.getElementById("code").value.trim();
if (code === '') {
var url = Google.client.generateAuthUrl({access_type: 'offline', scope: Google.scope});
var win = Components.classes['#mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('navigator:browser');
win.gBrowser.selectedTab = win.gBrowser.addTab(url);
} else {
Google.client.getToken(code, function(err, tokens) {
if(!err) {
Google.client.setCredentials(tokens);
// store token
alert('Succesfully authorized');
} else {
alert('Not authorized: ' + err); // always ends here
}
});
}
}
But this yields the error Not authorized: Invalid protocol: https:
It is possible though, depending on the use case, it might also of limited interest.
Firefox ships with a tiny http server, just the bare bones. It is included for test purposes but this is not a reason to overlook it.
Lets follow the quickstart guide for running a Drive app in Javascript
The tricky part is to set the Redirect URIs and the Javascript Origins. Obviously the right setting is http://localhost, but how can you be sure that every user has port 80 available?
You can't and, unless you have control over your users, no port is guaranteed to work for everyone. With this in mind lets choose port 49870 and pray.
So now Redirect URIs and the Javascript Origins are set to http://localhost:49870
Assuming you use Add-on SDK, save the quickstart.html (remember to add your Client ID) in the data directory of your extension. Now edit your main.js
const self = require("sdk/self");
const { Cc, Ci } = require("chrome");
const tabs = require("sdk/tabs");
const httpd = require("sdk/test/httpd");
var quickstart = self.data.load("quickstart.html");
var srv = new httpd.nsHttpServer();
srv.registerPathHandler("/gdrive", function handler(request, response){
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
let converter = Cc["#mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
response.write(converter.ConvertFromUnicode(quickstart));
})
srv.start(49870);
tabs.open("http://localhost:49870/gdrive");
exports.onUnload = function (reason) {
srv.stop(function(){});
};
Notice that quickstart.html is not opened as a local file, with a resource: URI. The Drive API wouldn't like that. It is served at the url http://localhost:49870/gdrive. Needless to say that instead of static html we can use a template or anything else. Also the http://localhost:49870/gdrive can be scripted with a regular PageMod.
I don't consider this a real solution. It's just better than nothing.
From here https://developer.mozilla.org/en/docs/Working_with_windows_in_chrome_code you could try window = window || content || {}
Use the JavaScript client API and not the node.js client. Although browserify will make it work. You will have to expose your client secret in the latter. The flow of client side authentication is very diff than server side. Refer to https://developers.google.com/accounts/docs/OAuth2
Having said all this. Its really not that difficult to implement an app with REST based calls. The methods in all client libraries mimic the corresponding REST URLs. You could set up some functions of your own to handle request and response and the rest would feel the same.
So, i'm on Phonegap, i use :
https://github.com/phonegap-build/PushPlugin/
https://github.com/argon/node-apn
So, i install the first plugin and i can get my phone token. After that, i created a node server.js file in my root directory with :
var apn = require('apn');
var token = "MY TOKEN";
var device = new apn.Device(token);
var notification = new apn.Notification();
notification.expiry = Math.floor(Date.now() / 1000) + 3600;
notification.badge = 1;
notification.alert = "This is a Push Notification=)";
notification.payload = {'prop': 'special value'};
notification.device = device;
var options = {
gateway: 'gateway.sandbox.push.apple.com',
cert: 'CER.pem',
key: 'KEY.pem',
passphrase: 'password'
}
var apnsConnection = new apn.Connection(options);
apnsConnection.pushNotification(notification, device);
When i start my server with node server.js in command line, i can see my push notification on my phone, so all it's ok.
But my question, i need to send push notification in different place in my code (phonegap). How can i do that ?
When my server.js is running, how can i send other push notification from my phonegap application ?
What you have above is code that you can collect together, and expose a function to call it multiple times. For example, a very simple implementation would be:
var apn = require('apn');
var options = {
gateway: 'gateway.sandbox.push.apple.com',
cert: 'CER.pem',
key: 'KEY.pem',
passphrase: 'password'
};
var apnsConnection = new apn.Connection(options);
module.exports.pushNotification = function(token, alert) {
var device = new apn.Device(token);
var notification = new apn.Notification();
notification.alert = alert;
notification.device = device;
apnsConnection.pushNotification(notification, device);
};
Imagine you name this file pns.js for "push notification service". Now in your server.js, you could instead require that module you just created and call the pushNotification function:
var pns = require("./pns.js");
pns.pushNotification("MY TOKEN", "This is a Push Notification");
Now you've got the same function when you execute the server.js. From here, you could instead pull this function into other modules that need to call it from the Node.js side of things.
If you need to call it from a remote process, you could look into a web framework like Express, and build an API which calls the same code. The token and alert message could then be passed in to this function call. Doing this would likely turn your server.js into a running web server which listens for requests and sends push notifications on demand.
A bit late, but for people with the same question, look at this tool:
https://www.npmjs.com/package/node-pushserver
...it does exactly what you want. It supports both iOS and Android.
Run this on a server and your app can: register the device by POSTing to http://yourserver:8000:/subscribe. Devices are stored in a mongodb database. By POSTing a http request to http://yourserver:8000/send, you can send push notifications to a single registered device, a subset or all of them.
Have fun!