PureBasic and REST APIs - A LIVE Working Example

Share your advanced PureBasic knowledge/code with the community.
User avatar
TI-994A
Addict
Addict
Posts: 2700
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

PureBasic and REST APIs - A LIVE Working Example

Post 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

###########################################
Last edited by TI-994A on Fri Aug 09, 2024 2:16 pm, edited 2 times in total.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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:
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
dige
Addict
Addict
Posts: 1391
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post by dige »

@TI-994A: nice! thanks for that
"Daddy, I'll run faster, then it is not so far..."
User avatar
TI-994A
Addict
Addict
Posts: 2700
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post by TI-994A »

dige wrote: Thu Aug 08, 2024 7:38 am@TI-994A: nice! thanks for that
I appreciate the appreciation, dige. :D
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
TI-994A
Addict
Addict
Posts: 2700
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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? :?
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post by Fred »

May be you can also have it working with HTTPRequest(), it should handle HTTPS and be easier to use.
User avatar
Caronte3D
Addict
Addict
Posts: 1355
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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.
Quin
Addict
Addict
Posts: 1127
Joined: Thu Mar 31, 2022 7:03 pm
Location: Colorado, United States
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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... :(
infratec
Always Here
Always Here
Posts: 7582
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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
User avatar
TI-994A
Addict
Addict
Posts: 2700
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
hdt888
User
User
Posts: 47
Joined: Sun Jul 07, 2024 8:42 am

Re: PureBasic and REST APIs - A LIVE Working Example

Post by hdt888 »

Magic !. I'm learning about this.
PB 5.x + 6.x + Win10. Feel the ...Pure... Power.
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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() 

User avatar
TI-994A
Addict
Addict
Posts: 2700
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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?
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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.
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PureBasic and REST APIs - A LIVE Working Example

Post 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.
Post Reply