From efcb3c9556d47eded4cbccde4f6035b1311f010a Mon Sep 17 00:00:00 2001 From: Kaliumhexacyanoferrat Date: Tue, 25 Jun 2024 14:29:43 +0200 Subject: [PATCH] Rewrite shared logic --- website/content/documentation/_index.md | 2 +- .../documentation/content/concepts/caches.md | 1 + .../content/concepts/conversion.md | 45 -- .../content/concepts/definitions.md | 672 +++++++++++++++++- .../content/concepts/injection.md | 29 - .../content/concepts/resources.md | 1 + .../documentation/content/concepts/results.md | 22 - .../content/concepts/serialization.drawio | 38 + .../content/concepts/serialization.png | Bin 0 -> 16523 bytes .../documentation/content/concerns/_index.md | 59 +- .../content/concerns/authentication.md | 80 --- .../content/concerns/concerns.md | 63 -- .../content/concerns/range-support.md | 2 +- .../content/frameworks/controllers.md | 6 +- .../content/frameworks/functional.md | 54 +- .../frameworks/single-page-applications.md | 14 +- .../content/frameworks/static-websites.md | 35 +- .../content/frameworks/webservices.md | 79 +- .../handlers.md => handlers/_index.md} | 5 +- .../{providers => handlers}/downloads.md | 0 .../{providers => handlers}/layouting.md | 0 .../{providers => handlers}/listing.md | 0 .../{providers => handlers}/listing.png | Bin .../{providers => handlers}/load-balancer.md | 0 .../{providers => handlers}/redirects.md | 0 .../{providers => handlers}/reverse-proxy.md | 0 .../{providers => handlers}/static-content.md | 0 .../{providers => handlers}/virtual-hosts.md | 0 .../documentation/content/providers/_index.md | 6 - .../documentation/content/templates.md | 16 +- .../content/documentation/testing/_index.md | 4 +- website/content/legal.md | 6 +- 32 files changed, 875 insertions(+), 364 deletions(-) delete mode 100644 website/content/documentation/content/concepts/conversion.md delete mode 100644 website/content/documentation/content/concepts/injection.md delete mode 100644 website/content/documentation/content/concepts/results.md create mode 100644 website/content/documentation/content/concepts/serialization.drawio create mode 100644 website/content/documentation/content/concepts/serialization.png delete mode 100644 website/content/documentation/content/concerns/concerns.md rename website/content/documentation/content/{concepts/handlers.md => handlers/_index.md} (94%) rename website/content/documentation/content/{providers => handlers}/downloads.md (100%) rename website/content/documentation/content/{providers => handlers}/layouting.md (100%) rename website/content/documentation/content/{providers => handlers}/listing.md (100%) rename website/content/documentation/content/{providers => handlers}/listing.png (100%) rename website/content/documentation/content/{providers => handlers}/load-balancer.md (100%) rename website/content/documentation/content/{providers => handlers}/redirects.md (100%) rename website/content/documentation/content/{providers => handlers}/reverse-proxy.md (100%) rename website/content/documentation/content/{providers => handlers}/static-content.md (100%) rename website/content/documentation/content/{providers => handlers}/virtual-hosts.md (100%) delete mode 100644 website/content/documentation/content/providers/_index.md diff --git a/website/content/documentation/_index.md b/website/content/documentation/_index.md index dfcfe71..5a9f88d 100644 --- a/website/content/documentation/_index.md +++ b/website/content/documentation/_index.md @@ -52,7 +52,7 @@ When you run this sample it can be accessed in the browser via http://localhost: The example project above gives you a basic idea on how projects developed with the GenHTTP may look like. To create more complex web applications -(such as [webservices](/documentation/content/webservices)), follow the guides in the following sections: +(such as [webservices](/documentation/content/frameworks/webservices/)), follow the guides in the following sections: - [Providing Content](/documentation/content/) - [Testing](/documentation/testing/) diff --git a/website/content/documentation/content/concepts/caches.md b/website/content/documentation/content/concepts/caches.md index e58af78..a2e99ad 100644 --- a/website/content/documentation/content/concepts/caches.md +++ b/website/content/documentation/content/concepts/caches.md @@ -1,5 +1,6 @@ --- title: Caches +weight: 5 cascade: type: docs --- diff --git a/website/content/documentation/content/concepts/conversion.md b/website/content/documentation/content/concepts/conversion.md deleted file mode 100644 index c439d65..0000000 --- a/website/content/documentation/content/concepts/conversion.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Conversion -cascade: - type: docs ---- - -As content serialization and deserialization is required by various application -frameworks (such as [webservices](./webservices), [controllers](./controllers), or [functional handlers](./functional)), -there is a modular approach that can be used where needed. - -Serialization is provided by a `SerializationRegistry` that can be passed into -the application framework builder. By default, handlers may consume and produce JSON and XML entities. -To customize this behavior, add a custom `ISerializationFormat` -implementation to the serialization registry. - -The following example shows how to add Protocol Buffers support to a webservice resource: - -```csharp -var registry = Serialization.Default() - .Add(new FlexibleContentType("application/protobuf"), new ProtobufFormat()); - -var api = Layout.Create() - .AddService("books", resource, formatters: registry); -``` - -## Formatting - -Similar to serialization, there is a mechanism which allows to control how types that are used as parameters -in your services a serialized and deserialized. The default configuration allows you to use types like -`Guid`, `DateOnly`, enumerations or simple types such as `int` or `string` as path parameters and within -form encoded content. - -Formatting is provided by a `FormatterRegistry` that can be passed whereever you create a service, inline -or controller instance. To customize this behavior, add a custom `IFormatter` implementation to the default -registry: - -```csharp -var registry = Formatting.Default() - .Add(new MyTypeFormat()); - -var controllers = Layout.Create() - .Add("my", formatters: registry); -``` - -This will allow you to use `MyType` as a path parameter or within form encoded data in your controller. diff --git a/website/content/documentation/content/concepts/definitions.md b/website/content/documentation/content/concepts/definitions.md index 212d042..18ac776 100644 --- a/website/content/documentation/content/concepts/definitions.md +++ b/website/content/documentation/content/concepts/definitions.md @@ -1,5 +1,6 @@ --- title: Method Definitions +weight: 3 cascade: type: docs --- @@ -121,7 +122,7 @@ Some frameworks allow to further restrict path parameters using a regular expres ### Complex Types When using a complex type in a parameter declaration, the value will be [deserialized](#serialization-formats) from the -request body. By default, handlers will accept content declared as XML, JSON or Form Encoded. If +request body. By default, handlers will accept content declared as XML, JSON or form encoded. If the client does not declare the `Content-Type`, the server will try to treat the body as JSON. {{< tabs items="Webservices,Functional,Controllers" >}} @@ -142,7 +143,7 @@ the client does not declare the `Content-Type`, the server will try to treat the {{< tab >}} ```csharp [ControllerAction(RequestMethod.POST)] - public int Save(MyClass data) { /* ... */ } + public void Save(MyClass data) { /* ... */ } ``` {{< /tab >}} @@ -150,34 +151,695 @@ the client does not declare the `Content-Type`, the server will try to treat the ### HTML Forms +Form data can be used to populate both complex types and primitive parameters. Browsers +will encode the content as `application/x-www-form-urlencoded` and the framework will +populate the arguments as needed. This allows such endpoints to be used both from +browsers and as a regular API. + +Example form: + +```html +
+ + +

+ + + +

+ + +
+``` + +Can be read using the following definitions: + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod(RequestMethod.POST)] + public void Save(int id, string name) { /* ... */ } + + // or + + [ResourceMethod(RequestMethod.POST)] + public void Save(MyRecord record) { /* ... */ } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Post("/save", (int id, string name) => { /* ... */ }) + + // or + + .Post("/save", (MyRecord data) => { /* ... */ }) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + [ControllerAction(RequestMethod.POST)] + public void Save(int id, string name) { /* ... */ } + + // or + + [ControllerAction(RequestMethod.POST)] + public void Save(MyRecord data) { /* ... */ } + ``` +{{< /tab >}} + +{{< /tabs >}} + +Both mechanisms can also be mixed (read one argument as a parameter and all +others as a custom type). + ### Request Injection +To access information about the currently executed request you can add a parameter of +type `IRequest` to your method definition which will automatically be populated. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public string? GetUserAgent(IRequest request) => request.UserAgent; + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get("/user-agent", (IRequest request) => request.UserAgent) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public string? UserAgent(IRequest request) => request.UserAgent; + ``` +{{< /tab >}} + +{{< /tabs >}} + +Injecting requests is required if you would like to generate [custom responses](#custom-responses). + +If you frequently access the request in your endpoints to achieve a certain functionality, +think about adding [custom primitives](#custom-primitives), [custom injectors](#custom-injection) +or a [custom concern](../../concerns/). + ### Handler Injection -### Streaming +Similar to request injection you can also inject the `IHandler` which is responsible for the +current request. This is typically not required but can be used to create and return +[custom requests handlers](#handlers) on the fly. + +### Streams + +The request body can be injected as a `Stream`, e.g. when implementing +file uploads. This stream represents the processed request payload, so it +will already be decompressed and not in a chunked format. Depending on the +size of request body this will either be a stream backed by memory or by a file +and is therefore well suited for very large payloads. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod(RequestMethod.PUT, "upload")] + public void Upload(Stream file) { /* ... */ } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Put("/upload", (Stream file) => { /* ... */ }) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + [ControllerAction(RequestMethod.PUT)] + public void Upload(Stream file) { /* ... */ } + ``` +{{< /tab >}} + +{{< /tabs >}} ### Custom Injection +To inject custom types besides the built-in capabilities, you can configure +a custom `InjectionRegistry` and use it with your API services. The registry accepts +`IParameterInjector` instances that define what types are supported and how +they are determined from the current request and environment. + +The following injector will inspect the requests headers for a correlation ID +and create a new one if not present. + +```csharp +public record class CorrelationID(string ID); + +public class CorrelationInjector : IParameterInjector +{ + + public bool Supports(Type type) => type == typeof(CorrelationID); + + public object? GetValue(IHandler handler, IRequest request, Type targetType) + { + if (request.Headers.TryGetValue("X-Correlation-ID", out var id)) + { + return new CorrelationID(id); + } + + return new CorrelationID(Guid.NewGuid().ToString()); + } + +} +``` + +The injector can than be added to the default injection registry: + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + var injection = Injection.Default() + .Add(new CorrelationInjector()); + + var api = Layout.Create() + .AddService("service", injection); + + public class MyService + { + + [ResourceMethod] + public string GetCorrelationID(CorrelationID cor) => cor.ID; + + } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + var injection = Injection.Default() + .Add(new CorrelationInjector()); + + var api = Inline.Create() + .Injectors(injection) + .Get((CorrelationID cor) => cor.ID); + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + var injection = Injection.Default() + .Add(new CorrelationInjector()); + + var api = Layout.Create() + .AddController("controller", injection); + + public class MyController + { + + public string GetCorrelationID(CorrelationID cor) => cor.ID; + + } + ``` +{{< /tab >}} + +{{< /tabs >}} + ### User Injection +To inject the [authenticated user](../../concerns/authentication/), you can +add a typed injector to your [injection registry](#custom-injection). + +```csharp +var injection = Injection.Default() + .Add(new UserInjector()); +``` + ## Response Generation +This section describes the various mechanisms to generate a service response. + ### Primitives +By default, the following types can be used as a return type within a method definition: +`string`, `bool`, `enum`, `Guid`, `DateOnly` and any other primitive type (such as +`int`). + +If declared nullable, the server will generate a `HTTP 204 No Content` if `null` is returned. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public int Length(string text) => text.Length; + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get((string text) => text.Length) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public int Index(string text) => text.Length; + ``` +{{< /tab >}} + +{{< /tabs >}} + ### Complex Types +When returning a complex type, the value will be [serialized](#serialization-formats) and sent +to the client. The response format is negated with the client using the `Accept` request +header. By default, the server is capable of generating XML, JSON or form encoded responses. If +no format is specified by the client, the implementation will fall back to JSON. + +If declared nullable, the server will generate a `HTTP 204 No Content` if `null` is returned. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public MyType DoWork() => new(); + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get(() => new MyType()) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public MyType Index() => new(); + ``` +{{< /tab >}} + +{{< /tabs >}} + ### Custom Responses +When injecting the request into your method, you can directly generate an `IResponse` +or `IResponseBuilder` and return it to the client. This allows you to take full control +over the response generation but is less readable than the typed versions. + + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public IResponseBuilder Respond(IRequest request) + { + var content = Resource.FromString("Hello World") + .Build(); + + return request.Respond() + .Header("X-My-Header", "my-value") + .Content(content) + .Type(ContentType.TextPlain); + } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get((IRequest request) => { + var content = Resource.FromString("Hello World") + .Build(); + + return request.Respond() + .Header("X-My-Header", "my-value") + .Content(content) + .Type(ContentType.TextPlain); + }) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public IResponseBuilder Respond(IRequest request) + { + var content = Resource.FromString("Hello World") + .Build(); + + return request.Respond() + .Header("X-My-Header", "my-value") + .Content(content) + .Type(ContentType.TextPlain); + } + ``` +{{< /tab >}} + +{{< /tabs >}} + +// ToDo: Doku zu möglichen Contents (verlinken bei custom handler?) + ### Results +Results are type-safe responses that can still be adjusted to modify the +generated HTTP response. Therefore, results can be considered an advanced +way to generate responses without the need to fully generate the response +in the first place. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public Result DoWork() + { + return new Result("Hello World").Header("X-My-Header", "my-value"); + } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get(() => new Result("Hello World").Header("X-My-Header", "my-value")) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public Result Index() + { + return new Result("Hello World").Header("X-My-Header", "my-value"); + } + ``` +{{< /tab >}} + +{{< /tabs >}} + +The `Result` class allows to adjust any response property besides the actual content +by implementing the same interfaces as the `IResponseBuilder`. This does not only work +for data structures, but also for special types such as streams. + ### Handlers +Instead of generating a response you can also return an `IHandler` or `IHandlerBuilder` instance. +This allows you to provide a whole segment on your web application by re-using the +[existing handlers](../../handlers/) or by implementing [custom ones](../handlers/). + +The following example will render a fully navigable directory listing view depending on +the tenant ID passed to the method: + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod("files/:tenant")] + public IHandlerBuilder Files(int tenant) + { + var tree = ResourceTree.FromDirectory($"/data/tenants/{tenant}"); + + return Listing.From(tree); + } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get("/files/:tenant", (int tenant) => Listing.From(ResourceTree.FromDirectory($"/data/tenants/{tenant}"))) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public IHandlerBuilder Files(int tenant) + { + var tree = ResourceTree.FromDirectory($"/data/tenants/{tenant}"); + + return Listing.From(tree); + } + ``` +{{< /tab >}} + +{{< /tabs >}} + ### Streams +To return files or similar content, you can directly return a `Stream` instance +from your method. +The framework will automatically seek and dispose the stream. Returning streams +is not thread-safe as streams are stateful, so you will need to create a new +instance for every request to be answered. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public Stream GetFile() => File.OpenRead("..."); + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get(() => File.OpenRead("...")) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public Stream File() => File.OpenRead("..."); + ``` +{{< /tab >}} + +{{< /tabs >}} + ### Empty Responses -## Behavior +Methods with a `void` return type will automatically generate a `HTTP 204 No Content` +response. This is also the case when `null` is returned. -### Serialization Formats +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public void DoWork() { } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get(() => { }}) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public void DoWork() { } + ``` +{{< /tab >}} + +{{< /tabs >}} ### Asynchronous Execution + +Service methods returning a `Task` or `ValueTask` will be executed +asynchronously. All the features described in this document will work +for asynchronous execution as well. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + [ResourceMethod] + public async ValueTask DoWork() { /* ... */ } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + .Get(async () => await ...) + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + public async ValueTask DoWork() { /* ... */ } + ``` +{{< /tab >}} + +{{< /tabs >}} + +## Registries + +This section describes registries that can be used for both injection as well as +response generation. + +### Custom Primitives + +Primitives (such as `Guid` or `int`) used in parameters or as a response type are automatically +handled using the built-in `FormatterRegistry`. You can add support for a custom type by +implementing an `IFormatter` and adding it to a custom registry which is then used by your +services. + +The following implementation will add support for a `Point` type with `x` and `y` coordinates +so it can be used in a service. + +```csharp +public record class Point(int X, int Y); + +public class PointFormatter : IFormatter +{ + + public bool CanHandle(Type type) => type == typeof(Point); + + public object? Read(string value, Type type) + { + var parts = value.Split('-'); + + return new Point(int.Parse(parts[0]), int.Parse(parts[1])); + } + + public string? Write(object value, Type type) + { + var point = (Point)value; + + return $"{point.X}-{point.Y}"; + } + +} +``` + +This formatter can then be added to the default formatting registry. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + var registry = Formatting.Default() + .Add(new PointFormatter()); + + var api = Layout.Create() + .AddService("service", registry); + + public class MyService + { + + [ResourceMethod("invert/:point")] + public Point Invert(Point point) => new(point.Y, point.X); + + } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + var registry = Formatting.Default() + .Add(new PointFormatter()); + + var api = Inline.Create() + .Formatters(registry) + .Get("/invert/:point", (Point point) => new(point.Y, point.X)); + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + var registry = Formatting.Default() + .Add(new PointFormatter()); + + var api = Layout.Create() + .AddController("controller", registry); + + public class MyController + { + + public Point Invert([FromPath] Point point) => new(point.Y, point.X); + + } + ``` +{{< /tab >}} + +{{< /tabs >}} + +The serialization format is implemented by our formatter, so the API can be called +via `/invert/8-10` and will return `10-8` as a text formatted response. + +### Serialization Formats + +Serialization allows to read and write complex types, so they can be used in +your method definitions. By default, services can consume and produce entities +in XML, JSON or in form encoding, with a default fallback to JSON. + +When sending an entity to your service, the client should specify the `Content-Type` +of the body so the server can choose the correct deserializer to read the data with. The `Accept` +header sent by the client tells the server which serialization format is preferred by +the client when a response is generated. The server will tell the client which +serialization format was used to generate the body of the response by specifying the +`Content-Type` header again. + +![HTTP Content Flow](serialization.png) + +To add support for an additional format you can implement the `ISerializationFormat` and +add your implementation to a registry which is then passed to your service. + +For example, the nuget package `GenHTTP.Modules.Protobuf` adds support for +[Protocol Buffers](https://protobuf.dev/) which is not enabled by default. The +following snippet shows how to register the protobuf format and use it in +a service. + +{{< tabs items="Webservices,Functional,Controllers" >}} + +{{< tab >}} + ```csharp + var registry = Serialization.Default() + .Add(new FlexibleContentType("application/protobuf"), new ProtobufFormat()); + + var api = Layout.Create() + .AddService("service", registry); + + public class MyService + { + + [ResourceMethod(RequestMethod.PUT)] + public ResponseType Store(RequestType data) { /* ... */ } + + } + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + var registry = Serialization.Default() + .Add(new FlexibleContentType("application/protobuf"), new ProtobufFormat()); + + var api = Inline.Create() + .Serializers(registry) + .Put((RequestType data) => { /* ... */ }); + ``` +{{< /tab >}} + +{{< tab >}} + ```csharp + var registry = Serialization.Default() + .Add(new FlexibleContentType("application/protobuf"), new ProtobufFormat()); + + var api = Layout.Create() + .AddController("controller", registry); + + public class MyController + { + + [ControllerAction(RequestMethod.PUT)] + public ResponseType Store(RequestType data) { /* ... */ } + + } + ``` +{{< /tab >}} + +{{< /tabs >}} diff --git a/website/content/documentation/content/concepts/injection.md b/website/content/documentation/content/concepts/injection.md deleted file mode 100644 index 7dde00d..0000000 --- a/website/content/documentation/content/concepts/injection.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Parameter Injection -cascade: - type: docs ---- - -Frameworks that allow to define custom methods in code (such as [webservices](./webservices), [controllers](./controllers), -or [functional handlers](./functional)) may be extended with custom parameter resolvers which -allow to inject custom types into method invocations. - -By default, the framework will inject `IRequest`, `IHandler` and the request body as a `Stream`. This can -be extended by configuring and passing a custom `InjectionRegistry`. The registry -accepts `IParameterIParameterInjector` instances that define what types are supported -and how they are determined from the current request and environment. - -The following example shows how to inject the currently authenticated user into a custom method: - -```csharp -var auth = BasicAuthentication.Create() - .Add("my_user", "v3rys4v3p4ssw0rd"); - -var injectors = Injection.Default() - .Add(new UserInjector()); - -var content = Inline.Create() - .Get((BasicAuthenticationUser user) => user.DisplayName) - .Injectors(injectors) - .Authentication(auth); -``` diff --git a/website/content/documentation/content/concepts/resources.md b/website/content/documentation/content/concepts/resources.md index 9737a1b..bd9f00c 100644 --- a/website/content/documentation/content/concepts/resources.md +++ b/website/content/documentation/content/concepts/resources.md @@ -1,5 +1,6 @@ --- title: Resources +weight: 4 cascade: type: docs --- diff --git a/website/content/documentation/content/concepts/results.md b/website/content/documentation/content/concepts/results.md deleted file mode 100644 index a6a89e5..0000000 --- a/website/content/documentation/content/concepts/results.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Results -cascade: - type: docs ---- - -When writing [webservices](./webservices), [controllers](./controllers), or [functional handlers](./functional), -you might want to adjust the generated response without the need of bulding an `IResponse` yourself. The `Result` -allows you to keep strong return types while being able to modify the generated response: - -```csharp -[ResourceMethod(RequestMethod.PUT)] -public Result AddBook(Book book) -{ - var created = ... // do work - - return new(created).Status(ResponseStatus.Created); -} -``` - -The `Result` follows the semantics of the `IResponseBuilder` so you can adjust the generated response as needed. -This does not only work for data structures, but also for special types such as streams. diff --git a/website/content/documentation/content/concepts/serialization.drawio b/website/content/documentation/content/concepts/serialization.drawio new file mode 100644 index 0000000..b65601e --- /dev/null +++ b/website/content/documentation/content/concepts/serialization.drawio @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/content/documentation/content/concepts/serialization.png b/website/content/documentation/content/concepts/serialization.png new file mode 100644 index 0000000000000000000000000000000000000000..0a21178938a90b3f546db37496e93e3f243716c9 GIT binary patch literal 16523 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 literal 0 HcmV?d00001 diff --git a/website/content/documentation/content/concerns/_index.md b/website/content/documentation/content/concerns/_index.md index da9723b..efffad0 100644 --- a/website/content/documentation/content/concerns/_index.md +++ b/website/content/documentation/content/concerns/_index.md @@ -4,4 +4,61 @@ weight: 4 cascade: type: docs --- -ToDo + +Concerns allow to add behavior to a section of your web application by intercepting requests and responses. +The framework allows any handler to be extended by using concerns. The following example will add +a custom HTTP header to every response generated by the server: + +```csharp +public class CustomConcern : IConcern +{ + + public IHandler Content { get; } + + public IHandler Parent { get; } + + public CustomConcern(IHandler parent, Func contentFactory) + { + Parent = parent; + Content = contentFactory(this); + } + + public IAsyncEnumerable GetContentAsync(IRequest request) => Content.GetContentAsync(request); + + public ValueTask PrepareAsync() => Content.PrepareAsync(); + + public async ValueTask HandleAsync(IRequest request) + { + var response = await Content.HandleAsync(request); + + if (response != null) + { + response.Headers.Add("X-Custom-Header", "Custom Concern"); + } + + return response; + } + +} + +public class CustomConcernBuilder : IConcernBuilder +{ + + public IConcern Build(IHandler parent, Func contentFactory) + { + return new CustomConcern(parent, contentFactory); + } + +} + +var handler = Layout.Create() + .Index(Content.From("Hello World")) + .Add(new CustomConcernBuilder()); + +Host.Create() + .Handler(handler) + .Run(); +``` + +The GenHTTP SDK uses the same mechanism to achieve functionality such as [compression](./compression) +or support for [CORS](./cors). diff --git a/website/content/documentation/content/concerns/authentication.md b/website/content/documentation/content/concerns/authentication.md index f63bd05..e7cffec 100644 --- a/website/content/documentation/content/concerns/authentication.md +++ b/website/content/documentation/content/concerns/authentication.md @@ -68,83 +68,3 @@ securedContent.Authentication((user, password) => { return new(new BasicAuthenticationUser(user)); }); ``` - -## Web Authentication - -> NOTE The feature described in this section [is deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496) and will be removed with GenHTTP 9. - -This concern will secure your website by adding a login form that needs to be completed by -the app users to authenticate themselves.Additionally, the concern provides a setup functionality -that will allow users to create an admin account on the first run of your application. -The integration is handled by the implementation of an interface passed to the builder. - -You can either select a simple way to integrate which will use built-in controllers to -render login forms or pass a more complex integration model that will invoke your custom -controllers which allows to render a more complex login UI. - -As the implementation uses advanced features such as controllers, there is an additional -[nuget package](https://www.nuget.org/packages/GenHTTP.Modules.Authentication.Web/) for this. - -The following code shows a simple example that will store user and session records in memory: - -```csharp -var auth = WebAuthentication.Simple(); - -var project = Content.From(Resource.FromString("Hello World")) - .Add(auth); - -Host.Create() - .Handler(project) - .Defaults() - .Development() - .Console() - .Run(); - -record class User(string Name, string Password) : IUser -{ - public string DisplayName => Name; -} - -class Integration : ISimpleWebAuthIntegration -{ - private readonly List _Users = new(); - - private readonly Dictionary _Sessions = new(); - - public ValueTask CheckSetupRequired(IRequest request) => new(_Users.Count == 0); - - public ValueTask PerformSetup(IRequest request, string username, string password) - { - _Users.Add(new(username, password)); - return ValueTask.CompletedTask; - } - - public ValueTask PerformLoginAsync(IRequest request, string username, string password) - { - return new(_Users.FirstOrDefault(u => u.Name == username && u.Password == password)); - } - - public ValueTask StartSessionAsync(IRequest request, User user) - { - var id = Guid.NewGuid().ToString(); - - _Sessions[id] = user; - - return new(id); - } - - public ValueTask VerifyTokenAsync(IRequest request, string sessionToken) - { - if (_Sessions.TryGetValue(sessionToken, out var user)) - { - return new(user); - } - - return new(); - } - -} -``` - -For the advanced integration you can have a look at the controllers that are used -to render the simple login forms. diff --git a/website/content/documentation/content/concerns/concerns.md b/website/content/documentation/content/concerns/concerns.md deleted file mode 100644 index b5b14ff..0000000 --- a/website/content/documentation/content/concerns/concerns.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Concerns -cascade: -type: docs ---- - -Concerns allow to add behavior to a section of your web application by intercepting requests and responses. -The framework allows any handler to be extended by using concerns. The following example will add -a custom HTTP header to every response generated by the server: - -```csharp -public class CustomConcern : IConcern -{ - - public IHandler Content { get; } - - public IHandler Parent { get; } - - public CustomConcern(IHandler parent, Func contentFactory) - { - Parent = parent; - Content = contentFactory(this); - } - - public IAsyncEnumerable GetContentAsync(IRequest request) => Content.GetContentAsync(request); - - public ValueTask PrepareAsync() => Content.PrepareAsync(); - - public async ValueTask HandleAsync(IRequest request) - { - var response = await Content.HandleAsync(request); - - if (response != null) - { - response.Headers.Add("X-Custom-Header", "Custom Concern"); - } - - return response; - } - -} - -public class CustomConcernBuilder : IConcernBuilder -{ - - public IConcern Build(IHandler parent, Func contentFactory) - { - return new CustomConcern(parent, contentFactory); - } - -} - -var handler = Layout.Create() - .Index(Content.From("Hello World")) - .Add(new CustomConcernBuilder()); - -Host.Create() - .Handler(handler) - .Run(); -``` - -The GenHTTP SDK uses the same mechanism to achieve functionality such as [compression](./compression) -or support for [CORS](./cors). diff --git a/website/content/documentation/content/concerns/range-support.md b/website/content/documentation/content/concerns/range-support.md index 5e90572..f8a7727 100644 --- a/website/content/documentation/content/concerns/range-support.md +++ b/website/content/documentation/content/concerns/range-support.md @@ -25,7 +25,7 @@ to be requested (which would then result in a multipart response). ## Scope -As the range support is implemented as a [concern](./concerns), you +As the range support is implemented as a [concern](../concepts/concerns.md), you may add this functionality to any handler as needed. ```csharp diff --git a/website/content/documentation/content/frameworks/controllers.md b/website/content/documentation/content/frameworks/controllers.md index faad651..0a37e40 100644 --- a/website/content/documentation/content/frameworks/controllers.md +++ b/website/content/documentation/content/frameworks/controllers.md @@ -1,5 +1,6 @@ --- title: Controllers +weight: 3 cascade: type: docs --- @@ -16,7 +17,7 @@ so this is just another flavor of defining a web API. Controller based APIs can quickly be created by using a [project template](../../templates). {{< /callout >}} -## Creating a controller based API +## Hosting an API The following example shows how an API controlling an IoT device could look like when implemented using the controller framework. @@ -80,7 +81,4 @@ The following capabilities are shared by various application frameworks: {{< cards >}} {{< card link="../../concepts/definitions" title="Method Definitions" icon="chip" >}} -{{< card link="../../concepts/conversion" title="Serialization and deserialization" icon="document-text" >}} -{{< card link="../../concepts/injection" title="Parameter Injection" icon="arrow-right" >}} -{{< card link="../../concepts/results" title="Results" icon="arrow-left" >}} {{< /cards >}} diff --git a/website/content/documentation/content/frameworks/functional.md b/website/content/documentation/content/frameworks/functional.md index d0d1638..28613b6 100644 --- a/website/content/documentation/content/frameworks/functional.md +++ b/website/content/documentation/content/frameworks/functional.md @@ -1,39 +1,59 @@ --- title: Functional Handlers +weight: 2 cascade: type: docs --- +{{< cards >}} +{{< card link="https://www.nuget.org/packages/GenHTTP.Modules.Functional/" title="GenHTTP.Modules.Functional" icon="link" >}} +{{< /cards >}} + With this module, requests can be handled in a functional manner, reducing -the boiler plate code to be written by a web application developer. +the boilerplate code to be written by a web application developer. -> NOTE Apps can quickly be created by using a [project template](./templates). +{{< callout type="info" >}} +Apps can quickly be created by using a [project template](../../templates). +{{< /callout >}} -## Basic Structure +## Hosting an API -The following program will provide a simple web service to increment and -decrement given numbers. +To host an API using this framework you can create an `Inline` handler and add +your operations as needed. ```csharp -using GenHTTP.Modules.Functional; +using GenHTTP.Engine; -var handler = Inline.Create() - .Get("/increment", (i) => i + 1) // GET /increment?i=1 - .Get("/decrement/:i", (i) => i - 1); // GET /decrement/2 +using GenHTTP.Modules.Functional; +using GenHTTP.Modules.Layouting; + +var bookService = Inline.Create() + // GET http://localhost:8080/books/?page=1&pageSize=20 + .Get((int page, int pageSize) => /* ... */) + // GET http://localhost:8080/books/4711 + .Get(":id", (int id) => /* ... */) + // PUT http://localhost:8080/books/ + .Put((Book book) => /* ... */) + // POST http://localhost:8080/books/ + .Post((Book book) => /* ... */) + // DELETE http://localhost:8080/books/4711 + .Delete(":id", (int id) => /* ... */); + +var api = Layout.Create() + .Add("books", bookService); Host.Create() - .Handler(handler) + .Handler(api) + .Development() + .Console() .Run(); ``` -As with the [webservice module](./webservices), functions can use various -parameter and return types, including `IRequest`, `IResponse`, `IHandler` and -`Stream`. Both synchronous and `async` methods are supported. - ## Further Resources The following capabilities are shared by various application frameworks: -- [Serialization and deserialization](./conversion) -- [Parameter injection](./injection) -- [Results](./results) +{{< cards >}} +{{< card link="../../concepts/definitions" title="Method Definitions" icon="chip" >}} +{{< /cards >}} + diff --git a/website/content/documentation/content/frameworks/single-page-applications.md b/website/content/documentation/content/frameworks/single-page-applications.md index dfc5cf9..c089128 100644 --- a/website/content/documentation/content/frameworks/single-page-applications.md +++ b/website/content/documentation/content/frameworks/single-page-applications.md @@ -1,12 +1,23 @@ --- title: Single Page Applications (SPA) +weight: 5 cascade: type: docs --- +{{< cards >}} +{{< card link="https://www.nuget.org/packages/GenHTTP.Modules.SinglePageApplications/" title="GenHTTP.Modules.SPAs" icon="link" >}} +{{< /cards >}} + This handler provides an easy way to serve a single page application (for example a Vue.js, React, or Angular app) to your clients. +{{< callout type="info" >}} +Apps can quickly be created by using a [project template](../../templates). +{{< /callout >}} + +## Hosting a SPA + ```csharp var tree = ResourceTree.FromDirectory("/var/html/my-webapp"); @@ -22,9 +33,6 @@ var app = SinglePageApplication.From(tree); This example will automatically search for an index file (such as `index.html`) in the specified directory and serve it to clients accessing http://localhost:8080/. -With this handler, a single page application can be hosted with just a few lines of code -using [Docker](/documentation/hosting/). - ## Routing If you would like to use path based routing in your application, the server needs to diff --git a/website/content/documentation/content/frameworks/static-websites.md b/website/content/documentation/content/frameworks/static-websites.md index 80d28ad..2a5ec89 100644 --- a/website/content/documentation/content/frameworks/static-websites.md +++ b/website/content/documentation/content/frameworks/static-websites.md @@ -1,20 +1,24 @@ --- title: Static Websites +weight: 4 cascade: type: docs --- +{{< cards >}} +{{< card link="https://www.nuget.org/packages/GenHTTP.Modules.StaticWebsites/" title="GenHTTP.Modules.StaticWebsites" icon="link" >}} +{{< /cards >}} + This handler provides an easy way to serve a static website such as a [Hugo](https://gohugo.io/) application to your clients. -> NOTE Static websites can quickly be created by using a [project template](./templates). +{{< callout type="info" >}} +Static websites can quickly be created by using a [project template](../../templates). +{{< /callout >}} ## Creating a Static Website -The following example will make the specified application available on http://localhost:8080/. -The handler will search for index files (such as `index.html`) and automatically -generate a sitemap and robots instruction file. If -those files already exist in the given web root, they will be served directly instead. +The following example will host the specified application available on http://localhost:8080/. ```csharp var tree = ResourceTree.FromDirectory("/var/html/my-website"); @@ -27,24 +31,3 @@ var app = StaticWebsite.From(tree); .Handler(app) .Run(); ``` - -With this handler, a static website can be hosted with just a few lines of code -using [Docker](/documentation/hosting/). - -If you would like to combine a static website with dynamic content such as a webservice, -you can use the handler as an additional root of a [Layout](./layouting): - -```csharp -var api = Layout.Create() - .AddService<...>("..."); - -var content = Layout.Create() - .Add("api", api) - .Add(StaticWebsite.From(...)) - - Host.Create() - .Console() - .Defaults() - .Handler(app) - .Run(); -``` diff --git a/website/content/documentation/content/frameworks/webservices.md b/website/content/documentation/content/frameworks/webservices.md index 0a56479..b530358 100644 --- a/website/content/documentation/content/frameworks/webservices.md +++ b/website/content/documentation/content/frameworks/webservices.md @@ -1,92 +1,79 @@ --- title: Webservices +weight: 1 cascade: type: docs --- +{{< cards >}} +{{< card link="https://www.nuget.org/packages/GenHTTP.Modules.Webservices/" title="GenHTTP.Modules.Webservices" icon="link" >}} +{{< /cards >}} + The webservice module provides an easy way to implement RESTful services that can be consumed by clients as needed. -> NOTE Webservices can quickly be created by using a [project template](./templates). +{{< callout type="info" >}} +Webservices can quickly be created by using a [project template](../../templates). +{{< /callout >}} + +## Hosting an API -## Creating a Webservice +To host an API using this framework you can define a new class that +hosts your operations as dedicated methods. The signature of those methods will +define how your API can be called via HTTP. -The concept is very similar to popular webservice frameworks -such as [JAX-RS](https://github.com/jax-rs): +The following example shows how to define and host a service that can be used +to manage an entity (books in this case). ```csharp +using GenHTTP.Engine; + +using GenHTTP.Api.Protocol; + using GenHTTP.Modules.Webservices; using GenHTTP.Modules.Security; +using GenHTTP.Modules.Layouting; -public class BookResource +public class BookService { + // GET http://localhost:8080/books/?page=1&pageSize=20 [ResourceMethod] public List GetBooks(int page, int pageSize) { /* ... */ } + // GET http://localhost:8080/books/4711 [ResourceMethod(":id")] public Book? GetBook(int id) { /* ... */ } + // PUT http://localhost:8080/books/ [ResourceMethod(RequestMethod.PUT)] public Book AddBook(Book book) { /* ... */ } - + + // POST http://localhost:8080/books/ [ResourceMethod(RequestMethod.POST)] public Book UpdateBook(Book book) { /* ... */ } + // DELETE http://localhost:8080/books/4711 [ResourceMethod(RequestMethod.DELETE, ":id")] public Book? DeleteBook(int id) { /* ... */ } } var service = Layout.Create() - .AddService("books") + .AddService("books") .Add(CorsPolicy.Permissive()); Host.Create() .Handler(service) + .Development() + .Console() .Run(); ``` -The service will be available at http://localhost:8080/books. -As the functionality is provided on handler level, -all other concerns such as [authentication](./authentication), [error handling](./error-handling) -or [CORS](./cors) can be implemented using regular server mechanisms. - -By default, parameter values (within the path) are expected -to be alphanumeric. If needed, a custom pattern can be specified -to define the format accepted by the service: - -```csharp -[ResourceMethod("(?[0-9]{12,13})")] // EAN-13 -public Book? GetBook(int id) { /* ... */ } -``` - -Because the resource hander needs to utilize regular expressions -and reflection to achieve the required functionality, its performance -will be slow compared to `IHandler` instances -providing the same functionality. - -The implementation supports all typical features such as -serialization and deserialization of complex types, simple -types, enums, streams, raw `IRequest`, `IHandler`, and `IResponseBuilder` -arguments, query parameters, path parameters, `async` methods returning -a `Task` or `ValueTask`, and exception handling: - -```csharp -[ResourceMethod(RequestMethod.PUT)] -public async ValueTask Stream(Stream input) { /* ... */ } - -[ResourceMethod] -public IResponseBuilder RequestResponse(IRequest request, IHandler handler) { return request.Respond() /* ... */; } - -[ResourceMethod] -public void Exception() => throw new ProviderException(ResponseStatus.AlreadyReported, "Already reported!"); -``` - ## Further Resources The following capabilities are shared by various application frameworks: -- [Serialization and deserialization](./conversion) -- [Parameter injection](./injection) -- [Results](./results) +{{< cards >}} +{{< card link="../../concepts/definitions" title="Method Definitions" icon="chip" >}} +{{< /cards >}} diff --git a/website/content/documentation/content/concepts/handlers.md b/website/content/documentation/content/handlers/_index.md similarity index 94% rename from website/content/documentation/content/concepts/handlers.md rename to website/content/documentation/content/handlers/_index.md index 80f9ac9..b4d260e 100644 --- a/website/content/documentation/content/concepts/handlers.md +++ b/website/content/documentation/content/handlers/_index.md @@ -1,5 +1,6 @@ ---- +--- title: Handlers +weight: 3 cascade: type: docs --- @@ -66,6 +67,6 @@ Host.Create() ``` As handlers are invoked for every request handled by the server, it is usually worth it to -optimize them for performance. For example, as the content served by our `CustomHandler` does +optimize them for performance. For example, as the content served by our `CustomHandler` does not change depending on the request, the string resource instance could be cached by a field or property set in the constructor. diff --git a/website/content/documentation/content/providers/downloads.md b/website/content/documentation/content/handlers/downloads.md similarity index 100% rename from website/content/documentation/content/providers/downloads.md rename to website/content/documentation/content/handlers/downloads.md diff --git a/website/content/documentation/content/providers/layouting.md b/website/content/documentation/content/handlers/layouting.md similarity index 100% rename from website/content/documentation/content/providers/layouting.md rename to website/content/documentation/content/handlers/layouting.md diff --git a/website/content/documentation/content/providers/listing.md b/website/content/documentation/content/handlers/listing.md similarity index 100% rename from website/content/documentation/content/providers/listing.md rename to website/content/documentation/content/handlers/listing.md diff --git a/website/content/documentation/content/providers/listing.png b/website/content/documentation/content/handlers/listing.png similarity index 100% rename from website/content/documentation/content/providers/listing.png rename to website/content/documentation/content/handlers/listing.png diff --git a/website/content/documentation/content/providers/load-balancer.md b/website/content/documentation/content/handlers/load-balancer.md similarity index 100% rename from website/content/documentation/content/providers/load-balancer.md rename to website/content/documentation/content/handlers/load-balancer.md diff --git a/website/content/documentation/content/providers/redirects.md b/website/content/documentation/content/handlers/redirects.md similarity index 100% rename from website/content/documentation/content/providers/redirects.md rename to website/content/documentation/content/handlers/redirects.md diff --git a/website/content/documentation/content/providers/reverse-proxy.md b/website/content/documentation/content/handlers/reverse-proxy.md similarity index 100% rename from website/content/documentation/content/providers/reverse-proxy.md rename to website/content/documentation/content/handlers/reverse-proxy.md diff --git a/website/content/documentation/content/providers/static-content.md b/website/content/documentation/content/handlers/static-content.md similarity index 100% rename from website/content/documentation/content/providers/static-content.md rename to website/content/documentation/content/handlers/static-content.md diff --git a/website/content/documentation/content/providers/virtual-hosts.md b/website/content/documentation/content/handlers/virtual-hosts.md similarity index 100% rename from website/content/documentation/content/providers/virtual-hosts.md rename to website/content/documentation/content/handlers/virtual-hosts.md diff --git a/website/content/documentation/content/providers/_index.md b/website/content/documentation/content/providers/_index.md deleted file mode 100644 index 3f128a6..0000000 --- a/website/content/documentation/content/providers/_index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Providers -weight: 3 -cascade: - type: docs ---- diff --git a/website/content/documentation/content/templates.md b/website/content/documentation/content/templates.md index 1462296..9c962fc 100644 --- a/website/content/documentation/content/templates.md +++ b/website/content/documentation/content/templates.md @@ -30,15 +30,15 @@ be used from there to quickly create new projects: The following templates are available to be used: -| Template | Description | -| ------------- |------------- | -| `genhttp-webservice` | A project that will host a new [REST web service](./webservices). | +| Template | Description | +|------------------------------|------------- | +| `genhttp-webservice` | A project that will host a new [REST web service](./webservices). | | `genhttp-webservice-minimal` | A project that will host a minimal web service in a single file using the [functional module](./functional). | -| `genhttp-website` \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | A [website](./websites), mainly for static content (such as a business website). | -| `genhttp-website-mvc-razor` \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | Dynamic website using the [MVC pattern](./controllers) and [Razor](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-5.0) as a templating engine. | -| `genhttp-website-mvc-scriban` \[[deprecated](https://github.com/Kaliumhexacyanoferrat/GenHTTP/issues/496)\] | Dynamic website using the [MVC pattern](./controllers) and [Scriban](https://github.com/scriban/scriban/) as a templating engine. | -| `genhttp-website-static` | Serves a [static website](./static-websites) from the file system. | -| `genhttp-spa` | Serves the distribution files of a [Single Page Application (SPA)](./single-page-applications). | +| `genhttp-website-static` | Serves a [static website](./static-websites) from the file system. | +| `genhttp-spa` | Serves the distribution files of a [Single Page Application (SPA)](./single-page-applications). | + +After creating the project, you can run it with `dotnet run` which make the application +available on [http://localhost:8080](http://localhost:8080). ## Updating Templates diff --git a/website/content/documentation/testing/_index.md b/website/content/documentation/testing/_index.md index 1440b6f..4b84fc6 100644 --- a/website/content/documentation/testing/_index.md +++ b/website/content/documentation/testing/_index.md @@ -9,7 +9,9 @@ your application using a test framework of your choice. It provides both the ability to host your project in an isolated mode as well as convenience methods to run HTTP requests against your server. -> NOTE Projects created via [project templates](../content/templates) already feature a basic test setup. +{{< callout type="info" >}} +Projects created via [project templates](../content/templates/) already feature a basic test setup. +{{< /callout >}} ## Writing Tests diff --git a/website/content/legal.md b/website/content/legal.md index 1d219e7..de39f24 100644 --- a/website/content/legal.md +++ b/website/content/legal.md @@ -2,9 +2,7 @@ Andreas Nägeli -Waldseemüller-Str. 10
-79227 Schallstadt
-Germany +Waldseemüller-Str. 10, 79227 Schallstadt, Germany andr.naegeli@gmail.com @@ -14,4 +12,4 @@ If you would like to report a bug or ask a question, please visit [our GitHub pa This website is intended to provide information about the GenHTTP web server project. Personal data and cookies are not required for this, but your IP address is logged by the server for the purpose of technical operation with every request sent by your browser. This information will -reside on the server until it is restarted. Besides this, no other personal data will be collected. \ No newline at end of file +reside on the server until it is restarted. Besides this, no other personal data will be collected.