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.
Related
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.
I am old to JAVA but very new to the topics of JSPs & Servlets. I am trying to do some jdbc operations by taking the values from JSP into servlet. To do that, I have written a JSP with a drop down list and a submit button.
Jsp:
<%# page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<div align='left' >
<div align='left' >
<label class="text-white mb-3 lead">Which report do you want to generate?</label>
<select id="reportSelection" data-style="bg-white rounded-pill px-5 py-3 shadow-sm " class="selectpicker w-100" name="reportselection">
<option>Outage</option>
<option>DataQuality</option>
<option>Latency</option>
</select>
</head>
<body>
<p id = "demo"> </p>
<script>
var d = new Date();
document.getElementById("demo").innerHTML = d;
</script>
</body>
</div>
</div>
</body>
<hr class="colorgraph">
<div class="row">
<div class="col-xs-12 col-md-6"><input type="submit" value="Submit" class="btn btn-primary btn-block btn-lg register" tabindex="7"></div>
</div>
</body>
</html>
And this is how my servlet class looks like.
#WebServlet("/getdata.do")
public class DataServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
GetTableColumn gt = new GetTableColumn();
String issue = request.getParameter("reportSelection");
String message;
try {
if ("Latency".equals(issue)) {
message = gt.process("latency");
} else if ("DataQuality".equals(issue)) {
message = gt.process("DataQuality");
System.out.println("Data quality");
} else if ("Outage".equals(issue)) {
message = gt.process("Outage");
}
} catch (SQLException s) {
s.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
I am reading the JSP drop down values in my servlet class and passing a String to method process based on the value received. I looked online to configure the web.xml file as below.
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DataServlet</servlet-name>
<display-name>DataServlet</display-name>
<description>Begin servlet</description>
<servlet-class>com.servlets.DataServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DataServlet</servlet-name>
<url-pattern>/parse</url-pattern>
</servlet-mapping>
I am trying to run the code on IntelliJ and here is how I have configured my tomcar server on IntelliJ.
When I run the code, I see the page is generating the jsp as expected.
What I don't understand is how to configure the submit with onclick so that I click on submit and the java program in the backed triggers. I have written the java code just to read values from a database by taking the input from the method process. This was running fine and I was asked to take the input from JSP and display the result back on a JSP.
When I click on submit button, I don't see any progress in the console output. I guess I didn't map it correctly.
Most of the links online contain JSP & JAVA together which is even more confusing.
Could anyone let me know how can I trigger the program by clicking the submit button
Since you are using #WebServlet, you do not need mapping in web.xml. Just add the following line inside body of your JSP:
<form action="getdata.do" method="post">
Look at your JSP file, pay attention at your head and body tag. I think it's a wrong that you have body inside other body and closing head tag inside body.
Other case that can be more important that to send a form by clicking to submit button you should put it inside tag form, something like this.
<form action = "getdata.do" method = "POST">
First Name: <input type = "text" name = "first_name">
<br />
Last Name: <input type = "text" name = "last_name" />
<input type = "submit" value = "Submit" />
</form>
I am trying to create a simple registration form and insert the data from the user into mongodb.
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sign Up</title>
<link href="/css/main.css" rel="stylesheet">
</head>
<body>
<h2>Sign Up</h2>
<#if user?? >
Your submitted data<br>
Name: ${user.user_id}<br>
Password: ${user.password}<br>
Email: ${user.email}<br>
<#else>
<form action="/form" method="post">
First name:<br>
<input type="text" name="Name">
<br><br>
Pasword:<br>
<input type="text" name="password">
<br><br>
Email:<br>
<input type="text" name="email">
<br><br>
<input type="submit" value="Submit">
</form>
</#if>
<script src="/js/main.js"></script>
</body>
</html>
I have a User class, and I want to create an instance of it, to fetch the data and insert it to mongodb. This is the part I don't know how to do. How do I create the controller to pass the data from HTML to the User instance?
This is what I have so far -
//sign up page
Spark.get(new Route("signup") {
#Override
public Object handle(Request request, Response response) {
StringWriter writer = new StringWriter();
User user = new User(); // create user to fetch results
try{
Template signupTemplate = configuration.getTemplate("signup.ftl");
}catch (Exception e){
e.printStackTrace();
}
}
});
}
How do I go on from here? Can I do that only with freemarker and sparkjava?
To post form data to server, you should define a spark route to listen for POST method.
A spark route comprises of three entities (see doc's),
verb: get, post, put, delete etc.
path: Request path for which the Route applies
callback: Handler class that will be invoked when the incoming request matches the path.
In your case it should be,
Spark.post("/form", new Route() {
#Override
public Object handle(Request request, Response response) {
// process request and return response
}
});
If your working with Java 8, you could simplify the above,
Spark.post("/form", (request, response) -> {
// process request and return response
});
Now to read form data, you could do so by fetching the raw request(HttpServletRequest) and using getParameter() inside the handle method.
HttpServletRequest httpRequest = request.raw();
String name = httpRequest.getParameter("Name");
String email = httpRequest.getParameter("email");
Is it better to invalidate a session in a servlet in which it is declared or in the JSP page where its values will be used ?
I am posting the code of servlet below -
package Controller.UploadInfo;
import File.FileOperations;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import Controller.DatabaseException.*;
public class AttendenceInfoUpload extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
HttpSession session;
if (FileOperations.fileUpload(request)) {
try {
FileOperations.excelToAttendence();
request.getRequestDispatcher("UploadSuccess.jsp").forward(request, response);
} catch (DBException e) {
session = request.getSession(true);
session.setAttribute("exception",e);
request.getRequestDispatcher("FileUpload.jsp").forward(request, response);
session.invalidate();
}
} else {
session = request.getSession(true);
session.setAttribute("exception"," File Upload Failed " );
request.getRequestDispatcher("FileUpload.jsp").forward(request, response);
}
}
}
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
#Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
}
In the given servlet above I have invalidated the session right after the getRequestDispatcher() in the catch block. Although the code is working, my concern is will it cause the exception message to loose before it can be displayed in the JSP page. Or is it better to invalidate the session declared in the servlet in the JSP page where its values will be displayed.
The JSP page -
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<%#page import = "java.io.*" %>
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>Excel File Upload Example</h1>
<form name="form1" method="post" action="AttendenceInfoUpload" enctype="multipart/form-data">
<table border="1">
<col width="120">
<tr>
<td>Upload Excel File:</td>
<td><input type="file" name="Select File"/></td>
</tr>
<tr>
<td> </td>
<td><input name="" type="submit" value="upload" /></td>
</tr>
</table>
</form>
<c:if test="${not empty exception}">
<label>
<font color="red">
<c:out value="Error:${exception}"></c:out>
</font>
</label>
</c:if>
</body>
</html>
One can suggest an alternate solution as well?
The best solution is probably to invalidate the session in the servlet but make sure that any values required by the JSP are stored in the request rather than in the session. I say this because it is best practice to put all logic in beans or servlet code and keep JSPs for layout only.
There's no need to invalidate a session. Also there's no need to use a session attribute. If you want to forward to a error page you can use request attribute. Using a session heavily is a bad practice because it requires a lot of memory to utilize those variables you put into it.
} catch (DBException e) {
request.setAttribute("exception",e);
request.getRequestDispatcher("FileUpload.jsp").forward(request, response);
}
as I said it's not a good practice to invalidate a session when you are going to forward to a error page. Even if JSP page is rendered in the same thread other threads that can use the same session might not work. And you can't use session variables after the session is invalidated. However it's rarely happens but other request might invalidate a session while JSP is rendered.
How can I redirect a response of a servlet to the same jsp page from where I get the request. Suppose I have a tab called status on my jsp page
http://localhost:8080/Example/status.jsp. Now when I send a request and when I get a response,it should display the response on the same page, ie. it should show response on
http://localhost:8080/Example/status.jsp. But it is showing me the response in
http://localhost:8080/Example/Statuswhere Status is the url-pattern in web.xml file. Please any help is appreciated. below is my code.
How do I get the response in status.jsp.
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws
IOException {
try{
String showstatus =req.getParameter("showstatus");
PrintWriter out = res.getWriter();
Runtime rt = Runtime.getRuntime();
Process pr = rt.exec("C:\\tools\\server\\util stat -a" );
BufferedReader stdInput = new BufferedReader(new
InputStreamReader(pr.getInputStream()));
BufferedReader input = new BufferedReader(stdInput);
String stat = "";
while((stat = input.readLine()) != null){
out.println(stat);
}
}
catch (Throwable t)
{
t.printStackTrace();
}
finally {
} }
JSP CODE:
<div id= "status" style="display:none;">
<FORM action="status" METHOD="POST">
<input type=submit name=showstatus
id=txtSubmit value=Status />
</FORM>
</div>
WEB.XML:
<servlet>
<servlet-name>Status</servlet-name>
<servlet-class>com.emc.clp.license.Status</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Status</servlet-name>
<url-pattern>/status</url-pattern>
</servlet-mapping>
In your servlet, rather than printing the output of util directly to the response, hold it in memory and pass it to the jsp:
String stat;
StringBuffer utilOutput = new StringBuffer();
while((stat = input.readLine()) != null){
utilOutput.append(stat + "\n");
}
req.setAttribute("utilOutput", utilOutput.toString());
req.getRequestDispatcher("/Example/status.jsp").forward(req, res);
In your jsp, I assume you have a div or something where you want to see the output:
<div id= "status" style="display:none;">
<form action="/status" method="POST">
<input type="submit" name="showstatus" id="txtSubmit" value="Status" />
</form>
</div>
<div id="result">
<pre>
${requestScope.utilOutput}
</pre>
</div>
Notice that I added a forward slash in the form action. If your user is submitting the form from /Example/status.jsp, then the relative path would have the post going to /Example/status, but your servlet only handles requests to /status.
Your request object, req, has a map of attributes, and you can put any relevant objects in it using the setAttribute() method on the request. The request dispatcher forwards your request and response objects on to another resource (your jsp) for processing. The jsp can access these values by name, using the EL notation above.
The display:none in your inline style will always prevent that form from being seen on the page if it loads that way. I think the most apparent way to display it would be with javascript on the client side. You'd need to change the display value when the tab is clicked. You don't mention what your tab elements are, but let's say they're divs. You could write a javascript function to load the form, or you could even do it all inline:
<div id="mytab" onclick="document.getElementById('status').style.display = 'block';">Load the form</div>