Web client

Top  Previous  Next

//-----------------------------------------------------------------------------
// web client.vpl, created 2018-03-16 09:58
//
// This is a VERY simple web client. It connects to a web server and downloads
// a web page, which is stored on the internal drive.
// The example shows how to establish a TCP/IP connection, how to enable
// a secure connection, and how to dictate which network interface is used.
//-----------------------------------------------------------------------------
INCLUDE rtcu.inc
 
//  Input variables that can be configured via the configuration dialog (These are global)
VAR_INPUT
END_VAR;
 
//  Output variables that can be configured via the configuration dialog (These are global)
VAR_OUTPUT
END_VAR;
 
//  These are the global variables of the program
VAR
   buf      : ARRAY [1..1024] OF SINT;
   timer    : TON;
END_VAR;
 
 
//////////////////////////////////////////////////////////////////////////////
// Opens a connection to the server
//
// Input:
//    host      - The server domain name
//    port      - The server port number
//    iface     - The network interface to use
//    https     - Use HTTP (FALSE) or HTTPS (TRUE) to get the page
//
// Returns:
//    0         - Success
//    -1        - Socket error
//    -2        - Could not connect to server
FUNCTION server_open : INT;
VAR_INPUT
   host     : STRING;
   port     : DINT;
   iface    : SINT;
   https    : BOOL;
   handle   : ACCESS SYSHANDLE;
END_VAR;
VAR
   address  : STRING;
   rc       : INT;
END_VAR;
 
   // Open socket
   rc := soCreate(socket := handle);
   IF rc < 1 THEN
      // Socket error
      DebugFmt(message := "soCreate = \1", v1 := rc);
      server_open := -1;
      return;
   END_IF;
 
   // Bind to network interface
   IF iface > 0 THEN
      // Get the address of the network interface
      address := soAddrFromInterface(iface := iface);
 
      // Bind the socket
      rc := soBind(socket := handle, address := address);
      IF rc < 1 THEN
         // Socket error
         DebugFmt(message := "soBind = \1", v1 := rc);
         soClose(socket := handle);
         server_open := -1;
         return;
      END_IF;
   END_IF;
 
   // Enable secure connection
   IF https THEN
      rc := soConfigTLS(
                        socket := handle,
                        enable := TRUE
                       );
      IF rc < 1 THEN
         // Socket error
         DebugFmt(message := "soConfigTLS = \1", v1 := rc);
         soClose(socket := handle);
         server_open := -1;
         return;
      END_IF;
   END_IF;
 
   // Generate socket address of destination
   rc := soAddrInetSet(address := address, host := host, port := port);
   IF rc < 1 THEN
      // Not a valid address
      DebugFmt(message := "soAddrInetSet = \1", v1 := rc);
      soClose(socket := handle);
      server_open := -2;
      return;
   END_IF;
 
   // Connect to server
   rc := soConnect(socket := handle, address := address);
   IF rc < 1 THEN
      // Not a valid address
      DebugFmt(message := "soConnect = \1", v1 := rc);
      soClose(socket := handle);
      server_open := -2;
      return;
   END_IF;
 
   // Switch to non-blocking, as some servers leave the socket open
   rc := soConfigNonblock(socket := handle, enable := TRUE);
   IF rc < 1 THEN
      // Not a valid address
      DebugFmt(message := "soConfigNonblock = \1", v1 := rc);
      soClose(socket := handle);
      server_open := -1;
      return;
   END_IF;
 
END_FUNCTION;
//////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////
// Read response header from server
//
// Input:
//    handle    - The socket connection
//
// Returns:
//    HTTP response code
FUNCTION read_line : INT;
VAR_INPUT
   handle   : SYSHANDLE;
   line     : ACCESS STRING;
END_VAR;
VAR
   ch       : SINT;
   size     : DINT;
   state    : INT;
   rc       : INT;
END_VAR;
 
   // Default
   line  := "";
   state := 0;
   timer(trig := OFF);
   timer(pt := 10000, trig := ON);
 
   // Iterate incoming data
   WHILE TRUE DO
      // Read character
      rc := soRecv(
                   socket  := handle,
                   data    := ADDR(ch),
                   maxsize := 1,
                   size    := size
                  );
      IF rc < 1 AND rc <> -4 THEN
         DebugFmt(message := "soRecv=\1", v1 := rc);
         RETURN;
      END_IF;
 
      // Handle character
      IF rc <> -4 THEN
         IF ch = 16#0A OR ch = 16#0D THEN
            state := state + 1;
         ELSE
            line  := line + strFromMemory(src := ADDR(ch), len := 1);
         END_IF;
         timer(trig := OFF);
      END_IF;
 
      timer(trig := ON);
      IF timer.THEN
         // Timeout waiting for data
         RETURN;
      END_IF;
      IF state = 2 THEN
         EXIT;
      END_IF;
   END_WHILE;
 
   // Completed
   read_line := 1;
 
END_FUNCTION;
//////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////
// Read response header from server
//
// Input:
//    handle    - The socket connection
//
// Returns:
//    HTTP response code
FUNCTION reply_header : INT;
VAR_INPUT
   handle   : SYSHANDLE;
END_VAR;
VAR
   line     : STRING;
   http     : INT;
   rc       : INT;
END_VAR;
 
// Iterate response
WHILE TRUE DO
   // Read line
   rc := read_line(handle := handle, line := line);
   IF rc < 1 THEN
      RETURN;
   END_IF;
 
   // Check for header end
   IF strLen(str := line) = 0 THEN
      // The header is received
      EXIT;
   END_IF;
 
   // Check for HTTP reply
   IF strCompare(str1 := strLeft(str := line, length := 5), str2 := "http/") = 0 THEN
      http := strToInt(str := strMid(str := line, start := 10));
   END_IF;
END_WHILE;
 
   // Completed
   reply_header := http;
 
END_FUNCTION;
//////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////
// Read response body from server
//
// Input:
//    handle    - The socket connection
//
// Returns:
//    HTTP response code
FUNCTION reply_body : INT;
VAR_INPUT
   handle   : SYSHANDLE;
   filename : STRING;
END_VAR;
VAR
   rc       : INT;
   fd       : FILE;
   sent     : DINT;
END_VAR;
 
   // Open file
   fd := fsFileCreate(name := filename);
 
   // Restart receive timer
   timer(trig := OFF);
   timer(pt := 10000, trig := ON);
 
   // Loop until no more data
   WHILE TRUE DO
      rc := soRecv(socket := handle, data := ADDR(buf), maxsize := SIZEOF(buf), size := sent);
      IF rc < 1 AND rc <> -3 AND rc <> -4 THEN
         // Communication error
         DebugFmt(message := "soRecv=\1", v1 := rc);
         fsFileClose(fd := fd);
         RETURN;
      END_IF;
      IF rc = -3 OR sent = 0 THEN
         EXIT;
      END_IF;
 
      // Write to file
      IF rc <> -4 THEN
         fsFileWrite(fd := fd, buffer := ADDR(buf), length := INT(sent));
         timer(trig := OFF);
      END_IF;
 
      // Timer
      timer(trig := ON);
      IF timer.THEN
         // Timeout waiting for data
         EXIT;
      END_IF;
   END_WHILE;
 
   // Close file
   fsFileClose(fd := fd);
 
   // Completed
   reply_body := 1;
 
END_FUNCTION;
//////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////
// Builds the HTTP GET command
//
// Input:
//    host      - The server domain name
//    page      - The path to the page on the server
//
// Returns:
//    String containing the HTTP GET command
FUNCTION build_http_get : STRING;
VAR_INPUT
   host     : STRING;
   page     : STRING;
END_VAR;
VAR
   cmd      : STRING;
END_VAR;
   // HTTP command
   cmd  := "GET ";
 
   // Add the page
   cmd  := cmd + page;
 
   // Add HTTP
   cmd  := cmd + " HTTP/1.1$N";
 
   // Add IP address
   cmd  := cmd + "Host: ";
   cmd  := cmd + host;
   cmd  := cmd + "$N";
 
   // Terminate command
   cmd  := cmd + "$N";
 
   build_http_get := cmd;
END_FUNCTION;
//////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////
// Get a WEB page and store it as a file
//
// Input:
//    host      - The server domain name
//    port      - The server port number
//    iface     - The network interface to use
//    page      - The path to the page on the server
//    https     - Use HTTP (FALSE) or HTTPS (TRUE) to get the page
//    filename  - The filename where the page is stored
//
// Returns:
//    0         - Success.
//    -1        - Could not connect to server
//    -2        - Failed to send request
//    -3        - Failed to receive web page
FUNCTION GetPage : INT;
VAR_INPUT
   host     : STRING;      // The server domain name
   port     : DINT := 80;  // The server port. default: HTTP (80)
   iface    : SINT;        // The network interface to use
   page     : STRING;      // The page to get
   filename : STRING;      // The filename to store the web page
   https    : BOOL;        // Use https to get the page
END_VAR;
VAR
   handle   : SYSHANDLE;
   sent     : DINT;
   len      : INT;
   rc       : INT;
   request  : STRING;
END_VAR;
 
   // Generate HTTP request
   request := build_http_get(host := host, page := page);
 
   // Open a socket to the server
   DebugMsg(message := "Connecting...");
   rc := server_open(host := host, port := port, iface := iface, https := https, handle:= handle);
   IF rc < 0 THEN
      DebugFmt(message := "server_open=\1", v1 := rc);
      GetPage := -1;
      RETURN;
   END_IF;
 
   // Send request
   DebugMsg(message := "Send request...");
   len := strLen(str := request);
   strToMemory(dst := ADDR(buf), str := request, len := len);
   rc := soSend(socket := handle, data := ADDR(buf), size := len, sent := sent);
   IF rc < 1 THEN
      // Communication error
      DebugFmt(message := "soSend=\1", v1 := rc);
      soClose(socket := handle);
      GetPage := -2;
      RETURN;
   END_IF;
 
   // Receive response
   DebugMsg(message := "Wait for reply...");
   rc := reply_header(handle := handle);
   IF rc < 1 THEN
      // Communication error
      DebugFmt(message := "reply_header=\1", v1 := rc);
      soClose(socket := handle);
      GetPage := -3;
      RETURN;
   END_IF;
   DebugFmt(message := "Reply = \1", v1 := rc);
 
   // Receive web page
   rc := reply_body(handle := handle, filename := filename);
   IF rc < 1 THEN
      // Communication error
      DebugFmt(message := "reply_body=\1", v1 := rc);
      soClose(socket := handle);
      GetPage := -3;
      RETURN;
   END_IF;
 
   // Close
   soClose(socket := handle);
 
END_FUNCTION;
//////////////////////////////////////////////////////////////////////////////
 
PROGRAM web_client;
VAR
   rc : INT;
END_VAR;
 
   DebugMsg(message := "----------------------------------------");
   DebugMsg(message := "Initialize...");
 
   // Open filesystem
   fsMediaOpen(media := 1);
   Sleep(delay := 2000);
   fsDirChange(path := "B:/");
 
   // Open network interface
   netOpen(iface := _NET_IFACE_LAN1);
 
   // Wait for network connection
   WHILE NOT netConnected(iface := _NET_IFACE_LAN1) DO
      DebugMsg(message := "Wait for connection...");
      Sleep(delay := 3000);
   END_WHILE;
 
   DebugMsg(message := "----------------------------------------");
   rc := GetPage(
                 host      := "www.google.dk"
                 ,https    := ON
                 ,port     := 443
                 ,iface    := _NET_IFACE_LAN1
                 ,page     := "/"
                 ,filename := "index.htm"
                );
   DebugFmt(message := "GetPage=\1", v1 := rc);
 
BEGIN
END;
END_PROGRAM;