Page 1 of 1

PureBasic and REST APIs - A LIVE Working Example

Posted: Wed Aug 07, 2024 4:58 pm
by TI-994A
Most of my professional work these days involve the use of REST APIS. So, I'd decided to try consuming similar APIs from PureBasic applications. Fundamentally, this simply constitute two parts: sending a request query to a server that is hosting the API script, and reading the responses from it.

In this working POC example, the program sends two types of requests: one to save some data onto the server, and another to retrieve all the available saved data. The client is written in PureBasic, of course; but the API is a simplified, easy-to-understand PHP script. To better demonstrate the code, the script is being hosted live on one of my shared servers, and I will leave it accessible as long as the bandwidth doesn't kick into overdrive ($$$). :lol:

So, please feel free to post your own data and test it.

the PureBasic client:

Code: Select all

;==========================================================
;
;   pbPost demonstrates the use of the PureBasic 
;   network functions in consuming online REST APIs
;      
;   tested & working on:
;   1. Windows XP w/ PureBasic 5.44 LTS (x86)
;   2. Windows 8.1 w/ PureBasic 5.73 LTS (x64)
;   3. Windows 10 w/ PureBasic 6.11 LTS (x64)
;   4. macOS High Sierra w/ PureBasic 5.70 LTS (x64)
;   5. macOS Catalina w/ PureBasic 5.73 LTS (x64)
;   6. macOS Sonoma w/ PureBasic 6.11 LTS (arm64)
;   7. Ubuntu Linux 18.04.2 w/ PureBasic 6.11 LTS (x64)
;
;   by TI-994A - free to use, improve, share...
;
;   20th July 2024 - Singapore
;
;==========================================================
;
;   Please ensure that the following option is enabled:
;   Compiler > Compiler Options > Create Threadsafe Exe
;
;==========================================================

EnableExplicit

; the InitNetwork() call is required for
; earlier versions of PureBasic (below v6.0)
CompilerIf #PB_Compiler_Version < 600
  InitNetwork()
CompilerEndIf

; HTTP connections are performed over port 80
; HTTPS connections are performed over port 443
#httpPort = 80

; this is the URL to my test server
#host = "server.syed.sg"

; the network calls are performed in a thread
; to enable non-blocking asynchronous program
; execution. here the thread is set to terminate
; in 10 seconds if there is no network activity.
#threadTimeOut = 10000   ; (milliseconds)

; allocating 1k of memory for the buffer to
; be used by the ReceiveNetworkData() function.
; regardless of the size of the server responses,
; the function will read it in blocks of this
; value until all the data has been consumed.
#dataBufferLen = 1000

Enumeration
  #window
  #frame
  #editor
  #buttonPost
  #buttonRetrieve
  #textNewTitle
  #textOldTitle
  #textName
  #textCountry
  #stringName
  #stringCountry
EndEnumeration

Define event, appQuit
Global conn, *dataBuffer
Global.s headers, serverData

Macro header(header)
  headers + header + #CRLF$
EndMacro

; HTTP calls must be preceded by standard headers to
; notify the server of the required transmission protocols

; more information on the specifications can be found here: 
; https://en.wikipedia.org/wiki/List_of_HTTP_header_fields

; these are the minimum required headers notifying the server
; of the API script (pbPost.php), the request method (POST),
; the host (server.syed.sg), the client (this app), the type
; of connection (close - meaning close after transmission), and
; the format of the content being transmitted (application/x...).
header("POST /pbPost.php HTTP/1.1")
header("Host: " + #host)
header("Connection: close")
header("User-Agent: PureBasic Network Client")
header("Content-Type: application/x-www-form-urlencoded")

; this is a threaded function which processes
; the requests and responses to/from the server
Procedure post(*query)  
  Define clientEvent, nwQuit, dataLen = -1, cycle
  Define.s query, queryLen, httpHeader
  conn = OpenNetworkConnection(#host, #httpPort)
  *dataBuffer = AllocateMemory(#dataBufferLen)  
  If conn   
    query = PeekS(*query)
    queryLen = Str(StringByteLength(query, #PB_UTF8))
    httpHeader = headers + "Content-Length: " + 
                 queryLen + #CRLF$ + #CRLF$ + query        
    
    ; this function-call sends the entire pre-formatted
    ; header along with the query string to the API
    If SendNetworkString(conn, httpHeader, #PB_UTF8)      
      
      Repeat
        clientEvent = NetworkClientEvent(conn)                 
        If clientEvent                
          Select clientEvent          
            Case #PB_NetworkEvent_Data
              
              ; these function-calls receive the response from the server into the
              ; pre-allocated memory buffer - the data is then assigned to a string.
              ; this event will loop until all the response data has been consumed.
              dataLen = ReceiveNetworkData(conn, *dataBuffer, #dataBufferLen)              
              serverData + PeekS(*dataBuffer, dataLen, #PB_UTF8 | #PB_ByteLength)               
              
            Case #PB_NetworkEvent_Disconnect              
              nwQuit = #True        
          EndSelect
        EndIf            
      Until nwQuit   
    EndIf
  EndIf
  ProcedureReturn dataLen
EndProcedure

; this function creates the network thread, handles the 
; presentation of the response, closes the network 
; connection, and frees the allocated memory buffer.
Procedure postThread(query.s)  
  Define threadID = CreateThread(@post(), @query)
  If WaitThread(threadID, #threadTimeOut)
    
    ; an end-of-header delimiter was added by the API to
    ; conveniently display the response without the headers
    serverData = StringField(serverData, 2, "end_of_header")
    SetGadgetText(#editor, serverData)
    SetGadgetText(#stringName, "")
    SetGadgetText(#stringCountry, "")
  Else
    SetGadgetText(#editor, "The response timed out...")
  EndIf  
  If conn
    CloseNetworkConnection(conn)
  EndIf  
  If *dataBuffer
    FreeMemory(*dataBuffer)
  EndIf  
EndProcedure

; this function requests the API to UPDATE (action)
; the given NAME and COUNTRY values into the server
Procedure postNewEntry()
  Define.s name, country, query
  name = GetGadgetText(#stringName)  
  country = GetGadgetText(#stringCountry)  
  query = "action=update&" + "name=" + name + "&country=" + country
  postThread(query)
EndProcedure

; this function requests the API to RETRIEVE (action) and
; return the existing server data - the additional parameter
; values are not relevant for this action and will be ignored
Procedure retrieveEntries()
  Define.s query = "action=retrieve&name=&country="
  postThread(query)
EndProcedure

OpenWindow(#window, #PB_Ignore, #PB_Ignore, 640, 480, "REST API Example", 
           #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
FrameGadget(#frame, 20, 40, 480, 110, "")
EditorGadget(#editor, 20, 210, 600, 210)
ButtonGadget(#buttonPost, 510, 40, 110, 110, "POST")
ButtonGadget(#buttonRetrieve, 490, 430, 130, 30, "RETRIEVE")
TextGadget(#textNewTitle, 20, 10, 170, 25, "Participant's Information:")
TextGadget(#textOldTitle, 20, 180, 150, 25, "Previous Participants:")
TextGadget(#textName, 35, 60, 70, 25, "Name:")
TextGadget(#textCountry, 35, 100, 70, 25, "Country:")
StringGadget(#stringName, 110, 60, 350, 25, "")
StringGadget(#stringCountry, 110, 100, 350, 25, "")

Repeat
  event = WaitWindowEvent()
  Select event     
    Case #PB_Event_CloseWindow
      appQuit = #True    
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #buttonPost
          postNewEntry()          
        Case #buttonRetrieve
          retrieveEntries()
      EndSelect    
  EndSelect
Until appQuit

the PHP script:

Code: Select all

<?php
    // =============================================================
    //
    //    A simple API written in PHP to process 2 request types: 
    //    1. receive and save POSTed data to the server
    //    2. read and return the data from the server
    //
    //    This API will work with any client that complies with
    //    the query format and is able to process the responses.
    //
    //    by TI-994A - free to use, improve, share...
    //
    //    20th July 2024 - Singapore
    //
    // =============================================================


    // PHP stores query values in system-designated variables:
    // POST data is stored in a variable array named $_POST[]
    // GET data is stored in a variable array named $_GET[]
    // similarly, the array variable $_SERVER[] stores system
    // data pertaining to the client, host, protocols, etc.

    // the PureBasic app POSTs [action=Action&name=nameValue&country=countryValue]
    // here the named POST query values are being extracted into local variables 
    $action = trim($_POST['action']);
    $name = trim($_POST['name']);
    $country = trim($_POST['country']);


    // if the requester is the PureBasic application - an end-of-header delimiter
    // will be added before the response data to facilitate the removal of headers.
    if (trim($_SERVER['HTTP_USER_AGENT']) == "PureBasic Network Client")
        echo "end_of_header";
        

    // server data is usually stored in some SQL database for speed & efficiency - 
    // however, for simplicity and clarity, a simple file is being used here.

    // if the action value is [retrieve] - read and return the server data
    if ($action == "retrieve") {

        $index = 0;
        $records = file("datafile.txt");
        if ($records) {
            $records = array_reverse($records);   // list newest entries first
            foreach ($records as $record) {
                $index++;
                $recordSet .= $index . ". " . $record;
                if ($index > 99)   // limit response to 100 newest records only
                    break;
            }
            echo "Here are the records of the previous participants:" . PHP_EOL;
            echo $recordSet;
        } else {
            echo "Server file error! Records not retrieved.";
        }

    // if the action value is [update] - store the data into the server     
    } else if ($action == "update" && $name != "" && $country != "") {

        echo $_SERVER['HTTP_HOST'] . " says: Hello, " . $name . "." . PHP_EOL;
        $writeSuccess = true;
        
        $dataFile = fopen("datafile.txt", "a");
        if ($dataFile) {    

            // formats the record with the date and the requester's IP address
            $newData = $name . " from " . $country . " visited on " .
                date("d M Y") . " (IP: " . $_SERVER['REMOTE_ADDR'] . ")" . PHP_EOL;

            if (fwrite($dataFile, $newData)) {
                echo "Your records have been updated successfully!" . PHP_EOL;
                echo "Thank you for your participation. " . PHP_EOL;
                echo "Goodbye!" . PHP_EOL;
            } else {
                $writeSuccess = false;
            }
            fclose($dataFile);            
        } else {
            $writeSuccess = false;
        }
        if (!$writeSuccess) echo "Server file error! Records not updated.";
        

    } else {        

        // a friendly message to any requesters that do not meet the query parameters
        echo $_SERVER['HTTP_HOST'] . " says: Hello! You have reached a test site." . PHP_EOL;

    }
?>
I hope that it will prove useful to anyone interested in network programming with PureBasic. :D



###########################################

EDIT (08AUG2024): I have received some feedback regarding data posted with extended character sets. The application was not displaying them properly due to the PEEK() command settings. I have amended the code on line 120 to include the #PB_ByteLength flag as follows:

Code: Select all

serverData + PeekS(*dataBuffer, dataLen, #PB_UTF8 | #PB_ByteLength)

###########################################

UPDATE (09AUG2024): A new version of the PureBasic code, which uses the HTTPRequest() function, has been uploaded here:

https://www.purebasic.fr/english/viewto ... 60#p625660

###########################################

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Wed Aug 07, 2024 5:25 pm
by jacdelad
Oh no, why didn't you post this earlier? I tried to get the iTAC Rest API working and failed and gave up. Now that I leave my employer I don't need this anymore. :cry:

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 7:38 am
by dige
@TI-994A: nice! thanks for that

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 1:00 pm
by TI-994A
dige wrote: Thu Aug 08, 2024 7:38 am@TI-994A: nice! thanks for that
I appreciate the appreciation, dige. :D

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 1:11 pm
by TI-994A
There were some formatting issues earlier, but they seem to be fixed now with the applied code edits (amended in the original post).

However, an entry from the Middle-East is still baffling me. It was posted with the right-to-left (RTL) character set, and it is still not displaying properly.

It's supposed to be displayed like this:
عبد الرشيد from دبي visited on 22 Jul 2024 (IP: 83.110.250.231)
But it's displaying like this instead:
visited on 22 Jul 2024 (IP: 83.110.250.231) دبي from عبد الرشيد

Anyone have any ideas? :?

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 3:59 pm
by Fred
May be you can also have it working with HTTPRequest(), it should handle HTTPS and be easier to use.

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 6:10 pm
by Caronte3D
Fred wrote: Thu Aug 08, 2024 3:59 pm May be you can also have it working with HTTPRequest(), it should handle HTTPS and be easier to use.
In the past I needed to consume a Rest API (https) on an outdated Windows server (prior to Windows Server 2016) and I was unable to do it directly with HttpRequest, fortunately I was able to solve it with the excellent libcurl.pbi from Infratec.

The problem was something related to TSL or certificates, I can't remember now.

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 6:26 pm
by Quin
Caronte3D wrote: Thu Aug 08, 2024 6:10 pm The problem was something related to TSL or certificates, I can't remember now.
As it always is. I love SSL, but man it can be a pain sometimes. Especially configuring it... :(

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Thu Aug 08, 2024 7:23 pm
by infratec
I do a lot of stuff with REST-APIs and I always use HTTPRequest() and https without any problems.

NextCloud, EspoCRM, ProxMox, Paperless-NGX, SysPass, ... , and own REST-APIs written in NodeJS

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Fri Aug 09, 2024 2:09 pm
by TI-994A
Fred wrote: Thu Aug 08, 2024 3:59 pmMay be you can also have it working with HTTPRequest(), it should handle HTTPS and be easier to use.
Thanks for the suggestion, Fred. And thanks for your visit too. :D

I was indeed struggling to get it to work with HTTPS. Here's an HTTPRequest() version, which works with the API through both HTTP as well as HTTPS connections. I've made a small modification in the API code, to stamp the http protocol with the visitor data to demonstrate this. Simply change the URL prefix (line 94) between http and https to test this.

Code: Select all

;==========================================================
;   
;   pbHTTP is a modification of pbPost, which implements
;   the PureBasic HTTPRequest() function added in v5.70
;   instead of the older networking functions in consuming
;   online REST APIs - it also supports the HTTPS protocol
;
;   this is the forum thread for the orignal pbPost code:
;   www.purebasic.fr/english/viewtopic.php?p=625568#p625568
;      
;   tested & working on:
;   1. Windows 8.1 w/ PureBasic 5.73 LTS (x64)
;   2. Windows 10 w/ PureBasic 6.11 LTS (x64)
;   3. macOS High Sierra w/ PureBasic 5.70 LTS (x64)
;   4. macOS Catalina w/ PureBasic 5.73 LTS (x64)
;   5. macOS Sonoma w/ PureBasic 6.11 LTS (arm64)
;   6. Ubuntu Linux 18.04.2 w/ PureBasic 6.11 LTS (x64)
;
;   by TI-994A - free to use, improve, share...
;
;   9th August 2024 - Singapore
;
;==========================================================
;
;   Please ensure that the following option is enabled:
;   Compiler > Compiler Options > Create Threadsafe Exe
;
;==========================================================

EnableExplicit

; the InitNetwork() call is required for
; earlier versions of PureBasic (below v6.0)
CompilerIf #PB_Compiler_Version < 600
  InitNetwork()
CompilerEndIf

; the http protocol transfers data in plain text
; the https protocol transfers data in encrypted format
#http = "http://"
#https = "https://"

; http servers typically respond with status codes
; prior to sending the actual requested datasets.
; more information on these codes can be found here:
; https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
#httpSuccess = 200

; these are the URLs to the test server and API script
#host = "server.syed.sg"
#apiURL = "/pbPost.php"

Enumeration
  #window
  #frame
  #editor
  #buttonPost
  #buttonRetrieve
  #textNewTitle
  #textOldTitle
  #textName
  #textCountry
  #stringName
  #stringCountry
EndEnumeration

Define event, appQuit

; HTTP calls must be preceded by standard headers to
; notify the server of the required transmission protocols.

; more information on the specifications can be found here: 
; https://en.wikipedia.org/wiki/List_of_HTTP_header_fields.

; these are the minimum required headers notifying the server
; of the API script (pbPost.php), the request method (POST),
; the host (server.syed.sg), the client (this app), the type
; of connection (close - meaning close after transmission), and
; the format of the content being transmitted (application/x...).

NewMap headers.s()
headers("Host") = #host
headers("Connection") = "close"
headers("User-Agent") = "PureBasic Network Client"
headers("Content-Type") = "application/x-www-form-urlencoded"

; this is the main function which processes
; the requests and responses to/from the server.
Procedure post(query.s)    
  Shared headers()
  Define.s queryLen, serverData, apiURL, httpRequest.i
  queryLen = Str(StringByteLength(query, #PB_UTF8))
  headers("Content-Length") = queryLen  
  apiUrl = #https + #host + #apiURL
  
  ; HTTPRequest() is an integrated function that performs all the networking
  ; actions, from opening, configuring, and negotiating HTTP/HTTPS connections,
  ; formatting and sending queries and headers, to configuring, receiving, and
  ; formatting the responses which could then be read by the HTTPInfo() function.    
  httpRequest = HTTPRequest(#PB_HTTP_Post, apiURL, query, #Null, headers())
  
  If httpRequest
    If Val(HTTPInfo(httpRequest, #PB_HTTP_StatusCode)) = #httpSuccess
      serverData = HTTPInfo(httpRequest, #PB_HTTP_Response)    
      
      ; an end-of-header delimiter was added by the API to
      ; conveniently display the response without the headers.     
      serverData = StringField(serverData, 2, "end_of_header")
      
      SetGadgetText(#editor, serverData)    
      SetGadgetText(#stringName, "")
      SetGadgetText(#stringCountry, "")      
    EndIf      
  EndIf  
  
  If Trim(serverData) = ""
    SetGadgetText(#editor, "Network connection error!") 
    Debug HTTPInfo(httpRequest, #PB_HTTP_Response)    
  EndIf
  
  FinishHTTP(httpRequest)  
EndProcedure

; this function requests the API to UPDATE (action)
; the given NAME and COUNTRY values into the server
Procedure postEntry()
  Define.s name, country, query
  name = GetGadgetText(#stringName)  
  country = GetGadgetText(#stringCountry)
  query = URLEncoder("action=update&" + "name=" + name + "&country=" + country)
  post(query)
EndProcedure

; this function requests the API to RETRIEVE (action) and
; return the existing server data - the additional parameter
; values are not relevant for this action and will be ignored
Procedure retrieveEntries()
  Define.s query = URLEncoder("action=retrieve&name=&country=")
  post(query)
EndProcedure

OpenWindow(#window, #PB_Ignore, #PB_Ignore, 640, 480, "REST API w/HTTPRequest", 
           #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
FrameGadget(#frame, 20, 40, 480, 110, "")
EditorGadget(#editor, 20, 210, 600, 210)
ButtonGadget(#buttonPost, 510, 40, 110, 110, "POST")
ButtonGadget(#buttonRetrieve, 490, 430, 130, 30, "RETRIEVE")
TextGadget(#textNewTitle, 20, 10, 170, 25, "Participant's Information:")
TextGadget(#textOldTitle, 20, 180, 150, 25, "Previous Participants:")
TextGadget(#textName, 35, 60, 70, 25, "Name:")
TextGadget(#textCountry, 35, 100, 70, 25, "Country:")
StringGadget(#stringName, 110, 60, 350, 25, "")
StringGadget(#stringCountry, 110, 100, 350, 25, "")

Repeat
  event = WaitWindowEvent()
  Select event     
    Case #PB_Event_CloseWindow
      appQuit = #True    
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #buttonPost
          postEntry()          
        Case #buttonRetrieve
          retrieveEntries()
      EndSelect    
  EndSelect
Until appQuit

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Fri Aug 09, 2024 3:44 pm
by hdt888
Magic !. I'm learning about this.

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Sat Aug 10, 2024 12:01 am
by idle
I made a modification to use https with HTTPRequestMemory its not quite the same as I reimplemented the php script in PB

Code: Select all

;==========================================================
;
;   pbPost demonstrates the use of the PureBasic 
;   network functions in consuming online REST APIs
;      
;   tested & working on:
;   1. Windows XP w/ PureBasic 5.44 LTS (x86)
;   2. Windows 8.1 w/ PureBasic 5.73 LTS (x64)
;   3. Windows 10 w/ PureBasic 6.11 LTS (x64)
;   4. macOS High Sierra w/ PureBasic 5.70 LTS (x64)
;   5. macOS Catalina w/ PureBasic 5.73 LTS (x64)
;   6. macOS Sonoma w/ PureBasic 6.11 LTS (arm64)
;   7. Ubuntu Linux 18.04.2 w/ PureBasic 6.11 LTS (x64)
;
;   by TI-994A - free to use, improve, share...
;
;   20th July 2024 - Singapore
;
;==========================================================
;
;   Please ensure that the following option is enabled:
;   Compiler > Compiler Options > Create Threadsafe Exe
;
;==========================================================

EnableExplicit

; the InitNetwork() call is required for
; earlier versions of PureBasic (below v6.0)
CompilerIf #PB_Compiler_Version < 600
  InitNetwork()
CompilerEndIf

; HTTP connections are performed over port 80
; HTTPS connections are performed over port 443
; this is the URL to my test server
;#httpPort = 80; 443
;#host = "http://server.syed.sg"

#httpPort = 443
#host = "https://atomicwebserver.com"

; the network calls are performed in a thread
; to enable non-blocking asynchronous program
; execution. here the thread is set to terminate
; in 10 seconds if there is no network activity.
#threadTimeOut = 10000   ; (milliseconds)

; allocating 1k of memory for the buffer to
; be used by the ReceiveNetworkData() function.
; regardless of the size of the server responses,
; the function will read it in blocks of this
; value until all the data has been consumed.
#dataBufferLen = 1000

Enumeration
  #window
  #frame
  #editor
  #buttonPost
  #buttonRetrieve
  #textNewTitle
  #textOldTitle
  #textName
  #textCountry
  #stringName
  #stringCountry
EndEnumeration

Define event, appQuit
Global conn, *dataBuffer
Global.s headers, serverData

Macro header(header)
  headers + header + #CRLF$
EndMacro

; HTTP calls must be preceded by standard headers to
; notify the server of the required transmission protocols

; more information on the specifications can be found here: 
; https://en.wikipedia.org/wiki/List_of_HTTP_header_fields

Procedure post(*query) 
  
  Protected HttpRequest
  Protected NewMap header$() 
  
  header$("User-Agent") = "PureBasic Network Client"
  header$("Content-Type") = "application/x-www-form-urlencoded"
    
  Protected uri.s = #host + "/pbPost.php?" + PeekS(*query,-1,#PB_Unicode)
      
  HttpRequest = HTTPRequestMemory(#PB_HTTP_Get,uri,0,0, #PB_HTTP_WeakSSL | #PB_HTTP_Debug,header$())
  If HttpRequest
    Debug "StatusCode: " + HTTPInfo(HTTPRequest, #PB_HTTP_StatusCode)
    serverData = URLDecoder(HTTPInfo(HTTPRequest, #PB_HTTP_Response)) 
    FinishHTTP(HTTPRequest)
  Else
    Debug "Request creation failed"
  EndIf


EndProcedure 

; this function creates the network thread, handles the 
; presentation of the response, closes the network 
; connection, and frees the allocated memory buffer.
Procedure postThread(query.s)  
  Define threadID = CreateThread(@post(), @query)
  If WaitThread(threadID, #threadTimeOut)
    ; an end-of-header delimiter was added by the API to
    ; conveniently display the response without the headers
    SetGadgetText(#editor, serverData)
    SetGadgetText(#stringName, "")
    SetGadgetText(#stringCountry, "")
  Else
    SetGadgetText(#editor, "The response timed out...")
  EndIf  
 
EndProcedure

; this function requests the API to UPDATE (action)
; the given NAME and COUNTRY values into the server
Procedure postNewEntry()
  Define.s name, country, query
  name = GetGadgetText(#stringName)  
  country = GetGadgetText(#stringCountry)  
  query = URLEncoder("action=update&" + "name=" + name + "&country=" + country) 
  postThread(query)
EndProcedure

; this function requests the API to RETRIEVE (action) and
; return the existing server data - the additional parameter
; values are not relevant for this action and will be ignored
Procedure retrieveEntries()
  Define.s query = "action=retrieve&name=&country="
  postThread(query)
EndProcedure

OpenWindow(#window, #PB_Ignore, #PB_Ignore, 640, 480, "REST API Example", 
           #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
FrameGadget(#frame, 20, 40, 480, 110, "")
EditorGadget(#editor, 20, 210, 600, 210)
ButtonGadget(#buttonPost, 510, 40, 110, 110, "POST")
ButtonGadget(#buttonRetrieve, 490, 430, 130, 30, "RETRIEVE")
TextGadget(#textNewTitle, 20, 10, 170, 25, "Participant's Information:")
TextGadget(#textOldTitle, 20, 180, 150, 25, "Previous Participants:")
TextGadget(#textName, 35, 60, 70, 25, "Name:")
TextGadget(#textCountry, 35, 100, 70, 25, "Country:")
StringGadget(#stringName, 110, 60, 350, 25, "")
StringGadget(#stringCountry, 110, 100, 350, 25, "")

Repeat
  event = WaitWindowEvent()
  Select event     
    Case #PB_Event_CloseWindow
      appQuit = #True    
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #buttonPost
          postNewEntry()          
        Case #buttonRetrieve
          retrieveEntries()
      EndSelect    
  EndSelect
Until appQuit
The php in PB

Code: Select all

XIncludeFile "Atomic_Web_Server3.pbi"

#Server_IP = "192.168.1.7"  

Global tid,event,server1,title.s = "atomic_webserver 3"

Procedure.s DateUTCS() 
  Protected date = DateUTC()
  Protected Week.s = "Sun, Mon,Tue,Wed,Thu,Fri,Sat"
  Protected MonthsOfYear.s = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec" 
  Protected DayOfWeek.s = StringField("Sun, Mon,Tue,Wed,Thu,Fri,Sat", DayOfWeek(Date) + 1, ",")
  Protected Day = Day(Date)
  Protected Month.s = StringField("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", Month(Date), ",")
  Protected Year.s = Str(Year(Date))
  ProcedureReturn DayOfWeek + " " + Str(day) + " " + Month + " " + year + " " + FormatDate("%hh:%ii:%ss GMT", Date)
EndProcedure 

Procedure CBAPI(*request.Atomic_Server_Request) 
   
  Protected content.s,name.s,country.s   
  Protected *client.Atomic_Server_Client = *request\clientID 
  Static number 
   
  If FindMapElement(*request\RequestHeaders(),"User-Agent") 
    If *request\RequestHeaders() = "PureBasic Network Client" 
      If FindMapElement(*request\parameters(),"action") 
        
        If *request\parameters() = "update" 
          
          If FindMapElement(*request\parameters(),"name") 
            If Len(*request\parameters()) > 60 
              name = Left(*request\parameters(),60) 
            Else 
              name = *request\parameters() 
            EndIf 
          EndIf  
          
          If FindMapElement(*request\parameters(),"country") 
            If Len(*request\parameters()) > 60 
              country = Left(*request\parameters(),60) 
            Else 
              country = *request\parameters() 
            EndIf 
          EndIf  
          
          If name <> "" And country <> ""   
            
            number + 1 
            If ListSize(visitors()) > 100 
              LastElement(visitors()) 
              DeleteElement(visitors()) 
            EndIf  
            FirstElement(visitors()) 
            AddElement(visitors()) 
            visitors()\name = name 
            visitors()\country = country 
            visitors()\IP = *client\Ip 
            visitors()\id = number 
            visitors()\date = DateUTCS() 
            
            content = "Recent visitors" + #CRLF$
            content + Str(number) + " " + visitors()\name  + " from " +  visitors()\country +  " visited " + visitors()\date  + " ( " + visitors()\IP + ")" 
                       
          EndIf 
          
        ElseIf *request\parameters() = "retrieve" 
          
          content = "Recent visitors" + #CRLF$
          ForEach visitors() 
            content + visitors()\id + " " + visitors()\name  + " from " +  visitors()\country +  " visited " + visitors()\date  + " ( " + visitors()\IP + ")" + #CRLF$
          Next   
        EndIf   
        
        If content <> "" 
         
          *request\ContentType = "text/plain"
          ProcedureReturn UTF8(content)    
          
        EndIf 
        
      EndIf  
    EndIf 
  EndIf 
    
EndProcedure   

server1 = Atomic_Server_Init(title,"./www/",#Server_IP,"atomicwebserver.com",443)  
Atomic_Server_Init_TLS(server1,"./certs/","atomicwebserver.com","certificate.crt","private.key","ca_bundle.crt")   
Atomic_Server_Add_Handler(server1,"atomicwebserver.com/pbPost.php",@CBAPI()) 

OpenConsole()
tid = CreateThread(@updateDNS(),600000)
Atomic_Server_start(server1,1,1) 
Repeat 
Until Input() = "quit" 
gquit =1 
Atomic_Server_Exit(server1)

CloseConsole() 


Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Sat Aug 10, 2024 7:06 am
by TI-994A
idle wrote: Sat Aug 10, 2024 12:01 amI made a modification to use https with HTTPRequestMemory...
Hi idle. I see that you've included the #PB_HTTP_WeakSSL flag with the function. I've been having some issues implementing the #PB_HTTP_Asynchronous directive. Perhaps asynchronous transmissions require some special handling? :?

Would you have any idea?

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Sat Aug 10, 2024 1:05 pm
by idle
I figure if it's in a thread there's not much point in doing it with the asynchronous call as it already is threaded. I will have a look in my morning.

Re: PureBasic and REST APIs - A LIVE Working Example

Posted: Sun Aug 11, 2024 12:01 am
by idle
This works with the #PB_HTTP_Asynchronous
I only used the weak SSL flag as I didn't know the parameter could be and thought that might have been the problem

Code: Select all

; this is the main function which processes
; the requests and responses to/from the server.
Procedure post(query.s)    
  Shared headers()
  Define.s queryLen, serverData, apiURL, httpRequest.i,progress.i
  queryLen = Str(StringByteLength(query, #PB_UTF8))
  headers("Content-Length") = queryLen  
  apiUrl = #https + #host + #apiURL
  
  ; HTTPRequest() is an integrated function that performs all the networking
  ; actions, from opening, configuring, and negotiating HTTP/HTTPS connections,
  ; formatting and sending queries and headers, to configuring, receiving, and
  ; formatting the responses which could then be read by the HTTPInfo() function.    
  httpRequest = HTTPRequest(#PB_HTTP_Post, apiURL, query, #PB_HTTP_Asynchronous, headers())
  
  If httpRequest
    
    Repeat 
      progress = HTTPProgress(HttpRequest)
      
      Select progress 
        Case #PB_HTTP_Success 
          serverData = HTTPInfo(httpRequest, #PB_HTTP_Response)    
          ; an end-of-header delimiter was added by the API to
          ; conveniently display the response without the headers.     
          serverData = StringField(serverData, 2, "end_of_header")
          
          SetGadgetText(#editor, serverData)    
          SetGadgetText(#stringName, "")
          SetGadgetText(#stringCountry, "")      
          Break 
        Case #PB_HTTP_Failed,#PB_HTTP_Aborted 
          Break 
        Default 
          Debug "Current download: " + Progress
      EndSelect 
      Delay(100)
    ForEver      
  EndIf  
  
  If Trim(serverData) = ""
    SetGadgetText(#editor, "Network connection error!") 
    Debug HTTPInfo(httpRequest, #PB_HTTP_Response)    
  EndIf
  
  FinishHTTP(httpRequest)  
EndProcedure

I'm not sure but there might be a bug in the documentations as it shows that FinishHTTP() returns a buffer.
and it crashed when I did that.