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 ($$$).

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;
}
?>

###########################################
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
###########################################