URL Registration¶
Routes Registration¶
The application can register its list of HTTP APIs in the CALLBACK_ROUTES callback. When CALLBACK_ROUTES is invoked, a mapper object will be passed along with it. URLs can be connected to this mapper through this callback. Here we use Routes module version 2.2.0 to manage the URL routing.
Every URL to be accessed should start with /<provider-code>/<app-code>/
where <provider-code>
and <app-code>
corresponds to the application’s provider
code and app code registered with the Aalam developer portal respectively. Any URL
which violates the above format can never be accessed.
To know more about the structure of the URL to be registered, one can go through the documentation of python-routes modules.
Following are the type of URLs along with their formats.
Static URL with no dynamic arguments. Only the requests matching this exact format will be routed to this handler.
/aalam/base/users
A URL with one dynamic argument. The dynamic argument should be enclosed in
{}
. For this URL the name of the argument will be passed as a parameter to the handler./aalam/base/user/{user_email_id}
In the above,
{user_email_id}
is a dynamic argument and the handler method will be passed with an argument with the value of {user_email_id}. The above URL format will be a match for/aalam/base/user/user1@test.test /aalam/base/user/self
A URL with multiple dynamic arguments. The arguments to the handler will be passed in the order of their appearence.
/aalam/base/user/{email_id}/status/{status}/mark
In the above example,
email_id
andstatus
are passed to the action method in the order of the appearence. The action method can be defined likedef mark_user_status(self, request, email_id, status): # Actual logic here pass
The dynamic arguments defined like above can be just one element in the URL path. If a URL wants to have sub portion of itself as a dynamic argument, it can defined like
/aalam/base/statics/{path_info:.*}
The above URL format will be a match for
/aalam/base/statics/1 /aalam/base/statics/1/2 /aalam/base/statics/1/2/3/4/5
The URLs are overlapping. In such case, only the first registration will be chosen. Let’s say we have the following URL formats.
/aalam/base/static/{f}/{s}/{t} /aalam/base/static/{path_info:.*}
Above URLs both will be a match for /aalam/base/static/1/2/3
. If we register
/aalam/base/static/{f}/{s}/{t}
before the {path_info.*}, any three path element
will be routed to the former.
Mapper Arguments¶
When a URL is registered, following kwargs will be used by the framework.
handler
Object of a class that inherits BaseHandler which defines the actions and serializers for the URL. This argument is mandatory.
action
Name of the method in the handler which actually processes the URL functionality. This argument is mandatory. If an invalid method name is passed, a response of ‘503 - Not Implemented’ will be given as a response to all the requests for this URL.
See Action handler
permissions
Permissions object, describing the list of rules and permissions to use the URL. This is an optional parameter, if this parameter is present, it means anyone can use this URL.
conditions
A Dictionary object. It should have a key value ‘method’ which should be a list with the list of HTTP methods that are applicable on this URL. Ex.
conditions={"method": ["GET"]}
hook_data
The hook input data sent by ‘B’ Hooks. The hook input will be relevant only if the URL is interested in the hook inputs.
This is a dictionary object with keys being the hooker application in the format
provider_code/app_code
, and the value being the data returned by the hooker.static_file
Though applications can pre-inform the static URL and the way to access the static data through it’s PKG-INFO, there might be some situation which would want to send a static file on a non-static URL. For example, if an application has a static-url prefix as
/aalam/base/s/
and a valid static directory. Support the application wants to serve a static file in a URL that does not have a prefix/aalam/base/s/
, or if the file is not on the usual place that the framework can find, it can inform the framework about this static resource through this attribute.This attribute needs to be set by the request action handler. The value to this attribute must be a dictionary with two keys
path
- The path to the resourceresource
- A unique name of this resource. This name should not clash with any of the other static resources.
Ex.
request.static_file = {"path": "/absolute/path/to/the/static/file", "resource": "should be a unique resource"}
By doing so, the framework will take care of sending the response.
deserializer
Name of the method to deserialize the input content for a URL. This method should accept one parameter and should return a dictionary object. The returned dictionary object will be passed to the
action
method as kwargs. For example, if a URL/aalam/users
accepts a json input data like{ "name": "Some name", "age": 50, "occupation": "Some occupation", }
the
action
callback should be likedef action_handler(self, request, name=None, age=None, occupation=None): # Do something here pass
or it can be like
def action_handler(self, request, **kwargs): # Do something here pass
serializer
Name of the method to serialize the data returned by
action
method to return to the client. This method accepts two parameters in the following order- Data object returned by
action
method - Webob response object.
The data has to be serialized to it’s correct format and will set the in the response body. Any manipulation to be done on the response object like modifying headers, can be done here. This will be called only when the
action
method returns a non-null value. The default serializer is thejson_serializer
defined in theBaseHandler
class.Sample code registering the argument can be seen below
from aalam_common.wsgi import BaseHandler from aalam_common.role_mgmt import Permissions class UserHandler(BaseHandler): def create_user_details(self, request, email_id, **kwargs): name = kwargs.get("name", None) occupation = kwargs.get("occupation", None) age = kwargs.get("age", None) def routes_callback(mapper): user_handler = UserHandler() mapper.connect("/aalam/base/user/{email_id}", handler=user_handler, action="create_user_details", conditions={"method": ["PUT"]}, permissions=Permissions.deny_anon(), serializer="html_serializer", deserializer="xml_serializer")
The above url registration, registers a ‘PUT’ method for the path that matches
/aalam/base/user/{email_id}
. This URL has an input data in XML format and the input data is deserializer byxml_serializer
method in theuser_handler
object. It also sends a html response that is serialized by thehtml_serializer
method ofuser_handler
object. This URL is not permitted for the anonymous users as described thepermissions
argument.create_user_details
method processes the functionality of this URL.- Data object returned by
Submappers¶
Submapper is a concept where in if many URLs have the same set of mapper arguments, it can be grouped in one sub mapper object and the varying arguments can be connected to that sub mapper object. For example,
/aalam/base/users
/aalam/base/user/{email_id}
/aalam/base/user/{email_id}/status/{status}
all have the same ‘handler’. Instead of passing the same handler object for every mapper.connect(), on can create a submapper object with the handler argument and connect the url to the submapper object. A submapper can be used like
with mapper.submapper(handler=UserHandler(),
permissions=Permissions.deny_anon()) as sub_mapper:
sub_mapper.connect("/aalam/base/users", action="get_users",
conditions={"method": ["GET"]})
sub_mapper.connect("/aalam/base/user/email_id}",
action="get_user",
conditions={"method": ["GET"]})
Base Handler¶
The base handler for all the URL handler objects can be imported from
aalam_common.wsgi
. The base handler should be inherited by the handler objects
and the action/serializer/deserializer methods for the handler object should
be defined in it.
Input content type of application/json
can be automatically deserialized
by BaseHandler.json_deserializer()
. If the input content type is not json and
there is no deserializer set for the URL, the input content will be ignored.
BaseHandler
has an inbuilt html_serizlier which accepts a string data. It
sends minified html content. This reformats the HTML content internally, if
the html data is desired to be sent in its original form, a custom serializer
should be defined.
Action Handler¶
Action handlers are the methods that process the logic of any URL. This is
set in the action
argument while registering a URL and is mandatory for
all URL registrations.
Action handlers are expected to either return some data or raise a webob exception.
If data is a tuple, the first parameter should be an integer, which will be the status code that needs to be set in the response. The second parameter is the output data. If data is not a tuple then it is treated as the output data.
Output data will be serialized on to the response by the serializer
set for the URL. If no serializers are set and output data is not None,
aalam_common.wsgi.BaseHandler:json_serializer
will be used.
If there is an unexpected exception arising from the action handler,
the client will receive 500 Internal Server Error
response.
The first parameter to the ‘action’ method is object whose class inherits Webob request. In addition to the attributes of the webob request, one can access the following attributes.
- sqa_session
An sqlalchemy session to the MYSQL database of this application. This session is established specifically for this request. If the request returns a status code from the range of the 400 - 599, any transaction pending on the session will not be committed to the database.
- auth
The authentication parameters. See authentication for more details.
- user_perms
Permissions enabled for the user requesting this request. This will be a list permission ids.aalam_common.role_mgmt
module has the following helper methods that makes use of this attribute.
is_user_admin(email_id or request-object)
to check if the user requesting the url is an administrator.is_client_authorized(request, permission_group_name, permission_name)
to check if the user requesting this url has a needed permission.
Route permissions¶
The permissions attribute for the URL registration should be an object of
class aalam_common.role_mgmt.Permissions
. The permissions object describes the
rules and the list of permissions for a client to access a URL. An Administrator
user can however be allowed to access any URL and the permissions object will
not be applicable to an Administrator
The following static methods defines list of applicable permissions and the condition on the permissions.
all(*args)
This static method accepts the list of permissions in the form “<permission-group-name>/<permission-name>”. It returns aPermissions
object. The object, when used by the URL, will allow clients with all the permissions in *args to access the URL.
any(*args)
This static method accepts the list of permissions in the form “<permission-group-name>/<permission-name>”. It returns aPermissions
object. This object, when used by the URL, will allow any client with atleast one permission in *args to access the URL.
Other rules can be applied on a URL by the following method. The following methods return the permissions object so that the rules can be chained like
Permissions.any('pg1/p1', 'pg1/p2').deny_anon().deny_ext().deny_exc('aalam/base')
or like
Permissions().deny_anon().deny_ext().deny_exc('aalam/base')
deny_anon(self)
This method creates a rule for the URL to be denied for all anonymous requests. An anonymous request means that the client accessing this URL has not logged in or does not have a valid authentication token.
deny_ext(self)
This method creates a rule for the URL to be denied to all the external requests. External requests are the requests that are originating from an authenticated remote program. This will be used in the future.
deny_exc(self, *args)
This method creates a rule for the URL to be denied to a list of applications running internally and requesting this URL. Every list member should of the form<provider-code>/<app-code>
. It can optionally bePermission.ALL_APPS
which means it is denied for all the applications running internally.
If the route permissions does not have any permissions defined only the rules will be applicable.
If the URL cannot choose a permissions statically, or if it wants to authorize based on dynamic condition like the user requesting it, it can do this check in the action handler. For example,
GET /aalam/base/user/{user_email}
In the above URL, if user_email
is self
, any user can access it. But if
user_email
is a valid email_id, it will only be allowed to users with
“Users/manage” permissions. This kind of check can be done in the action
handler like below.
def get_user_details(self, request, user_email):
if user_email == 'self':
(_, user_email) = aalam_common.auth.get_auth_user_id(
request, deny_anon=True)
else:
if not aalam_common.role_mgmt.is_client_authorized(
request, "Users", "manage"):
raise webob.exc.HTTPForbidden()