LessonsExpert: BeyondLesson 56

The web and the internet

Fetch pages and data with URL, send forms and JSON with post, set headers, load in the background, download files, and run a small web server of your own.

  • Expert
  • Lesson 56 of 83
  • About 12 minutes
  • 8 examples

A web server for the examples

So that this lesson's examples run anywhere, even with no internet, each one starts a tiny web server on your own computer first, with the HTTP server library that comes with OXT-Beyond: httpdStart names the handler that answers each request and a port (0 lets the system choose a free one, which it puts in it), and the handler answers with httpdResponse. The address is then http://127.0.0.1: and the port: 127.0.0.1 always means this computer.

Everything on the client side works the same with any address on the internet, such as https://example.com/. And the server side is useful too: a program can serve a page to a phone on the same network, or answer another program.

Fetching: URL

URL with an http: or https: address is a container, like URL "file:" (Reading and writing files): reading it fetches the page. The script waits until it arrives, and the result says what went wrong, if anything:

ExampleRun it in the playground
on serve pSocket, pRequest
   if pRequest["resource"] is "/hello" then
      httpdResponse pSocket, 200, "Hello, web"
   else
      httpdResponse pSocket, 404, "No such page"
   end if
end serve

httpdStart "serve", 0
put it into tPort

put URL ("http://127.0.0.1:" & tPort & "/hello")
put URL ("http://127.0.0.1:" & tPort & "/missing") into tPage
if the result is not empty then put "problem:" && the result

httpdStop tPort
Output
Hello, web
problem: error 404 Not Found

Asking with parameters

A request can carry values in its address, after a ?: ?q=tea&page=2. A value with spaces, & or accents must be encoded first. Encode text as UTF-8 and then with urlEncode, which is what servers expect today:

ExampleRun it in the playground
on serve pSocket, pRequest
   httpdResponse pSocket, 200, "you searched for" && textDecode(pRequest["parameters"]["q"], "UTF-8") && \
         "on page" && pRequest["parameters"]["page"]
end serve

httpdStart "serve", 0
put it into tPort

put urlEncode(textEncode("café & tea", "UTF-8")) into tQuery
put tQuery
put URL ("http://127.0.0.1:" & tPort & "/search?q=" & tQuery & "&page=2")
httpdStop tPort
Output
caf%C3%A9+%26+tea
you searched for café & tea on page 2

Sending: post

post sends data to a server, as a form does, and puts the server's answer in it. Form data is name=value pairs joined with &, each value encoded. To send JSON, say so with a Content-Type header in the httpHeaders, the extra headers sent with each request:

ExampleRun it in the playground
on serve pSocket, pRequest
   if pRequest["content"] is empty then
      httpdResponse pSocket, 200, "form: name is" && pRequest["parameters"]["name"]
   else
      httpdResponse pSocket, 200, "body:" && pRequest["content"]
   end if
end serve

httpdStart "serve", 0
put it into tPort

post "name=" & urlEncode("Ada Lovelace") to URL ("http://127.0.0.1:" & tPort & "/save")
put it

set the httpHeaders to "Content-Type: application/json"
post "{" & quote & "name" & quote & ":" & quote & "Ada" & quote & "}" to URL ("http://127.0.0.1:" & tPort & "/save")
set the httpHeaders to empty
put it

httpdStop tPort
Output
form: name is Ada Lovelace
body: {"name":"Ada"}

Set the httpHeaders back to empty when you are done, or the headers go with every later request too. A web service that needs a key usually takes it in a header: Authorization: Bearer and the key.

What the server sent back

The server's answer starts with headers of its own: the kind of data, its size, cookies. libURLLastRHHeaders() gives those of the last request, a header a line. HTTP ends each line with a carriage return before the line feed, so take those out first:

ExampleRun it in the playground
on serve pSocket, pRequest
   httpdResponse pSocket, 200, "{}", "Content-Type: application/json"
end serve

httpdStart "serve", 0
put it into tPort
get URL ("http://127.0.0.1:" & tPort & "/data")
put libURLLastRHHeaders() into tHeaders
replace numToChar(13) with empty in tHeaders
put line 1 of tHeaders
filter tHeaders with "Content-Type:*"
put tHeaders
httpdStop tPort
Output
HTTP/1.1 200 OK
Content-Type: application/json

Loading in the background

Reading URL makes the script wait, and a slow server makes the program wait with it. load URL starts the download and returns at once; when it has finished, the message you name arrives with the address and its status, and the page is then in the cache, where URL reads it at once. unload URL frees the cache:

ExampleRun it in the playground
local sPort

on serve pSocket, pRequest
   httpdResponse pSocket, 200, "loaded in the background"
end serve

on pageLoaded pAddress, pStatus
   put "status:" && pStatus
   put URL pAddress
   unload URL pAddress
   httpdStop sPort
end pageLoaded

httpdStart "serve", 0
put it into sPort
load URL ("http://127.0.0.1:" & sPort & "/news") with message "pageLoaded"
put "the program carries on while it loads"
Output
the program carries on while it loads
status: cached
loaded in the background

The status is cached when the download worked, and error, timeout or contacted when it did not get that far. URLStatus(address) gives the same while it is still going, with how much has arrived, for a progress bar.

Downloading a file

A download is a fetch put straight into a binfile: URL, which keeps the bytes exactly as they came (Binary data):

ExampleRun it in the playground
on serve pSocket, pRequest
   httpdResponse pSocket, 200, binaryEncode("CCa3", 0, 255, "PNG")
end serve

httpdStart "serve", 0
put it into tPort
put specialFolderPath("temporary") & "/oxt-lesson-download.bin" into tPath

put URL ("http://127.0.0.1:" & tPort & "/file") into tData
if the result is empty then
   put tData into URL ("binfile:" & tPath)
   put the number of bytes of URL ("binfile:" & tPath) && "bytes saved"
end if
delete file tPath
httpdStop tPort
Output
5 bytes saved

Opening the user's browser

launch url opens an address in the user's own web browser, or a mailto: address in their email program. It does not fetch anything for the script:

ScriptPut it in an object's script
on mouseUp
   launch url "https://github.com/SethMorrowSoftware/OpenXTalk-Beyond"
end mouseUp

Try it yourself

Build a tiny web service and its client. The server answers /square?n= with JSON such as {"n": 12, "square": 144}, and a 400 error when n is not a number. The client asks for 12 and for twelve, reads the JSON, and puts the square or the problem.

Show a solution
ExampleRun it in the playground
on serve pSocket, pRequest
   local tN, tAnswer
   put pRequest["parameters"]["n"] into tN
   if tN is not a number then
      httpdResponse pSocket, 400, "n must be a number"
      exit serve
   end if
   put tN + 0 into tAnswer["n"]
   put tN * tN into tAnswer["square"]
   httpdResponse pSocket, 200, JSONExport(tAnswer), "Content-Type: application/json"
end serve

httpdStart "serve", 0
put it into tPort
repeat for each item tAsk in "12,twelve"
   put URL ("http://127.0.0.1:" & tPort & "/square?n=" & urlEncode(tAsk)) into tReply
   if the result is not empty then
      put tAsk & ":" && the result & "," && tReply
   else
      put JSONImport(tReply) into tData
      put tData["n"] && "squared is" && tData["square"]
   end if
end repeat
httpdStop tPort
Output
12 squared is 144
twelve: error 400 Bad Request, n must be a number

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.