Server Sent Event and ASP Classic (with Javascript) - asp-classic

So I'm trying to implement some server side event in ASP Classic. This is because I can't do push notifications with ASP Classic. It consists of a Javascript and ASP code. The idea is to send a notice in the form of a sound when you have a new message.
ASP Code in a file named "poller.asp". It polls the private messages and if a new message is found, it executes this code. The code is basically the 'event' that makes up the server event.
Response.ContentType = "text/event-stream"
Response.Expires = -1
response.write ("retry: 10000") & vbcrlf
Response.Write("data: New Mail" & vbCrLf & vbCrLf )
Response.Flush()
and the Javascript:
<!DOCTYPE html>
<html><body>
<div id="result"></div>
<script src="/poller/audio-fx.js"></script>
<script>
var pool = AudioFX('/poller/alert',
{ formats: ['mp3'], volume: 0.8, autoplay: false });
if (!!window.EventSource) {
var source = new EventSource("/poller/poller.asp");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "<br>";
pool.play();
}
}
</script>
</body>
</html>
So this code WORKS in that it receives a message when a new private message is detected. It reads it from the stream (poller.asp). There are two issues however:
1) This only works if a new message exists from the time the script is run. If there's no new messages, the stream seems to close. Let's say there are no new messages, then someone sends me one, nothing happens. If there is a new message when I run the script, it works, but if I delete it and receive another one, it also doesn't notify me. I suspect this is something to do with the stream closing.
2) You will notice the Audio-FX. JS and the pool.play. This plays a short mp3 "You have a new message". The issue is that it fails on the first two tries. Say I have a new message waiting. It will say "New Mail" as per the
document.getElementById("result").innerHTML += event.data + ""
but only after doing so twice, will it play the audio. I think maybe it's a buffering issue. As it plays every time thereafter.
Any help would be appreciated :)

Related

execute code on load in vb.net aspx page

I appreciate help for this issue which stoled a lot of hours.
I have this code:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
Label1.Text = "924 695 302"
Label2.Text = "690 142 449"
Dim ipvisitante = Request.ServerVariables("remote_addr")
Dim hoje = DateTime.Now
Dim informacao = ipvisitante & " --- " & hoje
'Send e-mail
Dim strFrom = "fernandopessoa#fpessoa.net" ''IMPORTANT: This must be same as your smtp authentication address.
Dim strTo = "francopessoa.espana#hotmail.com"
Dim MailMsg As New MailMessage(New MailAddress(strFrom.Trim()), New MailAddress(strTo))
MailMsg.BodyEncoding = Encoding.Default
MailMsg.Subject = "This is a test"
MailMsg.Body = "This is a sample message using SMTP authentication"
MailMsg.Priority = MailPriority.High
MailMsg.IsBodyHtml = True
'Smtpclient to send the mail message
Dim SmtpMail As New SmtpClient
Dim basicAuthenticationInfo As New Net.NetworkCredential("fernandopessoa#fpessoa.net", "---------")
''IMPORANT: Your smtp login email MUST be same as your FROM address.
SmtpMail.Host = "mail.fpessoa.net"
SmtpMail.UseDefaultCredentials = False
SmtpMail.Credentials = basicAuthenticationInfo
MsgBox("O ficheiro existe", MsgBoxStyle.Information, "SIM")
'Write to txt File
FileOpen(1, "visitas.txt", OpenMode.Append)
WriteLine(1, informacao)
FileClose()
End Sub
Now, when the page Loads, the text apears in the Labels.
Surprisingly, it doesn't execute the rest of the code, Display Msgbox, Write to the .txt File and send the e-mail.
Can anyone give me a clue of what's going wrong with my code?
Thanks in advance.
The code does execute... it runs on the Web Server. It does not run in the client's web browser, and never will.
That explains the MsBox() and file, though the web server may also be getting hung up waiting for someone to click "Okay" on a MsgBox no one will ever see. For the e-mail, you never call SmtpMail.Send(MailMsg)
While I'm here, that file code is using an antique api.
It sounds like you need a quick primer on how this all works, so here is what happens step by step:
User clicks a link to your page or types your page address in their address bar.
The browser sends an HTTP request to your server.
Your server receives the request, creates a new instance of your page class in a worker thread.
Code runs in your page class for ALL phases of the ASP.Net Page Lifecycle .
The ASP.Net runtime uses your page class instance to render an HTTP response (usually in html) and send it to the browser.
Your page class instance is destroyed.
The browser receives the response, parses a new Document Object Model (DOM), and renders that DOM to the display.
The user sees and interacts with your page, causing a post-back.
Go to step 2, taking special note of the "new instance" phrase when you reach step 3.

Classic ASP / End of session redirect

I want to automatically redirect to the login page when the users session has expired.
I have been using the following code in an include file that sits at the top of every page in my application:
Session.Timeout = 60
Response.AddHeader "Refresh", CStr(CInt(Session.Timeout + 1) * 60)
Response.AddHeader "cache-control", "private"
Response.AddHeader "Pragma","No-Cache"
Response.Buffer = True
Response.Expires = 0
Response.ExpiresAbsolute = 0
If Session("accountID") = "" Then
Response.Redirect("http://www.mydomain.com/")
End If
This works but there is very slight bug. Every now and then the page will refresh even though the session is still alive and it seems that it refreshes before the 60 minutes is up!
Can anybody see what the problem is or can you suggest a different method?
Seeing as though you have to do this client side I'd favour JavaScript/jQuery and AJAX over that method. Here's an example of how to do it.
Essentially you just set-up an AJAX call to poll a script which returns (in JSON format) whether the user is logged in or not; if they're not then you can transfer them to another page.
The benefits to this method are that you can poll whenever you want; e.g. every 10 seconds to see whether the user is still logged in rather than having to wait a full hour. It also means that you don't need to state the session time-out figure in your code and so you can leave that to be determined in IIS. Also if the user logged off elsewhere in your system, or your application pool recycled and their session was reset this would detect it fairly quickly.
I notice from your profile that you're a Paparazzi photographer. I'd consider this the DSLR method and the response header method the cheap phone camera method :o.
To build your session checker page create a file called session.asp (in the same folder as your other files to make life simpler). In it put:
<%
Response.ContentType = "application/json"
If Session("LoggedOn") Then
Response.Write "{""loggedOn"": true}"
Else
Response.Write "{""loggedOn"": false}"
End If
%>
If the user is logged in it returns {"loggedOn": true}, if they're not {"loggedOn": false}. This is what we'll use on your other page to poll if they're logged in by calling this page periodically and reading the response.
Now onto your pages which originally had your Response.AddHeader code in. Remove all of your code as this replaces it.
First make sure you have a reference to jQuery on your pages:
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
And then put under this line the following:
<script type="text/javascript">
$(document).ready(function() {
var checkLoggedOn = function() {
$.getJSON('session.asp', function(data) {
if (!data.loggedOn)
window.location.replace("http://stackoverflow.com");
});
};
// Call checkLoggedOn every x milliseconds
setInterval(checkLoggedOn, 30000);
});
</script>
All being well, it should work. I set the above to poll every 30 seconds (30000) but you could increase/decrease this to whatever you wanted.
Note I borrowed large parts of the code above from https://stackoverflow.com/a/4928564/171703 and https://stackoverflow.com/a/2709160/171703.
Update:
From the comments below, if you want the user's session to expire after the timeout figure (whether they are keeping their session alive or not) then you could do this.
When the user is logged in, set a new session variable for LoginExpiration:
Session("LoginExpiration") = DateAdd("n", Session.TimeOut, Now())
This takes the current time and adds to it the session timeout figure - giving you the time when their session should be destroyed.
If you now modify your session.asp to the following it takes the LoginExpiration figure and returns that the user is not logged in the event of:
The users session has timed out (IIS application pool reset, or they clicked logoff etc)
The current date/time is greater than the set LoginExpiration time
Which is:
<%
Response.ContentType = "application/json"
LoggedOn = "false"
LoginExpiration = Session("LoginExpiration")
DateNow = Now()
If IsDate(LoginExpiration) Then
If DateNow < LoginExpiration Then
LoggedOn = "true"
End If
End If
Response.Write "{"
Response.Write """loggedOn"": " & LoggedOn & ", "
Response.Write """loginExpiration"": """ & LoginExpiration & """"
Response.Write "}"
%>
I've put the loginExpiration figure into the JSON response so you could work with it client side if you wanted too.
'If the session variable is False or does not exist (IsNull)
'then redirect the user to the unauthorised user page
If Session("accountID") = False or IsNull(Session("accountID")) = True then
'Redirect to unathorised user page
Response.Redirect "pagename.asp"
End If
Place this in an include file that you include in all pages you need protected.
<%#LANGUAGE="VBSCRIPT"%>
<!--#include file="checkmem.asp"-->
<!--#include file="includes/dtdsql.asp" -->
<!--#include file="includes/functions.asp" -->
The ASP Global.asa file may be what you're looking for. It allows you to run code at certain events, such as Session start and end. See https://www.w3schools.com/asp/asp_globalasa.asp for more info, but I believe the following will work:
Global.asa
<script language="vbscript" runat="server">
sub Session_OnEnd
Response.Redirect("http://www.example.com/")
end sub
</script>

ASP.NET HttpWebRequest stop sending requests, high CPU usage

I have an old ASP.NET application. We send out httpWebRequest to a remote REST server and retrieve XML back, most of the time the application works fine. Recently, we got some high CPU usage issue several times a day.
During the high CPU usage time, we monitored those httpWebRequest connections (by checking netstat for the w3wp process). At the very beginning, the connections change to "CLOSE_WAIT" status from "ESTABLISHED", then after those connections timeout, those connections disappeared one by one, and then there is no connection any more.
After resetting the IIS, when the w3wp.exe process start again, we still could not find any connections to httpWebRequest target server. So the CPU usage keep staying at high level. Even after several round of reset, it won't solve the issue, until we saw some connections start to connect to httpWebRequest target server, the CPU usage went down.
I actually thought it could be the issue of my code not handling the httpWebRequest properly, I posted another question here: How to close underlying connections after catch httpwebrequest timeout?.
As mentioned in that question, I also found lots of timeout exceptions for System.Net.HttpWebRequest.GetResponse(). We found 3500 of the same exception within 5 minutes when CPU usage is really high.
What could cuase this type of issue and what could be the medicine? Why won't the application send out request any more (since there is no connection in netstat)?
Here is the source code just in case:
System.Net.HttpWebResponse httpWebResponse = null;
System.IO.Stream stream = null;
XmlTextReader xmlTextReader = null;
try
{
System.Net.HttpWebRequest httpWebRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(request);
httpWebRequest.ReadWriteTimeout = 10000;
httpWebRequest.Timeout = 10000;
httpWebRequest.KeepAlive = false;
httpWebRequest.Method = "GET";
httpWebResponse = (System.Net.HttpWebResponse)httpWebRequest.GetResponse();
stream = httpWebResponse.GetResponseStream();
xmlTextReader = new XmlTextReader(stream);
xmlTextReader.Read();
xmlDocument.Load(xmlTextReader);
//Document processing code.
//...
}
catch
{
//Catch blcok with error handle
}
finally
{
if (xmlTextReader != null)
xmlTextReader.Close();
if (httpWebResponse != null)
httpWebResponse.Close();
if (stream != null)
stream.Close();
}
From your description, it is not clear to me that your high CPU utilization is related to your outgoing HTTP requests. High CPU utilization could be due to a bug in your code, a bug in CLR, IIS, or something else. Without knowing which component is consuming the CPU, you wont be able to do anything further.
If I were you, I would first try to attach a sampling profiler to the W3WP process, and see which component is consuming the CPU. That should point you to the next steps in resolving this issue.
I would suggest you to try sending requests asynchronously to avoid blocking the main thread:
using (var client = new WebClient())
{
client.OpenReadCompleted += (sender, e) =>
{
using (var reader = XmlReader.Create(e.Result))
{
// Process the XML document here
}
};
client.OpenReadAsync(new Uri("http://www.example.com"));
}
Finding the reason for the high CPU utilization can take some time since you will have to locate the code that is causing the problem. I am working through this right now on a vb.net app that I recently developed. In the meantime, I have developed a page that has a button which an Administrative user can click to stop the W3WP.exe process. It is a great stop gap measure until the problem code can be identified and updated. Here is the code I used. Just create a .aspx page with a button that call the following code on the corresponding .aspx.vb page. The code uses the command prompt to get the Tasklist and writes this to a file. I then parse the text file for the PID of the W3WP.exe worker process. Then, I access the command prompt programmatically again to terminate the W3WP.exe process using the PID.
Imports System.Web
Partial Class TAP
Inherits System.Web.UI.Page
Protected Sub btnStop_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnStop.Click
Call thStop_IIS()
End Sub
Function thStop_IIS()
Dim varRetval As String
Dim varPID As String
dim x as long
Dim savePath As String = Request.PhysicalApplicationPath + "exports\"
If Dir(savePath + "filename.txt") = "filename.txt" Then
Kill(savePath + "filename.txt")
End If
varRetval = Shell("cmd /c tasklist > " + savePath + "filename.txt")
For x = 1 To 90000000
Next x
varPID = thParse_File_Return_PID(savePath + "filename.txt")
varRetval = Shell("cmd /c taskkill /pid " & varPID & " /F")
Return True
End Function
Function thParse_File_Return_PID(ByVal varFileToParse As String) As Integer
On Error GoTo handler_err
'Dim FILE_NAME As String = "C:\Users\Owner\Documents\test.txt"
Dim FILE_NAME As String = varFileToParse
Dim TextLine As String
Dim varPID As Integer
Dim x As Long
If System.IO.File.Exists(FILE_NAME) = True Then
Dim objReader As New System.IO.StreamReader(FILE_NAME)
Do While objReader.Peek() <> -1
'TextLine = TextLine & objReader.ReadLine() & vbNewLine
TextLine = objReader.ReadLine() & vbNewLine
If InStr(TextLine, "w3wp.exe") Then
varPID = Mid(TextLine, 31, 4)
End If
Loop
thParse_File_Return_PID = varPID
Else
thParse_File_Return_PID = 0
End If
handler_exit:
Exit Function
handler_err:
'MsgBox(Err.Number & " " & Err.Description & ":" & "thParse_File_Return_Pages")
Resume
End Function
End Class

Using JQuery to retrieve a physical file and then display it in the browser

UPDATE:
Most of the answers given have just been some way of injecting the returned document into the html of the page, but this does not seem to be working. It might help if you could see what the aspx code is doing after all:
Try
RenderDocument = Session("docSelected")
If Not String.IsNullOrEmpty(Request.QueryString("download")) Then
asDownload = CBool(Request.QueryString("download"))
End If
If RenderDocument IsNot Nothing Then
Dim docBytes() As Byte
Dim docExtension As String = ""
Dim docFileSize As Long
GetBytesAndExtension(docBytes, docExtension, docFileSize)
If docBytes IsNot Nothing Then
Dim ms As New MemoryStream(docBytes)
With ms
Dim dataLengthToRead As Long = ms.Length
Dim blocksize As Integer
If dataLengthToRead >= 5000 Then
blocksize = 5000
Else
blocksize = dataLengthToRead
End If
Dim buffer(dataLengthToRead) As Byte
Response.Clear()
Response.ClearContent()
Response.ClearHeaders()
Response.BufferOutput = True
If asDownload = True Then
Response.AddHeader("Content-Disposition", "attachment; filename=" & RenderDocument.Name & "." & docExtension)
Else
Response.AddHeader("Content-Disposition", "inline; filename=" & RenderDocument.Name & "." & docExtension)
End If
Response.AddHeader("Content-Length", docFileSize.ToString())
Response.ContentType = "application/octet-stream"
While dataLengthToRead > 0 And Response.IsClientConnected
Dim lengthRead As Integer = ms.Read(buffer, 0, blocksize)
Response.OutputStream.Write(buffer, 0, lengthRead)
dataLengthToRead = dataLengthToRead - lengthRead
End While
Response.Flush()
Response.Close()
End With
Response.End()
Else
ResponseWithMessage("No Data")
End If
Else
ResponseWithMessage("No Document")
End If
Catch ex As Exception
ResponseWithMessage("ERROR: " & ex.Message)
End Try
What is the significance of the return type of the $.post() function? In my code I use 'html', but actually it could be a file being returned.
===================
ORIGINAL QUESTION:
The scenario is I want to load a html page which will call a specific ASP.Net script, this script returns a document based on some data stored in the session. It's function is irrelevant, essentailly it returns the document as an http response, however it may return one of several error strings depending on what has occured.
The html page shows a loading gif image and then when the document or error message is returned it needs to handle these appropriately.
I can get the page to display the error messages without a problem, but I don't know how best to handle the document request being returned, at the moment it just opens a new window and calls the url again, but that is clunky and calls the whole document retrieval process again (synchronously!) which defeats the purpose of using a nice ajax style load.
This is my JQuery code:
$(document).ready(function(){
$.post("displaydocument.aspx",null,function(data){
if (data == 'No Document'){
UpdateControls('No document has been specified to load');
}
if (data == 'No Data'){
UpdateControls('No data was found for that document');
}
if (data.indexOf('ERROR',0) != -1) {
UpdateControls(data);
}
window.open("displaydocument.aspx","doc");
window.close;
}
, "html");
});
function UpdateControls(message){
$("#txtInfo").html(message);
$("#imgLoading").hide();
}
This is my html:
<div style="text-align: center; font-family: 'Trebuchet MS';">
<img alt="loading" src="/images/loader64.gif" id="imgLoading" />
<br />
<span style="font-size: small" id="txtInfo">Document is loading...</span>
</div>
Your assistance is as always highly valued.
Thanks
Updated: I think the problem is that you are trying to do too much in one method. I would split it into two operations: check that the document is available and download the document. Have the method you call via POST (GET would work too since you aren't passing any data) do the existence check then create a link to a document handler URL that does the actual download. Add the link to the page and programmatically click it to invoke the download handler. The download handler would consist of your existing code and simply return an error page if invoked on a non-existent document. Note that the latter will not happen in normal operation, but could if someone bookmarked the downloaded document and it was removed.
Also, I'd be tempted to use JSON for the return value instead of HTML to make handling the error easier.
$(function() {
$.get('checkfordocument.aspx',null,function(data) {
if (!data.Success) {
UpdateControls(data.Message);
}
else {
UpdateControls('');
$("<a href='"
+ data.Url
+ "' target='_blank' class='docLink'>"
+ "Click here if your document does not start downloading</a>")
.appendTo('#txtInfo')
.trigger('click');
}
,'json');
});
Open your window and set it's contents using JS instead of sending another request to the server.
Instead of
window.open("displaydocument.aspx","doc");
do something like:
var win = window.open();
win.document.write(data);
This will set the contents of the new window without retrieving the data again.
I would create a web/wcf service who's responsibility is to notify you if a certain file exists. You could then return a simple json string with either a success flag and if it fails a relevent error message.
This also saves the need to request the file twice and you would also gain a reusable service for future pages & projects. I know the response for the document should be cached however the above functionality allows you to give greater error message notification to the user.
$.getJson('fileExistsService.svc', { filename : someName }, function(data){
if (data.success){
//open document
window.open("displaydocument.aspx","doc");
}
else {
//display error to use
//alert(data.errorMessage)
}
})

How do I call javascript just before a response redirect

I'm trying to run some java script just before a page redirect but it fails to run.
When I comment out the Response.Redirect all works fine but this goes against the particular requirements. Any ideas on how to implement this functionality?
Dim strscript As String = "<script>alert('hello');</script>"
If Not ClientScript.IsClientScriptBlockRegistered("clientscript") Then
ClientScript.RegisterStartupScript(Me.GetType(), "clientscript", strscript)
End If
Response.Redirect("http://www.google.com")
Your problem is that the Response.Redirect redirects the response (...) before anything is sent back to the client. So what the client gets is a response from Google rather than from your server.
In order to write some javascript on the page and have it execute before sending the client to Google, you'll need to do your redirect in javascript after the alert.
Dim strscript As String = "<script>alert('hello');window.location.href='http://www.google.com'</script>"
If Not ClientScript.IsClientScriptBlockRegistered("clientscript") Then
ClientScript.RegisterStartupScript(Me.GetType(), "clientscript", strscript)
End If
The client isn't getting a chance to load. Try redirecting from the client side:
Dim strscript As String = "<script>alert('hello');window.location.href("http://www.google.com");</script>"
If Not ClientScript.IsClientScriptBlockRegistered("clientscript") Then
ClientScript.RegisterStartupScript(Me.GetType(), "clientscript", strscript)
End If
If you want to execute some javascript before redirecting, you will need to do the redirect in javascript and not in ASP.NET.
Dim strscript As String = "<script>alert('hello'); window.location.href='http://www.google.com';</script>"
If Not ClientScript.IsClientScriptBlockRegistered("clientscript") Then
ClientScript.RegisterStartupScript(Me.GetType(), "clientscript", strscript)
End If
Not Working:
string sScript = "<script language='javascript'>alert(\"" + Alertstr + "\"); alert('Record has been Updated Successfully'); </script>";
ClientScript.RegisterStartupScript(typeof(Page), "alert", sScript);
response.redirect("LandingPage.aspx");
Working:
string sScript = "<script language='javascript'>alert(\"" + Alertstr + "\"); alert('Record has been Updated Successfully'); window.location.href = 'LandingPage.aspx'; </script>";
ClientScript.RegisterStartupScript(typeof(Page), "alert", sScript);
If you use a Response.Redirect it actually sends a 3XX response to the browser which causes it to send a request to the URL in the redirect. It won't actually load/render any data contained with the response (actually I don't think any data is sent). If you want it to redirect after the page loads, you may want to either include a META refresh header that redirects a certain amount of time after load or use javascript do to the redirect at the end of your script.

Resources