valum-framework / valum

Web micro-framework written in Vala
https://valum-framework.readthedocs.io/en/latest/
GNU Lesser General Public License v3.0
226 stars 23 forks source link

template-glib engine #149

Closed chergert closed 7 years ago

chergert commented 8 years ago

Hi,

I wrote a templating engine for GLib/GObject-Introspection this last week. Not sure if it is a fit for your project or not, but thought I'd mention it. https://git.gnome.org/browse/template-glib

Unlike some others I've seen, it has a lexer/parser done with flex/bison. It can also extract GObject properties and call into GObject functions via GObject introspection. The syntax is somewhat similar to what you see in other templating systems out there.

I have some more plans for this, but we are going to be using it for the templating engine in Builder (for file/project templates) and I always love having collaboration :)

{{obj.my_property.sub_obj_property.foo("foo", true, 10 * 30)}}
{{for item in my_list_model}}
  {{item.foo}}
  {{"=" * 100}}
{{end}}

Anyway, you get the idea. The library is introspectable, so it should be usable from Vala. I tested Gjs and Python, which both work fine.

Cheers!

arteymix commented 8 years ago

It just seems to be the best of two worlds (language & environment).

With #148 it should be possible to generate binding for Gjs and Python as that it would remove unfriendly dependencies from libvalum and libvsgi.

It's best to keep templating outside Valum and let people should choose how they want to do it.

I will write recipes and examples for templating that will cover CTPL, Mustache-GLib and hopefully template-glib ;)

If I can contribute in any possible way, please let me know. I am not really proficient in C (I try to, be assured), but I have a good background in templating language and Vala.

I maintain a copr repository (https://copr.fedoraproject.org/coprs/arteymix/valum-framework/) with interesting packages related to web development. I can surely make it available from there as soon as it's usable in Vala.

Just a side note: how would waf sit in Builder?

chergert commented 8 years ago

For waf, we basically just need someone who knows the build system to implement the various components. In particular, you need to implement IdeBuildSystem, IdeBuilder, and generally, an IdeBuildResult.

The good news, is that can be done via Python or Vala plugins these days.

arteymix commented 8 years ago

I'll check that out, waf has waflib.Scripting for that kind of things.

arteymix commented 8 years ago

@chergert is it possible to lower dependencies for Template-GLib? At least support GLib 2.32?

I went through the code and APIs look really nice! It looks very promising and I am sure it will integrate like a charm with Valum.

I am seriously reconsidering the value of a Mustache implementation. It's a very nice language though.

I've been working a bit on the routing and I figured I can sort rules lexicographically, which can sure help figuring out the next route in logarithmic time.

Hey just saying, the throughput of the framework is really impressive (about 6.3k req/sec on my chromebook).

arteymix commented 8 years ago

@chergert

Just a few remarks:

chergert commented 8 years ago

We can probably lower the dependencies just fine, I didn't use g_autoptr() for that reason.

As for throughput, it's hard to tell if 6.3k is impressive or not without a couple of other data points. For example:

I remember working on a libsoup web-server a few years ago and getting over 10k, so there might be some room for improvement (or not, hard to tell without more measurements).

Another place you might run into contention is the thread pool. I haven't looked to see if you're doing your own thread pool, but GIO's internal thread pool for I/O workers is rather small. Additionally, GThreadPool will contend on a single GAsyncQueue as you scale up the CPUs. This is why I wrote the IrisWSScheduler (work-stealing scheduler) a few years back. I'm not suggesting using it, its sort of out-dated, but it's about a linear 8x speedup over a traditional thread-pool when running at load.

Cheers!

arteymix commented 8 years ago

It's for relatively small messages (only a few bytes) served with SCGI under lighttpd. For that kind of load, it's around 1MB/s. I need to test different load types on a decent machine.

Soup does not perform that well however, but maybe it's not used optimally. I rather see it as a development/testing implementation.

Right now, it's single-threaded, but it might be interesting to dispatch in worker threads. I'll look into that, it would probably just be a thin rework in VSGI.Server. I'm not much into under-the-hood I/O and try to keep things simple.

I try to focus on features and correctness for now and complete the roadmap for the 0.3. Once there, Valum will be sufficiently complete to address real use cases and it will be adequate to do some plumbing!

That would be really nice, it's kind of inconvenient to pass a template loader and not use it to load the actual template.

This is something I try to address in Mustache-GLib by making node renders into a stream (i.e. https://github.com/valum-framework/mustache-glib/blob/master/src/mustache-template.vala#L29)

This, and the streaming parser never holds the whole AST in-memory: it consumes and render incrementally while keeping minimal information about the section stack.

It's still a work-in-progress though.

It's really nice to see projects intersect like that.

chergert commented 8 years ago

I'm not sure what the value is of unloading AST nodes from memory. How many templates will a project have? I'd venture a guess to say the normal use case would be to keep a parsed-template around and reuse it rather than constantly reparse them. It also allows, if someone cared to, JIT the expansion code (although I'm not signing up to work on another JIT) :)

As for writing out to streams, generally we want to coalesce writes to file-descriptors, so to get decent performance we'd use a memory stream (on top of X stream) anyway. We do buffer to a GString, which is almost the same thing (unless you are generating many-MB responses).

Another problem with writing directly to output streams (without a memory stream), is that if the client is slowly reading from the server, you can block the generation process while it blocks on write (unless your kernel buffers everything). If you do everything async, this can be okay'ish, but not really if you are holding on to network resources like a database connection, cache connection, etc during the template expansion.

arteymix commented 8 years ago

The problem with template is not the size of AST, but the size of tokens! Of course, that kind of use case are rarely occurring, but it's not impossible to have to deal with an heavy template, especially in bio-informatics ;)

This is why we have buffered streams: provide a stream API with bulk operations. But here again, if you have a heavy template and loops in it, be prepared!

An hybrid approach would probably be interesting: keep the AST in memory, but swap the text/heavy tokens with some cache algorithm (FIFO would work best). Implement a JIT upon that and you get the ultimate template engine.

It's not really appropriate to hold anything while doing I/O, not matter how it's done.

Basically, you want the capability to write directly: see Server-Sent Events for example. It's always possible to wrap the response body using FilteredResponse for more specific use case. There's some work on that in #117

chergert commented 8 years ago

FWIW, template-glib does not keep around the lexer tokens, only the AST nodes.

arteymix commented 8 years ago

Yes, but the terminals can hold text data. This is what I meant by tokens.

chergert commented 8 years ago

If you wanted ultra lightweight, just mmap() the template files. Then you can have a string pointer + length.

More realistically, we could put them into a GStringChunk so they are contiguous in memory. It's probably only a few pages of memory in most cases, and you save the fragmentation too.

arteymix commented 8 years ago

@chergert is there an equivalent for GBytes?

arteymix commented 8 years ago

Just found memory slices..