The following examples show how to make HTTP requests using the HttpRequest class.

Simple Get Request


Make a Get request to a server and retrieve the status code, the response headers, and the response text body:

response = HttpRequest.Get('https://httpbin.org/')
print, response.status_code
print, response.headers
print, response.text

Get Request and Save to a File


Make a Get request to a server and save the response to a file:

response = HttpRequest.Get('https://httpbin.org/', FILENAME='c:/temp/result.txt')
print, response.status_code
print, file_lines('c:/temp/result.txt')

Get Request with Parameters


Here we send a couple of parameters to a server. Since our parameters contain disallowed characters (such as spaces) we use the ESCAPE keyword:

params = hash('param1', 'my value', 'param2', -123.4)
response = HttpRequest.Get('https://httpbin.org/', params=params, /escape)
print, response.url

IDL prints:

http://myserver.com/?param1=my%20value¶m2=-123.400

Tip: You can also use the HttpRequest.Escape and Unescape methods to convert strings with disallowed or special characters.

Get Request with Basic Authentication


Make a request using basic authentication:

basic = 'Basic ' + idl_base64(byte('myname:mypass'))
response = HTTPRequest.Get('myserver.org', headers=hash('Authorization', basic))
print, `status_code = ${response.status_code}`

Note: This is a fake URL so this example will not actually run; you will need to use your own URL and the correct username and password.

Handling Errors


Make a request to a nonexistent server. In this case you will get an internal CURL error (response code < 100) and the error message will be in the response.text field:

response = HTTPRequest.Get('http://myfancyserver.org')
print, `status_code = ${response.status_code}`
print, response.text

IDL prints:

status_code = 6
Could not resolve host: myfancyserver.org

Now make a request to a real server but a nonexistent path. In this case you will get a 404 response code and the server's response will be in the response.text field:

response = HTTPRequest.Get('http://google.com/mybadpath')
print, `status_code = ${response.status_code}`
print, response.text

IDL prints:

status_code = 404
<!DOCTYPE html>
<html lang=en>
...
<a href=//www.google.com/><span id=logo aria-label=Google></span></a>
<p><b>404.</b> <ins>That's an error.</ins>
<p>The requested URL <code>/mybadpath</code> was not found on this server.  <ins>That's all we know.</ins>

Post Request with Key/Value Pairs


Make a post request, passing in two key/value pairs as parameters:

params = dictionary('key1', 'value1', 'key2', 'value2')
response = HTTPRequest.Post('https://httpbin.org/post', params=params)
print, `status_code = ${response.status_code}`
print, response.text

IDL prints:

status_code = 200
{
  ...
  "form": {
    "key1": "value1",
    "key2": "value2"
  },
  "headers": {
    "Accept": "*/*",
    "Content-Length": "23",
    "Content-Type": "application/x-www-form-urlencoded",
    ...
  },
  ...
}

Post Request with JSON Data


Make a post request and pass in two key/value pairs as JSON data. The response body is in JSON format so we use the HttpRequest.Json method:

formdata = dictionary('key1', 'value1', 'key2', 'value2')
response = HTTPRequest.Post('https://httpbin.org/post', json=formdata)
print, response.json(), /implied

IDL prints:

{
  "data": "{\"key2\":\"value2\",\"key1\":\"value1\"}",
  "headers": {
    "Accept": "*/*",
    "Content-Length": "33",
    "Content-Type": "application/json",
    ...
  },
  "json": {
    "key1": "value1",
    "key2": "value2"
  },
  ...
}

Post Request with Multipart Form and Files


Make a post request with a multipart form that includes a single file:

multipart= hash('myawesomedata', '@c:\users\chris\mytextfile.txt')
response = HTTPRequest.Post('https://myserver.org/', multipart=multipart)

Here we have a more complicated multipart where we need to specify the mimetype for a JPEG file and a different mimetype for some byte data:

myfile = filepath('md1107g8a.jpg', subdir=['examples', 'data'])
multipart = dictionary('name', 'Dave Stern', $
  'mydata1', bindgen(10), $
  'mydata2', {value: bindgen(10), mimetype: 'application/octet-stream'}, $
  'myimage', {file: myfile, mimetype: 'image/jpeg'})
response = HTTPRequest.Post('https://myserver.org/', multipart=multipart)

Note: This is a fake URL so this example will not actually run; it is only here to show how to pass multipart data. To use the ::Post method, you will need to consult the documentation for your web server to determine the appropriate form fields and data/file formats.

Get Request with a Callback


First we define a callback function that will print out a progress status. Our callback also looks for a keypress: if you press "q" then the function will return zero and the request will be canceled.

function mycallback, downTotal, downNow, upTotal, upNow, callbackData
  compile_opt idl2
  if (get_kbrd(0) eq 'q') then return, 0
  percent = round(100 * float(downNow) / (downTotal > 1))
  if (callbackData eq percent) then return, 1
  callbackData = percent
  if (percent gt 0) then print, `${percent}% ${('*').dup(percent)}`
  return, 1
end

Now download a large file, making sure to pass in our callback function and our initial percentage:

response = HTTPRequest.Get('http://mattmahoney.net/dc/enwik8.zip', $
  CALLBACK_FUNCTION='mycallback', CALLBACK_DATA=0)

IDL prints:

1% *
...
50% **************************************************
...
100% ****************************************************************************************************

Patch Request


Unlike POST or PUT, a PATCH request allows you to update existing resources on the server. For example, to upload a file helloworld.txt that contains the text "Hello World":

IDL> data = hash("name", "patched file", "file", "@helloworld.txt")
IDL> response = HttpRequest.Patch('https://httpbin.org/patch', multipart = data)
IDL> response.status
200
IDL> response.json()
{
  "args": {
  },
  "data": "",
  "files": {
    "file": "Hello World\r\n"
  },
  "form": {
    "name": "patched file"
  },
  ...
}

Note: This example assumes that the server is looking for form data with a field called "name" along with an associated file. To use the ::Patch method, you will need to consult the documentation for your web server to determine the appropriate form fields and data/file formats.

WebSocket Example


First, create a Python program to run a sample WebSocket server on localhost. Make sure your Python has the websockets package installed:

pip install websockets

Then create the following program:

Copy
import asyncio
import websockets
async def handler(websocket):
    try:
        print(f"New connection from {websocket.remote_address}", flush=True)
        await websocket.send("Initial message from server")
        async for message in websocket:
            if isinstance(message, str):  # Text data
                msg = message
            else:  # Binary data
                if len(message) > 10:
                    msg = f"{len(message)} bytes"
                else:
                    msg = f"{message}"
            print("Received: " + msg)
            await websocket.send("Response: " + msg)
        # Loop ended normally - client sent close frame
        print(f"Client sent close frame: code={websocket.close_code}, reason={websocket.close_reason}", flush=True)
        # Explicitly send close response
        await websocket.close()
    except websockets.exceptions.ConnectionClosed as e:
        print(f"Connection closed: code={e.code}, reason={e.reason}", flush=True)
async def main():
    async with websockets.serve(handler, "localhost", 8765, max_size=None):
        print("WebSocket server running on ws://localhost:8765")
        await asyncio.Future()  # run forever
if __name__ == "__main__":
    asyncio.run(main())

Save this file as sample_websocket.py and run it:

python sample_websocket.py

Next, create an IDL program which will open a connection to this WebSocket server, send some data, and wait for responses using an IDL Timer:

pro test_callsocket, id, ws
  on_error, 2
  catch, err
  if (err ne 0 || ws.curl eq 0) then begin
    catch, /cancel
    !null = timer.cancel(id)
    message, /reissue_last
    return
  endif
  result = ws.Receive()
  if isa(result, 'byte') || strlen(result) gt 0 then begin
    print, result
    if (isa(result, 'string') && result.contains("Close")) then begin
      ; Close our WebSocket connection
      ws.Send, /CLOSE
      !null = timer.cancel(id)
    endif
  endif
end
print, 'Connecting...'
ws = HttpRequest.WebSocket("ws://localhost:8765")
; Uncomment to try out a remote secure WebSocket server
;  ws = HttpRequest.WebSocket("wss://echo.websocket.org", $
;      options={SSL_VERIFYPEER: 0, SSL_VERIFYHOST: 0})
id = timer.set(0.5, 'test_callsocket', ws, /repeat)
ws.Send, 'Hello from IDL'
ws.Send, bindgen(10) + 1b
ws.Send, "Close"
end

Save this code in a file called test_websocket.pro, and then run it in IDL. You should see the following output:

IDL> .r test_websocket
Connecting...
Initial message from server
Response: Hello from IDL
Response: b'\x01\x02\x03\x04\x05\x06\x07\x08\t\n'
Response: Close

Over in the Python console, you should see the server output:

WebSocket server running on ws://localhost:8765
New connection from ('::1', 63535, 0, 0)
Received: Hello from IDL
Received: b'\x01\x02\x03\x04\x05\x06\x07\x08\t\n'
Received: Close
Client sent close frame: code=1000, reason=OK

Note: Most WebSocket servers (including this example Python one) send out "keepalive" pings at a regular interval. For example, the default for Python is 20 seconds. Your IDL code should call the Receive method at a regular interval that is shorter than this keepalive ping interval. Calling Receive will check for any existing data on the pipeline and will also automatically respond to pings, thus keeping the connection alive.

Version History


9.0

Introduced

9.2

Added example of ::Get FILENAME keyword, added examples of using @ to upload files. Added PATCH example.

9.3 Added WebSocket example

See Also


HttpRequest