Requests and responses¶
When a web request arrives at the front-end nginx server, it’s first subjected to the url rewriting. After that, the destiny of the request is decided as follows:
- if the request is GET or POST, and the request url starts with
/_
, it’s routed to the front webapp - if the request is GET and the url starts with
/gws-client
, it’s routed to theapp/www
folder, which contains our client. This folder is populated by the client build - otherwise, if the request is GET, it’s served by nginx itself from the configured web root folder
- otherwise (POST and other request methods), it’s a 400 bad request error
In simpler words, static content is being served unless the request url starts with an underscore.
When a GET request lands in the web app, it’s expected to provide parameters, either in the usual way (e.g. the query string), or encoded in the path, which is assumed to be like /parameter/value/parameter2/value2...
. For example, this url
/_/cmd/mapHttpGetBox/layerUid/test.one?bbox=11,22,33,44
results in this
{
cmd: "mapHttpGetBox"
layerUid: "test.one"
bbox: "11,22,33,44"
}
When processing a POST request, the payload is expected to be in JSON or MsgPack format, depending on the Content-Type
, and to have the following structure
{
cmd: "actionName",
params: {
// parameters as specified in the respective action handler
}
}
Once the parameters structure has been parsed, the webapp takes the cmd
parameter and determines the action module (from ext/actions
) and the command handler method, for example
mapHttpGetBox => module=map, method=http_get_box
assetHttpGetPath => module=asset, method=http_get_path
printerSnapshot => module=printer, method=api_snapshot
If the module doesn’t exist or is not configured for the current application or project, or the current user doesn’t have a permission to use the module, the request is rejected with the error 404 or 403.
Now, the webapp uses the specifications generated by make spec
to ensure the parameters names and types match the structure declared in the action method. For JSON/MsgPack requests, the validation is strict (that is, a property declared as int
should actually be an integer), http requests use “soft” validation (e.g. if an int
is required, the parameter value can be a numeric string). If the validation fails, the request is rejected with the error 400.
After the validation completes and the parameters have been converted, the webapp picks the Object
object from the respective action module and invokes the command method with two arguments: the request object and the parameters object.
The command method is supposed to return a Response
object or to raise a HTTPException
as defined is web/error.py
. Returns of type HttpResponse
or FileResponse
are sent to directly to the client, other responses are encoded in the same format as the request (JSON or MsgPack).
Illustration¶
Here’s a code snippet that illustrates the above concepts
"""Example action object that provides an API method `hello`, which can be invoked in the client as `exampleHello`.
The code must be placed in gws/ext/action/example/__init__.py
"""
# import the generic Action object
import gws.common.action
# import web errors
import gws.web.error
# import our types
import gws.types as t
# define a Config object for this action
# this object will be automatically checked when the configurator encounters `action`: `{type: example}`
class Config(t.Config):
"""Configuration for the Example action"""
helloString: str = "Hi"
# define the parameters structure for the `hello` command
class HelloParams(t.Params):
color: str #: color for the message
# define the response structure for `hello`
class HelloResponse(t.Response):
message: str
# define the action object. It extends the generic action Object which is an IObject
class Object(gws.common.action.Object):
# the mandatory configuration method
def configure(self):
# it's imperative to invoke super().configure() every time
super().configure()
# get the value of a configuration parameter and save it for the later use
self.hello = self.var('helloString')
# request handler method for `hello`
# all action handlers accept the request object and parameters structure
def api_hello(self, req: t.IRequest, p: HelloParams) -> HelloResponse:
# check the params
if p.color == 'red':
# red is not implemented
raise gws.web.error.NotImplemented()
# use the request, the params and the preconfigured value to create a message
message = f'<font color={p.color}>{self.hello}, {req.user.display_name}</font>'
# create and return the response object
return HelloResponse(message=message)