Mercurial > hg > orthanc-python
annotate Sources/RestCallbacks.cpp @ 117:1029689d7b6e
fix
author | Alain Mazy <am@osimis.io> |
---|---|
date | Mon, 28 Aug 2023 16:18:37 +0200 |
parents | 32a67788a336 |
children | a3f77cf16396 |
rev | line source |
---|---|
0 | 1 /** |
2 * Python plugin for Orthanc | |
114
65ec5597ec70
upgrade to year 2023
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
3 * Copyright (C) 2020-2023 Osimis S.A., Belgium |
65ec5597ec70
upgrade to year 2023
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
4 * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium |
0 | 5 * |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU Affero General Public License | |
8 * as published by the Free Software Foundation, either version 3 of | |
9 * the License, or (at your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * Affero General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU Affero General Public License | |
17 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 **/ | |
19 | |
20 | |
21 #include "RestCallbacks.h" | |
22 | |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
63
diff
changeset
|
23 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
0 | 24 #include "Autogenerated/sdk.h" |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
63
diff
changeset
|
25 #include "PythonString.h" |
0 | 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 | |
98
62f2731094ae
fix build on ubuntu 16.04
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
67 void RestCallbackHandler(OrthancPluginRestOutput* output, |
62f2731094ae
fix build on ubuntu 16.04
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
68 const char* uri, |
62f2731094ae
fix build on ubuntu 16.04
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
69 const OrthancPluginHttpRequest* request) |
0 | 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 */)); | |
63
32de70a1e4c7
New functions from the SDK wrapped in Python: CreateDicom, RegisterFindCallback, RegisterMoveCallback
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
56
diff
changeset
|
87 PyObject *pInst = PyObject_CallObject((PyObject*) GetOrthancPluginRestOutputType(), args.GetPyObject()); |
0 | 88 |
89 | |
90 /** | |
91 * Construct the arguments tuple (output, uri) | |
92 **/ | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
93 PythonString str(lock, uri); |
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
94 |
0 | 95 PythonObject args2(lock, PyTuple_New(2)); |
96 PyTuple_SetItem(args2.GetPyObject(), 0, pInst); | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
97 PyTuple_SetItem(args2.GetPyObject(), 1, str.Release()); |
0 | 98 // No need to decrement refcount with "PyTuple_SetItem()" |
99 | |
100 /** | |
101 * Construct the named arguments from the "request" argument | |
102 **/ | |
103 const char* method; | |
104 switch (request->method) | |
105 { | |
106 case OrthancPluginHttpMethod_Get: | |
107 method = "GET"; | |
108 break; | |
109 | |
110 case OrthancPluginHttpMethod_Post: | |
111 method = "POST"; | |
112 break; | |
113 | |
114 case OrthancPluginHttpMethod_Put: | |
115 method = "PUT"; | |
116 break; | |
117 | |
118 case OrthancPluginHttpMethod_Delete: | |
119 method = "DELETE"; | |
120 break; | |
121 | |
122 default: | |
123 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); | |
124 } | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
125 |
0 | 126 PythonObject kw(lock, PyDict_New()); |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
127 |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
128 // We must handle memory ourselves for all members of kw that are set through PyDict_SetItem or PyDict_SetItemString. |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
129 // The Python documentation states that these methods do not steal references to the objects. |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
130 // We have indeed observed memory leaks when not handling memory this way. |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
131 std::unique_ptr<PythonObject> pyGet; |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
132 std::vector<boost::shared_ptr<PythonString> > pyGetValues; |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
133 std::unique_ptr<PythonObject> pyHeaders; |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
134 std::vector<boost::shared_ptr<PythonString> > pyHeaderValues; |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
135 std::unique_ptr<PythonObject> pyBody; |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
136 |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
137 PythonString pyMethod(lock, method); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
138 PyDict_SetItemString(kw.GetPyObject(), "method", pyMethod.GetPyObject()); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
139 |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
140 PythonObject pyGroups(lock, PyTuple_New(request->groupsCount)); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
141 for (uint32_t i = 0; i < request->groupsCount; i++) |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
142 { |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
143 PythonString tmp(lock, request->groups[i]); |
117 | 144 PyTuple_SetItem(pyGroups.GetPyObject(), i, tmp.Release()); // this PyTuple_SetItem method "steals" a reference -> no need to manage memory by ourselves ! |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
145 } |
0 | 146 |
117 | 147 PyDict_SetItemString(kw.GetPyObject(), "groups", pyGroups.GetPyObject()); |
0 | 148 |
149 if (request->method == OrthancPluginHttpMethod_Get) | |
150 { | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
151 pyGet.reset(new PythonObject(lock, PyDict_New())); |
0 | 152 for (uint32_t i = 0; i < request->getCount; i++) |
153 { | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
154 boost::shared_ptr<PythonString> value(new PythonString(lock, request->getValues[i])); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
155 PyDict_SetItemString(pyGet->GetPyObject(), request->getKeys[i], value->GetPyObject()); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
156 pyGetValues.push_back(value); |
0 | 157 } |
158 | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
159 PyDict_SetItemString(kw.GetPyObject(), "get", pyGet->GetPyObject()); |
0 | 160 } |
161 | |
162 { | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
163 pyHeaders.reset(new PythonObject(lock, PyDict_New())); |
0 | 164 for (uint32_t i = 0; i < request->headersCount; i++) |
165 { | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
166 boost::shared_ptr<PythonString> value(new PythonString(lock, request->headersValues[i])); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
167 PyDict_SetItemString(pyHeaders->GetPyObject(), request->headersKeys[i], value->GetPyObject()); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
168 pyHeaderValues.push_back(value); |
0 | 169 } |
170 | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
171 PyDict_SetItemString(kw.GetPyObject(), "headers", pyHeaders->GetPyObject()); |
0 | 172 } |
173 | |
174 if (request->method == OrthancPluginHttpMethod_Post || | |
175 request->method == OrthancPluginHttpMethod_Put) | |
176 { | |
116
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
177 pyBody.reset(new PythonObject(lock, PyBytes_FromStringAndSize( |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
178 reinterpret_cast<const char*>(request->body), request->bodySize))); |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
179 |
32a67788a336
fix memory leaks in orthanc.RestApiPost() and sibling methods
Alain Mazy <am@osimis.io>
parents:
114
diff
changeset
|
180 PyDict_SetItemString(kw.GetPyObject(), "body", pyBody->GetPyObject()); |
0 | 181 } |
182 | |
183 /** | |
184 * Call the user-defined function | |
185 **/ | |
186 PythonObject result(lock, PyObject_Call( | |
187 (*it)->GetCallback(), args2.GetPyObject(), kw.GetPyObject())); | |
3
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
188 |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
189 std::string traceback; |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
190 if (lock.HasErrorOccurred(traceback)) |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
191 { |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
192 OrthancPlugins::LogError("Error in the REST callback, traceback:\n" + traceback); |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
193 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
194 } |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
195 |
0 | 196 return; |
197 } | |
198 } | |
199 | |
200 // Should never happen | |
201 OrthancPlugins::LogError("Unable to find the Python handler for URI: " + std::string(uri)); | |
202 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); | |
203 } | |
204 | |
205 | |
206 | |
207 PyObject* RegisterRestCallback(PyObject* module, PyObject* args) | |
208 { | |
209 // The GIL is locked at this point (no need to create "PythonLock") | |
210 | |
211 // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c | |
212 const char* uri = NULL; | |
213 PyObject* callback = NULL; | |
214 | |
215 if (!PyArg_ParseTuple(args, "sO", &uri, &callback) || | |
216 uri == NULL || | |
217 callback == NULL) | |
218 { | |
219 PyErr_SetString(PyExc_ValueError, "Expected a string (URI) and a callback function"); | |
220 return NULL; | |
221 } | |
222 | |
223 OrthancPlugins::LogInfo("Registering a Python REST callback on URI: " + std::string(uri)); | |
224 OrthancPlugins::RegisterRestCallback<RestCallbackHandler>(uri, true /* thread safe */); | |
225 | |
226 restCallbacks_.push_back(new RestCallback(uri, callback)); | |
227 | |
228 Py_INCREF(Py_None); | |
229 return Py_None; | |
230 } | |
231 | |
232 | |
233 | |
234 void FinalizeRestCallbacks() | |
235 { | |
236 PythonLock lock; | |
237 | |
238 for (std::list<RestCallback*>::iterator it = restCallbacks_.begin(); | |
239 it != restCallbacks_.end(); ++it) | |
240 { | |
241 assert(*it != NULL); | |
242 delete *it; | |
243 } | |
244 | |
245 restCallbacks_.clear(); | |
246 } |