Access Http content over the Web
The wwHttp class make it very easy to access Http content from FoxPro by providing simple functions to retrieve and post content to and from a Web Server, as well as providing full access to advanced Http features.
Web content includes anything that is accessible via the Http protocol. This can be Html text, plain text, JSON or XML data, binary content like Zip files, images or PDFs. You can easily use any Http Verb like GET, POST, PUT, DELETE etc., access secure content, set and access Http headers and automatically handle authentication, redirect and GZip compression/decompression.
The simplest Thing Possible
Let's start with a few common scenarios using the simplest thing possible. We'll talk about error handling and more advanced options later on.
Simple Content Download
First off lets download some Web content from a Url - in this case retrieving a plain Html response which is returned as Html text.
*** For Shareware versions replace with `DO wwClient` or `DO wconnect`
DO wwHttp && load libraries
loHttp=CREATEOBJECT("wwHttp")
lcHtml = loHttp.Get("https://west-wind.com")
? lcHtml && Raw Html
Any of wwhttp's access methods (like .Get(), .Post() etc.) can retrieve any kind of data but in this case it returns an Html string. Other common return types might be JSON, XML or binary data like PDF, images, Zip etc.
Binary content works the same way and is also returned as FoxPro string or can optionally via the second parameter be streamed directly to file.
loHttp=CREATEOBJECT("wwHttp")
lcData = loHttp.Get("https://west-wind.com/files/wwClientTools.zip")
STRTOFILE(lcData, "c:\temp\wwClientTools.zip")
Binary content is downloaded into a string, and you can use
STRTOFILE()to save the data or useCAST(lcData as BLOB)to get a true binary response.
Sending raw Data: Posting JSON to a Server
A common scenario for Http calls is to send raw data to the server and get a response back. This is a common scenario for calling REST Web Services or APIs which use JSON as its transport data via Http POST or PUT operations.
Here's how you can send JSON and get a JSON response back and error handling :
loHttp = CREATEOBJECT("wwHttp")
loHttp.cContentType = "application/json"
*** Optionally add headers
loHttp.AppendHeader("Authorization","Bearer c42ffadef3f3aa3d5c97da77e")
lcJson = '{ "name": "Rick", "company": "West Wind", "key": "x32af2dro3"}'
lcJsonResult = loHttp.Post(lcUrl, lcJson)
? lcJsonResult
A more complete example could add JSON serialization and de-serialization:
DO wwHttp
DO wwJsonSerializer
*** Use any FoxPro object here including nested objects
loData = CREATEOBJECT("Empty")
ADDPROPERTY(loData,"firstName", "Rick")
ADDPROPERTY(loData,"company","West Wind")
ADDPROPERTY(loData,"merchantKey","x32af2dro3")
ADDPROPERTY(loData,"dateEntered",Date())
*** Serialize the object
loSer = CREATEOBJECT("wwJsonSerializer")
*** Enforce Json field case - all others are auto-lower cased
loSer.PropertyNameOverrides = "firstName,merchantKey,dateEntered"
lcJson = loSer.Serialize(loData)
*** Send Json to the server and retrieve Json result
loHttp = CREATEOBJECT("wwHttp")
loHttp.cContentType = "application/json"
lcJsonResult = loHttp.Post(lcUrl, lcJson)
*** always handle errors from Http calls!
IF (lohttp.nError != 0)
*** Handle error
? "Error: " + loHttp.cErrorMsg
RETURN
ENDIF
*** Deserialize and display nested object
loResult = loSer.Deserialize(lcJsonResult)
? loResult.Message
? loResult.StatusObject.ResultCode
Posting Form Data to a Server
These days most APIs work with JSON data, but there are still many APIs that expect form data to be sent. Form data differs from raw data in that it contains key value pairs that each contain individual values.
There are two types of form post operations:
- UrlEncoded Forms (Html Form Style)
- Multi-Part Forms (for binary uploads -
nHttpPostMode=2)
Here's how you post Form data to a server:
loHttp=CREATEOBJECT("wwHttp")
* loHttp.nHttpPostMode = 2 && for multi-part forms
*** Add POST form variables (url encoded by default)
loHttp.AddPostKey("FirstName","Rick")
loHttp.AddPostKey("LastName","Strahl")
loHttp.AddPostKey("Company","West Wind Technologies")
lcHtml = loHttp.Post("https://west-wind.com/wconnect/TestPage.wwd")
? lcHtml && Raw Html result
Multi-part forms tend to be used for file uploads which looks like this:
loHttp = CREATEOBJECT("wwHttp")
loHttp.nHttpPostMode = 2 && Multipart form encoding
*** Post a file
loHttp.AddPostFile("File","d:\temp\wws_invoice.pdf", "invoice.pdf")
*** Post normal text (part of the same form)
loHttp.AddPostKey("txtFileNotes","test note")
lcHtml = loHttp.Post("http://localhost/wconnect/FileUpload.wwd")
Access Http Content
Ok now that you have the basics let's provide a little more detail by adding error handling and some of the options. The following requests repeat some of the basics with a little more detail.
The simplest requests are GET operations to retrieve content only. The following example captures some Html text from a URL:
DO wwHttp && load dependencies
loHttp=CREATEOBJECT("wwHttp")
*** Issue an Http GET operation
lcHtml = loHttp.Get("https://west-wind.com")
*** Check for errors
IF (loHttp.nError != 0)
? loHttp.cErrorMsg
RETURN
ENDIF
? lcHtml && Raw Html
The raw result from the request is returned as a string in the return value (or a result file via the llDownloadFile parameter ) and in most cases that may be all you need to know.
Note: Any Html content downloaded doesn't include dependencies like images, stylesheets, JavaScript etc. The downloaded page also doesn't 'run' JavaScript. You simply get the raw Html (or other) content from the server as a string or BLOB.
Download Non-Html and Binary Data
The content you download doesn't have to be Html or even text. You can download non-Html text data like JSON and XML data, but you can also download binary content like Images, Pdf or Zip files.
The following downloads and saves a Zip file.
DO wwHttp
loHttp=CREATEOBJECT("wwHttp")
*** Issue an Http GET operation
lcZip = loHttp.Get("https://west-wind.com/files/wwClient.zip")
lcFile = "c:\temp\wwCient.zip"
StrToFile(lcZip, lcFile)
ShellExec(lcFile)
For file downloads it's often useful to stream the file directly to disk using the lcOutputFile parameter:
*** Stream download directly to a file
loHttp.Get("https://west-wind.com/files/wwClient.zip", "c:\temp\wwclient.zip")
If you download large files it's recommended you go directly to file to avoid excessive memory usage. It also lets you download files much larger than FoxPro's 16mb string limit.
Retrieving Http Response Headers and Error Information
For proper content and result identification - especially for API calls or binary downloads - you also should check for errors and frequently look at the Http headers to identify the content type, and the response code of whether a request worked or not and what type of content it returned. You can use the Http Headers to do this.
lcHtml = loHttp.Get("https://markdownmonster.west-wind.com")
*** Check for errors
IF loHttp.nError != 0
? loHttp.cErrorMsg
RETURN
ENDIF
*** Get Http Response Code
? loHttp.cResultCode && 200, 500, 401, 404, 401.1 etc.
*** Get full Http Response Header (multiple lines)
? loHttp.cHttpHeaders
*** Retrieve any Http header from the result
? loHttp.GetHttpHeader("Content-Type")
Http headers for the request above look like this:
Http/1.1 200 OK
Cache-Control: private
Content-Type: text/Html; charset=utf-8
Server: Microsoft-IIS/10.0
Date: Tue, 23 Feb 2021 21:13:55 GMT
Content-Length: 39718
Always check for Errors
When making Http calls you should always check for errors after the call to ensure you got data. At minimum check for empty results, but you can use the
nErrorandcErrorMsgproperties to get detailed error information.
Http Headers
Http headers includes information about a request - like the Content Type, Encoding, Content Length, Authentication Information as well as any custom headers the server is sending. Not every request needs to look at this, but it's important for generic applications that process Http content, or even for binary downloads that need to determine what type of content you are dealing with (for example downloading an image or file and saving it to disk).
Posting Form Data and sending Headers to a Server
To post data to a server you generally use either .Post() or .Put(). Semantically POST is used for adding, PUT for updating, but for most Web backends this distinction is not significant and you can use the two interchangeably. POST tends to be more common.
To post data to a server using Html Form style form submissions with urlencoded Key/Value pairs (application/x-www-form-urlencoded content), you can use the .AddPostKey() method. Use that method to add key and value pairs to POST to the server. You can also add HttpHeaders by using .AppendHeader() to add any standard or custom Http headers to your request.
Generic Send()
At the lowest level there's the Send() method which is the core method that sends all Http requests (also: HttpGet() which runs the same code). This method requires that all properties are set via properties and methods.
To make calls for specific Http Verbs easier, the library provides shorthand methods specific to each verb such as Get(),Post(),Put(),Delete(). These methods automatically set the verb and provide optional parameters for data to be sent.
All Http Verbs are supported
Note: Although only a few of the available and most common Http verbs have custom methods, you can use any Http Verb by using the
Send()method by specifying thecHttpVerbexplicitly (ie.loHttp.cHttpVerb = "OPTIONS").
Posting Html Form Data
You then call .Post() (or .Put()) to make the request:
loHttp=CREATEOBJECT("wwHttp")
* loHttp.nHttpPostMode = 2 && for multi-part forms
*** Add POST form variables (url encoded by default)
loHttp.AddPostKey("FirstName","Rick")
loHttp.AddPostKey("LastName","Strahl")
loHttp.AddPostKey("Company","West Wind Technologies")
*** Optionally add headers
loHttp.AppendHeader("Authorization","Bearer c42ffadef3f3aa3d5c97da77e")
loHttp.AppendHeader("Custom-Header","Custom value")
lcHtml = loHttp.Post("https://west-wind.com/wconnect/TestPage.wwd")
ShowHtml( lcHtml )
This formats the Http POST buffer for sending the variables to the Web server which simulates an Html Web form submission which is quite common.
The above example posts using Html Form type POST data that is provided in key value pairs. wwHttp supports 2 form data post modes via the nHttpPostMode property:
- 1 -
application/x-www-form-urlencoded- Html form based encoding (default) - 2 -
multi-part/form-data- used for file uploads and forms with binary data
POSTING Raw Data like JSON or XML
For raw data posts that provide a full buffer of data as-is rather than the key/value pairs shown above, you can can use the cContentType property to specify the content type, then POST the raw data as a single string.
Don't mix nHttpPostMode and cContentType
Use either
nHttpPostModeorcContentType- but don't use both! UsenHttpPostModefor form submissions withAddPostKey(key,value)(ie. UrlEncoded or binary) and usecContentTypeto send raw data buffers withAddPostKey(lcData)or directly in.Post(lcUrl, lcData)or.Put(lcUrl, lcData).
The following is sufficient for sending raw JSON content to the server:
loHttp = CREATEOBJECT("wwHttp")
loHttp.cContentType = "application/json"
*** Not required when using explicit .Post()/.Put() etc. methods
* loHttp.cHttpVerb = "POST" && PUT, DELETE etc.
lcJson = '{ "name": "Rick", "company": "West Wind", "key": "x32af2dro3"}'
lcJsonResult = loHttp.Post(lcUrl, lcJson)
*** Check for errors
IF (loHttp.nError # 0)
RETURN
ENDIF
? lcJsonResult
Likewise to send XML data to a server you can use the following code:
loHttp = CREATEOBJECT("wwHttp")
*** Specify that we want to post raw data and a custom content type
loHttp.cContentType = "text/xml" && Content type of the data posted
*** Explicitly specify Http verb optionall
* loHttp.cHttpVerb = "POST" && (PUT/DELETE/HEAD/OPTIONS)
*** Load up the XML data any way you need
lcXML = FILETOSTR("XmlData.xml")
lcXmlResult = loHttp.Post("http://www.west-wind.com/SomeXmlService.xsvc", lcXml)
IF (loHttp.nError # 0)
RETURN
ENDIF
? lcJsonResult
If you've been using wwHttp for a while you might note that this syntax of using .Post() is new as is the lcData parameter. The old syntax that uses .AddPostKey(lcData) and .Send() also still works:
loHttp.cHttpVerb = "POST"
loHttp.AddPostKey(lcXml)
loHttp.Send(lcUrl) && or HttpGet()
with the same behavior as the example above.
.Send()is the low level interface and the wrappers -.Post(),.Put(),Delete()- all delegate to it setting the appropriate headers. For custom verbs likePATCH,OPTIONS,HEADetc. you can use.Send()explicitly.
Generic Http Requests via Send()
There are special methods for Get(),Post(),Put(),Delete() that map the corresponding Http verbs, but these are simply wrappers around the Send() method. If you need a custom Http verb, or you want more control over the configuration you can use Send() and explicitly set the cHttpVerb instead:
*** GET is the default
* loHttp.cHttpVerb = "GET"
lcHtml = loHttp.Send("https://west-wind.com")
*** POST
loHttp.cHttpVerb = "POST"
loHttp.cContentType = "application/json"
loHttp.AddPostKey('{ "message": "Hello World" }')
lcResult = loHttp.Send("https://somesite.com/messages/1")
Send()replaces the ambiguously namedHttpGet()method onwwHttpin version 7.2 and later. Going forward you please useSend(), but if you have existing code withHttpGet()continues to work.
Making a REST Service call with JSON
REST services are the latest rage, and when using REST you typically deal with JSON data instead of XML. The client tools include a wwJsonSerializer class that can handle serialization and deserialization for you.
DO wwHttp
DO wwJsonSerializer
loSer = CREATEOBJECT("wwJsonSerializer")
*** Or just generate the JSON as a string
lcJsonIn = loSer.Serialize(loInputData)
loHttp = CREATEOBJECT("wwHttp")
loHttp.cContentType = "application/json; charset=utf-8"
lcJsonResult = loHttp.Post(lcUrl, STRCONV(lcJsonIn,9)) && UTF-8 Encoded
loResultObject = loSer.DeserializeJson(lcJsonResult)
*** Do something with the result object
? loResultObject.status
? loResultObject.Data.SummaryValue
To make things even easier with JSON REST Services take a look at the JsonServiceClient class which handles all the Http calls and serialization, UTF-8 encoding and decoding, and error handling all via single CallService() method.
The following sends JSON data to a service and receives a JSON result back as a FoxPro object:
loProxy = CREATEOBJECT("wwJsonServiceClient")
lcUrl = "http://albumviewer.west-wind.com/api/album"
lvData = loAlbum && FoxPro object
lcVerb = "PUT" && Http Verb
*** Make the service call and returns an Album object
loAlbum2 = loProxy.CallService(lcUrl, lvData, lcVerb)
IF (loProxy.lError)
? loProxy.cErrorMsg
RETURN
ENDIF
? loAlbum2.Title
? loAlbum2.Artist.ArtistName
Sending Raw Binary Data
You can also send binary data, which in FoxPro is just represented as a string. Make sure you set the .cContentType property to specify what you are sending to the server:
loHttp = CREATEOBJECT("wwHttp")
*** Specify that we want to post raw data with a custom content type
loHttp.cContentType = "application/pdf"
lcPdf = FILETOSTR("Invoice.pdf")
loHttp.AddPostKey(lcPdf) && Add as raw string
*** Send to server and retrieve result
lcHtml = loHttp.Post("http://www.west-wind.com/SomeUril.xsvc")
Note that you can use the cContentType property to specify the content type of the data POSTed to the server.
Send a File As an Http Form Upload (multi-part forms)
Many APIs and applications may also require form based file uploads which uses a special Http format (known as multi-part encoding) to upload files via Http. This format requires that you use nHttpPostMode=2 and you can then use AddPostFile() and AddPostKey() to add form variables to the POST operation:
loHttp = CREATEOBJECT("wwHttp")
*** Posting a multi-part form with a File Upload
loHttp.nHttpPostMode = 2 && Multipart form encoding
*** Post a file
loHttp.AddPostFile("File","d:\temp\wws_invoice.pdf",.T.)
*** Post normal text (part of the same form)
loHttp.AddPostKey("txtFileNotes","test note")
lcHtml = loHttp.Post("http://localhost/wconnect/FileUpload.wwd")
Streaming Downloads directly to file
You can also stream the content from a URL directly to a file which is useful to avoid FoxPro's 16mb string limit and also because it's faster and less resource intensive.
To do this you can use additional parameters on the .Send(), .Get(), .Post(), .Put() and .Delete() methods which take an lcOutputFilename parameter to allow streaming the Http data into. This allows for large files to be downloaded without tying up memory as they normally would.
loHttp = CREATEOBJECT('wwHttp')
loHttp.Get("http://www.west-wind.com/","c:\test.htm")
IF loHttp.nError # 0
wait window loHttp.cErrorMsg
return
ENDIF
*** Browse the file from local disk
GoUrl("c:\test.htm")
Streaming to file allows you to bypass large memory usage as wwHttp directly streams into the file with small chunks read from the server. This allows files much larger than 16mb to be downloaded.
Reveiving Download Event Notifications
wwHttp also includes an OnHttpBufferUpdate event wich provides you download progress information via an event handler on the wwHttp class. You can find out more on how this works and an example here:
