blob: e959beca8e66cbf68c8a18f518662fa1c6bad679 [file] [log] [blame]
.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
.. include:: ../../common.defs
.. highlight:: cpp
.. default-domain:: cpp
.. _cripts-overview:
Overview
********
Cripts, *C-Scripts*, is a set of wrappers and include files for making simple ATS
plugins easy to generate, modify or create from scratch. A key design here is that
the Code is the Configuration, i.e. the intent really is to have a custom Cript file
for every remap rule in the running system.
Cripts can also be seen as the scripting configuration language for many custom
ATS setups. ATS can even load Cripts directly, if so desired. See the section on
using Cripts for more information.
.. _cripts-overview-building:
Building
========
Cripts is built as a separate library, which can be enabled with ``-DENABLE_CRIPTS=ON``
when configuring *cmake*. Building a Cript itself is just like compiling a normal
ATS plugin, except it will also need the Cripts library linked in: ``-lcripts``.
Plugins built using this method loads exactly like any other ATS plugin, and can be
used in the same way. The only difference is that the plugin will be a lot simpler
to write and maintain.
.. _cripts-overview-building-additions:
Building Additions
------------------
The ATS build system is already setup such that when building ATS, and therefore Cripts,
you can add additional modules and bundles to the build. This is done by adding the
module or bundle to the ``src/cripts``, ``src/cripts/Bundles``, ``include/cripts`` and
``include/cripts/Bundles`` directories. This has to be done before running the
``cmake`` command in your build process.
.. _cripts-overview-usage:
Usage
=====
The easiest way to use Cripts is to let ATS itself compile and load the Cript. This
requires three things:
1. The :file:`records.yaml` must have the ``plugin.compiler_path`` set to a build script.
2. The remap rule must specify the Cript file to load.
3. The Cript file must be in either the configuration or plugin installation directory.
The first step is to set the plugin.compiler_path in the records.yaml file. For example:
.. code-block:: yaml
records:
plugin:
compiler_path: /opt/ats/bin/compiler.sh
The second step is to specify the Cript file in the remap rule. For example:
::
map https://www.example.com https://origin.example.com \
@plugin=example.cript
The third step is to put the Cript file in either the configuration or plugin installation,
depending on your preference. The file must be readable by the ATS process. Example:
.. code-block:: cpp
#include <cripts/Preamble.hpp>
do_remap()
{
borrow url = cripts::Client::URL::Get();
url.query.Keep({"foo", "bar"});
}
#include <cripts/Epilogue.hpp>
.. note::
Both the Preamble and Epilogue must explicitly be included in every cript,
in the correct order. The Preamble must come first, setting up the environment,
and the Epilogue must come last, initiating the hooks and the plugin.
.. _cripts-overview-usage-compiler:
Compiler
--------
The compiler is a simple shell script that compiles the Cript file into a shared object
file. The script is responsible for setting up the environment and calling the compiler
with the correct arguments. The script must be executable and readable by the ATS process.
Two arguments are always given to the compiler script:
1. The path to the Cript file.
2. The path to the shared object file to create.
An example compiler script is provided in the ATS source directory, but an example is
also shown below:
.. code-block:: bash
#!/usr/bin/env bash
# This was done for a Mac, may need to be adjusted for other platforms.
ATS_ROOT=/opt/ats
CXX=clang++
OSFLAGS="-I/opt/homebrew/include -undefined dynamic_lookup"
CXXFLAGS="-std=c++20 -O3 -shared -fPIC -Wall -Werror -I${ATS_ROOT}/include -L${ATS_ROOT}/lib -lcripts"
SOURCE=$1
DEST=$2
${CXX} ${CXXFLAGS} ${OSFLAGS} -o $DEST $SOURCE
The example in the ATS source directory is more complex, as it also provides a basic
cache for the shared object files. This is useful for large setups with many Cript files.
In fact, moving the compilation details to be separate from the ATS process is a good
idea with a lot of flexibility for the user.
.. _cripts-overview-names:
Names
=====
All classes and class and instance methods in Cripts are following the same naming
convention, ``PascalCase``. Instance variables and members are always all lowercase. For example:
.. code-block:: cpp
auto url = cripts::Client::URL::Get();
url.query.Keep({"foo", "bar"});
.. note::
This naming convention does not apply to ``string`` and ``string_view`` variables, which
follow the standard C++ conventions for the standard method names. Cripts specific additions
do follow the ``PascalCase`` convention though.
.. _cripts-overview-naming-styles:
Hook Naming Styles
-------------------
Cripts supports two naming styles for hooks to accommodate different coding preferences:
1. **Lowercase with underscores** (traditional C style): ``do_remap()``, ``do_send_response()``
2. **PascalCase** (C++ style): ``DoRemap()``, ``DoSendResponse()``
Both styles are functionally identical and can be used interchangeably:
.. code-block:: cpp
// Traditional C-style naming
do_remap()
{
// Hook implementation
}
// Equivalent PascalCase naming
DoRemap()
{
// Same hook implementation
}
The same dual naming convention applies to global hooks:
.. code-block:: cpp
// Traditional style
glb_read_request() { /* ... */ }
// PascalCase style
GlbReadRequest() { /* ... */ }
Choose the style that best fits your team's coding standards and use it consistently
throughout your Cript files.
.. _cripts-overview-hooks:
Hooks
=====
Hooks are the main way to interact with ATS. The hooks are the same as the ATS hooks,
but with a few differences. The hooks are all in the global namespace, and the hooks
are all functions. Cripts provides a core set of hooks which are always available,
but they are not required to be used.
Not all ATS hooks are available in Cripts, but the most common ones are. Hooks are
implicitly called if they are defined in the Cript file. The Cript will never explicitly
setup the hooks, as this is done by the ATS process.
Normal Hooks
------------
Let's look at the normal hooks that are available in Cripts. Note here that the name
of the function dictates the underlying ATS hook.
.. _cripts-overview-hooks-do-remap:
do_remap()
^^^^^^^^^^
The ``do_remap()`` hook is called when a remap rule matches. This is the main hook for
modifying the request or response. In ATS plugin terms, this is the main entry point
for all remap plugins.
.. _cripts-overview-hooks-do-post-remap:
do_post_remap()
^^^^^^^^^^^^^^^
The ``do_post_remap()`` hook is called after the remap has been done. This can sometimes
be useful for modifying the request details *after* the remap has been done. More often
than not, this hook is not needed.
.. _cripts-overview-hooks-do-send-response:
do_send_response()
^^^^^^^^^^^^^^^^^^
The ``do_send_response()`` hook is called when the response is ready to be sent to the
client. In this hook you can modify or add to the response headers for example.
.. _cripts-overview-hooks-do-cache-lookup:
do_cache_lookup()
^^^^^^^^^^^^^^^^^
The ``do_cache_lookup()`` hook is called when a cache lookup is done. This can be useful
to take action on the response from the cache, once you know if the cache lookup succeeded
or not.
.. _cripts-overview-hooks-do-send-request:
do_send_request()
^^^^^^^^^^^^^^^^^
The ``do_send_request()`` hook is called when the request is ready to be sent to the
origin server. This is the main hook for modifying the request to the origin server.
.. _cripts-overview-hooks-do-read-response:
do_read_response()
^^^^^^^^^^^^^^^^^^
The ``do_read_response()`` hook is called when the response is being read from the origin
server. This is the main hook for modifying the response from the origin server.
.. _cripts-overview-hooks-do-txn-close:
do_txn_close()
^^^^^^^^^^^^^^
The ``do_txn_close()`` hook is called when the transaction is closed.
Instance Hooks
--------------
In addition to these normal hooks, there are also three hooks that are used for setting
up a cript and remap plugin instance. This is primarily useful when writing traditional
remap plugins in Cripts.
.. note::
These hooks are not needed for most Cripts that are used in remap rules. And you
will only use the instance variables (see below) if you are using these instance hooks.
.. _cripts-overview-hooks-do-init:
do_init()
^^^^^^^^^
The ``do_init()`` hook is called once when the plugin is loaded.
.. _cripts-overview-hooks-do-create-instance:
do_create_instance()
^^^^^^^^^^^^^^^^^^^^
The ``do_create_instance()`` hook is called when a new instance of the plugin is created.
Of particular interest here is the ability to read and save the instance parameters that
can be provided in the remap rule (e.g. ``@plugin=example.cript @pparam=value``).
.. _cripts-overview-hooks-do-delete-instance:
do_delete_instance()
^^^^^^^^^^^^^^^^^^^^
The ``do_delete_instance()`` hook is called when the instance is deleted.
.. _cripts-overview-instance-data:
Instance Data
=============
Instance data is a way to store data that is specific to a plugin instance. This is
primarily useful when writing traditional remap plugins in Cripts using the instance hooks.
Instance data is stored in a vector that is specific to the plugin instance, and each slot
is initially a string, as provided by the remap rule parameters.
The best way to understand this is to look at an example:
.. code-block:: cpp
#include <cripts/Preamble.hpp>
do_create_instance()
{
if (instance.size() == 3) {
// Convert string parameters to appropriate types
auto first = float(AsString(instance.data[0]));
auto second = integer(AsString(instance.data[1]));
auto third = boolean(AsString(instance.data[2]));
// Store converted values back to instance data
instance.data[0] = first;
instance.data[1] = second;
instance.data[2] = third;
}
}
do_remap()
{
// Retrieve typed values from instance data
auto first = AsFloat(instance.data[0]);
auto second = AsInteger(instance.data[1]);
auto third = AsBoolean(instance.data[2]);
// Use the instance parameters here in the Cript rules
CDebug("Instance params: {}, {}, {}", first, second, third);
}
#include <cripts/Epilogue.hpp>
.. note::
Instance data uses 0-based indexing. If you have 3 parameters, they are accessed
as ``instance.data[0]``, ``instance.data[1]``, and ``instance.data[2]``.
As you can see, it takes a little bit of work to manage these instance parameters, which
is a limitation of the language constructs in Cripts. But writing plugins like this is for
more advanced users, and users just writing simple Cripts for their remap rules will not
need to worry about this.
.. _cripts-overview-data-types:
Data Type Conversion
---------------------
Instance data supports various data types through conversion functions:
========================= =======================================================================
Function Description
========================= =======================================================================
``AsString(value)`` Convert to string representation.
``AsInteger(value)`` Convert to 64-bit signed integer.
``AsFloat(value)`` Convert to double-precision floating point.
``AsBoolean(value)`` Convert to boolean (true/false).
========================= =======================================================================
Example of parameter processing:
.. code-block:: cpp
do_create_instance()
{
// Remap rule: @plugin=example.cript @pparam=timeout:30 @pparam=enabled:true @pparam=rate:1.5
if (instance.size() >= 3) {
// Parse "timeout:30" -> 30
auto timeout_str = AsString(instance.data[0]);
auto timeout_val = integer(timeout_str.substr(timeout_str.find(':') + 1));
instance.data[0] = timeout_val;
// Parse "enabled:true" -> true
auto enabled_str = AsString(instance.data[1]);
auto enabled_val = boolean(enabled_str.substr(enabled_str.find(':') + 1) == "true");
instance.data[1] = enabled_val;
// Parse "rate:1.5" -> 1.5
auto rate_str = AsString(instance.data[2]);
auto rate_val = float(rate_str.substr(rate_str.find(':') + 1));
instance.data[2] = rate_val;
}
}