Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,9 @@ add_subdirectory(src/traffic_via)
if(ENABLE_CRIPTS)
add_subdirectory(src/cripts)
endif()

# HRW4U C++ parser library (optional, requires ANTLR4)
add_subdirectory(src/hrw4u)
if(ENABLE_AUTEST)
add_subdirectory(tests)
endif()
Expand Down Expand Up @@ -800,6 +803,9 @@ if(ENABLE_BENCHMARKS)
add_subdirectory(tools/benchmark)
endif()

# Add hrw_confcmp comparison tool (requires ANTLR4)
add_subdirectory(tools/hrw_confcmp)

add_custom_target(
clang-format-install
COMMAND ${CMAKE_SOURCE_DIR}/tools/clang-format.sh --install
Expand Down
83 changes: 27 additions & 56 deletions doc/admin-guide/configuration/hrw4u.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@ Overview
========

HRW4U replaces the free-form text parsing of ``header_rewrite`` with a formally defined
grammar using ANTLR. This makes HRW4U easier to parse, validate, and extend.
grammar using ANTLR. When |TS| is built with ANTLR4 support, the plugin **natively**
parses ``.hrw4u`` files. Simply use files with the ``.hrw4u`` extension:

Rather than repeating ``header_rewrite`` documentation, please refer to:
- :ref:`admin-plugins-header-rewrite` for feature behavior and semantics
- This page focuses on syntax and behavior *differences* in HRW4U
.. code-block:: none

# In plugin.config for global rules
header_rewrite.so rules.hrw4u

# In remap.config for per-mapping rules
map http://a http://b @plugin=header_rewrite.so @pparam=rules.hrw4u

For feature behavior and semantics, refer to :ref:`admin-plugins-header-rewrite`.
This page focuses on syntax differences in HRW4U.

Why HRW4U?
----------
Expand All @@ -43,52 +51,22 @@ HRW4U aims to improve the following:

- Structured grammar and parser
- Better error diagnostics (line/col, filename, hints)
- Proper nested condition support using `if (...)` and `{ ... }` blocks
- Proper nested condition support using ``if (...) { ... }`` blocks
- Symbol tables for variable declarations and usage
- Static validation of operand types and value ranges
- Explicit `VARS` declarations with typed variables (`bool`, `int8`, `int16`)
- Explicit ``VARS`` declarations with typed variables (``bool``, ``int8``, ``int16``)
- Optional debug output to trace logic evaluation

Building
--------

Currently, the HRW4U compiler is not built as part of the ATS build process. You need to
build it separately using Python 3.10+ and pyenv environments. There's a ``bootstrap.sh``
script in the ``tools/hrw4u`` directory that helps with the setup process.

Once set up, simply run:

.. code-block:: none

make
make package

This will produce a PIP package in the ``dist`` directory. You can install it in a
virtualenv or system-wide using:

.. code-block:: none

pipx install dist/hrw4u-1.4.0-py3-none-any.whl

Using
-----

Once installed, you will have a ``hrw4u`` command available. You can run it as
follows to produce the help output:

.. code-block:: none

hrw4u --help

Doing a compile is simply:
Standalone Compiler
-------------------

.. code-block:: none
A standalone Python compiler is available in ``tools/hrw4u`` for development:

hrw4u some_file.hrw4u
- Debug tracing (``--debug``)
- IDE integration via LSP (``hrw4u-lsp``)
- Reverse conversion (``u4wrh``) to convert header_rewrite to hrw4u

in Addition to ``hrw4u``, you also have the reverse tool, converting existing ``header_rewrite``
configurations to ``hrw4u``. This tool is named ``u4wrh``. For people using IDEs, the package also
provides an LSP for this language, named ``hrw4u-lsp``.
Build with Python 3.10+ using ``./bootstrap.sh && make package``.

Syntax Differences
==================
Expand Down Expand Up @@ -246,11 +224,13 @@ rm-destination QUERY ... [I] keep_query("foo,bar") Keep only specif
run-plugin foo.so "args" run-plugin("foo.so", "arg1", ...) Run an external remap plugin
set-body "foo" inbound.resp.body = "foo" Set the response body
set-body-from "\https://..." set-body-from("\https://...") Set the response body from a URL
set-cc-alg "cubic" set-cc-alg("cubic") Set the TCP congestion control algorithm
set-config <name> 12 set-config("name", 17) Set a configuration variable to a value
set-conn-dscp 8 inbound.conn.dscp = 8 Set the DSCP value for the connection
set-conn-mark 17 inbound.conn.mark = 17 Set the MARK value for the connection
set-cookie foo bar {in,out}bound.cookie.foo = "bar" Set a request/response cookie named foo
set-destination <C> bar {in,out}bound.url.<C> = "bar" Set a URL component, <:ref:`C<admin-plugins-header-rewrite-url-parts>`> is path, query etc.
set-effective-address "1.2.3" set-effective-address("1.2.3.4") Set the client's effective address
set-header X-Bar foo inbound.{req,resp}.X-Bar = "foo" Assign a client request/origin response header
set-plugin-cntl <C> <T> set-plugin-cntl(<C>) = <T> Set the plugin control <C> to <T>, see <:ref:`C<admin-plugins-header-rewrite-plugin-cntl>`>
set-redirect <Code> <URL> set-redirect(302, "\https://...") Set a redirect response
Expand Down Expand Up @@ -413,20 +393,11 @@ These can be used with both sets and equality checks, using the ``with`` keyword
...
}

Running and Debugging
=====================

To run HRW4U, just install and run the hrw4u compiler:

.. code-block:: none

hrw4u /path/to/rules.hrw4u

Run with `--debug all` to trace:
Debugging
=========

- Lexer, parser, visitor behavior
- Condition evaluations
- State and output emission
Syntax errors are reported with filename, line, and column position. For development,
the standalone compiler's ``--debug all`` option traces lexer, parser, and evaluation.

Examples
========
Expand Down
6 changes: 6 additions & 0 deletions doc/admin-guide/plugins/header_rewrite.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ to keep it in the same location as your other proxy configuration files.
The paths given to the configuration file(s) may be absolute (leading with a
``/`` character), or they may be relative to the |TS| configuration directory.

.. note::
An alternative syntax called **HRW4U** is available, offering cleaner syntax
with ``if/else`` blocks and better error messages. Files with the ``.hrw4u``
extension are automatically parsed using this format. See :ref:`admin-hrw4u`
for details.

There are two methods for enabling this plugin, based on whether you wish it to
operate globally on every request that passes through your proxy, or only on
some subset of the requests by enabling it only for specific mapping rules.
Expand Down
145 changes: 145 additions & 0 deletions include/hrw4u/Error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
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.
*/

#pragma once

#include <string>
#include <string_view>
#include <vector>
#include <functional>

namespace hrw4u
{

struct SourceLocation {
std::string filename;
std::string context;
size_t line = 0;
size_t column = 0;
size_t length = 0;

[[nodiscard]] std::string format() const;

[[nodiscard]] bool
is_valid() const
{
return line > 0;
}
};

enum class ErrorSeverity { Warning, Error, Fatal };

struct ParseError {
std::string message;
std::string code;
ErrorSeverity severity = ErrorSeverity::Error;
SourceLocation location;

[[nodiscard]] std::string format() const;
[[nodiscard]] std::string_view severity_str() const;
};

using ErrorCallback = std::function<void(const ParseError &)>;

class ErrorCollector
{
public:
ErrorCollector() = default;

explicit ErrorCollector(ErrorCallback callback);
void add_error(ParseError error);
void add_error(ErrorSeverity severity, std::string message, SourceLocation location = {}, std::string code = {});

void
warning(std::string message, SourceLocation location = {})
{
add_error(ErrorSeverity::Warning, std::move(message), std::move(location));
}

void
error(std::string message, SourceLocation location = {})
{
add_error(ErrorSeverity::Error, std::move(message), std::move(location));
}

void
fatal(std::string message, SourceLocation location = {})
{
add_error(ErrorSeverity::Fatal, std::move(message), std::move(location));
}

[[nodiscard]] bool has_errors() const;
[[nodiscard]] bool has_fatal() const;

[[nodiscard]] bool
has_messages() const
{
return !_errors.empty();
}

[[nodiscard]] size_t error_count() const;

[[nodiscard]] const std::vector<ParseError> &
errors() const
{
return _errors;
}

void clear();

[[nodiscard]] std::string format_all() const;
[[nodiscard]] std::string summary() const;

void
set_filename(std::string filename)
{
_current_filename = std::move(filename);
}

[[nodiscard]] const std::string &
current_filename() const
{
return _current_filename;
}

private:
std::vector<ParseError> _errors;
ErrorCallback _callback;
std::string _current_filename;
};

class ParseException : public std::exception
{
public:
explicit ParseException(ParseError error);
explicit ParseException(std::string message, SourceLocation location = {});

[[nodiscard]] const char *what() const noexcept override;

[[nodiscard]] const ParseError &
error() const
{
return _error;
}

private:
ParseError _error;
std::string _formatted;
};

} // namespace hrw4u
Loading