-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
897 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Change Log | ||
|
||
## 1.1.0 | ||
|
||
- Implement [Middleware][middleware] | ||
- This includes adding error handling middleware that facilitates [error handling][error-handling]. | ||
|
||
## 1.0.0 | ||
|
||
- Beta release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Error Handling | ||
|
||
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` | ||
|
||
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`. | ||
|
||
Both of these middleware have a default exception handler that will be used for anything not matching a registered handler. The default handler can be overwritten by registering a handler for the `Exception` class. | ||
|
||
Handlers are chosen by finding a handler for the most specific parent class of the thrown exception (including the class of the exception). These classes are searched in method resolution order. | ||
|
||
!!! note | ||
These handlers only handle exceptions that are subclasses of (and including) `Exception`. Exceptions that are only children of `BaseException` (e.g. `SystemExit`) will not be caught by these handlers. | ||
|
||
## Registering Exception Handlers | ||
|
||
```python | ||
import dnslib | ||
from nserver import NameServer, Query, Response | ||
|
||
server = NameServer("example") | ||
|
||
@server.exception_handler(NotImplementedError) | ||
def not_implemented_handler(exception: NotImplementedError, query: Query) -> Response: | ||
return Response(error_code=dnslib.RCODE.NOTIMPL) | ||
|
||
@server.raw_exception_handler(Exception) | ||
def print_debugger(exception: Exception, record: dnslib.DNSRecord) -> dnslib.DNSRecord: | ||
print(f"failed to process {record} due to {exception!r}) | ||
response = record.reply() | ||
response.header.rcode =dnslib.RCODE.SERVFAIL | ||
return response | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# Middleware | ||
|
||
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 Stacks | ||
|
||
Middleware operates in a stack with each middleware calling the middleware below it until one returns and the result is propagated back up the chain. NServer uses two stacks, the outmost stack deals with raw DNS records (`RawRecordMiddleware`), which will eventually convert the record to a `Query` which will then be passed to the main `QueryMiddleware` stack. | ||
|
||
Middleware can be added to the application until it is run. Once the server begins running the middleware cannot be modified. The ordering of middleware is kept in the order in which it is added to the server; that is the first middleware registered will be called before the second and so on. | ||
|
||
Some middleware is automatically added when the stacks are processed. | ||
|
||
## `QueryMiddleware` | ||
|
||
For most use cases you likely want to use [`QueryMiddleware`][nserver.middleware.QueryMiddleware]. This middleware uses the high-level `Query` and `Response` objects. | ||
|
||
### Registering `QueryMiddleware` | ||
|
||
```python | ||
from nserver import NameServer | ||
from nserver.middleware import QueryMiddleware | ||
|
||
server = NameServer("example") | ||
server.register_middleware(QueryMiddleware()) | ||
``` | ||
|
||
### Creating your own `QueryMiddleware` | ||
|
||
Using an unmodified `QueryMiddleware` isn't very interesting as it just passes the request onto the next middleware. To add your own middleware you should subclass `QueryMiddleware` and override the `process_query` method. | ||
|
||
```python | ||
# ... | ||
from typing import Callable | ||
from nserver import Query, Response | ||
|
||
class MyLoggingMiddleware(QueryMiddleware): | ||
def __init__(self, logging_name: str): | ||
super().__init__() | ||
self.logger = logging.getLogger(f"my-awesome-app.{name}") | ||
return | ||
|
||
def process_query( | ||
query: Query, call_next: Callable[[Query], Response] | ||
) -> Response: | ||
self.logger.info(f"processing {query.name}") | ||
response = call_next(query) | ||
self.logger.info(f"done processing, returning {response.error_code}") | ||
return response | ||
|
||
server.register_middleware(MyLoggingMiddleware("foo")) | ||
server.register_middleware(MyLoggingMiddleware("bar")) | ||
``` | ||
|
||
### Default `QueryMiddleware` stack | ||
|
||
Once processed the `QueryMiddleware` stack will look as follows: | ||
|
||
- [`ExceptionHandlerMiddleware`][nserver.middleware.ExceptionHandlerMiddleware] | ||
- Customisable error handler for `Exception`s originating from within the stack. | ||
- `<registered middleware>` | ||
- [`HookMiddleware`][nserver.middleware.HookMiddleware] | ||
- Runs hooks registered to the server. This can be considered a simplified version of middleware. | ||
- [`RuleProcessor`][nserver.middleware.RuleProcessor] | ||
- The entry point into our rule processing. | ||
|
||
|
||
## `RawRecordMiddleware` | ||
|
||
[`RawRecordMiddleware`][nserver.middleware.RawRecordMiddleware] allows for modifying the raw `dnslib.DNSRecord`s that are recevied and sent by the server. | ||
|
||
### Registering `RawRecordMiddleware` | ||
|
||
```python | ||
# ... | ||
from nserver.middleware import RawRecordMiddleware | ||
|
||
server.register_raw_middleware(RawRecordMiddleware()) | ||
``` | ||
|
||
### Creating your own `RawRecordMiddleware` | ||
|
||
Using an unmodified `RawRecordMiddleware` isn't very interesting as it just passes the request onto the next middleware. To add your own middleware you should subclass `RawRecordMiddleware` and override the `process_record` method. | ||
|
||
```python | ||
# ... | ||
|
||
class SizeLimiterMiddleware(RawRecordMiddleware): | ||
def __init__(self, max_size: int): | ||
super().__init__() | ||
self.max_size = max_size | ||
return | ||
|
||
def process_record( | ||
record: dnslib.DNSRecord, | ||
call_next: Callable[[dnslib.DNSRecord], dnslib.DNSRecord], | ||
) -> dnslib.DNSRecord: | ||
refused = record.reply() | ||
refused.header.rcode = dnslib.RCODE.REFUSED | ||
|
||
if len(record.pack()) > self.max_size: | ||
return refused | ||
|
||
response = call_next(query) | ||
|
||
if len(response.pack()) > self.max_size: | ||
return refused | ||
|
||
return response | ||
|
||
server.register_raw_middleware(SizeLimiterMiddleware(1400)) | ||
``` | ||
|
||
### Default `RawRecordMiddleware` stack | ||
|
||
Once processed the `RawRecordMiddleware` stack will look as follows: | ||
|
||
- [`RawRecordExceptionHandlerMiddleware`][nserver.middleware.RawRecordExceptionHandlerMiddleware] | ||
- Customisable error handler for `Exception`s originating from within the stack. | ||
- `<registered raw middleware>` | ||
- [`QueryMiddlewareProcessor`][nserver.middleware.QueryMiddlewareProcessor] | ||
- entry point into the `QueryMiddleware` stack. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" | |
|
||
[project] | ||
name = "nserver" | ||
version = "1.0.1" | ||
version = "1.1.0.dev1" | ||
description = "DNS Name Server Framework" | ||
authors = [ | ||
{name = "Nicholas Hairs", email = "[email protected]"}, | ||
|
Oops, something went wrong.