Examples - REST Example

Top  Previous  Next

//-----------------------------------------------------------------------------
// REST example
//
// Starts a REST server on port 80 or 443 and then performs a request on it every 30s.
//
// When using HTTPS, at least three certificates are needed:
// The client will use the certificate+key "client".
// The "client" certificate must be signed by the root certificate "ca".
// The server will use the certificate+key "server", which must be signed
// by a trusted root certificate.
//
//
//-----------------------------------------------------------------------------
INCLUDE rtcu.inc
 
// Set to 1 to use HTTPS, set to 0 to use HTTP.
#DEFINE USE_TLS   1
// Set to 1 to use the string template functions to create JSON,
// set to 0 to use the JSON API.
#DEFINE USE_ST   1
 
// Functions that parse JSON structures and dumps them to the device output.
FUNCTION INTERFACE dumpValue;
VAR_INPUT
  json   : SYSHANDLE;
  prefix : STRING;
  key    : STRING;
  idx    : INT := -1;
  type   : INT;
  txt    : STRING;
END_VAR;
END_FUNCTION;
 
FUNCTION dumpStructure;
VAR_INPUT
  json   : SYSHANDLE;
  prefix : STRING;
END_VAR;
VAR
  type, i, rc : INT;
  key         : STRING;
END_VAR;
  type := jsonGetType(o:=json, idx:=-1);
  IF type = 1 THEN
    // object
    DebugMsg(message:=prefix+"{");
    i := 0;
    REPEAT
        rc := jsonGetKey(o:=json, idx:= i, key:=key, type:=type);
        IF rc = 1 THEN
          dumpValue(json:=json, prefix:=prefix+" ", txt:=key+" ", key:=key, idx:=-1, type:=type);      
        END_IF;
        i := i+1;
    UNTIL rc < 0
    END_REPEAT;
    DebugMsg(message:=prefix+"}");
  ELSIF type = 2 THEN
    // array
    DebugMsg(message:=prefix+"[");
    FOR i := 0 TO jsonArraySize(o:=json)-1 DO
          dumpValue(json:=json, prefix:=prefix+" ", txt:="["+intToStr(v:=i)+"] ", key:="", idx:=i, type:=jsonGetType(o:=json, idx:= i));
    END_FOR;
    DebugMsg(message:=prefix+"]");
  END_IF;
END_FUNCTION;
 
FUNCTION IMPLEMENTATION dumpValue;
VAR
  rc  : INT;
  tmp : SYSHANDLE;
  str : STRING;
  d   : DINT;
  b   : BOOL;
  f   : DOUBLE;
END_VAR;
  CASE type OF
  1,2:// Object or array
    DebugFmt(message:=prefix+txt+":");
    rc := jsonGetValue(o:=json, key:=key, idx:=idx, value := tmp);
    dumpStructure(json:=tmp, prefix := prefix);
    rc := jsonFree(o:=tmp);
  3: // string
    jsonGetString(o:=json, key:=key, idx:=idx, value :=str);
    DebugFmt(message:=prefix+txt+": "+str);
  4: // int
    jsonGetInt(o:=json, key:=key, idx:=idx, value :=d);
    DebugFmt(message:=prefix+txt+": \4", v4:=d);            
  5: // Float
    jsonGetFloat(o:=json, key:=key, idx:=idx, value :=f);
    DebugFmt(message:=prefix+txt+": "+doubleToStr(v:=f));  
  6: // Bool
    jsonGetBool(o:=json, key:=key, idx:=idx, value :=b);
    IF b THEN
        DebugFmt(message:=prefix+txt+": TRUE");
    ELSE
        DebugFmt(message:=prefix+txt+": FALSE");
    END_IF;
  7: // NULL
    DebugFmt(message:=prefix+txt+": NULL");  
  END_CASE;
END_FUNCTION;
 
 
VAR
  clock     : clockLinsecToTime;
  jsonStats : jsonHandleStats;
END_VAR;
 
FUNCTION linsecToStr : STRING;
VAR_INPUT
  linsec : DINT;
END_VAR;
VAR
  str : STRING;
END_VAR;
  clock(linsec:=linsec);
  str:=strFormat(format:="\1-\2-\3 ", v1:=clock.year, v2:=clock.month, v3:=clock.day);
  str:=str+strFormat(format:="\1:\2:\3", v1:=clock.hour, v2:=clock.minute, v3:=clock.second);
 
  linsecToStr:=str;
END_FUNCTION;
 
// Debug function that prints all the information about a certificate.
FUNCTION dumpCert;
VAR_INPUT
  req : SYSHANDLE;
  rip : DINT;
END_VAR;
VAR
  rc  : INT;
  str : STRING;
  d   : DINT;
  i   : INT;
  ip  : DINT;
END_VAR;
  rc := restReqClientCertPresent(req:=req);
  IF rc = 1 THEN
    DebugFmt(message:="Client cert present");
    rc := restReqClientCertSubjectGet(req:=req, str:=str);
    DebugFmt(message:=" Subject: "+str+": \1", v1:=rc);
    rc := restReqClientCertSubjectCNGet(req:=req, str:=str);
    DebugFmt(message:=" CN: "+str+": \1", v1:=rc);
    rc := restReqClientCertIssuerGet(req:=req, str:=str);
    DebugFmt(message:=" Issuer: "+str+": \1", v1:=rc);
    rc := restReqClientCertVersionGet(req:=req);
    DebugFmt(message:=" Version: \1", v1:=rc);
    d := restReqClientCertValidFrom(req:=req);
    DebugFmt(message:=" Valid from: \4, "+linsecToStr(linsec:=d), v4:=d);
    d := restReqClientCertValidTo(req:=req);
    DebugFmt(message:=" Valid to: \4, "+linsecToStr(linsec:=d), v4:=d);
    rc := restReqClientCertSerialGet(req:=req, str:=str);
    DebugFmt(message:=" Serial: "+str+": \1", v1:=rc);
    rc := restReqClientCertFingerprintGet(req:=req, type:=0, str:=str);
    DebugFmt(message:=" SHA1  : "+str+": \1", v1:=rc);
    rc := restReqClientCertFingerprintGet(req:=req, type:=1, str:=str);
    DebugFmt(message:=" MD51  : "+str+": \1", v1:=rc);
     
    rc := restReqClientCertCheckHostname(req:=req, hostname:="localhost");
    DebugFmt(message:=" Match localhost: \1", v1:=rc, v2:=i);
       
    rc := restReqClientCertCheckEmail(req := req, email := "test@example.com");
    DebugFmt(message:=" Match test@example.com: \1", v1 := rc);
 
 
    i := 0;
    REPEAT
        rc := restReqClientCertSANGet(req:=req, idx:=i, san:=str);
        DebugFmt(message:= "  SAN[\1]: \2: "+str, v1:=i, v2:=rc);
        IF rc = 4 THEN
          ip := soAddrToIP(address :=str);
          IF ip = rip THEN
              DebugMsg(message:="Matching IP found: "+str);
          END_IF;
        END_IF;
        i := i + 1;
    UNTIL rc = -21
    END_REPEAT;
     
  ELSE
    DebugFmt(message:="No client cert present");
  END_IF;
END_FUNCTION;
 
 
 
// Callback triggered when a POST request is sent to "/devices".
FUNCTION CALLBACK devicesPostCallback: INT;
VAR_INPUT
  req           : SYSHANDLE; | Request
  resp          : SYSHANDLE; | Response
  arg           : DINT;     | User argument
END_VAR;
VAR
  talq_version  : STRING;
  client_address: STRING;
  id            : STRING;
  json          : STRING;
  str           : STRING;
END_VAR;
  restReqHeaderGet(req:=req, name:="talq-version", value:=talq_version);
  restReqQueryGet(req:=req, name:="clientAddress", value:=client_address);
  restReqQueryGet(req:=req, name:="id", value:=id);
  DebugFmt(message:="Request: id: "+id+",  client address: "+client_address);
  restReqClientAddressGet(req:=req, address:=str);
  DebugFmt(message:="Address: "+str);
  // Dump all certificate info
  dumpCert(req:=req, rip := soAddrToIP(address :=str));
 
  // Get content of body as string
  restReqBodyGetString(req:=req, str := json);
 
  IF json <> "" THEN
    // Build response - just return the same JSON as received for now
    restRespBodySetString(resp:=resp, str := json);
    restRespHeaderSet(resp:=resp, name:="Content-Type", value:="application/json");
     
    devicesPostCallback := 201;
  ELSE
    // Error response
    restRespBodySetString(resp:=resp, str := "{$"key$":$"payloadError$" }");
    restRespHeaderSet(resp:=resp, name:="Content-Type", value:="application/json");
     
    devicesPostCallback := 400;
  END_IF;
END_FUNCTION;
 
FUNCTION GenerateJSONFromTemplate:INT;
VAR_INPUT
  json            : ACCESS SYSHANDLE;
  deviceName      : STRING;
  deviceClassName : STRING;
  displayName     : STRING;
  assetId         : STRING;
  lampTypeId      : STRING;
END_VAR;
VAR
  st   : SYSHANDLE;
  rc   : INT;
  str  : STRING;
  jStr : STRING;
END_VAR;
  // Template string
  str := STRING_BEGIN
  [
   {
      "address": "00000000-0000-0000-0000-000000000000",
      "name": "<deviceName>",
      "class": "<deviceClassName>",
      "functions": [
        {
           "id": "basic001",
           "type": "BasicFunction",
           "displayName": {
             "value": "<displayName>"
           },
           "assetId": {
             "value": "<assetId>"
           }
         
        },
        {
           "id": "lampActuator001",
           "type": "LampActuatorFunction",
           "lampTypeId": {
             "value": "<lampTypeId>"
           },
           "maintenanceFactor": {
             "value": 50
           },
           "maintenancePeriod": {
             "value": 12
           }
        }
      ]
    }
  ]
STRING_END;
 
  // Create template
  rc := strTemplateCreate(st:=st, str:=str, sov := "<", eov := ">");
  DebugFmt(message:="strTemplateCreate: \1", v1:=rc);
 
  // Set template values
  rc := strTemplateSetVar(st:=st, name:="deviceName", value:=DeviceName);
  DebugFmt(message:="strTemplateSetVar: \1", v1:=rc);
  rc := strTemplateSetVar(st:=st, name:="deviceClassName", value:=deviceClassName);
  DebugFmt(message:="strTemplateSetVar: \1", v1:=rc);
  rc := strTemplateSetVar(st:=st, name:="assetId", value:=assetId);
  DebugFmt(message:="strTemplateSetVar: \1", v1:=rc);
  rc := strTemplateSetVar(st:=st, name:="lampTypeId", value:=lampTypeId);
  DebugFmt(message:="strTemplateSetVar: \1", v1:=rc);
 
  // Generate string
  rc := strTemplateGenerateString(st:=st, str:=jStr);
 
  // Clean up
  rc := strTemplateFree(st:=st);
  DebugFmt(message:="strTemplateFree: \1", v1:=rc);
 
  rc := jsonFromString(o:=json, str:=jStr);
  DebugFmt(message:="jsonFromString: \1", v1:=rc);
 
  GenerateJSONFromTemplate := rc;
END_FUNCTION;
 
 
FUNCTION GenerateJSON:INT;
VAR_INPUT
  json : ACCESS SYSHANDLE;
END_VAR;
VAR
  rc : INT;
  devices, device, functions, func, tmp : SYSHANDLE;
  handles, data:DINT;
END_VAR;
  // devices array
  rc := jsonCreateArray(o:=devices);
  // First device
  rc := jsonCreateObject(o:=device);
 
  rc := jsonSetValueString(o:=device, key := "address",
        value := "00000000-0000-0000-0000-000000000000");
  rc := jsonSetValueString(o:=device, key := "name", value := "<deviceName>");
  rc := jsonSetValueString(o:=device, key := "class", value := "<deviceClassName>");
 
  // functions array
  rc := jsonCreateArray(o:=functions);
 
  // first function
  rc := jsonCreateObject(o:=func);
  rc := jsonSetValueString(o:=func, key := "id", value := "basic001");
  rc := jsonSetValueString(o:=func, key := "type", value := "basicFunction");
  // displayName
  rc := jsonCreateObject(o:=tmp);
  rc := jsonSetValueString(o:=tmp, key := "value", value := "<displayName>");
  rc := jsonSetValue(o:=func, key := "displayName", value := tmp);
  rc := jsonFree(o:=tmp);
  // assetId
  rc := jsonCreateObject(o:=tmp);
  rc := jsonSetValueString(o:=tmp, key := "value", value := "<assetId>");
  rc := jsonSetValue(o:=func, key := "assetId", value := tmp);
 
 
  rc := jsonFree(o:=tmp);
  // Add func to functions
  rc := jsonAddValue(o:=functions, value:=func);
  rc := jsonFree(o:=func);
  // second function
  rc := jsonCreateObject(o:=func);
 
  rc := jsonSetValueString(o:=func, key := "id", value := "lampActuator001");
  rc := jsonSetValueString(o:=func, key := "type", value := "LampActuatorFunction");
  // lampTypeId
  rc := jsonCreateObject(o:=tmp);
  rc := jsonSetValueString(o:=tmp, key := "value", value := "<lampTypeId>");
  rc := jsonSetValue(o:=func, key := "lampTypeId", value := tmp);
  rc := jsonFree(o:=tmp);
  // maintenanceFactor
  rc := jsonCreateObject(o:=tmp);
  rc := jsonSetValueInt(o:=tmp, key := "value", value := 50);
  rc := jsonSetValue(o:=func, key := "maintenanceFactor", value := tmp);
  rc := jsonFree(o:=tmp);
  // maintenancePeriod
  rc := jsonCreateObject(o:=tmp);
  rc := jsonSetValueInt(o:=tmp, key := "value", value := 12);
  rc := jsonSetValue(o:=func, key := "maintenancePeriod", value := tmp);
  rc := jsonFree(o:=tmp);
  // Add func to functions
  rc := jsonAddValue(o:=functions, value:=func);
  rc := jsonFree(o:=func);
 
  // Add functions to device
  rc := jsonSetValue(o:=device, key := "functions", value := functions);
  rc := jsonFree(o:=functions);
  // Add device to devices
  rc := jsonAddValue(o:=devices, value:=device);
  rc := jsonFree(o:=device);
 
  json := devices;
END_FUNCTION;
 
 
 
 
FUNCTION SendRequest;
VAR
  str  : STRING;
  req, resp : SYSHANDLE;
  json : SYSHANDLE;
  rc   : INT;
END_VAR;
  DebugMsg(message:="--------");
 
  // Create JSON:
  IF USE_ST = 1 THEN
    rc := GenerateJSONFromTemplate(json:=json, deviceName:="dev1", assetId:="asset 1", lampTypeId:="lamp 1");
  ELSE
    rc := GenerateJson(json:=json);
  END_IF;
 
 
  // Perform request:
  IF USE_TLS = 1 THEN
    rc := restReqCreate(req:=req, method := "POST",
      url := "https://127.0.0.1/devices/1234",
      check_hostname:=OFF);
  ELSE
    rc := restReqCreate(req:=req, method := "POST",
      url := "http://127.0.0.1/devices/1234");
  END_IF;
 
  // Show usage of JSON handles, expected 1 handle
  jsonStats();
  IF jsonStats.status >0 THEN
    DebugFmt(message:="JSON status: \1 handle(s)", v1:=jsonStats.no_handles);
  END_IF;
 
  rc := restReqQuerySet(req:=req, name := "clientAddress",
        value:="110a8321-e34c-112p5-b567-566655648453");
  rc := restReqHeaderSet(req:=req, name := "talq-version", value:="2.1.0");
 
  rc := restReqBodySetJSON(req:=req, json:=json);
     
  rc := jsonFree(o:=json);
 
  rc := restReqHeaderSet(req:=req, name:="Accept", value:="application/json");
 
  IF USE_TLS = 1 THEN
    rc := restReqClientCertSet(req:=req, cert:="client");
    DebugFmt(message:="restReqClientCertSet: \1", v1:=rc);
  END_IF;
 
  // Send request
  rc := restClientRequest(req:=req, resp := resp);
  DebugFmt(message:="Send request: \1", v1:=rc);
  // Handle response
  IF rc = 201 THEN
    // success
    rc := restRespBodyGetString(resp:=resp, str := str);
    DebugFmt(message:="data: "+str);
     
    rc := restRespBodyGetJSON(resp:=resp, json := json);
     
    rc := jsonToString(o:=json, str:=str, indent:=0);
     
    // Parse JSON and dump it to the device output
    dumpStructure(json:=json);
     
    rc := jsonFree(o:=json);
 
    DebugFmt(message:="JSON: "+str);
 
    rc := restRespFree(resp:=resp);
  END_IF;
 
  rc := restReqFree(req:=req);
 
END_FUNCTION;
 
 
 
 
PROGRAM rest_1;
// These are the local variables of the program block
VAR
  serv : SYSHANDLE;
  rc   : INT;
END_VAR;
  // The next code will only be executed once after the program starts
 
  // Open ethernet to allow access to the server from the outside
  netOpen(iface:=2);
 
  // Create server
  IF USE_TLS = 1 THEN
    rc := restServerCreate(handle:=serv, port:=443, cert:="server", accept_ca:="ca");
  ELSE
    rc := restServerCreate(handle:=serv, port:=80);
  END_IF;
  DebugFmt(message:="restServerCreate: \1", v1:=rc);
  // Register callback for /devices/<id>
  rc := restServerEndpointAdd(handle:=serv, method:="*", url_prefix:="/devices",
    url_format:="/:id", cb_func:=@devicesPostCallback);
  // Start server
  rc := restServerStart(handle:=serv);
  DebugFmt(message:="restServerStart: \1", v1:=rc);
BEGIN
  // Code from this point until END will be executed repeatedly
 
  SendRequest();
 
  Sleep(delay:=30000);
END;
END_PROGRAM;