changeset 1055:6f923d52a46c

call Web services from Lua
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 24 Jul 2014 11:37:02 +0200
parents 1701dcb6f554
children 03c738276a04
files Core/Lua/LuaContext.cpp Core/Lua/LuaContext.h NEWS Resources/Samples/Lua/CallWebService.js Resources/Samples/Lua/CallWebService.lua UnitTestsSources/LuaTests.cpp
diffstat 6 files changed, 199 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Lua/LuaContext.cpp	Wed Jul 23 17:10:08 2014 +0200
+++ b/Core/Lua/LuaContext.cpp	Thu Jul 24 11:37:02 2014 +0200
@@ -94,22 +94,37 @@
   }
 
 
-  bool LuaContext::DoHttpQuery(lua_State* state,
-                               bool isJson)
+  int LuaContext::SetHttpCredentials(lua_State *state)
+  {
+    LuaContext& that = GetLuaContext(state);
+
+    // Check the types of the arguments
+    int nArgs = lua_gettop(state);
+    if (nArgs != 2 ||
+        !lua_isstring(state, 1) ||  // Username
+        !lua_isstring(state, 2))    // Password
+    {
+      LOG(ERROR) << "Lua: Bad parameters to SetHttpCredentials()";
+    }
+    else
+    {
+      // Configure the HTTP client
+      const char* username = lua_tostring(state, 1);
+      const char* password = lua_tostring(state, 2);
+      that.httpClient_.SetCredentials(username, password);
+    }
+
+    return 0;
+  }
+
+
+  bool LuaContext::AnswerHttpQuery(lua_State* state)
   {
     std::string str;
-    Json::Value json;
 
     try
     {
-      if (isJson)
-      {
-        httpClient_.Apply(json);
-      }
-      else
-      {
-        httpClient_.Apply(str);
-      }
+      httpClient_.Apply(str);
     }
     catch (OrthancException& e)
     {
@@ -117,14 +132,7 @@
     }
 
     // Return the result of the HTTP request
-    if (isJson)
-    {
-      PushJson(json);
-    }
-    else
-    {
-      lua_pushstring(state, str.c_str());
-    }
+    lua_pushstring(state, str.c_str());
 
     return true;
   }
@@ -136,9 +144,7 @@
 
     // Check the types of the arguments
     int nArgs = lua_gettop(state);
-    if ((nArgs != 1 && nArgs != 2) ||
-        !lua_isstring(state, 1) ||                 // URL
-        (nArgs >= 2 && !lua_isboolean(state, 2)))  // Interpret result as JSON
+    if (nArgs != 1 || !lua_isstring(state, 1))  // URL
     {
       LOG(ERROR) << "Lua: Bad parameters to HttpGet()";
       lua_pushstring(state, "ERROR");
@@ -147,12 +153,11 @@
 
     // Configure the HTTP client class
     const char* url = lua_tostring(state, 1);
-    bool isJson = (nArgs >= 2 && lua_toboolean(state, 2));
     that.httpClient_.SetMethod(HttpMethod_Get);
     that.httpClient_.SetUrl(url);
 
     // Do the HTTP GET request
-    if (!that.DoHttpQuery(state, isJson))
+    if (!that.AnswerHttpQuery(state))
     {
       LOG(ERROR) << "Lua: Error in HttpGet() for URL " << url;
       lua_pushstring(state, "ERROR");
@@ -169,10 +174,9 @@
 
     // Check the types of the arguments
     int nArgs = lua_gettop(state);
-    if ((nArgs != 1 && nArgs != 2 && nArgs != 3) ||
-        !lua_isstring(state, 1) ||                  // URL
-        (nArgs >= 2 && !lua_isstring(state, 2)) ||  // Body data
-        (nArgs >= 3 && !lua_isboolean(state, 3)))   // Interpret result as JSON
+    if ((nArgs != 1 && nArgs != 2) ||
+        !lua_isstring(state, 1) ||                // URL
+        (nArgs >= 2 && !lua_isstring(state, 2)))  // Body data
     {
       LOG(ERROR) << "Lua: Bad parameters to HttpPost() or HttpPut()";
       lua_pushstring(state, "ERROR");
@@ -181,7 +185,6 @@
 
     // Configure the HTTP client class
     const char* url = lua_tostring(state, 1);
-    bool isJson = (nArgs >= 3 && lua_toboolean(state, 3));
     that.httpClient_.SetMethod(method);
     that.httpClient_.SetUrl(url);
 
@@ -195,7 +198,7 @@
     }
 
     // Do the HTTP POST/PUT request
-    if (!that.DoHttpQuery(state, isJson))
+    if (!that.AnswerHttpQuery(state))
     {
       LOG(ERROR) << "Lua: Error in HttpPost() or HttpPut() for URL " << url;
       lua_pushstring(state, "ERROR");
@@ -239,7 +242,7 @@
     std::string s;
     if (!that.httpClient_.Apply(s))
     {
-      LOG(ERROR) << "Lua: Error in HttpPost() for URL " << url;
+      LOG(ERROR) << "Lua: Error in HttpDelete() for URL " << url;
       lua_pushstring(state, "ERROR");
     }
     else
@@ -334,6 +337,7 @@
     lua_register(lua_, "HttpPost", CallHttpPost);
     lua_register(lua_, "HttpPut", CallHttpPut);
     lua_register(lua_, "HttpDelete", CallHttpDelete);
+    lua_register(lua_, "SetHttpCredentials", SetHttpCredentials);
     
     lua_pushlightuserdata(lua_, this);
     lua_setglobal(lua_, "_LuaContext");
@@ -346,8 +350,8 @@
   }
 
 
-  void LuaContext::Execute(std::string* output,
-                           const std::string& command)
+  void LuaContext::ExecuteInternal(std::string* output,
+                                   const std::string& command)
   {
     log_.clear();
     int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") ||
@@ -359,6 +363,7 @@
 
       std::string description(lua_tostring(lua_, -1));
       lua_pop(lua_, 1); /* pop error message from the stack */
+      LOG(ERROR) << "Error while executing Lua script: " << description;
       throw LuaException(description);
     }
 
@@ -373,7 +378,7 @@
   {
     std::string command;
     EmbeddedResources::GetFileResource(command, resource);
-    Execute(command);
+    ExecuteInternal(NULL, command);
   }
 
 
@@ -383,4 +388,19 @@
     lua_getglobal(lua_, name);
     return lua_type(lua_, -1) == LUA_TFUNCTION;
   }
+
+
+  void LuaContext::Execute(Json::Value& output,
+                           const std::string& command)
+  {
+    std::string s;
+    ExecuteInternal(&s, command);
+
+    Json::Reader reader;
+    if (!reader.parse(s, output))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
 }
--- a/Core/Lua/LuaContext.h	Wed Jul 23 17:10:08 2014 +0200
+++ b/Core/Lua/LuaContext.h	Thu Jul 24 11:37:02 2014 +0200
@@ -58,6 +58,8 @@
 
     static int PrintToLog(lua_State *state);
 
+    static int SetHttpCredentials(lua_State *state);
+
     static int CallHttpPostOrPut(lua_State *state,
                                  HttpMethod method);
     static int CallHttpGet(lua_State *state);
@@ -65,11 +67,10 @@
     static int CallHttpPut(lua_State *state);
     static int CallHttpDelete(lua_State *state);
 
-    bool DoHttpQuery(lua_State* state,
-                     bool isJson);
+    bool AnswerHttpQuery(lua_State* state);
 
-    void Execute(std::string* output,
-                 const std::string& command);
+    void ExecuteInternal(std::string* output,
+                         const std::string& command);
 
     void PushJson(const Json::Value& value);
     
@@ -80,15 +81,18 @@
 
     void Execute(const std::string& command)
     {
-      Execute(NULL, command);
+      ExecuteInternal(NULL, command);
     }
 
     void Execute(std::string& output,
                  const std::string& command)
     {
-      Execute(&output, command);
+      ExecuteInternal(&output, command);
     }
 
+    void Execute(Json::Value& output,
+                 const std::string& command);
+
     void Execute(EmbeddedResources::FileResourceId resource);
 
     bool IsExistingFunction(const char* name);
--- a/NEWS	Wed Jul 23 17:10:08 2014 +0200
+++ b/NEWS	Thu Jul 24 11:37:02 2014 +0200
@@ -8,6 +8,7 @@
 * Access patient module at the study level to cope with PatientID collisions
 * On-the-fly conversion of JSON to XML according to the HTTP Accept header
 * C-Echo SCU in the REST API
+* Lua scripts can do HTTP requests, and thus can call Web services
 
 Plugins
 -------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/CallWebService.js	Thu Jul 24 11:37:02 2014 +0200
@@ -0,0 +1,80 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+/**
+ * This file is a simple echo Web service implemented using
+ * "node.js". Whenever it receives a POST HTTP query, it echoes its
+ * body both to stdout and to the client. Credentials are checked.
+ **/
+
+
+// Parameters of the ECHO server 
+var port = 8000;
+var username = 'alice';
+var password = 'alicePassword';
+
+
+var http = require('http');
+var authorization = 'Basic ' + new Buffer(username + ':' + password).toString('base64')
+
+var server = http.createServer(function(req, response) {
+  // Check the credentials
+  if (req.headers.authorization != authorization)
+  {
+    console.log('Bad credentials, access not allowed');
+    response.writeHead(401);
+    response.end();
+    return;
+  }
+
+  switch (req.method)
+  {
+  case 'POST':
+    {
+      var body = '';
+
+      req.on('data', function (data) {
+        response.write(data);
+        body += data;
+      });
+
+      req.on('end', function () {
+        console.log('Message received: ' + body);
+        response.end();
+      });
+
+      break;
+    }
+
+  default:
+    console.log('Method ' + req.method + ' is not supported by this ECHO Web service');
+    response.writeHead(405, {'Allow': 'POST'});
+    response.end();
+  }
+});
+
+server.listen(port);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/CallWebService.lua	Thu Jul 24 11:37:02 2014 +0200
@@ -0,0 +1,24 @@
+-- This sample shows how to call a remote Web service whenever an
+-- instance is received by Orthanc. For this sample to work, you have
+-- to start the "CallWebService.js" script next to this file using
+-- NodeJs.
+
+-- Download and install the JSON module for Lua by Jeffrey Friedl
+-- http://regex.info/blog/lua/json
+JSON = (loadstring(HttpGet('http://regex.info/code/JSON.lua'))) ()
+
+SetHttpCredentials('alice', 'alicePassword')
+
+function OnStoredInstance(instanceId, tags, metadata)
+   -- Build the POST body
+   local info = {}
+   info['InstanceID'] = instanceId
+   info['PatientName'] = tags['PatientName']
+   info['PatientID'] = tags['PatientID']
+
+   -- Send the POST request
+   local answer = HttpPost('http://localhost:8000/', JSON:encode(info))
+
+   -- The answer equals "ERROR" in case of an error
+   print('Web service called, answer received: ' .. answer)
+end
--- a/UnitTestsSources/LuaTests.cpp	Wed Jul 23 17:10:08 2014 +0200
+++ b/UnitTestsSources/LuaTests.cpp	Thu Jul 24 11:37:02 2014 +0200
@@ -240,35 +240,51 @@
 
 TEST(Lua, Http)
 {
-  const std::string url("http://orthanc.googlecode.com/hg/Resources/Configuration.json");
+  Orthanc::LuaContext lua;
 
-  Orthanc::LuaContext lua;
+#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1  
+  lua.Execute("JSON = loadstring(HttpGet('http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/JSON.lua')) ()");
+  const std::string url("http://orthanc.googlecode.com/hg/OrthancCppClient/SharedLibrary/Product.json");
+#endif
+
   std::string s;
   lua.Execute(s, "print(HttpGet({}))");
   ASSERT_EQ("ERROR", Orthanc::Toolbox::StripSpaces(s));
 
 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1  
   lua.Execute(s, "print(string.len(HttpGet(\"" + url + "\")))");
-  ASSERT_LE(1000, boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(s)));
+  ASSERT_LE(100, boost::lexical_cast<int>(Orthanc::Toolbox::StripSpaces(s)));
 
   // Parse a JSON file
-  lua.Execute(s, "print(HttpGet(\"" + url + "\", true)['Name'])");
-  ASSERT_EQ("MyOrthanc", Orthanc::Toolbox::StripSpaces(s));
-#endif
+  lua.Execute(s, "print(JSON:decode(HttpGet(\"" + url + "\")) ['Product'])");
+  ASSERT_EQ("OrthancClient", Orthanc::Toolbox::StripSpaces(s));
 
-#if 0
+#if 1
+  // This part of the test can only be executed if one instance of
+  // Orthanc is running on the localhost
+
+  lua.Execute("modality = {}");
+  lua.Execute("table.insert(modality, 'ORTHANC')");
+  lua.Execute("table.insert(modality, 'localhost')");
+  lua.Execute("table.insert(modality, 4242)");
+  
   lua.Execute(s, "print(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('hello world')\"))");
   ASSERT_EQ("hello world", Orthanc::Toolbox::StripSpaces(s));
 
-  lua.Execute(s, "print(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('[10,42,1000]')\", true)[2])");
+  lua.Execute(s, "print(JSON:decode(HttpPost(\"http://localhost:8042/tools/execute-script\", \"print('[10,42,1000]')\")) [2])");
   ASSERT_EQ("42", Orthanc::Toolbox::StripSpaces(s));
+
+  // Add/remove a modality with Lua
+  Json::Value v;
+  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities/lua'))");
+  ASSERT_EQ(0, Orthanc::Toolbox::StripSpaces(s).size());
+  lua.Execute(s, "print(HttpPut('http://localhost:8042/modalities/lua', JSON:encode(modality)))");
+  lua.Execute(v, "print(HttpGet('http://localhost:8042/modalities/lua'))");
+  ASSERT_TRUE(v.type() == Json::arrayValue);
+  lua.Execute(s, "print(HttpDelete('http://localhost:8042/modalities/lua'))");
+  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities/lua'))");
+  ASSERT_EQ(0, Orthanc::Toolbox::StripSpaces(s).size());
 #endif
 
-#if 0
-  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities'))");
-  lua.Execute(s, "print(HttpPut('http://localhost:8042/modalities/lua', '[ \"ORTHANC\", \"localhost\", 4242 ]'))");
-  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities'))");
-  lua.Execute(s, "print(HttpDelete('http://localhost:8042/modalities/lua'))");
-  lua.Execute(s, "print(HttpGet('http://localhost:8042/modalities'))");
 #endif
 }