comparison Sources/RestCallbacks.cpp @ 0:7ed502b17b8f

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 Mar 2020 18:47:01 +0100
parents
children 26762eb9d704
comparison
equal deleted inserted replaced
-1:000000000000 0:7ed502b17b8f
1 /**
2 * Python plugin for Orthanc
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the GNU Affero General Public License
7 * as published by the Free Software Foundation, either version 3 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 **/
18
19
20 #include "RestCallbacks.h"
21
22 #include "PythonObject.h"
23 #include "Autogenerated/sdk.h"
24
25 #include <OrthancPluginCppWrapper.h>
26
27 #include <boost/regex.hpp>
28
29
30 class RestCallback : public boost::noncopyable
31 {
32 private:
33 boost::regex regex_;
34 PyObject* callback_;
35
36 public:
37 RestCallback(const std::string& uri,
38 PyObject* callback) :
39 regex_(uri),
40 callback_(callback)
41 {
42 Py_XINCREF(callback_);
43 }
44
45 ~RestCallback()
46 {
47 Py_XDECREF(callback_);
48 }
49
50 bool IsMatch(const std::string& uri) const
51 {
52 return boost::regex_match(uri, regex_);
53 }
54
55 PyObject* GetCallback()
56 {
57 return callback_;
58 }
59 };
60
61
62 // Concurrent accesses to the callbacks are protected by the
63 // "PythonLock" (GIL mutex)
64 static std::list<RestCallback*> restCallbacks_;
65
66
67 static void RestCallbackHandler(OrthancPluginRestOutput* output,
68 const char* uri,
69 const OrthancPluginHttpRequest* request)
70 {
71 PythonLock lock;
72
73 for (std::list<RestCallback*>::const_iterator it = restCallbacks_.begin();
74 it != restCallbacks_.end(); ++it)
75 {
76 assert(*it != NULL);
77 if ((*it)->IsMatch(uri))
78 {
79 /**
80 * Construct an instance object of the "orthanc.RestOutput"
81 * class. This is done by calling the constructor function
82 * "sdk_OrthancPluginRestOutput_Type".
83 **/
84 PythonObject args(lock, PyTuple_New(2));
85 PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) output));
86 PyTuple_SetItem(args.GetPyObject(), 1, PyBool_FromLong(true /* borrowed, don't destruct */));
87 PyObject *pInst = PyObject_CallObject(GetOrthancPluginRestOutputType(), args.GetPyObject());
88
89
90 /**
91 * Construct the arguments tuple (output, uri)
92 **/
93 PythonObject args2(lock, PyTuple_New(2));
94 PyTuple_SetItem(args2.GetPyObject(), 0, pInst);
95 PyTuple_SetItem(args2.GetPyObject(), 1, PyUnicode_FromString(uri));
96 // No need to decrement refcount with "PyTuple_SetItem()"
97
98
99 /**
100 * Construct the named arguments from the "request" argument
101 **/
102 const char* method;
103 switch (request->method)
104 {
105 case OrthancPluginHttpMethod_Get:
106 method = "GET";
107 break;
108
109 case OrthancPluginHttpMethod_Post:
110 method = "POST";
111 break;
112
113 case OrthancPluginHttpMethod_Put:
114 method = "PUT";
115 break;
116
117 case OrthancPluginHttpMethod_Delete:
118 method = "DELETE";
119 break;
120
121 default:
122 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
123 }
124
125 PythonObject kw(lock, PyDict_New());
126 PyDict_SetItemString(kw.GetPyObject(), "method", PyUnicode_FromString(method));
127
128 {
129 PythonObject groups(lock, PyTuple_New(request->groupsCount));
130
131 for (uint32_t i = 0; i < request->groupsCount; i++)
132 {
133 PyTuple_SetItem(groups.GetPyObject(), i, PyUnicode_FromString(request->groups[i]));
134 }
135
136 PyDict_SetItemString(kw.GetPyObject(), "groups", groups.Release());
137 }
138
139 if (request->method == OrthancPluginHttpMethod_Get)
140 {
141 PythonObject get(lock, PyDict_New());
142
143 OrthancPlugins::LogError(boost::lexical_cast<std::string>(request->getCount));
144
145 for (uint32_t i = 0; i < request->getCount; i++)
146 {
147 PyDict_SetItemString(get.GetPyObject(), request->getKeys[i],
148 PyUnicode_FromString(request->getValues[i]));
149 }
150
151 PyDict_SetItemString(kw.GetPyObject(), "get", get.Release());
152 }
153
154 {
155 PythonObject headers(lock, PyDict_New());
156
157 OrthancPlugins::LogError(boost::lexical_cast<std::string>(request->headersCount));
158
159 for (uint32_t i = 0; i < request->headersCount; i++)
160 {
161 PyDict_SetItemString(headers.GetPyObject(), request->headersKeys[i],
162 PyUnicode_FromString(request->headersValues[i]));
163 }
164
165 PyDict_SetItemString(kw.GetPyObject(), "headers", headers.Release());
166 }
167
168 if (request->method == OrthancPluginHttpMethod_Post ||
169 request->method == OrthancPluginHttpMethod_Put)
170 {
171 PyDict_SetItemString(kw.GetPyObject(), "body", PyBytes_FromStringAndSize(
172 reinterpret_cast<const char*>(request->body), request->bodySize));
173 }
174
175
176 /**
177 * Call the user-defined function
178 **/
179 PythonObject result(lock, PyObject_Call(
180 (*it)->GetCallback(), args2.GetPyObject(), kw.GetPyObject()));
181
182 return;
183 }
184 }
185
186 // Should never happen
187 OrthancPlugins::LogError("Unable to find the Python handler for URI: " + std::string(uri));
188 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
189 }
190
191
192
193 PyObject* RegisterRestCallback(PyObject* module, PyObject* args)
194 {
195 // The GIL is locked at this point (no need to create "PythonLock")
196
197 // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c
198 const char* uri = NULL;
199 PyObject* callback = NULL;
200
201 if (!PyArg_ParseTuple(args, "sO", &uri, &callback) ||
202 uri == NULL ||
203 callback == NULL)
204 {
205 PyErr_SetString(PyExc_ValueError, "Expected a string (URI) and a callback function");
206 return NULL;
207 }
208
209 OrthancPlugins::LogInfo("Registering a Python REST callback on URI: " + std::string(uri));
210 OrthancPlugins::RegisterRestCallback<RestCallbackHandler>(uri, true /* thread safe */);
211
212 restCallbacks_.push_back(new RestCallback(uri, callback));
213
214 Py_INCREF(Py_None);
215 return Py_None;
216 }
217
218
219
220 void FinalizeRestCallbacks()
221 {
222 PythonLock lock;
223
224 for (std::list<RestCallback*>::iterator it = restCallbacks_.begin();
225 it != restCallbacks_.end(); ++it)
226 {
227 assert(*it != NULL);
228 delete *it;
229 }
230
231 restCallbacks_.clear();
232 }