I am trying implement websockets using spring with a java/web application to allow it to exchangte messages with a application written with c++ using qt (and the websockets library from it).
I have in my java/spring application this configuration:
WebScoketConfig.java
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketHandler(), "/name");
}
}
SocketHandler.java
#Component
public class SocketHandler extends TextWebSocketHandler {
List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException {
Map<String, String> value = new Gson().fromJson(message.getPayload(), Map.class);
session.sendMessage(new TextMessage("Hello " + value.get("name") + " !"));
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
}
and I created a very simple qt-creator project, with a main function and one class MainWindow, with two objects: a lineEdit, where the user types a message to send to server, and a pushButton, to proceed with the data send.
In my MainWindow class, I implement this slot to handle the data exchange:
void MainWindow::on_pushButton_clicked()
{
QString message = this->ui->lineEdit->text();
QWebSocket m_webSocket;
m_webSocket.open(QUrl(QStringLiteral("ws://localhost:8080/name")));
m_webSocket.sendTextMessage("Hello " + message + " !");
m_webSocket.close();
}
But when I execute both applications, and try send a message for the java/web application, nothing happens. I pretty sure the mistake I made it's on the c++/qt side, since in the java/spring side I have a html/javascript code which allow me testing the message exchage, nd works fine.
Anyone can tell me what I am doing wrong here?
update: minimal reproducible example - java/spring
the project can be generated with start.spring.io, only with spring-websocket as dependency. besides the 2 files I already add above, the project will have:
resources/static/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello WebSocket</title>
<link href="/main.css" rel="stylesheet">
</head>
<body>
<table>
<tr>
<td>
<button id="connect" type="button" onclick="connect();">Connect</button>
<button id="disconnect" type="button" disabled="disabled" onclick="disconnect();">Disconnect</button>
</td>
<td>
<label for="name">What is your name?</label>
<input type="text" id="name" placeholder="Your name here...">
<button id="send" type="button" onclick="send();">Send</button>
</td>
</tr>
</table>
<hr>
<table id="conversation" border="2">
<thead>
<tr>
<th>Greetings</th>
</tr>
</thead>
<tbody id="greetings">
</tbody>
</table>
<script src="/app.js"></script>
</body>
</html>
resources/app.js
var ws;
function connect() {
ws = new WebSocket('ws://localhost:8080/name');
ws.onmessage = function(text) {
var tr = document.createElement("tr");
var td = document.createElement("td");
td.innerText = text.data;
tr.appendChild(td);
document.querySelector("#greetings").appendChild(tr);
}
document.querySelector("#connect").setAttribute("disabled", "disabled");
document.querySelector("#disconnect").removeAttribute("disabled");
document.querySelector("#conversation").style.display = 'block';
document.querySelector("#greetings").innerHTML = "";
}
function disconnect() {
if (ws != null)
ws.close();
document.querySelector("#connect").removeAttribute("disabled");
document.querySelector("#disconnect").setAttribute("disabled", "disabled");
document.querySelector("#conversation").style.display = 'none';
document.querySelector("#greetings").innerHTML = "";
}
function send() {
var name = document.querySelector("#name");
var data = JSON.stringify({'name': name.value});
ws.send(data);
}
after build with mvn package, just run with java -jar target/app.jar.
update: minimal reproducible example - c++/qt
project is created with qt-creator, as type qt-widget. It will create a project with 5 files: websocket.pro, mainwindow.ui, mainwindow.h, mainwindow.cpp and main.cpp.
Open mainwindow.ui and add a lineEdit and pushButton from toolbar. right-click on the pushButton and choose Go to slot and select clicked(). Add the code above.
Update 2
void MainWindow::on_pushButton_clicked()
{
QString message = ui->lineEdit->text();
connect(&m_webSocket, &QWebSocket::connected, [this, message](){
QJsonObject object
{
{"name", message}
};
QJsonDocument d(object);
m_webSocket.sendTextMessage(d.toJson().toStdString().c_str());
m_webSocket.close();
});
m_webSocket.open(QUrl(QStringLiteral("ws://localhost:8080/name")));
}
The problem is that you are trying to send the text without verifying that the connection is successful. The solution is to use the connected signal, in addition to making m_webSocket a member of the class as advised in the comments:
*.h
private:
Ui::MainWindow *ui;
QWebSocket m_webSocket;
*.cpp
void MainWindow::on_pushButton_clicked()
{
QString message = ui->lineEdit->text();
connect(&m_webSocket, &QWebSocket::connected, [this, message](){
m_webSocket.sendTextMessage("Hello " + message + " !");
m_webSocket.close();
});
m_webSocket.open(QUrl(QStringLiteral("ws://localhost:8080/name")));
}
Update:
In your project I have noticed the following errors:
For some reason when I tested using Google Chrome I could not connect so I added registry.addHandler(new SocketHandler(), "/name").setAllowedOrigins("*"); to the configuration.
The variable "session" only handles sending data to a socket, if you want to send that information to all sockets (js and qt) then you must iterate.
When a session is disconnected do not remove it from "sessions" which can cause errors. You must remove session in the afterConnectionClosed method.
In your code you are calling to connect to the server in the slot associated with the connected signal which is silly since that slot is called after the connection and for this you should first call the open method. Anyway opening the connection, waiting for the connection to be established, sending the message and closing the connection is not a good idea, it is better to open the connection before sending the message and close it when necessary (for example when closing the GUI or the user wants to close it as it does in js since the sending of information is not instantaneous but is asynchronous).
The complete code is here.
Related
I am trying to use Asynchronous Servlets for Web Push Notifications using this example here on oracle page
The code mentioned in the example on this page uses post request to add new data and then consistently send a request using get to check for new data and then display it on all browser instances.
In my case, I would be getting data from the database so I thought to create a simple counter to mock the new data on the server itself and as the counter increases, display those in all browser instances
Like : 1 2 3 4 5 6 7 8 9
but this is not working, what am I mistaking?
Servlet
#WebServlet(urlPatterns = {"/shoutServlet"}, asyncSupported=true)
public class ShoutServlet extends HttpServlet {
private List<AsyncContext> contexts = new LinkedList<>();
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
final AsyncContext asyncContext = request.startAsync(request, response);
asyncContext.setTimeout(10 * 60 * 1000);
contexts.add(asyncContext);
}
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List<AsyncContext> asyncContexts = new ArrayList<>(this.contexts);
this.contexts.clear();
int counter=10;
int i =0;
while(i<counter) {
ServletContext sc = request.getServletContext();
if (sc.getAttribute("messages") == null) {
sc.setAttribute("messages", i);
} else {
String currentMessages = (String) sc.getAttribute("i");
sc.setAttribute("messages", i + currentMessages);
}
for (AsyncContext asyncContext : asyncContexts) {
try (PrintWriter writer = asyncContext.getResponse().getWriter()) {
writer.println(i);
writer.flush();
asyncContext.complete();
} catch (Exception ex) {
}
}
i++;
}
}
}
JSP
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>SHOUT-OUT!</h1>
<form method="POST" action="shoutServlet">
<table>
<tr>
<td>Your name:</td>
<td><input type="text" id="name" name="name"/></td>
</tr>
<tr>
<td>Your shout:</td>
<td><input type="text" id="message" name="message" /></td>
</tr>
<tr>
<td><input type="submit" value="SHOUT" /></td>
</tr>
</table>
</form>
<h2> Current Shouts </h2>
<div id="content">
<% if (application.getAttribute("messages") != null) {%>
<%= application.getAttribute("messages")%>
<% }%>
</div>
<script>
var messagesWaiting = false;
function getMessages(){
if(!messagesWaiting){
messagesWaiting = true;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
messagesWaiting = false;
var contentElement = document.getElementById("content");
contentElement.innerHTML = xmlhttp.responseText + contentElement.innerHTML;
}
}
xmlhttp.open("GET", "shoutServlet?t="+new Date(), true);
xmlhttp.send();
}
}
setInterval(getMessages, 1000);
</script>
</body>
</html>
It seems to me that you have not entirely understood how async servlets work.
The idea is that you release the web thread (usually on the http thread pool, the size of which can be configured on the application server) and then finish the work of the request on some other thread. At the end, you have to call AsyncContext.complete() to actually finish the request and return the response to the client (which is still waiting).
Note that the whole flow will not be any faster (actually it will be a tiny bit slower) than the normal, synchronous handling. The benefit of this is that you can release a HTTP thread sooner, so that it can handle other HTTP requests. Normally the HTTP thread is blocked for the entire time that your doGet/Post method is running, and if you do extensive processing and/or I/O there, this may fill up the HTTP thread pool, and when that happends, your application will not be able to handle any more HTTP requests. (Or, more presicely, a client connection will hang until one of the HTTP threads is available again to handle the request.)
In your code, however, I can't see where you're calling AsyncContext.complete() or finishing the doGet() logic, like return any data to the client. There may be other problems as well, but this is the most obvious reason it's not working.
Other than that, I think you have a typo in doPost() where you're doing sc.getAttribute("i");. The attribute i is not set anywhere, so this will always return null, which will cause a NPE in the next line i + currentMessages.
Also, just in case you expect that this will sum two numbers together - it will not. It will append them, since currentMessages is a String, and the + on a String does a concatenation, no mather what type the other operand is.
I have a script thats using papa parse to check for an entry in a CSV file, then redirecting based on if its there or not. It works perfectly fine in chrome on my desktop, has a few issues on firefox on my desktop, and completly doesnt work on my chrome browser on my android.
<body>
<form id="usrform">
<td><label>Unique ID</label></td>
<tr>
<td colspan="2"><input class="textBox" id="uniqueID" type="text" maxlength="30" required/></td>
</tr>
</form>
<button onclick="processClick()">Proceed</button>
</body>
<!-- Jquery import -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<!-- Papa Parse CSV -->
<script src="http://localhost/client/js/papaparse.min.js"></script>
<div id="loading" style="background:url(/images/loading.gif) no-repeat center center;width:20px;height:20px; visibility:hidden">
<img src="/images/loading.gif">
</div>
<script>
// hide loading icon
document.getElementById("loading").style.visibility = "hidden";
function processClick()
{
document.getElementById("loading").style.visibility = "visible";
if (document.getElementById("uniqueID").value == '' )
{
alert("Please fill in the uniqueID field");
document.getElementById("loading").style.visibility = "hidden";
}
else
{
parseData(**client site**/csv/csv.csv", searchArray);
}
}
function parseData(url, callBack) {
Papa.parse(url, {
download: true,
dynamicTyping: true,
complete: function(results) {
alert("parsed ready to callback");
//callBack(results.data);
}
});
}
function searchArray(data) {
//Data is usable here
console.log(" searching array");
for (a = 0; a < data.length; a++)
{
if (data[a][1] == document.getElementById('uniqueID').value)
{
// redirect
var target = "**clientsite**" + document.getElementById('uniqueID').value + ".html";
window.location.assign(target);
break;
}
else
{
console.log(" redirecting on fail");
// redirect to failure page
}
}
}
</script>
I used alerts to see where it stopped working on mobile, and it appears that the function parseData(url, callBack) { is not returning a value(whether its processing or not i cannot tell).
This works perfectly on chrome/desktop, which is the confusing part!
I imagine im missing something stupid here.
There was my error i didnt catch when uploading from testing on my local server. It was working as it could see my localhost file, but it wouldnt for anyone else!
I am trying to implement HTML5 web socket using Netbeans and Apache server..
the implementation is working fine in my laptop but when i am trying to access the same program on other machines connected in LAN using IP address ...i get the error "connection closed" in other machine.
here is my index.html file :
<!DOCTYPE html>
<html>
<head>
<title>Echo Chamber</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<center>
<div>
<input type="text" id="messageinput"/>
</div>
<br>
<div>
<button type="button" onclick="openSocket();" >Open</button>
<button type="button" onclick="send();" >Send</button>
<button type="button" onclick="closeSocket();" >Close</button>
</div>
<br>
<hr>
<br>
<!-- Server responses get written here -->
<div id="messages"></div>
</center>
<!-- Script to utilise the WebSocket -->
<script type="text/javascript">
var webSocket;
var messages = document.getElementById("messages");
function openSocket(){
// Ensures only one connection is open at a time
if(webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED){
writeResponse("WebSocket is already opened.");
return;
}
// Create a new instance of the websocket
webSocket = new WebSocket("ws://localhost:8080/HTML_5_Socket_Appache/echo");
/**
* Binds functions to the listeners for the websocket.
*/
webSocket.onopen = function(event){
// For reasons I can't determine, onopen gets called twice
// and the first time event.data is undefined.
// Leave a comment if you know the answer.
if(event.data === undefined)
return;
writeResponse(event.data);
};
webSocket.onmessage = function(event){
writeResponse(event.data);
};
webSocket.onclose = function(event){
writeResponse("Connection closed");
};
}
/**
* Sends the value of the text input to the server
*/
function send(){
var text = document.getElementById("messageinput").value;
webSocket.send(text);
}
function closeSocket(){
webSocket.close();
}
function writeResponse(text){
messages.innerHTML += "<br/>" + text;
}
</script>
</body>
</html>
and here is my java file :
import java.io.IOException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/**
* #ServerEndpoint gives the relative name for the end point
* This will be accessed via ws://localhost:8080/EchoChamber/echo
* Where "localhost" is the address of the host,
* "EchoChamber" is the name of the package
* and "echo" is the address to access this class from the server
*/
#ServerEndpoint("/echo") public class EchoServer
{
/**
* #OnOpen allows us to intercept the creation of a new session.
* The session class allows us to send data to the user.
* In the method onOpen, we'll let the user know that the handshake was
* successful.
*/
#OnOpen
public void onOpen(Session session){
System.out.println(session.getId() + " has opened a connection");
try {
session.getBasicRemote().sendText("Connection Established");
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* When a user sends a message to the server, this method will intercept the message
* and allow us to react to it. For now the message is read as a String.
*/
#OnMessage
public void onMessage(String message, Session session){
System.out.println("Message from " + session.getId() + ": " + message);
try {
session.getBasicRemote().sendText(message);
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* The user closes the connection.
*
* Note: you can't send messages to the client from this method
*/
#OnClose
public void onClose(Session session){
System.out.println("Session " +session.getId()+" has ended");
} }
please help me as to where i am getting wrong which prevents the code to run only in my machine and no other machines connected in the network.
The problem here is with Origin verification - you need to set your server to actually serve the (static) html page - not just deployed websocket endpoint.After that, Origin header will be added with the same host (by browser runtime) and everything should work as expected.
You should use something similar to [how to construct a websocket uri relate to the page uri], which would let you use relative uri for your websocket endpoint (compared to hardcoded value you have now).
EDIT: This has been resolved by checking the log file on the Google Developers Console. The error was not in connecting to the database, but was because I was compiling with JDK 1.8 instead of 1.7. It turned out that even though I had told Eclipse to use JDK 1.7 for the project, it was still using 1.8 because I had the following line in my Eclipse configuration file:
-vm
C:\Program Files\Java\jdk1.8.0_20\bin
when I changed this to:
-vm
C:\Program Files\Java\jdk1.7.0_71\bin
everything worked perfectly.
Original Question:
I am trying to connect to a Google Cloud SQL instance from my authorized Google App Engine application. I started by following the directions at https://cloud.google.com/appengine/docs/java/cloud-sql/#Java_Build_a_starter_application_and_database and modifying them slightly to work with my Cloud SQL instance (I did not implement the guestbook database, I used my own which also has just one table with three columns). I created the Cloud SQL instance using MySQL Workbench.
When I debug my application locally (run it at localhost:8888) it works perfectly with my local MySQL instance AND the Google Cloud SQL instance. However, when I try to deploy my application to Google App Engine it just spits back a 500 Server Error and I have no clue what went wrong (I am very new to web programming). I have been searching all over the web for a solution to this issue but I've yet to find something that does the trick.
Here is my code for the servlet:
package com.rmpm;
import java.io.*;
import java.sql.*;
import javax.servlet.http.*;
import com.google.appengine.api.utils.SystemProperty;
#SuppressWarnings("serial")
public class Synaptic_rmpmServlet extends HttpServlet {
#Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String url = null;
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
try {
if (SystemProperty.environment.value() ==
SystemProperty.Environment.Value.Production) {
// Load the class that provides the new "jdbc:google:mysql://" prefix.
Class.forName("com.mysql.jdbc.GoogleDriver");
url = "jdbc:google:mysql://<my-project-id>:<my-instance-id>/test"; // Doesn't work
} else {
// Local MySQL instance to use during development.
Class.forName("com.mysql.jdbc.Driver");
url = "jdbc:mysql://173.###.##.###:3306/test"; // Connects to Google Cloud SQL instance when root password is provided
//url = "jdbc:mysql://127.0.0.1:3306/test"; // Connects to local MySQL instance
}
} catch (Exception e) {
e.printStackTrace();
return;
}
try {
Connection conn = DriverManager.getConnection(url, "root", "");
try {
String pid = req.getParameter("projID");
String own = req.getParameter("owner");
String dur = req.getParameter("duration");
if (pid == "" || own == "" || dur == "") {
out.println(
"<html><head></head><body>You are missing either a projectID, owner name, or duration! Try again! " +
"Redirecting in 3 seconds...</body></html>");
}
else {
int duration = Integer.parseInt(dur);
String statement = "INSERT INTO project VALUES(?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(statement);
stmt.setString(1, pid);
stmt.setString(2, own);
stmt.setInt(3, duration);
int success = 2;
success = stmt.executeUpdate();
if (success == 1) {
out.println(
"<html><head></head><body>Success! Redirecting in 3 seconds...</body></html>");
} else if (success == 0) {
out.println(
"<html><head></head><body>Failure! Please try again! " +
"Redirecting in 3 seconds...</body></html>");
}
}
} finally {
conn.close();
}
}
catch (SQLException e) {
e.printStackTrace();
}
resp.setHeader("Refresh", "3; url=/databaseIO.jsp");
}
}
And my code for the databaseIO.jsp:
<%# page contentType="text/html;charset=UTF-8" language="java" %>
<%# page import="java.util.List" %>
<%# page import="java.sql.*" %>
<%# page import="com.google.appengine.api.utils.SystemProperty" %>
<html>
<body>
<%
String url = null;
if (SystemProperty.environment.value() ==
SystemProperty.Environment.Value.Production) {
// Load the class that provides the new "jdbc:google:mysql://" prefix.
Class.forName("com.mysql.jdbc.GoogleDriver");
url = "jdbc:google:mysql://<my-project-id>:<my-instance-id>/test"; // Doesn't work
} else {
// Local MySQL instance to use during development.
Class.forName("com.mysql.jdbc.Driver");
url = "jdbc:mysql://173.###.##.###:3306/test"; // Connects to Google Cloud SQL instance when root password is provided
//url = "jdbc:mysql://127.0.0.1:3306/test"; // Connects to local MySQL instance
}
Connection conn = DriverManager.getConnection(url, "root", "");
ResultSet rs = conn.createStatement().executeQuery(
"SELECT * FROM project");
%>
<table style="border: 1px solid black">
<tbody>
<tr>
<th width="40%" style="background-color: #CCFFCC; margin: 5px">projectID</th>
<th style="background-color: #CCFFCC; margin: 5px">owner</th>
<th style="background-color: #CCFFCC; margin: 5px">duration</th>
</tr>
<%
while (rs.next()) {
String aPID = rs.getString(1);
String anOwner = rs.getString(2);
int aDur = rs.getInt(3);
%>
<tr>
<td><%= aPID %></td>
<td><%= anOwner %></td>
<td><%= aDur %></td>
</tr>
<%
}
conn.close();
%>
</tbody>
</table>
<br />
No more projects!
<p><strong>Add a project:</strong></p>
<form action="/sign" method="post">
<div>Project ID: <input type="text" name="projID"></input></div>
<br />
<div>Owner: <input type="text" name="owner"></input></div>
<br />
<div>Duration: <input type="text" name="duration"></input></div>
<br />
<div><input type="submit" value="Insert Project"></input></div>
</form>
</body>
</html>
Based on what I've read online, I gather that I shouldn't need a password when connecting as the root user to my Google Cloud SQL instance from my authorized Google App Engine application. When I connect to the Google Cloud SQL instance locally in debug mode, I do provide the root password which I set in the Google Developers Console and it works perfectly.
What I've done:
I am already logged into Eclipse with the Google account that is linked to my Google App Engine application.
I have already included the <use-google-connector-j>true</use-google-connector-j> line in the appengine-web.xml file.
I have copied the mysql-connector-java-5.1.33-bin.jar to the project's war/WEB-INF/lib folder and to the GAE SDK directory at D:\Program_Files\Eclipse\plugins\com.google.appengine.eclipse.sdkbundle_1.9.13\appengine-java-sdk-1.9.13\lib\impl (reading similar questions online said to do this).
I have authorized my Google App Engine application to connect to my Google Cloud SQL instance.
I have authorized my local network to connect to my Google Cloud SQL instance.
I am 100% sure the <my-project-id> and <my-instance-id> fields are correct in the JDBC url.
I've tried using my Google Cloud SQL instance's IP address in the JDBC url instead of the project-id and instance-id.
What I'm using:
Eclipse SE Luna (4.4.1) with Google Plugin for Eclipse (4.4) and Google App Engine Java SDK (1.9.13). I deploy from Eclipse straight to Google App Engine with the plugin.
JDK 1.7
Windows 8.1 64-bit
If you need any additional information just post here as I'll be checking this regularly.
Local mysql will be working on your code , i was having the same problem but as i changed .GoogleDriver to .Driver it started working
im implementing a java web client which connects to two web services. so basically i have a table listing the status of these two web services. example:
<table>
<tr>
<th>webservice</th>
<th>status</th>
</tr>
<tr>
<td>webservice 1</td>
<td>connected</td>
</tr>
<tr>
<td>webservice 2</td>
<td>connected</td>
</tr>
</table>
my java controller:
class test {
#autowired
private WebServiceTemplate webservice1;
#autowired
private WebServiceTemplate webservice2;
public String mycontroller(...) {
webserviceReq request = new ObjectFactory().createwebserviceReq();
webserviceRes response = new ObjectFactory().createwebserviceRes();
try {
response = (webserviceRes)this.webservice1.marshalSendAndReceive(webserviceReq);
//...set all the data
}
catch(Exception e) {
}
try {
response = (webserviceRes)this.webservice2.marshalSendAndReceive(webserviceReq);
//...set all the data
}
catch(Exception e) {
}
}//end of function
}
if the connection to either webservice fails (mayb the webserivce crash or wat), show the status as disconnected.
currently the problem im facing is if either one connection fail, im getting http status 500, request processing failed.
how can i capture the connection failure for each webservice and print it into the status column?
In each of the catch blocks, extract relevant parts of the Exception in order to determine the specific error. Add them to the returned String.
Things might get easier by not catching Exception, but a subclass that is specifically thrown by your webservices. These might contain the relevant error information.