SSE example

mod_harbour is an Apache module that allows to run PRGs directly on the web !!!
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

dakosimo wrote:I will wait. It is not urgent.
Thank you.
Best regards.

Dako.
I've done the SSE functional test!!
https://www4.zzz.com.tw/phpBB3/viewtopic.php?f=2&t=358
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello,

May I ask if the method "printData()" in the class "TPER" is from your program? Do you run it on Mod Harbour?

Best regards,
Otto
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

Otto wrote:Hello,

May I ask if the method "printData()" in the class "TPER" is from your program? Do you run it on Mod Harbour?

Best regards,
Otto
Yes!! I'm run on mod_harbour!!

The TPER is a CLASS I wrote for mod_harbour implementation in the backend.
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello,

Thank you. Do you have a sample code?
I am working on a chat application.

I didn't know about Server-Sent Events (SSE) until you mentioned it here.

SSE is a technology that enables real-time communication between a web browser and a server. It allows the server to send events or updates to the client (web page) over a single, long-lived HTTP connection.

SSE can be a more efficient and reliable approach compared to interval requests with AJAX, which is what I currently use.

Would you be willing to share your code?

Best regards,
Otto
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

Otto wrote:Hello,

Thank you. Do you have a sample code?
I am working on a chat application.

I didn't know about Server-Sent Events (SSE) until you mentioned it here.

SSE is a technology that enables real-time communication between a web browser and a server. It allows the server to send events or updates to the client (web page) over a single, long-lived HTTP connection.

SSE can be a more efficient and reliable approach compared to interval requests with AJAX, which is what I currently use.

Would you be willing to share your code?

Best regards,
Otto
The way mod_harbour is implemented is to insert HTML into the PRG.
The way I implement mod_harbour is to include PRG in HTML.
The implementation is just like PHP, so you have to modify the example code.

I test it and httpd.exe cpu loading is 0%~0.5% only.

demo file download:
https://is.gd/lyVHyf

Code: Select all | Expand

<?prg
LOCAL cMethod := AP_Method()
LOCAL hParam  := hb_hash()
LOCAL hData   := hb_hash()
LOCAL i       := 0,;
      nTolRec := 100,;
      nFunc   := 0
LOCAL hRes    := {;
   'err_no'=>0;   // 錯誤碼
  ,'errmsg'=>'';  // 錯誤訊息
  ,'errtil'=>'';  // 錯誤抬頭
  ,'lEnd'=>0;     // 結束?<*0:No/1:Yes>
  ,'step'=>0;     // 目前進度指標(every)
  ,'total'=>0;    // 總筆數(total)
}
//
IF cMethod == 'POST'
  hParam := AP_PostPairs()
ELSE
  hParam := AP_GetPairs()
ENDIF
//
IF hb_hHasKey(hParam, 'func')
  nFunc := Val(hParam['func'])
ELSE
  nFunc := -1
ENDIF
// AP_RPuts('data method:'+cMethod,'<br>')
IF nFunc == 9
  // hData := hb_jsondecode(hParam, 'data')
  // 送出 SSE 專用表頭
  AP_SetContentType( "Cache-Control: no-cache"         ) // 關閉快取
  AP_SetContentType( "text/event-stream;charset=UTF-8" ) // 送出表頭格式
  //
  hRes['step' ] := 0
  hRes['total'] := 100
  sendMsg( hb_jsonencode( hRes ) )
  //
  FOR i := 1 To 100
    hRes['step' ] := i
    hRes['total'] := nTolRec
    sendMsg( hb_jsonencode( hRes ) )
  NEXT i
  hRes['lEnd'] := 1 // 利用此變數通知前端處理完畢!!
  sendMsg( hb_jsonencode( hRes ))
  RETURN
ENDIF
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SSE test!!</title>
<meta name="generator" content="WYSIWYG Web Builder 18 - https://www.wysiwygwebbuilder.com">
<link href="test.css" rel="stylesheet">
<link href="index.css" rel="stylesheet">
<script src="jquery-3.6.4.min.js"></script>
<script>   
   function go(){
         let url='index.prg';
         let data={
            a: 1
           ,b: 2
         };
         let param=url+'?func=9&data='+JSON.stringify(data);
   alert('Go...');
         source = new EventSource(param,{
            withCredentials: false // Whether to allow cross domain requests, default: false
         });
         // Handle postback events 'message'
         source.addEventListener('message',function(e){
           let o=JSON.parse(e.data); // analyze
           if(o.lEnd == 0){  // lEnd=1:End
             // Screen display progress processing
             let per=Math.round( o.step/o.total*100,2 ); // calculate percentage
             $('#progress').val(per);
             $('#progress_down').text( per+'%' );
           }else{
             source.close(); // Close SSE!! Don't send data back!!
             //
             if(o.err_no != 0){
               alert('Failed to read data!!'+o.err_msg, 'Error!!');
               return;
             }else{
               alert('success!!')
             }
           }
         });
         // Handle the event on start 'open'
         source.addEventListener('open', function(e) {
           // 
         });
         // Events when errors are handled 'error'
         source.addEventListener('error', function(e) {
           if(e.target.readyState == EventSource.CLOSED){
             alert('connect be close!!');
             source.close(); // stop SSE 
           }else{
             console.log('error!!');
           }
         });
         // Handle system status events 'status'
         source.addEventListener('status', function(e) {
           console.log('System status is now: ' + e.data);
         });
   }
</script>
</head>
<body>
   <input type="button" id="Button1" onclick="javascript:go();return false;" name="" value="Test" style="position:absolute;left:170px;top:143px;width:132px;height:52px;z-index:0;">
<!-- jquery -->
   <label for="" id="progress_down" style="position:absolute;left:0px;top:100px;width:465px;height:35px;line-height:35px;z-index:2;">Text</label>
   <span style="color:#000000;font-family:微軟正黑體;font-size:14px;"><strong id="progress_up"><br> Process wait...</strong></span>'
   <br><progress id="progress" value="0" max="100" style="height:48px;width:100%;">
   <br><label style="text-align:center;width:100%"></label>
</body>
</html>
<?prg
FUNC sendMsg(cRes)
    AP_SetContentType( "text/event-stream;charset=UTF-8" ) // 送出表頭格式
    AP_RWRITE( e"event: message\n" )
    AP_RWRITE( e"id: "+hb_ntos( int( hb_TToMSec( hb_dateTime() )))+e"\n" )
    AP_RWRITE( e"retry: 15000\n" )  // 一個整數值,指示斷開連接時的重新連接時間
    AP_RWRITE( e"data: "+cRes+e"\n" )
    AP_RWRITE( e"\n" ) // 跟上面 hb_eol() 組合成兩個 \n \n 代表送出完畢!!
    Delay(50)
    AP_RFLUSH()
RETURN NIL
FUNC Delay(nMSec)
LOCAL nSec    := hb_TToMSec( hb_dateTime() )
        Do While hb_TToMSec( hb_dateTime() ) - nSec < nMSec
           // SysRefresh()
           hb_releaseCPU()
        EndDo
RETURN  .T.
?>
add ap_rflush() function .c

Code: Select all | Expand

#include <http_protocol.h>
#include <hbapi.h>
#include <hbapiitm.h>
#include <hbvm.h>
request_rec * GetRequestRec( void );

HB_FUNC( AP_RFLUSH )
{
  hb_retni( (int) ap_rflush( GetRequestRec() ) );
}
 
result:
Image
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello ssbbs,

Thank you very much for that.

I will study and test your code.

I'm glad to hear that you're using mod_harbour APACHE and that it's working fine for you.

I want to remember that this question has been bothering me a lot:
The way mod_harbour is implemented is by inserting HTML into the PRG.
The way I implement mod_harbour is by embedding PRG into HTML.

Everything with mod_harbour is so new, and there is so much to learn. But it's really fun.

Thanks again for your help.

Best regards from Austria.
LG
Otto
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

Now there is one more important question!

It takes 2xxms to load a .prg each time, but only 5xms for php with the same function.
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello ssbbs,

I believe it is important to appreciate the functionality of the server-side program. In my case, since I am using DBF, I require mod Harbour.

Currently, without SSE (Server-Sent Events), I utilize `setInterval()`, which is similar to `timer()` in Harbour. Then, I use `fetch` to check if there is any new data on the server.

Best regards,
Otto


myInterval = setInterval(LoadItems, 3000);



function LoadItems() {
let recipients = {
"recipient1": document.getElementById("nav_recip1").checked,
"miterledigt": document.getElementById("nav_miterledigt").checked,
};

fetch("chat.prg?items=" + ctime, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(recipients)
})

.then(res => {
if (res.ok) {
return res.text();
} else {
throw new Error(res.status + " " + res.statusText);
}
}).then(data => {

intervalcounter += 1

console.log("LoadItems data");
console.log(intervalcounter);

if (intervalcounter === 20) {
clearInterval(myInterval);
alert("Aktualisierung pausiert.");
intervalcounter = 0;
myInterval = setInterval(LoadItems, 3000);
}

if (data.startsWith("stop")) {
clearInterval(myInterval);
alert("beendet");
}

if (tmpdata != data) {
document.getElementById('browse').innerHTML = data;
scrollToBottom("browse");
tmpdata = data;
}
});
}
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

No! What my mean is: the time it takes for the client to receive a .prg file from the host!!

Receiving index.prg file from host takes 262ms.

Image
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello ssbbs,
I understand.
Can you please post the php code, too.

Best regards,
Otto
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

index_php.php:

Code: Select all | Expand

<?php
$hRes=(object)[
   'err_no'=>0   // 錯誤碼
  ,'errmsg'=>''  // 錯誤訊息
  ,'errtil'=>''  // 錯誤抬頭
  ,'lEnd'=>0     // 結束?<*0:No/1:Yes>
  ,'step'=>0     // 目前進度指標(every)
  ,'total'=>0    // 總筆數(total)
];
$func=(Integer)(isset($_POST["func"]) ? $_POST["func"] : (isset($_GET["func"]) ? $_GET["func"] :  -1 ));
if($func == 9){
  // hData := hb_jsondecode(hParam, 'data')
  // 送出 SSE 專用表頭
  header('Content-Type: text/event-stream');
  header('Cache-Control: no-cache');
  //
  $hRes->step=0;
  $hRes->total=100;
  sendMsg( json_encode( $hRes ) );
  //
  for($i=1; $i<=100; $i++){
    $hRes->step=i;
    $hRes->total=nTolRec;
    sendMsg( json_encode( $hRes ) );
  }
  $hRes->lEnd=1;
  sendMsg( json_encode( $hRes ));
  return;
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SSE test!!(PHP)</title>
<meta name="generator" content="WYSIWYG Web Builder 18 - https://www.wysiwygwebbuilder.com">
<link href="test.css" rel="stylesheet">
<link href="index_php.css" rel="stylesheet">
<script src="jquery-3.6.4.min.js"></script>
<script>   
   function go(){
         let url='index.prg';
         let data={
            a: 1
           ,b: 2
         };
         let param=url+'?func=9&data='+JSON.stringify(data);
   alert('Go...');
         source = new EventSource(param,{
            withCredentials: false // Whether to allow cross domain requests, default: false
         });
         // Handle postback events 'message'
         source.addEventListener('message',function(e){
           let o=JSON.parse(e.data); // analyze
           if(o.lEnd == 0){  // lEnd=1:End
             // Screen display progress processing
             let per=Math.round( o.step/o.total*100,2 ); // calculate percentage
             $('#progress').val(per);
             $('#progress_down').text( per+'%' );
           }else{
             source.close(); // Close SSE!! Don't send data back!!
             //
             if(o.err_no != 0){
               alert('Failed to read data!!'+o.err_msg, 'Error!!');
               return;
             }else{
               alert('success!!')
             }
           }
         });
         // Handle the event on start 'open'
         source.addEventListener('open', function(e) {
           // 
         });
         // Events when errors are handled 'error'
         source.addEventListener('error', function(e) {
           if(e.target.readyState == EventSource.CLOSED){
             alert('connect be close!!');
             source.close(); // stop SSE 
           }else{
             console.log('error!!');
           }
         });
         // Handle system status events 'status'
         source.addEventListener('status', function(e) {
           console.log('System status is now: ' + e.data);
         });
   }
</script>
</head>
<body>
   <input type="button" id="Button1" onclick="javascript:go();return false;" name="" value="Test" style="position:absolute;left:170px;top:143px;width:132px;height:52px;z-index:0;">
<!-- jquery -->
   <label for="" id="progress_down" style="position:absolute;left:0px;top:100px;width:465px;height:35px;line-height:35px;z-index:2;">Text</label>
   <span style="color:#000000;font-family:微軟正黑體;font-size:14px;"><strong id="progress_up"><br> Process wait...</strong></span>'
   <br><progress id="progress" value="0" max="100" style="height:48px;width:100%;">
   <br><label style="text-align:center;width:100%"></label>
</body>
</html>
<?php
function sendMsg($cRes){
  echo "event: message".PHP_EOL;
  echo "id: ".time().PHP_EOL;
  echo "retry: 15000".PHP_EOL;
  echo "data: $cRes".PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
  usleep(200);
}
?>
index_php.css

Code: Select all | Expand

body
{
   background-color: #FFFFFF;
   color: #000000;
   font-family: Arial;
   font-weight: normal;
   font-size: 13px;
   line-height: 1.1875;
   margin: 0;
   padding: 0;
}
#Button1
{
   border: 1px solid #2E6DA4;
   border-radius: 4px;
   background-color: transparent;
   background-image: none;
   color: #000000;
   font-family: Arial;
   font-weight: normal;
   font-style: normal;
   font-size: 16px;
   padding: 1px 6px 1px 6px;
   text-align: center;
   -webkit-appearance: none;
   margin: 0;
}
}
#Button1:focus
{
   outline: 0;
}
#progress_down
{
   border: 0px solid #CCCCCC;
   border-radius: 4px;
   background-color: transparent;
   background-image: none;
   color: #000000;
   font-family: Arial;
   font-weight: normal;
   font-style: normal;
   font-size: 16px;
   margin: 0;
   text-align: center;
   vertical-align: top;
   padding: 4px 4px 4px 4px;
}
#progress_down:focus
{
   outline: 0;
}

 
result:
Image

gif:
Image
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello ssbbs,
thank you.
With the prg version I get following error:
Error: Unterminated string ''
called from: __PP_PROCESS, line: 0
called from: ..\source\exec.prg, EXECUTE, line: 64

Source:
0062: a: 1
0063: ,b: 2
0064 => };
0065: let param=url+'?func=9&data='+JSON.stringify(data);
0066: alert('Go...');
php version is running but not showing the meter.

Maybe you can help me once more.
Best regards,
Otto
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

Otto wrote:Hello ssbbs,
With the prg version I get following error:
Error: Unterminated string ''
called from: __PP_PROCESS, line: 0
called from: ..\source\exec.prg, EXECUTE, line: 64

Source:
0062: a: 1
0063: ,b: 2
0064 => };
0065: let param=url+'?func=9&data='+JSON.stringify(data);
0066: alert('Go...');
This seems to be a javascript problem.

What about your execution environment?
PHP ?
Apache ?
Browser?

my environment is:
Apache/2.4.52 (Win32) OpenSSL/1.1.1m PHP/7.4.27
Chrome 113.0.5672.127
line ID: ssbbstw
WeChat ID: ssbbstw
User avatar
Otto
Posts: 6378
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Re: SSE example

Post by Otto »

Hello ssbbs,


I can't remember, do I have to change something in mod harbour setup that

<?prg

LOCAL cMethod := AP_Method()

?>

is working?

Thank you and best regards,
Otto


https://forums.fivetechsupport.com/view ... 82#p251070
********************************************************************
mod harbour - Vamos a la conquista de la Web
modharbour.org
https://www.facebook.com/groups/modharbour.club
********************************************************************
User avatar
ssbbs
Posts: 104
Joined: Mon Oct 17, 2005 3:03 am
Location: Taiwan

Re: SSE example

Post by ssbbs »

you need to modify the code I mentioned in the previous letter.
Because the mod_harbour I used has been modified.
It is different from the version you are using.

my code style:

Code: Select all | Expand

<?prg
:
:
?>
<html>
:
:
</html>
<?prg
:
:
?>
 
you must modify to:

Code: Select all | Expand

function main()
local .....
:
   TEMPLATE
<html> 
:
:
</html>
   ENDTEXT
:
:
:
return

 
line ID: ssbbstw
WeChat ID: ssbbstw
Post Reply