Skip to content

Commit 490a4c9

Browse files
committed
refactor (Tornado): describe HTTP methods in BaseRequestHandler
1 parent 894a8cc commit 490a4c9

File tree

3 files changed

+103
-84
lines changed

3 files changed

+103
-84
lines changed

src/DIRAC/Core/Tornado/Server/TornadoREST.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
__RCSID__ = "$Id$"
1111

1212
import inspect
13-
from tornado import gen
14-
from tornado.ioloop import IOLoop
1513
from urllib.parse import unquote
1614

1715
from DIRAC import gLogger, S_OK
@@ -186,22 +184,6 @@ def _getMethodArgs(self, args):
186184

187185
return (positionalArguments, keywordArguments)
188186

189-
@gen.coroutine
190-
def get(self, *args, **kwargs): # pylint: disable=arguments-differ
191-
"""Method to handle incoming ``GET`` requests.
192-
Note that all the arguments are already prepared in the :py:meth:`.prepare` method.
193-
"""
194-
retVal = yield IOLoop.current().run_in_executor(*self._prepareExecutor(args))
195-
self._finishFuture(retVal)
196-
197-
@gen.coroutine
198-
def post(self, *args, **kwargs): # pylint: disable=arguments-differ
199-
"""Method to handle incoming ``POST`` requests.
200-
Note that all the arguments are already prepared in the :py:meth:`.prepare` method.
201-
"""
202-
retVal = yield IOLoop.current().run_in_executor(*self._prepareExecutor(args))
203-
self._finishFuture(retVal)
204-
205187
auth_echo = ["all"]
206188

207189
@staticmethod

src/DIRAC/Core/Tornado/Server/TornadoService.py

Lines changed: 41 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
import os
1515
from datetime import datetime
1616

17-
from tornado import gen
18-
from tornado.ioloop import IOLoop
19-
2017
import DIRAC
2118

2219
from DIRAC import gLogger, S_OK
@@ -94,10 +91,49 @@ def export_streamToClient(self, myDataToSend, token):
9491
9592
The handler only define the ``post`` verb. Please refer to :py:meth:`.post` for the details.
9693
94+
The ``POST`` arguments expected are:
95+
96+
* ``method``: name of the method to call
97+
* ``args``: JSON encoded arguments for the method
98+
* ``extraCredentials``: (optional) Extra informations to authenticate client
99+
* ``rawContent``: (optionnal, default False) If set to True, return the raw output
100+
of the method called.
101+
102+
If ``rawContent`` was requested by the client, the ``Content-Type``
103+
is ``application/octet-stream``, otherwise we set it to ``application/json``
104+
and JEncode retVal.
105+
106+
If ``retVal`` is a dictionary that contains a ``Callstack`` item,
107+
it is removed, not to leak internal information.
108+
109+
110+
Example of call using ``requests``::
111+
112+
In [20]: url = 'https://server:8443/DataManagement/TornadoFileCatalog'
113+
...: cert = '/tmp/x509up_u1000'
114+
...: kwargs = {'method':'whoami'}
115+
...: caPath = '/home/dirac/ClientInstallDIR/etc/grid-security/certificates/'
116+
...: with requests.post(url, data=kwargs, cert=cert, verify=caPath) as r:
117+
...: print r.json()
118+
...:
119+
{u'OK': True,
120+
u'Value': {u'DN': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]',
121+
u'group': u'dirac_user',
122+
u'identity': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]',
123+
u'isLimitedProxy': False,
124+
u'isProxy': True,
125+
u'issuer': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]',
126+
u'properties': [u'NormalUser'],
127+
u'secondsLeft': 85441,
128+
u'subject': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]/CN=2409820262',
129+
u'username': u'adminusername',
130+
u'validDN': False,
131+
u'validGroup': False}}
132+
97133
"""
98134

99-
# Prefix of methods names
100-
METHOD_PREFIX = "export_"
135+
# To access DIRAC services, RPC requests are used only through the HTTP POST method
136+
SUPPORTED_METHODS = ["POST"]
101137

102138
@classmethod
103139
def _getServiceName(cls, request):
@@ -151,67 +187,6 @@ def _getMethodArgs(self, args):
151187
args_encoded = self.get_body_argument("args", default=encode([]))
152188
return (decode(args_encoded)[0], {})
153189

154-
# Make post a coroutine.
155-
# See https://www.tornadoweb.org/en/branch5.1/guide/coroutines.html#coroutines
156-
# for details
157-
@gen.coroutine
158-
def post(self, *args, **kwargs): # pylint: disable=arguments-differ
159-
"""
160-
Method to handle incoming ``POST`` requests.
161-
Note that all the arguments are already prepared in the :py:meth:`.prepare`
162-
method.
163-
164-
The ``POST`` arguments expected are:
165-
166-
* ``method``: name of the method to call
167-
* ``args``: JSON encoded arguments for the method
168-
* ``extraCredentials``: (optional) Extra informations to authenticate client
169-
* ``rawContent``: (optionnal, default False) If set to True, return the raw output
170-
of the method called.
171-
172-
If ``rawContent`` was requested by the client, the ``Content-Type``
173-
is ``application/octet-stream``, otherwise we set it to ``application/json``
174-
and JEncode retVal.
175-
176-
If ``retVal`` is a dictionary that contains a ``Callstack`` item,
177-
it is removed, not to leak internal information.
178-
179-
180-
Example of call using ``requests``::
181-
182-
In [20]: url = 'https://server:8443/DataManagement/TornadoFileCatalog'
183-
...: cert = '/tmp/x509up_u1000'
184-
...: kwargs = {'method':'whoami'}
185-
...: caPath = '/home/dirac/ClientInstallDIR/etc/grid-security/certificates/'
186-
...: with requests.post(url, data=kwargs, cert=cert, verify=caPath) as r:
187-
...: print r.json()
188-
...:
189-
{u'OK': True,
190-
u'Value': {u'DN': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]',
191-
u'group': u'dirac_user',
192-
u'identity': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]',
193-
u'isLimitedProxy': False,
194-
u'isProxy': True,
195-
u'issuer': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]',
196-
u'properties': [u'NormalUser'],
197-
u'secondsLeft': 85441,
198-
u'subject': u'/C=ch/O=DIRAC/OU=DIRAC CI/CN=ciuser/[email protected]/CN=2409820262',
199-
u'username': u'adminusername',
200-
u'validDN': False,
201-
u'validGroup': False}}
202-
"""
203-
# Execute the method in an executor (basically a separate thread)
204-
# Because of that, we cannot calls certain methods like `self.write`
205-
# in _executeMethod. This is because these methods are not threadsafe
206-
# https://www.tornadoweb.org/en/branch5.1/web.html#thread-safety-notes
207-
# However, we can still rely on instance attributes to store what should
208-
# be sent back (reminder: there is an instance
209-
# of this class created for each request)
210-
retVal = yield IOLoop.current().run_in_executor(*self._prepareExecutor(args))
211-
212-
# retVal is :py:class:`tornado.concurrent.Future`
213-
self._finishFuture(retVal)
214-
215190
auth_ping = ["all"]
216191

217192
def export_ping(self):

src/DIRAC/Core/Tornado/Server/private/BaseRequestHandler.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import jwt
2020
import tornado
21+
from tornado import gen
2122
from tornado.web import RequestHandler, HTTPError
23+
from tornado.ioloop import IOLoop
2224
from tornado.concurrent import Future
2325

2426
import DIRAC
@@ -801,3 +803,63 @@ def srv_getURL(self):
801803
Return the URL
802804
"""
803805
return self.request.path
806+
807+
# Here we define all HTTP methods, but ONLY those defined in SUPPORTED_METHODS will be used.
808+
809+
# Make a coroutine, see https://www.tornadoweb.org/en/branch5.1/guide/coroutines.html#coroutines for details
810+
@gen.coroutine
811+
def get(self, *args, **kwargs): # pylint: disable=arguments-differ
812+
"""Method to handle incoming ``GET`` requests.
813+
.. note:: all the arguments are already prepared in the :py:meth:`.prepare` method.
814+
"""
815+
if "GET" in self.SUPPORTED_METHODS:
816+
# Execute the method in an executor (basically a separate thread)
817+
# Because of that, we cannot calls certain methods like `self.write`
818+
# in _executeMethod. This is because these methods are not threadsafe
819+
# https://www.tornadoweb.org/en/branch5.1/web.html#thread-safety-notes
820+
# However, we can still rely on instance attributes to store what should
821+
# be sent back (reminder: there is an instance of this class created for each request)
822+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
823+
self.__finishFuture(retVal)
824+
825+
@gen.coroutine
826+
def post(self, *args, **kwargs): # pylint: disable=arguments-differ
827+
"""Method to handle incoming ``POST`` requests."""
828+
if "POST" in self.SUPPORTED_METHODS:
829+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
830+
self.__finishFuture(retVal)
831+
832+
@gen.coroutine
833+
def head(self, *args, **kwargs): # pylint: disable=arguments-differ
834+
"""Method to handle incoming ``HEAD`` requests."""
835+
if "HEAD" in self.SUPPORTED_METHODS:
836+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
837+
self.__finishFuture(retVal)
838+
839+
@gen.coroutine
840+
def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
841+
"""Method to handle incoming ``DELETE`` requests."""
842+
if "DELETE" in self.SUPPORTED_METHODS:
843+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
844+
self.__finishFuture(retVal)
845+
846+
@gen.coroutine
847+
def patch(self, *args, **kwargs): # pylint: disable=arguments-differ
848+
"""Method to handle incoming ``PATCH`` requests."""
849+
if "PATCH" in self.SUPPORTED_METHODS:
850+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
851+
self.__finishFuture(retVal)
852+
853+
@gen.coroutine
854+
def put(self, *args, **kwargs): # pylint: disable=arguments-differ
855+
"""Method to handle incoming ``PUT`` requests."""
856+
if "PUT" in self.SUPPORTED_METHODS:
857+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
858+
self.__finishFuture(retVal)
859+
860+
@gen.coroutine
861+
def options(self, *args, **kwargs): # pylint: disable=arguments-differ
862+
"""Method to handle incoming ``OPTIONS`` requests."""
863+
if "OPTIONS" in self.SUPPORTED_METHODS:
864+
retVal = yield IOLoop.current().run_in_executor(*self.__prepareExecutor(args))
865+
self.__finishFuture(retVal)

0 commit comments

Comments
 (0)