Closed thierry-f-78 closed 1 month ago
Hi,
Thank you so much for both your interest in contributing and detailed suggestion.
That's a lot of beautiful work that you did and a lot for me to go over.
My biggest things to note:
Please allow me to point out that edge development for the facil.io functionality, including the HTTP layer, is now performed in the repo: https://github.com/facil-io/cstl
The new repo represents the transition to version 0.8.x.
The project avoids external dependencies when possible, especially dependencies that might raise licensing issues.
For this reason RegEx cannot be accepted (unless we roll our own and the RegEx Specification License allows us to do so while licensing our code under MIT / ISC).
For now, I suggest that we limit our URL section parser to glob matching that is already implemented, or perhaps rolling our own slightly expended approach.
By the way, does :year:int32
follow a convention of some type? Is it something already seen in the wild?
User experience is highly values in the facil.io approach. Everything should be easy.
For this reason, I would like to minimize the number of possible functions and provide named arguments for functions with more than 3 or 4 arguments.
Basically I hope to have as little as a single function that controls the router:
typedef struct fio_http_router_s fio_http_router_s;
/**
* Creates a route to URL and returns a pointer to a router object starting at that URL (sub-router).
*
* The sub-router allows calls to `fio_http_router_map` to treat `url` as the root of any new routes.
*
* The sub-router may be discarded or ignored, but it MUST NOT be freed manually (as it belongs to the main router object).
*/
fio_http_router_s * fio_http_router_map(fio_http_router_s *, const char * url, fio_http_router_options_s opt);
/** named arguments helper for the `fio_http_router_map` function. */
#define fio_http_router_map(router, url, ...) fio_http_router_map(router, url, (fio_http_router_options_s){__VA_ARGS__})
/* and, of course, init/destruct functions: */
fio_http_router_s * fio_http_router_new(void);
void fio_http_router_free(fio_http_router_s *);
void fio_http_router_destroy(fio_http_router_s *);
#define FIO_ROUTER_INIT {0}
The options passed should include callbacks for GET
, POST
etc', as well as a static file callback and a catch-all callback (in case of non-standard methods).
The sub_router
you mention should be detected automatically by the fio_http_router_map
function as it breaks down the URL to sections and creates sub-routers for each section
As for the content-type property as a router controller – I think that's a super interesting idea, but in general I haven't seen much use for it in the wild. If this is a use-case you encountered, we can definitely add it while keeping the NULL
content-type as a catch-all.
Please note that all names in facil.io use snake_case and types have a designated suffix that depends on what they refer to (i.e., _s
for struct
, _u
for union
, _e
for enum
).
We avoid the struct
and enum
keywords where possible by using typedef
, The suffix should be enough to indicate if this is a struct
, a union
, an enum
, or anything else.
To maintain name space integrity and avoid the risk of name collisions (especially with possible user code), the names of types and functions should follow the <library>_<module>_<function/type>
convention.
The library is always fio
for facil.io.
So, in this case, I would expect struct Router
to become a typedef
with the name fio_http_router_s
.
The moment we accept dynamic URL segments, we need to discuss the types we will use to store this data and how it would be made available.
I suggest that we adopt the FIOBJ
type system, but if you have a better idea, I'm all ears.
Again, thank you so much and I hope we can make this work :)
Please allow me to point out that edge development for the facil.io functionality, including the HTTP layer, is now performed in the repo: https://github.com/facil-io/cstl The new repo represents the transition to version 0.8.x.
Okay, no problem.
For this reason RegEx cannot be accepted (unless we roll our own and the RegEx Specification License allows us to do so while licensing our code under MIT / ISC).
I'm thinking of PCRE regex, but I'm not aware of the license issues. I don't think including a third-party library from the system (without providing the source) causes license problems. this library is not embedded in the source code, its just a compilation dependancy. (look in haproxy, embedding PCRE it juste changing compilation line https://github.com/haproxy/haproxy/blob/8427c5b5421a93ee29170fb6ca3093478acd7ab7/Makefile#L770) Note that "libc" regex exists (https://www.gnu.org/software/libc/manual/html_node/Regular-Expressions.html). These regex functions are slower than PCRE, but they exist, are provided with libc, and are POSIX specified. The compilation with one library or the other could be defined at compilation time by a define.
By the way, does :year:int32 follow a convention of some type? Is it something already seen in the wild?
You can find the :
notation in some frameworks:
We chose the :
character because it's a reserved character and it's forbidden in URLs. Note that :
can be used in a URL if it's URL-encoded like %3A
. This case is processed using ::
to designate :
at the start and handle uri like GET /%3Aaction
.
The extension :
<type-description>
is personal brainstorming.
There are other examples of routers:
<>
: https://flask.palletsprojects.com/en/2.3.x/quickstart/#variable-rules{}
: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-7.0{}
: https://symfony.com/doc/current/routing.html#route-parametersUser experience is highly values in the facil.io approach. Everything should be easy.
I absolutely agree. And a good documentation with many examples will be welcome. The minimum code for using my proposition (disregarding naming, which will be changed later) is:
router = router_create();
err = router_add_route(router, METH("GET", "POST"), "/resources/:id:hex(32)/read", get_resource);
This contains the minimum information to design a simple project. Other functions are used for advanced usage. Look at the example at the end of my previous post.
Perhaps the functions exposed in the API should be separated into two parts. One part that presents simple and concise functions that allow handling most simple projects, and another part that presents advanced functions that also allow handling complex projects.
The sub_router you mention should be detected automatically by the fio_http_router_map function as it breaks down the URL to sections and creates sub-routers for each section
Sub-routers are strongly used to configure complex applications with inheritance, like this:
The main router splits the application into 4 parts, processed by 4 teams:
main.c:
void main() {
router *v1;
router *v2;
router *app
router *static
router *main
v1 = router_create();
v2 = router_create();
app = router_create();
static = router_create();
main = router_create();
router_add_route(router, METH("GET"), "/heath-check, get_health_check);
router_add_subrouter(router, v1, "/v1")
router_add_subrouter(router, v2, "/v2")
router_add_subrouter(router, app, "/app")
router_add_subrouter(router, static, "/static")
init_v1(v1);
init_v1(v2);
init_v1(app);
init_v1(static);
}
v1.c:
void init_v1(r *router) {
router_add_route(r, METH("GET"), "/users, get_users);
}
v2.c:
void init_v2(r *router) {
router_add_route(r, METH("GET"), "/users, get_users);
}
app.c:
void init_app(r *router) {
router_add_route(r, METH("GET"), "/login, get_login);
router_add_route(r, METH("POST"), "/login, post_login);
}
static.c
void init_static(r *router) {
router_add_route(r, METH("GET"), "/, get_static_content);
}
Note, I've already seen this case at a client's (a very large publicly traded company). Each team handles a part of the application, and the routing was done by the application server (old C technology). Its current usage with other framework of other languages.
As for the content-type property as a router controller – I think that's a super interesting idea, but in general I haven't seen much use for it in the wild.
Content-type based routing is necessary for compatible APIs. POST with JSON is processed by a specific handler, while POST with form-data is processed by another one.
If this is a use-case you encountered, we can definitely add it while keeping the NULL content-type as a catch-all.
NULL content-type is handled. Alternative functions without the content-type parameter could be exposed (but this would be one more function).
Naming Conventions … So, in this case, I would expect struct Router to become a typedef with the name fio_http_router_s.
Okay, no problem. It's a detail to rename all of this.
Dès que nous acceptons des segments d'URL dynamiques, nous devons discuter des types que nous utiliserons pour stocker ces données et de la manière dont elles seront rendues disponibles.
Absolutely, for me, the most important thing is defining an API, and then types.
Obviously, types used by the API are different from runtime types, which must be optimized for speed.
All the router/subrouter must be solved as unique descriptor in a sort of compilation phase.
Quickly, I'm thinking of some TREEs with one root per METHOD. Each tree node contains:
Closing, discussion moved to: https://github.com/facil-io/cstl/issues/26
Are you yet interested by this subject. Doing C HTTP framework is one of my favorite subject with c10k problem and asynchronous design in C
I just see your project, and router design is an interesting subject. I start to brainstorm about design. the following is preliminary and not terminated.
And exemple usage (generated by IA) :
Are you interested by this job ? can I continue the brainstorm ?