# HG changeset patch # User Sebastien Jodogne # Date 1406194622 -7200 # Node ID 6f923d52a46c255f8f6cf3e591ca176286d8722a # Parent 1701dcb6f5545828f25b446ca90425efe8a1e45d call Web services from Lua diff -r 1701dcb6f554 -r 6f923d52a46c Core/Lua/LuaContext.cpp --- 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); + } + } + } diff -r 1701dcb6f554 -r 6f923d52a46c Core/Lua/LuaContext.h --- 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); diff -r 1701dcb6f554 -r 6f923d52a46c NEWS --- 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 ------- diff -r 1701dcb6f554 -r 6f923d52a46c Resources/Samples/Lua/CallWebService.js --- /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); diff -r 1701dcb6f554 -r 6f923d52a46c Resources/Samples/Lua/CallWebService.lua --- /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 diff -r 1701dcb6f554 -r 6f923d52a46c UnitTestsSources/LuaTests.cpp --- 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(Orthanc::Toolbox::StripSpaces(s))); + ASSERT_LE(100, boost::lexical_cast(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 }