Mercurial > hg > orthanc-python
annotate Sources/RestCallbacks.cpp @ 220:7ecdfdcb49d5
NEWS
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 04 Jul 2024 08:32:24 +0200 |
parents | 3678a028f1f6 |
children |
rev | line source |
---|---|
219
3678a028f1f6
making the project REUSE-compliant
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
202
diff
changeset
|
1 /** |
3678a028f1f6
making the project REUSE-compliant
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
202
diff
changeset
|
2 * SPDX-FileCopyrightText: 2020-2023 Osimis S.A., 2024-2024 Orthanc Team SRL, 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain |
3678a028f1f6
making the project REUSE-compliant
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
202
diff
changeset
|
3 * SPDX-License-Identifier: AGPL-3.0-or-later |
3678a028f1f6
making the project REUSE-compliant
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
202
diff
changeset
|
4 */ |
3678a028f1f6
making the project REUSE-compliant
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
202
diff
changeset
|
5 |
0 | 6 /** |
7 * Python plugin for Orthanc | |
166
6fada29b6759
updated copyright, as Orthanc Team now replaces Osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
155
diff
changeset
|
8 * Copyright (C) 2020-2023 Osimis S.A., Belgium |
6fada29b6759
updated copyright, as Orthanc Team now replaces Osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
155
diff
changeset
|
9 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium |
155
71d305c29cfa
updated year to 2024
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
131
diff
changeset
|
10 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium |
0 | 11 * |
12 * This program is free software: you can redistribute it and/or | |
13 * modify it under the terms of the GNU Affero General Public License | |
14 * as published by the Free Software Foundation, either version 3 of | |
15 * the License, or (at your option) any later version. | |
16 * | |
17 * This program is distributed in the hope that it will be useful, but | |
18 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 * Affero General Public License for more details. | |
21 * | |
22 * You should have received a copy of the GNU Affero General Public License | |
23 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
24 **/ | |
25 | |
26 | |
27 #include "RestCallbacks.h" | |
28 | |
200 | 29 #include "PythonHeaderWrapper.h" |
30 | |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
63
diff
changeset
|
31 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" |
0 | 32 #include "Autogenerated/sdk.h" |
123
5f0b7795afaf
fixing the first leak
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
33 #include "PythonBytes.h" |
68
0b3ef425db31
refactoring using ICallbackRegistration::Apply()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
63
diff
changeset
|
34 #include "PythonString.h" |
0 | 35 |
36 #include <boost/regex.hpp> | |
202 | 37 #include <boost/thread.hpp> |
0 | 38 |
39 | |
40 class RestCallback : public boost::noncopyable | |
41 { | |
42 private: | |
43 boost::regex regex_; | |
44 PyObject* callback_; | |
45 | |
46 public: | |
47 RestCallback(const std::string& uri, | |
48 PyObject* callback) : | |
49 regex_(uri), | |
50 callback_(callback) | |
51 { | |
52 Py_XINCREF(callback_); | |
53 } | |
54 | |
55 ~RestCallback() | |
56 { | |
57 Py_XDECREF(callback_); | |
58 } | |
59 | |
60 bool IsMatch(const std::string& uri) const | |
61 { | |
62 return boost::regex_match(uri, regex_); | |
63 } | |
64 | |
65 PyObject* GetCallback() | |
66 { | |
67 return callback_; | |
68 } | |
69 }; | |
70 | |
71 | |
201
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
72 static boost::shared_mutex restCallbacksMutex_; |
0 | 73 static std::list<RestCallback*> restCallbacks_; |
74 | |
75 | |
98
62f2731094ae
fix build on ubuntu 16.04
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
76 void RestCallbackHandler(OrthancPluginRestOutput* output, |
62f2731094ae
fix build on ubuntu 16.04
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
77 const char* uri, |
62f2731094ae
fix build on ubuntu 16.04
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
68
diff
changeset
|
78 const OrthancPluginHttpRequest* request) |
0 | 79 { |
201
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
80 boost::shared_lock<boost::shared_mutex> restCallbacksLock(restCallbacksMutex_); |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
81 |
0 | 82 for (std::list<RestCallback*>::const_iterator it = restCallbacks_.begin(); |
83 it != restCallbacks_.end(); ++it) | |
84 { | |
85 assert(*it != NULL); | |
86 if ((*it)->IsMatch(uri)) | |
87 { | |
201
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
88 PythonLock lock; |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
89 |
0 | 90 /** |
91 * Construct an instance object of the "orthanc.RestOutput" | |
92 * class. This is done by calling the constructor function | |
93 * "sdk_OrthancPluginRestOutput_Type". | |
94 **/ | |
95 PythonObject args(lock, PyTuple_New(2)); | |
96 PyTuple_SetItem(args.GetPyObject(), 0, PyLong_FromSsize_t((intptr_t) output)); | |
97 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
|
98 PyObject *pInst = PyObject_CallObject((PyObject*) GetOrthancPluginRestOutputType(), args.GetPyObject()); |
0 | 99 |
100 | |
101 /** | |
102 * Construct the arguments tuple (output, uri) | |
103 **/ | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
104 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
|
105 |
0 | 106 PythonObject args2(lock, PyTuple_New(2)); |
107 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
|
108 PyTuple_SetItem(args2.GetPyObject(), 1, str.Release()); |
0 | 109 // No need to decrement refcount with "PyTuple_SetItem()" |
110 | |
111 /** | |
112 * Construct the named arguments from the "request" argument | |
113 **/ | |
114 const char* method; | |
115 switch (request->method) | |
116 { | |
117 case OrthancPluginHttpMethod_Get: | |
118 method = "GET"; | |
119 break; | |
120 | |
121 case OrthancPluginHttpMethod_Post: | |
122 method = "POST"; | |
123 break; | |
124 | |
125 case OrthancPluginHttpMethod_Put: | |
126 method = "PUT"; | |
127 break; | |
128 | |
129 case OrthancPluginHttpMethod_Delete: | |
130 method = "DELETE"; | |
131 break; | |
132 | |
133 default: | |
134 ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange); | |
135 } | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
136 |
0 | 137 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
|
138 |
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
139 { |
53 | 140 PythonString tmp(lock, method); |
126
9ffe8881f856
fixing more PyDict_SetItemString()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
123
diff
changeset
|
141 PyDict_SetItemString(kw.GetPyObject(), "method", tmp.GetPyObject()); |
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 } |
0 | 143 |
144 { | |
145 PythonObject groups(lock, PyTuple_New(request->groupsCount)); | |
146 | |
147 for (uint32_t i = 0; i < request->groupsCount; i++) | |
148 { | |
53 | 149 PythonString tmp(lock, request->groups[i]); |
150 PyTuple_SetItem(groups.GetPyObject(), i, tmp.Release()); | |
0 | 151 } |
152 | |
126
9ffe8881f856
fixing more PyDict_SetItemString()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
123
diff
changeset
|
153 PyDict_SetItemString(kw.GetPyObject(), "groups", groups.GetPyObject()); |
0 | 154 } |
155 | |
156 if (request->method == OrthancPluginHttpMethod_Get) | |
157 { | |
158 PythonObject get(lock, PyDict_New()); | |
159 | |
160 for (uint32_t i = 0; i < request->getCount; i++) | |
161 { | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
162 PythonString value(lock, request->getValues[i]); |
126
9ffe8881f856
fixing more PyDict_SetItemString()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
123
diff
changeset
|
163 PyDict_SetItemString(get.GetPyObject(), request->getKeys[i], value.GetPyObject()); |
0 | 164 } |
165 | |
126
9ffe8881f856
fixing more PyDict_SetItemString()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
123
diff
changeset
|
166 PyDict_SetItemString(kw.GetPyObject(), "get", get.GetPyObject()); |
0 | 167 } |
168 | |
169 { | |
170 PythonObject headers(lock, PyDict_New()); | |
171 | |
172 for (uint32_t i = 0; i < request->headersCount; i++) | |
173 { | |
45
ee76cced46a5
Fix issue #185 (segfaults on non-UTF8 special characters in request URI)
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
36
diff
changeset
|
174 PythonString value(lock, request->headersValues[i]); |
126
9ffe8881f856
fixing more PyDict_SetItemString()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
123
diff
changeset
|
175 PyDict_SetItemString(headers.GetPyObject(), request->headersKeys[i], value.GetPyObject()); |
0 | 176 } |
177 | |
126
9ffe8881f856
fixing more PyDict_SetItemString()
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
123
diff
changeset
|
178 PyDict_SetItemString(kw.GetPyObject(), "headers", headers.GetPyObject()); |
0 | 179 } |
180 | |
181 if (request->method == OrthancPluginHttpMethod_Post || | |
182 request->method == OrthancPluginHttpMethod_Put) | |
183 { | |
123
5f0b7795afaf
fixing the first leak
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
184 PythonBytes bytes(lock, request->body, request->bodySize); |
5f0b7795afaf
fixing the first leak
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
185 PyDict_SetItemString(kw.GetPyObject(), "body", bytes.GetPyObject()); |
0 | 186 } |
187 | |
188 /** | |
189 * Call the user-defined function | |
190 **/ | |
191 PythonObject result(lock, PyObject_Call( | |
192 (*it)->GetCallback(), args2.GetPyObject(), kw.GetPyObject())); | |
3
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
193 |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
194 std::string traceback; |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
195 if (lock.HasErrorOccurred(traceback)) |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
196 { |
171
c8de83fe7faa
removed deprecation warnings
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
166
diff
changeset
|
197 ORTHANC_PLUGINS_LOG_ERROR("Error in the REST callback, traceback:\n" + traceback); |
3
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
198 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin); |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
199 } |
26762eb9d704
reporting of exceptions
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
0
diff
changeset
|
200 |
0 | 201 return; |
202 } | |
203 } | |
204 | |
205 // Should never happen | |
171
c8de83fe7faa
removed deprecation warnings
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
166
diff
changeset
|
206 ORTHANC_PLUGINS_LOG_ERROR("Unable to find the Python handler for URI: " + std::string(uri)); |
0 | 207 ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); |
208 } | |
209 | |
210 | |
211 | |
212 PyObject* RegisterRestCallback(PyObject* module, PyObject* args) | |
213 { | |
214 // The GIL is locked at this point (no need to create "PythonLock") | |
215 | |
216 // https://docs.python.org/3/extending/extending.html#calling-python-functions-from-c | |
217 const char* uri = NULL; | |
218 PyObject* callback = NULL; | |
219 | |
220 if (!PyArg_ParseTuple(args, "sO", &uri, &callback) || | |
221 uri == NULL || | |
222 callback == NULL) | |
223 { | |
224 PyErr_SetString(PyExc_ValueError, "Expected a string (URI) and a callback function"); | |
225 return NULL; | |
226 } | |
201
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
227 |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
228 { |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
229 boost::unique_lock<boost::shared_mutex> restCallbacksLock(restCallbacksMutex_); |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
230 |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
231 ORTHANC_PLUGINS_LOG_INFO("Registering a Python REST callback on URI: " + std::string(uri)); |
202 | 232 OrthancPlugins::RegisterRestCallback<RestCallbackHandler>( |
233 uri, true /* thread safe, thus OrthancPluginRegisterRestCallbackNoLock() is used internally */); | |
0 | 234 |
201
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
235 restCallbacks_.push_back(new RestCallback(uri, callback)); |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
236 } |
0 | 237 |
238 Py_INCREF(Py_None); | |
239 return Py_None; | |
240 } | |
241 | |
242 | |
243 | |
244 void FinalizeRestCallbacks() | |
245 { | |
201
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
246 boost::unique_lock<boost::shared_mutex> restCallbacksLock(restCallbacksMutex_); |
2a2fae513366
clarified locking in RestCallbacks
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
200
diff
changeset
|
247 |
0 | 248 for (std::list<RestCallback*>::iterator it = restCallbacks_.begin(); |
249 it != restCallbacks_.end(); ++it) | |
250 { | |
251 assert(*it != NULL); | |
252 delete *it; | |
253 } | |
254 | |
255 restCallbacks_.clear(); | |
256 } |