Skip to content

Commit

Permalink
[server] Add blueprints, also make this v2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nhairs committed Dec 20, 2023
1 parent e8fb9ec commit 75edfb4
Show file tree
Hide file tree
Showing 12 changed files with 580 additions and 280 deletions.
54 changes: 54 additions & 0 deletions docs/blueprints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Blueprints

[`Blueprint`][nserver.server.Blueprint]s provide a way for you to compose your application. They support most of the same functionality as a `NameServer`.

Use cases:

- Split up your application across different blueprints for maintainability / composability.
- Reuse a blueprint registered under different rules.
- Allow custom packages to define their own rules that you can add to your own server.

Blueprints require `nserver>=2.0`

## Using Blueprints

```python
from nserver import Blueprint, NameServer, ZoneRule, ALL_CTYPES, A

# First Blueprint
mysite = Blueprint("mysite")

@mysite.rule("nicholashairs.com", ["A"])
@mysite.rule("www.nicholashairs.com", ["A"])
def nicholashairs_website(query: Query) -> A:
return A(query.name, "159.65.13.73")

@mysite.rule(ZoneRule, "", ALL_CTYPES)
def nicholashairs_catchall(query: Query) -> None:
# Return empty response for all other queries
return None

# Second Blueprint
en_blueprint = Blueprint("english-speaking-blueprint")

@en_blueprint.rule("hello.{base_domain}", ["A"])
def en_hello(query: Query) -> A:
return A(query.name, "1.1.1.1")

# Register to NameServer
server = NameServer("server")
server.register_blueprint(mysite, ZoneRule, "nicholashairs.com", ALL_CTYPES)
server.register_blueprint(en_blueprint, ZoneRule, "au", ALL_CTYPES)
server.register_blueprint(en_blueprint, ZoneRule, "nz", ALL_CTYPES)
server.register_blueprint(en_blueprint, ZoneRule, "uk", ALL_CTYPES)
```

### Middleware, Hooks, and Error Handling

Blueprints maintain their own `QueryMiddleware` stack which will run before any rule function is run. Included in this stack is the `HookMiddleware` and `ExceptionHandlerMiddleware`.

## Key differences with `NameServer`

- Does not use settings (`Setting`).
- Does not have a `Transport`.
- Does not have a `RawRecordMiddleware` stack.
4 changes: 3 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Change Log

## 1.1.0
## 2.0.0

- Implement [Middleware][middleware]
- This includes adding error handling middleware that facilitates [error handling][error-handling].
- Add [`StaticRule`][nserver.rules.StaticRule] and [`ZoneRule`][nserver.rules.ZoneRule].
- Refector [`NameServer.rule`][nserver.server.NameServer.rule] to use expanded [`smart_make_rule`][nserver.rules.smart_make_rule] function.
- Although this change should not affect rules using this decorator from being called correctly, it may change the precise rule type being used. Specifically it may use `StaticRule` instead of `WildcardStringRule` for strings with no substitutions.
- Add [Blueprints][blueprints]
- Include refactoring `NameServer` into a new shared based `Scaffold` class.

## 1.0.0

Expand Down
2 changes: 1 addition & 1 deletion docs/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Custom exception handling is handled through the [`ExceptionHandlerMiddleware`][nserver.middleware.ExceptionHandlerMiddleware] and [`RawRecordExceptionHandlerMiddleware`][nserver.middleware.RawRecordExceptionHandlerMiddleware] [Middleware][middleware]. These middleware will catch any `Exception`s raised by their respective middleware stacks.

Error handling requires `nserver>=1.1.0`
Error handling requires `nserver>=2.0`

In general you are probably able to use the `ExceptionHandlerMiddleware` as the `RawRecordExceptionHandlerMiddleware` is only needed to catch exceptions resulting from `RawRecordMiddleware` or broken exception handlers in the `ExceptionHandlerMiddleware`. If you only write `QueryMiddleware` and your `ExceptionHandlerMiddleware` handlers never raise exceptions then you'll be good to go with just the `ExceptionHandlerMiddleware`.

Expand Down
2 changes: 1 addition & 1 deletion docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Middleware can be used to modify the behaviour of a server seperate to the individual rules that are registered to the server. Middleware is run on all requests and can modify both the input and response of a request.

Middleware requires `nserver>=1.1.0`
Middleware requires `nserver>=2.0`

## Middleware Stacks

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ nav:
- quickstart.md
- middleware.md
- error-handling.md
- blueprints.md
- production-deployment.md
- changelog.md
- external-resources.md
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "nserver"
version = "1.1.0.dev1"
version = "2.0.0"
description = "DNS Name Server Framework"
authors = [
{name = "Nicholas Hairs", email = "[email protected]"},
Expand Down
4 changes: 2 additions & 2 deletions src/nserver/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .models import Query, Response
from .rules import StaticRule, ZoneRule, RegexRule, WildcardStringRule
from .rules import ALL_QTYPES, StaticRule, ZoneRule, RegexRule, WildcardStringRule
from .records import A, AAAA, NS, CNAME, PTR, SOA, MX, TXT, CAA
from .server import NameServer
from .server import NameServer, Blueprint
from .settings import Settings
16 changes: 8 additions & 8 deletions src/nserver/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
def coerce_to_response(result: RuleResult) -> Response:
"""Convert some `RuleResult` to a `Response`
New in `1.1.0`.
New in `2.0`.
Args:
result: the results to convert
Expand Down Expand Up @@ -76,7 +76,7 @@ def coerce_to_response(result: RuleResult) -> Response:
class QueryMiddleware:
"""Middleware for interacting with `Query` objects
New in `1.1.0`.
New in `2.0`.
"""

def __init__(self) -> None:
Expand Down Expand Up @@ -118,7 +118,7 @@ class ExceptionHandlerMiddleware(QueryMiddleware):
matches the class or parent class of the exception in method resolution order. If no handler
is registered will use this classes `self.default_exception_handler`.
New in `1.1.0`.
New in `2.0`.
Attributes:
exception_handlers: registered exception handlers
Expand Down Expand Up @@ -182,7 +182,7 @@ class HookMiddleware(QueryMiddleware):
hook or from the next function in the middleware chain. They take a `Response` input
and must return a `Response`.
New in `1.1.0`.
New in `2.0`.
Attributes:
before_first_query: `before_first_query` hooks
Expand Down Expand Up @@ -257,7 +257,7 @@ class RuleProcessor:
This class serves as the bottom of the `QueryMiddleware` stack.
New in `1.1.0`.
New in `2.0`.
"""

def __init__(self, rules: List[RuleBase]) -> None:
Expand All @@ -284,7 +284,7 @@ def __call__(self, query: Query) -> Response:
class RawRecordMiddleware:
"""Middleware to be run against raw `dnslib.DNSRecord`s.
New in `1.1.0`.
New in `2.0`.
"""

def __init__(self) -> None:
Expand Down Expand Up @@ -332,7 +332,7 @@ class RawRecordExceptionHandlerMiddleware(RawRecordMiddleware):
Exception handlers are expected to be robust - that is, they must always
return correctly even if they internally encounter an `Exception`.
New in `1.1.0`.
New in `2.0`.
Attributes:
exception_handlers: registered exception handlers
Expand Down Expand Up @@ -389,7 +389,7 @@ class QueryMiddlewareProcessor:
This class serves as the bottom of the `RawRcordMiddleware` stack.
New in `1.1.0`.
New in `2.0`.
"""

def __init__(self, query_middleware: QueryMiddlewareCallable) -> None:
Expand Down
10 changes: 5 additions & 5 deletions src/nserver/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
ALL_QTYPES: List[str] = list(dnslib.QTYPE.reverse.keys())
"""All supported Query Types
New in `1.1.0`.
New in `2.0`.
"""

_wildcard_string_regex = re.compile(r"[*]|\{base_domain\}")
Expand All @@ -39,7 +39,7 @@ def smart_make_rule(rule: "Union[Type[RuleBase], str, Pattern]", *args, **kwargs
it will be a `WildcardStringRule`, else a `StaticRule`.
- `Pattern` then a `RegexRule`.
New in `1.1.0`
New in `2.0`
Args:
rule: input to process
Expand All @@ -57,7 +57,7 @@ def smart_make_rule(rule: "Union[Type[RuleBase], str, Pattern]", *args, **kwargs
# vary between versions of python and other bugs.
# see also: https://stackoverflow.com/questions/6102019/type-of-compiled-regex-object-in-python
return RegexRule(rule, *args, **kwargs)
raise ValueError(f"Could not handle rule: {rule!r}")
return rule(*args, **kwargs)


### CLASSES
Expand Down Expand Up @@ -93,7 +93,7 @@ class StaticRule(RuleBase):
`StaticRule` is more efficient than using a `WildcardStringRule` for static strings.
New in `1.1.0`.
New in `2.0`.
"""

def __init__(
Expand Down Expand Up @@ -141,7 +141,7 @@ class ZoneRule(RuleBase):
An empty zone (`""`) will match any domain as this refers to the domain root (`.`).
New in `1.1.0`.
New in `2.0`.
"""

def __init__(
Expand Down
Loading

0 comments on commit 75edfb4

Please sign in to comment.