Fetch a HTML document from a HTTPS server with BlitzMax NG

Started by Midimaster, October 06, 2023, 09:44:35

Previous topic - Next topic

Midimaster

Auto-Update my App from my Server

I want to write code that enables the user  to update the app within the app.

There are several steps to do:
  • Find out the date of my EXE on my server and publish it
  • A BlitzMax function to fetch this result
  • Compare the dates and decide to update
  • Download the new version
  • A System Call of an Installer and close the app
  • Restart the app


Some of the steps are very easy, but some seem to be not possible with the current version of BlitzMax NG. So this Worklog reportsabout the problems and solutions and in the end it can be a tutorial for others.

Step 1: Find out the date of my EXE on my server and publish it

This was very easy. A PHP-Script creates a HTML-document which contains only the TimeStamp.

The PHP-function filemtime() return: "Seconds since 1970-01-01":

FileTime.php
<?php
    ScanFile
('MyApp.exe');

    function 
ScanFile($datei){
        
$filename 'folder/' $datei;
        if (
file_exists($filename)) {
           echo 
filemtime($filename);
        } else {
           echo 
'0';
        }
    }
?>

code again as 'qoute' (Syntaxbomb seems to have a problem with php-snipplets):
Quote<?php
    ScanFile('MyApp.exe');

    function ScanFile($datei){
        $filename = 'folder/' . $datei;
        if (file_exists($filename)) {
          echo filemtime($filename);
        } else {
          echo  '0';
        }
    }
?>

Note: This will be expanded with a #GET-Query in future to ask for different files.



Step 3: The Timestamp of the current EXE on the users computer

This was also easy. The BlitzMax function FileTime() already return the same UNIX-Timestamp like in PHP.

Additional the new TChrono module offers something like TimeStamp for the current time. My function UnixNow() returns the current date in the same format like the PHP function: "Seconds since 1970-01-01"

UnixTime()
Print "File time=" + FileTime("MyApp.exe")

Print UnixNow()

Function UnixNow:Long()
    Local Time:ULong =  TChrono.GetTimestamp()
    Return time/1000000000
End Function

Note: TCHrono is descriped as "returning nano-seconds", but this is fake. It simply asks for millisecs and multiplies this value with 106 when returning



Step 2: A BlitzMax function to fetch this result

Makes heavy problems at the moment: It looks like BlitzMax is not able to fetch HTML-documents from a HTTPS-Server. The Bah.LibCurl seems to be not longer in the Bah.Mod and each experiment with this ends in ACCESS DENIED frm the server.


...back from Egypt

Midimaster

Step 2: A BlitzMax function to fetch the result of the server-php

There is a big confusion about fetching HTML-documents since the protocol changed to HTTPS on most of the servers.

At first I would guess, you can complete forget to do this on old versions of BlitzMax. With Vanilla BlitzMax you can only contact HTTP-Servers. And with out-dated BlitzMax NG you will have TYPE CASTING problems in the compiler.

The first version, that can handle this is BlitzMax NG newer than BMK 3.5.1 and GCC 8.1.0

Also I have to say, that the Bah.LibCurl is out. We now need the Net.Mod module, which is avaiable at
https://github.com/bmx-ng/net.mod

In the examples you will find example8, which show us the way

SuperStrict
Import Net.libcurl
Local curl:TCurlEasy = TCurlEasy.Create()

curl.setWriteString()
curl.setOptInt(CURLOPT_VERBOSE, 1)
curl.setOptInt(CURLOPT_FOLLOWLOCATION, 1)

curl.setOptString(CURLOPT_CAINFO, "../certificates/cacert.pem") ' the cert bundle
curl.setOptString(CURLOPT_URL, "https://www.google.co.uk")

Local result:Int = curl.perform()
If result Then
Print CurlError(result)
End
End If

curl.cleanup()
Print curl.toString()

for me this mean...

copying the cacert.pem into my app dir, change the URL to my FileDate.php and store the result in a variable:

SuperStrict
Import Net.libcurl

Print ServerFileTime("")

Function ServerFileTime:Long(URL:String)
Local curl:TCurlEasy = TCurlEasy.Create()
curl.setWriteString()
curl.setOptString(CURLOPT_CAINFO, "cacert.pem")
curl.setOptString(CURLOPT_URL, "https://www.myserver.de/filetime.php")
curl.perform()
curl.cleanup()
Return curl.toString()[3..13].toInt()
End Function

At the moment I still struggle with 3 strange characters at the beginning of the string. As a workaround I use the SLICE [3..13]
...back from Egypt

Derron

if stuff at the begin of the output happens - check if you output utf8 or similar encoding signs. You can output various headers with php.


bye
Ron

Midimaster

Step 4: Download the new version

With the new Net.mod this is no problem. You nearly use the same code like for html-documents, but this time we send the arriving bytes to a stream and not to a text.

Downloader.bmx:
SuperStrict
Import Net.libcurl

ServerDownload()

Function ServerDownload()
Local curl:TCurlEasy = TCurlEasy.Create()
Local Stream:TStream = WriteStream("downloaded.exe")
curl.setWriteStream Stream
curl.setOptString(CURLOPT_CAINFO, "cacert.pem")
curl.setOptString(CURLOPT_URL, "https://myserver.com/folder/MyNewApp.exe")
curl.perform()
curl.cleanup()
CloseStream Stream
End Function

Again you need the file cacert.pem next to the Downloader.bmx. This contains the certificates which are necessary for HTTPS.

When running the app you will get the downloaded.exe next to the Downloader.bmx

Note: It is to you to add more comfort to this function. This code is minimalistic for tutorial purposes.



Step 5 & 6: Call an Installer, close the app and restart the app

Therefore we have the type TProcess and it's functions, which is already part of the default BlitzMax.

Main.bmx:
Graphics 800,600

Repeat
    If KeyHit(KEY_U)
        StartOtherAppAndCloseMe()
    Endif
    Flip
Until AppTerminate()

Function StartOtherAppAndCloseMe()
Local Process1:TProcess = CreateProcess("Installer.exe")
DetachProcess Process1
End
End Function

This three lines are sufficient:
The 1st line CreateProcess() starts a second app Installer.exe.
The 2nd line DetachProcess() divides the connections between both apps.
And the 3rd line End() closes the main app.

Note: If you dont use the DetachProcess() the second app keeps connected to the main app. This means if you quit the main app, also the second app will quit.


To restart the main app you code the same procedure in Installer.exe.

Installer.exe:
Graphics 800,600

Repeat
    If KeyHit(KEY_U)
        DeleteFile "MainApp.exe"
        RenameFile "Downloaded.exe" , "MainApp.exe"
    Endif
   Flip
Until AppTerminate()
StartOtherAppAndCloseMe()

Function StartOtherAppAndCloseMe()
Local Process1:TProcess = CreateProcess("MainApp.exe")
DetachProcess Process1
End
End Function

...back from Egypt