calling rest API(locally running) from nodejs failed - java

We created a rest API on python and it is locally running. And the 'http://127.0.0.1:5002/business' API is showing contents {"business name": "something"} if I open it on google chrome. However, when we call this API in nodejs, it always gives me the error. But if I use another API(exactly same code but different api in nodejs), it is working.
async function get_recommend_initial(){
//https://ViolaS.api.stdlib.com/InitialRecommendation#dev/
// // agent.add('providing recommendations...');
const options = {
method: 'GET'
,uri: 'http://127.0.0.1:5002/business'
// ,uri:'https://ViolaS.api.stdlib.com/InitialRecommendation#dev/'
// ,json: true
};
// return request(options).then(response => {
// console.log(response)
// return (response)
// }).catch(function (err) {
// console.log('No recommend data');
// console.log(err);
// });
return requestAPI(options).then(function(data)
{
let initial_recommendation = JSON.parse(data);
console.log(initial_recommendation);
//return initial_recommendation.information[0].name;
}).catch(function (err) {
console.log('No recommend data');
console.log(err);
});
}
1
The API that is created by python file which is running locally. You can see the API code figure by moving your mouth above 1. Thanks!!!
The python code is as follows:
app = Flask(__name__)
#Add resources to be much cleaner
api = Api(app)
features = {}
class Business(Resource):
def get(self):
return {'business name': 'something'} # Fetches first column that is Employee ID
def post(self):
some_json = request.get_json()
print(some_json)
countNumber = features.get('count',0) + 1
features['count'] = countNumber
return {'You sent': some_json,
'Count:':countNumber}, 201
def put(self):
some_json = request.get_json()
print(some_json)
#record the count number
countNumber = features.get('count',0) + 1
features['count'] = countNumber
features['ok'] = 'yes'
return {'You sent': some_json,
'Count:':countNumber,
'Ok:': features['ok']}, 201
api.add_resource(Business, '/business') # Route_1
if __name__ == '__main__':
app.run(port='5002')
The error is as following:
dialogflowFirebaseFulfillment
Error: Unknown response type: "undefined" at WebhookClient.addResponse_ (/srv/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:277:13) at WebhookClient.add (/srv/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:245:12) at Sys_Recommend (/srv/index.js:31:11) at <anonymous>
And the log is:
No recommend data

Related

React jest and MSAL getting BrowserAuthError : crypto

I'm trying to test a few components that are using MSAL for authentication.
Thus far, I have a simple test, which test if my component can render, as follows:
// <MsalInstanceSnippet>
const msalInstance = new PublicClientApplication({
auth: {
clientId: config.appId,
redirectUri: config.redirectUri
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true
}
});
When I run the test, I'm getting the following error:
BrowserAuthError: crypto_nonexistent: The crypto object or function is not available. Detail:Browser crypto or msCrypto object not available.
10 |
11 | // <MsalInstanceSnippet>
> 12 | const msalInstance = new PublicClientApplication({
| ^
13 | auth: {
14 | clientId: config.appId,
15 | redirectUri: config.redirectUri
at BrowserAuthError.AuthError [as constructor] (node_modules/#azure/msal-common/dist/error/AuthError.js:27:24)
at new BrowserAuthError (node_modules/#azure/msal-browser/src/error/BrowserAuthError.ts:152:9)
at Function.Object.<anonymous>.BrowserAuthError.createCryptoNotAvailableError (node_modules/#azure/msal-browser/src/error/BrowserAuthError.ts:172:16)
at new BrowserCrypto (node_modules/#azure/msal-browser/src/crypto/BrowserCrypto.ts:31:36)
at new CryptoOps (node_modules/#azure/msal-browser/src/crypto/CryptoOps.ts:45:30)
at PublicClientApplication.ClientApplication (node_modules/#azure/msal-browser/src/app/ClientApplication.ts:108:58)
at new PublicClientApplication (node_modules/#azure/msal-browser/src/app/PublicClientApplication.ts:49:9)
at Object.<anonymous> (src/App.test.tsx:12:22)
I'm unsure what the above means, but as far as I can understand, this error is occurring because the session is not authenticated.
My question can therefore be divided into the following:
What does this error mean?
How can I solve this error? (Can we bypass MSAL by any chance for testing purposes?)
You need to add crypto to your Jest config in jest.config.js:
module.exports = {
// ...
globals: {
// ...
crypto: require("crypto")
}
};
For eslint issue
try this way
import crypto from 'crypto';
module.exports = {
// ...
globals: {
// ...
crypto,
}
};
I tried adding crypto to my jest.config.js, but it didn't work. Then I tried adding it package.json. It was also pointless giving this error.
Out of the box, Create React App only supports overriding these Jest options:
• clearMocks
• collectCoverageFrom
• coveragePathIgnorePatterns
• coverageReporters
• coverageThreshold
• displayName
• extraGlobals
• globalSetup
• globalTeardown
• moduleNameMapper
• resetMocks
• resetModules
• restoreMocks
• snapshotSerializers
• testMatch
• transform
• transformIgnorePatterns
• watchPathIgnorePatterns.
These options in your package.json Jest configuration are not currently supported by Create React App:
In my case, I have a custom hook that has a dependency with msalInstance
I can prevent the above error by mocking my hook as said here
But still, this wasn't a good solution because if I have many hooks like this. So what I did was mock msalInstance in setupTests.ts file
jest.mock('./msal-instance', () => ({
getActiveAccount: () => ({}),
acquireTokenSilent: () => Promise.resolve({ accessToken: '' }),
}));
This is my msal-instance.ts
import {
PublicClientApplication,
EventType,
EventMessage,
AuthenticationResult,
} from '#azure/msal-browser';
import { msalConfig } from './authConfig';
const msalInstance = new PublicClientApplication(msalConfig);
// Account selection logic is app dependent. Adjust as needed for different use cases.
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
msalInstance.addEventCallback((event: EventMessage) => {
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
const payload = event.payload as AuthenticationResult;
const { account } = payload;
msalInstance.setActiveAccount(account);
}
});
export default msalInstance;
For react version 17.x.x you can install "#wojtekmaj/enzyme-adapter-react-17" package and after that you can create a src/setupTests.js file. You can add all your environment variables and other configurations to this file as follows:
//This is for the issue above
const nodeCrypto = require("crypto");
window.crypto = {
getRandomValues: function (buffer) {
return nodeCrypto.randomFillSync(buffer);
},
};
//It is also possible to add ENV variables
window.ENV = {
ApiURL: {
lessonsUrl: "https://myApiURL.com/APIendpoint",
}
CloudUiUrl: "localhost:3000",
};
When you run your tests #wojtekmaj/enzyme-adapter-react-17 will take the settings in this file automatically.

Android Firebase Firestore real time update with pagination [duplicate]

I am working with Firestore right now and have a little bit of a problem with pagination.
Basically, I have a collection (assume 10 items) where each item has some data and a timestamp.
Now, I am fetching the first 3 items like this:
Firestore.firestore()
.collection("collectionPath")
.order(by: "timestamp", descending: true)
.limit(to: 3)
.addSnapshotListener(snapshotListener())
Inside my snapshot listener, I save the last document from the snapshot, in order to use that as a starting point for my next page.
So, at some time I will request the next page of items like this:
Firestore.firestore()
.collection("collectionPath")
.order(by: "timestamp", descending: true)
.start(afterDocument: lastDocument)
.limit(to: 3)
.addSnapshotListener(snapshotListener2()) // Note that this is a new snapshot listener, I don't know how I could reuse the first one
Now I have the items from index 0 to index 5 (in total 6) in my frontend. Neat!
If the document at index 4 now updates its timestamp to the newest timestamp of the whole collection, things start to go down.
Remember that the timestamp determines its position on account of the order clause!
What I expected to happen was, that after the changes are applied, I still show 6 items (and still ordered by their timestamps)
What happened was, that after the changes are applied, I have only 5 items remaining, since the item that got pushed out of the first snapshot is not added to the second snapshot automatically.
Am I missing something about Pagination with Firestore?
EDIT: As requested, I post some more code here:
This is my function to return a snapshot listener. Well, and the two methods I use to request the first page and then the second page I posted already above
private func snapshotListener() -> FIRQuerySnapshotBlock {
let index = self.index
return { querySnapshot, error in
guard let snap = querySnapshot, error == nil else {
log.error(error)
return
}
// Save the last doc, so we can later use pagination to retrieve further chats
if snap.count == self.limit {
self.lastDoc = snap.documents.last
} else {
self.lastDoc = nil
}
let offset = index * self.limit
snap.documentChanges.forEach() { diff in
switch diff.type {
case .added:
log.debug("added chat at index: \(diff.newIndex), offset: \(offset)")
self.tVHandler.dataManager.insert(item: Chat(dictionary: diff.document.data() as NSDictionary), at: IndexPath(row: Int(diff.newIndex) + offset, section: 0), in: nil)
case .removed:
log.debug("deleted chat at index: \(diff.oldIndex), offset: \(offset)")
self.tVHandler.dataManager.remove(itemAt: IndexPath(row: Int(diff.oldIndex) + offset, section: 0), in: nil)
case .modified:
if diff.oldIndex == diff.newIndex {
log.debug("updated chat at index: \(diff.oldIndex), offset: \(offset)")
self.tVHandler.dataManager.update(item: Chat(dictionary: diff.document.data() as NSDictionary), at: IndexPath(row: Int(diff.oldIndex) + offset, section: 0), in: nil)
} else {
log.debug("moved chat at index: \(diff.oldIndex), offset: \(offset) to index: \(diff.newIndex), offset: \(offset)")
self.tVHandler.dataManager.move(item: Chat(dictionary: diff.document.data() as NSDictionary), from: IndexPath(row: Int(diff.oldIndex) + offset, section: 0), to: IndexPath(row: Int(diff.newIndex) + offset, section: 0), in: nil)
}
}
}
self.tableView?.reloadData()
}
}
So again, I am asking if I can have one snapshot listener that listens for changes in more than one page I requested from Firestore
Well, I contacted the guys over at Firebase Google Group for help, and they were able to tell me that my use case is not yet supported.
Thanks to Kato Richardson for attending to my problem!
For anyone interested in the details, see this thread
I came across the same use case today and I have successfully implemented a working solution in Objective C client. Below is the algorithm if anyone wants to apply in their program and I will really appreciate if google-cloud-firestore team can put my solution on their page.
Use Case: A feature to allow paginating a long list of recent chats along with the option to attach real time listeners to update the list to have chat with most recent message on top.
Solution: This can be made possible by using pagination logic like we do for other long lists and attaching real time listener with limit set to 1:
Step 1: On page load fetch the chats using pagination query as below:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self fetchChats];
}
-(void)fetchChats {
__weak typeof(self) weakSelf = self;
FIRQuery *paginateChatsQuery = [[[self.db collectionWithPath:MAGConstCollectionNameChats]queryOrderedByField:MAGConstFieldNameTimestamp descending:YES]queryLimitedTo:MAGConstPageLimit];
if(self.arrChats.count > 0){
FIRDocumentSnapshot *lastChatDocument = self.arrChats.lastObject;
paginateChatsQuery = [paginateChatsQuery queryStartingAfterDocument:lastChatDocument];
}
[paginateChatsQuery getDocumentsWithCompletion:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (snapshot == nil) {
NSLog(#"Error fetching documents: %#", error);
return;
}
///2. Observe chat updates if not attached
if(weakSelf.chatObserverState == ChatObserverStateNotAttached) {
weakSelf.chatObserverState = ChatObserverStateAttaching;
[weakSelf observeChats];
}
if(snapshot.documents.count < MAGConstPageLimit) {
weakSelf.noMoreData = YES;
}
else {
weakSelf.noMoreData = NO;
}
[weakSelf.arrChats addObjectsFromArray:snapshot.documents];
[weakSelf.tblVuChatsList reloadData];
}];
}
Step 2: On success callback of "fetchAlerts" method attach the observer for real time updates only once with limit set to 1.
-(void)observeChats {
__weak typeof(self) weakSelf = self;
self.chatsListener = [[[[self.db collectionWithPath:MAGConstCollectionNameChats]queryOrderedByField:MAGConstFieldNameTimestamp descending:YES]queryLimitedTo:1]addSnapshotListener:^(FIRQuerySnapshot * _Nullable snapshot, NSError * _Nullable error) {
if (snapshot == nil) {
NSLog(#"Error fetching documents: %#", error);
return;
}
if(weakSelf.chatObserverState == ChatObserverStateAttaching) {
weakSelf.chatObserverState = ChatObserverStateAttached;
}
for (FIRDocumentChange *diff in snapshot.documentChanges) {
if (diff.type == FIRDocumentChangeTypeAdded) {
///New chat added
NSLog(#"Added chat: %#", diff.document.data);
FIRDocumentSnapshot *chatDoc = diff.document;
[weakSelf handleChatUpdates:chatDoc];
}
else if (diff.type == FIRDocumentChangeTypeModified) {
NSLog(#"Modified chat: %#", diff.document.data);
FIRDocumentSnapshot *chatDoc = diff.document;
[weakSelf handleChatUpdates:chatDoc];
}
else if (diff.type == FIRDocumentChangeTypeRemoved) {
NSLog(#"Removed chat: %#", diff.document.data);
}
}
}];
}
Step 3. On listener callback check for document changes and handle only FIRDocumentChangeTypeAdded and FIRDocumentChangeTypeModified events and ignore the FIRDocumentChangeTypeRemoved event. We are doing this by calling "handleChatUpdates" method for both FIRDocumentChangeTypeAdded and FIRDocumentChangeTypeModified event in which we are first trying to find the matching chat document from local list and if it exist we are removing it from the list and then we are adding the new document received from listener callback and adding it to the beginning of the list.
-(void)handleChatUpdates:(FIRDocumentSnapshot *)chatDoc {
NSInteger chatIndex = [self getIndexOfMatchingChatDoc:chatDoc];
if(chatIndex != NSNotFound) {
///Remove this object
[self.arrChats removeObjectAtIndex:chatIndex];
}
///Insert this chat object at the beginning of the array
[self.arrChats insertObject:chatDoc atIndex:0];
///Refresh the tableview
[self.tblVuChatsList reloadData];
}
-(NSInteger)getIndexOfMatchingChatDoc:(FIRDocumentSnapshot *)chatDoc {
NSInteger chatIndex = 0;
for (FIRDocumentSnapshot *chatDocument in self.arrChats) {
if([chatDocument.documentID isEqualToString:chatDoc.documentID]) {
return chatIndex;
}
chatIndex++;
}
return NSNotFound;
}
Step 4. Reload the tableview to see the changes.
my solution is to create 1 maintainer query - listener to observe on those removed item from first query, and we will update it every time there's new message coming.
To make pagination with snapshot listener first we have to create reference point document from the collection.After that we are listening to collection based on that reference point document.
Let's you have a collection called messages and timestamp called createdAt with each document in that collection.
//get messages
getMessages(){
//first we will fetch the very last/latest document.
//to hold listeners
listnerArray=[];
const very_last_document= await this.afs.collectons('messages')
.ref
.limit(1)
.orderBy('createdAt','desc')
.get({ source: 'server' });
//if very_last.document.empty property become true,which means there is no messages
//present till now ,we can go with a query without having a limit
//else we have to apply the limit
if (!very_last_document.empty) {
const start = very_last_document.docs[very_last_document.docs.length - 1].data().createdAt;
//listner for new messages
//all new message will be registered on this listener
const listner_1 = this.afs.collectons('messages')
.ref
.orderBy('createdAt','desc')
.endAt(start) <== this will make sure the query will fetch up to 'start' point(including 'start' point document)
.onSnapshot(messages => {
for (const message of messages .docChanges()) {
if (message .type === "added")
//do the job...
if (message.type === "modified")
//do the job...
if (message.type === "removed")
//do the job ....
}
},
err => {
//on error
})
//old message will be registered on this listener
const listner_2 = this.afs.collectons('messages')
.ref
.orderBy('createdAt','desc')
.limit(20)
.startAfter(start) <== this will make sure the query will fetch after the 'start' point
.onSnapshot(messages => {
for (const message of messages .docChanges()) {
if (message .type === "added")
//do the job...
if (message.type === "modified")
//do the job...
if (message.type === "removed")
//do the job ....
}
this.listenerArray.push(listner_1, listner_2);
},
err => {
//on error
})
} else {
//no document found!
//very_last_document.empty = true
const listner_1 = this.afs.collectons('messages')
.ref
.orderBy('createdAt','desc')
.onSnapshot(messages => {
for (const message of messages .docChanges()) {
if (message .type === "added")
//do the job...
if (message.type === "modified")
//do the job...
if (message.type === "removed")
//do the job ....
}
},
err => {
//on error
})
this.listenerArray.push(listner_1);
}
}
//to load more messages
LoadMoreMessage(){
//Assuming messages array holding the the message we have fetched
//getting the last element from the array messages.
//that will be the starting point of our next batch
const endAt = this.messages[this.messages.length-1].createdAt
const listner_2 = this.getService
.collections('messages')
.ref
.limit(20)
.orderBy('createdAt', "asc") <== should be in 'asc' order
.endBefore(endAt) <== Getting the 20 documnents (the limit we have applied) from the point 'endAt';
.onSnapshot(messages => {
if (messages.empty && this.messages.length)
this.messages[this.messages.length - 1].hasMore = false;
for (const message of messages.docChanges()) {
if (message.type === "added")
//do the job...
if (message.type === "modified")
//do the job
if (message.type === "removed")
//do the job
}
},
err => {
//on error
})
this.listenerArray.push(listner_2)
}

How to implement simple retry using AsyncHttpClient and scala

Im using https://github.com/AsyncHttpClient/async-http-client this library in my scala project, and im performing some http calls with it, but now on some http calls I need to retry a call if I dont get my expected result for 3 times.
How should I implement something like this?
thaknks
This is an example of retry function based on Future.recoverWith
If you run it you can see that it prints "run process" until the Future is successful but not more than 'times' times
object X extends App{
type Request = String
type Response = String
import scala.concurrent.ExecutionContext.Implicits.global
def retry(request: Request, process: Request => Future[Response], times: Int): Future[Response] ={
val responseF = process(request)
if(times > 0)
responseF.recoverWith{
case ex => println("fail")
retry(request, process, times - 1)
}
else
responseF
}
def process(s: Request): Future[Response] = {
println("run process")
if(Random.nextBoolean()) Future.successful("good") else Future.failed(new Exception)
}
val result = retry("", process, 3)
import scala.concurrent.duration._
println(Await.result(result, 1.second))
}

HTTP Node.js Java API

I am creating a Node.js Java backend. The Node.js middleware receives HTTP requests from an Android application and then relays it to the Java code. The reason for choosing this technologies is to create a highly scalable backend from scratch.
I want the Node.js api to receive the HTTP requests, pass it to the Java-side of the backend, the Java code does its calculations, sends back the result to the Node.js API and then finishes the process by sending the result back to the Android application.
I can receive and parse HTTP requests:
var BodyParser = require('body-parser');
var Express = require('express');
var JavaClient = require('./NodeJavaBridge.js');
var JavaClientInstance = new JavaClient();
var app = Express();
///// Receive message logic \\\\\
app.use(BodyParser.json());
app.post('/', function (request, response)
{
var task = request.body;
response.writeHead(200, { 'content-type': 'text/plain' });
var otherObject = { SomeData: 1234 };
var json = JSON.stringify({
data: otherObject
});
response.end(json);
});
console.log("START --> Java Client Instance");
JavaClientInstance.run();
app.listen(8080); //to port on which the express server listen
console.log("Server listening on: " + 8080);
I can also send and receive data between Node.js and Java:
var Util = require('util');
var EventEmitter = require('events').EventEmitter;
var ChildProc = require('child_process');
var JavaClient = function () {
var _self = this;
// The child process object we get when we spawn the java process
var _javaSpawn = null;
// buffer for receiving messages in part and piecing them together later
var _receiveBuffer = null;
// The location of java and the - we're making these public because maybe
// we want to change them in the user of this module.
_self.javaPath = 'java';
_self.jarPath = 'C:/Dev/Backend_Java.jar';
_self.verbose = true;
// list of events emitted - for informational purposes
_self.events = [
'spawn', 'message', 'exception', 'unknown', 'sent', 'java_error',
// Response messages that then become events themselves
'Error', 'Hello', 'Info'
];
/**
* Attach our own event handler to reply to the hello message.
* This is just a convenience part of the protocol so that clients don't have to do it.
* Also connects if connection data was supplied.
*/
_self.on('Hello', function () {
_self.sendHello();
});
/**
* Executes the java process to begin sending and receiving communication
*/
_self.run = function () {
// Invoke the process
_javaSpawn = ChildProc.spawn(_self.javaPath, ['-jar', _self.jarPath]);
// Wire up events
_javaSpawn.stdout.on('data', onData);
_javaSpawn.stderr.on('data', onJavaError);
_javaSpawn.on('exit', function (code) {
console.log("The java program exited with code " + code + ".");
});
// Emit our own event to indicate to others that we have spawned
_self.emit('spawn', _javaSpawn);
}
// sends the hello request message
_self.sendHello = function () {
sendMessage(
{
messageName : 'Hello',
version : '1.1'
});
}
// sends a message that will be echoed back as an Info message
_self.sendEcho = function (message) {
sendMessage(
{
messageName : "Echo",
message : message
});
}
// sends a message telling the java app to exit
_self.sendGoodbye = function () {
sendMessage(
{
"messageName" : "Goodbye"
});
}
/**
* Sends a message object as a JSON encoded string to the java application for processing.
*/
function sendMessage(aMsg)
{
// convert to json and prepare buffer
var aJsonString = JSON.stringify(aMsg);
var lByteLength = Buffer.byteLength(aJsonString);
var lMsgBuffer = new Buffer(4 + lByteLength);
// Write 4-byte length, followed by json, to buffer
lMsgBuffer.writeUInt32BE(lByteLength, 0);
lMsgBuffer.write(aJsonString, 4, aJsonString.length, 'utf8');
// send buffer to standard input on the java application
_javaSpawn.stdin.write(lMsgBuffer);
_self.emit('sent', aMsg);
}
/**
* Receive data over standard input
*/
function onData(data)
{
// Attach or extend receive buffer
_receiveBuffer = (null == _receiveBuffer) ? data : Buffer.concat([_receiveBuffer, data]);
// Pop all messages until the buffer is exhausted
while (null != _receiveBuffer && _receiveBuffer.length > 3)
{
var size = _receiveBuffer.readInt32BE(0);
// Early exit processing if we don't have enough data yet
if ((size + 4) > _receiveBuffer.length)
{
break;
}
// Pull out the message
var json = _receiveBuffer.toString('utf8', 4, (size + 4));
// Resize the receive buffer
_receiveBuffer = ((size + 4) == _receiveBuffer.length) ? null : _receiveBuffer.slice((size + 4));
// Parse the message as a JSON object
try
{
var msgObj = JSON.parse(json);
// emit the generic message received event
_self.emit('message', msgObj);
// emit an object-type specific event
if ((typeof msgObj.messageName) == 'undefined')
{
_self.emit('unknown', msgObj);
}
else
{
_self.emit(msgObj.messageName, msgObj);
}
}
catch (ex)
{
_self.emit('exception', ex);
}
}
}
/**
* Receive error output from the java process
*/
function onJavaError(data)
{
_self.emit('java_error', data.toString());
}
}
// Make our JavaClient class an EventEmitter
Util.inherits(JavaClient, EventEmitter);
// export our class
module.exports = JavaClient;
My problem: How do I let the POST request send a request to my JavaClient instance, wait for a response and then send it back to origin (Android app).
Here is an example of how I am trying to get the logic working:
var client = require('./JavaClient');
var instance = new client();
instance.on('message', function(msg) {
console.log('Received a message...');
console.log(msg);
});
instance.on('sent', function(msg) {
console.log('Sent a message...');
console.log(msg);
});
instance.on('Info', function(msg) {
console.log("Received info");
console.log(msg.message);
});
(function() {
// Start it up (Hello exchanges happen)
instance.run();
// Receive acknowledgement of hello
instance.once('Info', function() {
// Try echoing something
instance.sendEcho("ECHO!");
});
})();
If I should make something more clear please let me know (it's really late and I assume that my writing capabilities is taking a dive). I would appreciate any answer/suggestion/thisisabadidea type of comments.
Thanks!
var Util = require('util');
var EventEmitter = require('events').EventEmitter;
var ChildProc = require('child_process');
var JavaClient = function () {
var _self = this;
// The child process object we get when we spawn the java process
var _javaSpawn = null;
// buffer for receiving messages in part and piecing them together later
var _receiveBuffer = null;
// The location of java and the - we're making these public because maybe
// we want to change them in the user of this module.
_self.javaPath = 'java';
_self.jarPath = 'C:/Dev/Backend_Java.jar';
_self.verbose = true;
// list of events emitted - for informational purposes
_self.events = [
'spawn', 'message', 'exception', 'unknown', 'sent', 'java_error',
// Response messages that then become events themselves
'Error', 'Hello', 'Info'
];
/**
* Attach our own event handler to reply to the hello message.
* This is just a convenience part of the protocol so that clients don't have to do it.
* Also connects if connection data was supplied.
*/
_self.on('Hello', function () {
_self.sendHello();
});
/**
* Executes the java process to begin sending and receiving communication
*/
_self.run = function () {
// Invoke the process
_javaSpawn = ChildProc.spawn(_self.javaPath, ['-jar', _self.jarPath]);
// Wire up events
_javaSpawn.stdout.on('data', onData);
_javaSpawn.stderr.on('data', onJavaError);
_javaSpawn.on('exit', function (code) {
console.log("The java program exited with code " + code + ".");
});
// Emit our own event to indicate to others that we have spawned
_self.emit('spawn', _javaSpawn);
}
// sends the hello request message
_self.sendHello = function () {
sendMessage(
{
messageName : 'Hello',
version : '1.1'
});
}
// sends a message that will be echoed back as an Info message
_self.sendEcho = function (message) {
sendMessage(
{
messageName : "Echo",
message : message
});
}
// sends a message telling the java app to exit
_self.sendGoodbye = function () {
sendMessage(
{
"messageName" : "Goodbye"
});
}
/**
* Sends a message object as a JSON encoded string to the java application for processing.
*/
function sendMessage(aMsg)
{
// convert to json and prepare buffer
var aJsonString = JSON.stringify(aMsg);
var lByteLength = Buffer.byteLength(aJsonString);
var lMsgBuffer = new Buffer(4 + lByteLength);
// Write 4-byte length, followed by json, to buffer
lMsgBuffer.writeUInt32BE(lByteLength, 0);
lMsgBuffer.write(aJsonString, 4, aJsonString.length, 'utf8');
// send buffer to standard input on the java application
_javaSpawn.stdin.write(lMsgBuffer);
_self.emit('sent', aMsg);
}
/**
* Receive data over standard input
*/
function onData(data)
{
// Attach or extend receive buffer
_receiveBuffer = (null == _receiveBuffer) ? data : Buffer.concat([_receiveBuffer, data]);
// Pop all messages until the buffer is exhausted
while (null != _receiveBuffer && _receiveBuffer.length > 3)
{
var size = _receiveBuffer.readInt32BE(0);
// Early exit processing if we don't have enough data yet
if ((size + 4) > _receiveBuffer.length)
{
break;
}
// Pull out the message
var json = _receiveBuffer.toString('utf8', 4, (size + 4));
// Resize the receive buffer
_receiveBuffer = ((size + 4) == _receiveBuffer.length) ? null : _receiveBuffer.slice((size + 4));
// Parse the message as a JSON object
try
{
var msgObj = JSON.parse(json);
// emit the generic message received event
_self.emit('message', msgObj);
// emit an object-type specific event
if ((typeof msgObj.messageName) == 'undefined')
{
_self.emit('unknown', msgObj);
}
else
{
_self.emit(msgObj.messageName, msgObj);
}
}
catch (ex)
{
_self.emit('exception', ex);
}
}
}
/**
* Receive error output from the java process
*/
function onJavaError(data)
{
_self.emit('java_error', data.toString());
}
}
// Make our JavaClient class an EventEmitter
Util.inherits(JavaClient, EventEmitter);
// export our class
module.exports = JavaClient;
var client = require('./JavaClient');
var instance = new client();
instance.on('message', function(msg) {
console.log('Received a message...');
console.log(msg);
});
instance.on('sent', function(msg) {
console.log('Sent a message...');
console.log(msg);
});
instance.on('Info', function(msg) {
console.log("Received info");
console.log(msg.message);
});
(function() {
// Start it up (Hello exchanges happen)
instance.run();
// Receive acknowledgement of hello
instance.once('Info', function() {
// Try echoing something
instance.sendEcho("ECHO!");
});
})();

DJ Native Swing javascript command problems

Using DJ Native Swing it is possible to show a web page within a java application. When you do this it is also possible to communicate from the browser to the java runtime environment using the "command" protocol. The documentation has a code snippet which demonstrates it's usage:
function sendCommand( command ){
var s = 'command://' + encodeURIComponent( command );
for( var i = 1; i < arguments.length; s+= '&' + encodeURIComponent( arguments[i++] ) );
window.location = s;
}
As it looks here it seems to be a regular GET request to an url using the command protocol instead of http. Although when I create and image, script tag or just and ajax get request there is no response and the breakpoint in the java runtime isn't triggered.
I don't want to set the window.location because I don't want to navigate away from the page I am currently at. Using the link to navigate to a command url does work though but it also navigates away from the current page. The page uses OpenLayers and dojo. (I have also tried dojo.io.script)
After some work I have found a neat way to communicate with the java runtime which doesn't trigger a refresh of the page every time there is communication. It is inspired on the way JSONP works to get around the cross domain restriction in most browsers these days. Because an iFrame will also trigger a command:// url it possible to do a JSONP like action using this technique. The code on the client side (browser):
dojo.provide( "nmpo.io.java" );
dojo.require( "dojo.io.script" );
nmpo.io.java = dojo.delegate( dojo.io.script, {
attach: function(/*String*/id, /*String*/url, /*Document?*/frameDocument){
// summary:
// creates a new tag pointing to the specified URL and
// adds it to the document.
// description:
// Attaches the script element to the DOM. Use this method if you
// just want to attach a script to the DOM and do not care when or
// if it loads.
var frame = dojo.create( "iframe", {
id: id,
frameborder: 0,
framespacing: 0
}, dojo.body( ) );
dojo.style( frame, { display: "none" } );
dojo.attr( frame, { src: url } );
return frame;
},
_makeScriptDeferred: function(/*Object*/args){
//summary:
// sets up a Deferred object for an IO request.
var dfd = dojo._ioSetArgs(args, this._deferredCancel, this._deferredOk, this._deferredError);
var ioArgs = dfd.ioArgs;
ioArgs.id = dojo._scopeName + "IoScript" + (this._counter++);
ioArgs.canDelete = false;
//Special setup for jsonp case
ioArgs.jsonp = args.callbackParamName || args.jsonp;
if(ioArgs.jsonp){
//Add the jsonp parameter.
ioArgs.query = ioArgs.query || "";
if(ioArgs.query.length > 0){
ioArgs.query += "&";
}
ioArgs.query += ioArgs.jsonp
+ "="
+ (args.frameDoc ? "parent." : "")
+ "nmpo.io.java.jsonp_" + ioArgs.id + "._jsonpCallback";
ioArgs.frameDoc = args.frameDoc;
//Setup the Deferred to have the jsonp callback.
ioArgs.canDelete = true;
dfd._jsonpCallback = this._jsonpCallback;
this["jsonp_" + ioArgs.id] = dfd;
}
return dfd; // dojo.Deferred
}
});
When a request is sent to the java runtime a callback argument will be supplied and a webBrowser.executeJavascript( callbackName + "(" + json + ");" ); action can be executed to trigger the callback in the browser.
Usage example client:
dojo.require( "nmpo.io.java" );
nmpo.io.java.get({
// For some reason the first paramater (the one after the '?') is never in the
// paramater array in the java runtime. As a work around we stick in a dummy.
url: "command://sum?_",
callbackParamName: "callback",
content: {
numbers: [ 1, 2, 3, 4, 5 ].join( "," )
},
load: function( result ){
console.log( "A result was returned, the sum was [ " + result.result + " ]" );
}
});
Usage example java:
webBrowser.addWebBrowserListener(new WebBrowserAdapter() {
#Override
public void commandReceived(WebBrowserCommandEvent e) {
// Check if you have the right command here, left out for the example
// Parse the paramaters into a Hashtable or something, also left out for the example
int sum = 0;
for( String number : arguments.get( "numbers" ).split( "," ) ){
sum += Integer.parseInt( number );
}
// Execute the javascript callback like would happen with a regular JSONP call.
webBrowser.executeJavascript( arguments.get( "callback" ) + "({ result: " + sum + " });" );
}
});
Also with IE in the frame I can highly recommend using firebug lite, the dev tools for IE are not available.

Categories