Home Java @teqfw/http2

@teqfw/http2

by admin

It took me a very long time to see JavaScript as a general-purpose programming language.Add falling " snowflakes " on a corporate web page before Christmas – that’s what JS was actually invented for, it seemed to me at first. I mean, what could be done seriously in a language that didn’t have the file system functionality, let alone access to the database?

But now, after two decades, I think JS has evolved enough to be the main programming language for building web applications. It now has everything to do with it. In fact, this article isn’t about how to use an HTTP/2 server in web applications, it’s about how you can write applications with modern JS. And about HTTP/2.

@teqfw/http2

Historical digression

When I first started in 1999, web applications were built with the assumption that JS could be disabled in the client browser. SSR ruled, although there was no such abbreviation yet (it came later when rendering on the client appeared).At that time among the top web developers were Java (it was shoved in the client applets and hoped that a little more and " real " the N.P. will displace from the browser " this misunderstanding "), ASP (from " corporations of good " of the time) and PHP (" poor, but fast ").And then there’s Thema Lebedev with his parser – This is for the totally geeks, which for a while included me too (luckily, I got out quickly). Even when the AJAX I didn’t see much promise in JS. Yes, JS had no alternatives on the client, but to use JS on the client was to conflict with the search engines. They ignored JS 100% at first.

Then there was jQuery, SPA , ExtJS and GWT I’ve only dealt with jQuery on an as-is basis, and GWT pretty extensively. JS was still not the language I wanted to write code in. Even when nodejs, TypeScript and ES2015 (ES6) came along I didn’t understand how JS could be used to build large modular applications, especially in a browser.

Since 2012, I’ve been sitting tight on Magento (written in PHP, but enterprise-style – batch-modular, with namespaces, IoC and other architectural patterns), but trying to work with JS from Magento was a headache (who had ui components Magento, the one in the know).

A brief switch to python was disappointing – the same PHP, only without namespaces and with compatibility problems between versions 2 and 3. First angular – too complicated despite the nice documentation. I didn’t even touch React – the idea of adding JS to HTML seemed acceptable to me, but HTML to JS ( JSX ) – too extravagant. PHP, ASP, JSP – everywhere the program code was added to the markup code, not the other way around. I was looking towards the more traditional Ember and Vue.

The turning point was the Magento conference in Riga in 2019, where I saw a demonstration of Vue Storefront It was already something akin to what quot was supposed to be; beautiful " web applications from my perspective. I dove into the PWA and saw that new browser capabilities are very much a game changer, shifting the functional emphasis from the back to the front – web applications " are being installed " into the client’s browser, operating in " mode; offline " and architecturally closer to desktop applications (there’s even its own IndexedDB). And on the front end, JS has no alternative. Or rather, all alternatives (including Java Applets , Macromedia Flash , Microsoft Silverlight , Google Dart ) so ine were able to push JS.

Three steps to happiness

However, the JS programming process was still a far cry from what I was used to during my time with Java and PHP. To begin with, I really missed being able to address any code element (class, function, constant). In Java/PHP packages and namespaces allowed you to uniquely identify elements (" Copy Reference " in IDEA), and JS either gave just the name of the method/class without reference to the file, or the name of the file and the line number in it. This was obviously not enough to support development in JIRA. The problem was resolved with JSDoc annotations and PHP Zend 1-style code element names ( Vnd_Prj_Mod_Name ).

The next stumbling block was the DI. You get used to a good thing, and DI is a very good thing. I used Spring Framework in Java and then DI in Magento 2. In Magento 2, the object manager didn’t just create objects along with dependencies, but allowed some objects to be substituted for others ( plugins ). I wanted to find something similar for JS, but it turned out that just a DI that would work both in the browser and in nodejs applications was problematic to find. I had to deal with the function import() and do DI yourself

The simplest but most recent is interfaces. There are no interfaces in JS. But there are in JSDoc. IDEA is very good at parsing JSDoc annotations and " sees " the relationship between the interface :

/** @interface */export default class TeqFw_Web_Front_Api_Gate_IAjaxLed {on() {}off() {}reset() {}}

and implementation :

/** @implements TeqFw_Web_Front_Api_Gate_IAjaxLed */export default class Fl32_Ap_Front_Rewrite_Web_Gate_AjaxLed {on() {console.log('AP app:Ajax LED On');}off() {console.log('AP app: Ajax LED Off');}reset() {console.log('AP app: Ajax LED Reset');}}

Given that I have my own DI-container, adding instructions on how to swap interfaces with their implementations is a matter of technique :

{"di": {"replace": [{"orig": "TeqFw_Web_Front_Api_Gate_IAjaxLed", "alter": "Fl32_Ap_Front_Rewrite_Web_Gate_AjaxLed", "area": "front"}]}}

Yes, this is the setting for the whole container, but if I need to, I can add settings for the individual es-module as well – because I have namespaces.

To sum it up: at the moment I am quite satisfied with JavaScript, and I can build up a toolkit for typical tasks in the field of progressive web-applications.

Descriptor plugin

Now, about how I apply all this. I’ll start with the descriptor /teqfw.json plugin @teqfw/http2 (about the descriptor – " Plugins "). It’s not too big, so I’m giving it in its entirety :

{"di": {"autoload": {"ns": "TeqFw_Http2", "path": "./src"}}, "core": {"commands": ["TeqFw_Http2_Back_Cli_Server_Start", "TeqFw_Http2_Back_Cli_Server_Stop"]}}

The descriptor defines the namespace for the es-modules of the plugin ( TeqFw_Http2 ), which uses a DI container. You can set up the DI-container in different ways. PSR-4 Bind the namespace prefix to a directory and use the logical name of the es-module to calculate the path to the file on disk (nodejs) or server (browser).

The second point – the logical names allow you to address the important from the point of view of the application code elements. The plugin descriptor specifies the modules that contain the application’s console command code (cf. @teqfw/core ).

Object Constructor

Now about how dependencies are injected. Here is the code for the HTTP/2 server :

export default class TeqFw_Http2_Back_Server {constructor(spec) {// EXTRACT DEPS/** @type {Function|TeqFw_Http2_Back_Server_Stream.action}*/const process = spec['TeqFw_Http2_Back_Server_Stream$'];/** @type {TeqFw_Web_Back_Handler_Registry}*/const registryHndl = spec['TeqFw_Web_Back_Handler_Registry$'];// MAIN FUNCTIONALITY...}}

Having the information from the descriptor ( /di/autoload ), you can determine the path to the file, where the sources are located, by the name of the class.

I have developed this (extended) form of constructor notation for myself, although I started with this :

export default class TeqFw_Http2_Back_Server {constructor({TeqFw_Http2_Back_Server_Stream$, TeqFw_Web_Back_Handler_Registry$}) {}}

The second form is more similar to the classic form of dependency injection through a constructor in Java:

@Componentpublic class Car {@Autowiredpublic Car(Engine engine, Transmission transmission) {}}

The first, more detailed, variant allows to add JSDoc annotations to variables (instructions for the IDE) and makes it clearer which data is actually injected. My DI-container is designed so that the same code can be injected into the constructor in different ways :

  • as es-module (I practically don’t use it, but maybe);
  • As a class/function;
  • as a singleton object (representative of a class or the result of a constructor function);
  • as a new instance of a class or a new result of a constructor function (used much less often than singletons);

Over time, you begin to discern by eye these variations :

const module = spec['Vnd_Prj_Mod'];const klass = spec['Vnd_Prj_Mod#'];const singleton = spec['Vnd_Prj_Mod$'];const instance = spec['Vnd_Prj_Mod$$'];const fn = spec['Vnd_Prj_Mod#fn'];

but such an entry helps the IDE, which then helps you :

/** @type {typeof Vnd_Prj_Mod} */const klass = spec['Vnd_Prj_Mod#'];/** @type {Vnd_Prj_Mod} */const singleton = spec['Vnd_Prj_Mod$'];/** @type {Function|Vnd_Prj_Mod.fn} */const fn = spec['Vnd_Prj_Mod#fn'];

In general, this approach allows us to move away from imports (which are tied to the file structure) and get dependencies not only at the level of code elements, but also at the level of application object instances.So the identifier of a singleton dependency is Vnd_Prj_Mod$ in any constructor corresponds to the same object, and it makes no difference which constructor this object was first created for.

The implication of this approach: all objects must be created through a DI-container or through a separate type of code elements – object factories (discussed in the article about DTO ). I very rarely use the operator new outside of factories in my applications – you never know how dependencies in constructors will change.

Commands

http2 -plugin adds two commands for starting and stopping the HTTP/2-server in addition to the HTTP/1-server start/stop commands from the web-plugin :

$ node ./bin/tequila.mjs help...Commands:http2-server-start [options] Start the HTTP/2 server.http2-server-stop Stop the HTTP/2 server.web-server-start [options] Start the HTTP/1 server.web-server-stop Stop the HTTP/1 server.

Integration of plugins

HTTP/1-server from web -plugin uses handlers to process requests, for which the interface defines TeqFw_Web_Back_Api_Request_IHandler , and the request context (with the interface TeqFw_Web_Back_Api_Request_IContext ). The HTTP/2 server just needs to be compatible with these interfaces. The main point is that http and http2 servers from the libraries nodejs provide different access to the headers and body of the request.

Here is the es-module hierarchy for the HTTP/1 server and its factory for creating request contexts :

/** @type {TeqFw_Web_Back_Api_Request_IContext.Factory} */const fContext = spec['TeqFw_Web_Back_Server_Request_Context#Factory$'];

For HTTP/2-server this is the module hierarchy and factory injection :

/** @type {TeqFw_Web_Back_Api_Request_IContext.Factory} */const fContext = spec['TeqFw_Http2_Back_Server_Request_Context#Factory$'];

That’s it – at this level in the application, one server is replaced by another completely unnoticed by the code of the other plugins in the application – from handlers and beyond.

All these " interfaces " and " implementations " are only needed as a signal to developers where in the code they have gaps between application units (like the clutch between the engine and the chassis in a car), using which they can safely substitute one part or another (such as "engine").

Interfaces are very widely used in statically typed languages, but in languages with " duck " typing, they are still used much less frequently. But it is while

Use of servers

HTTP/1-server can be accessed directly from the browser. Just start the server :

$ node ./bin/tequila.mjs web-server-start...2021/07/21 08:52:44 (info): HTTP/1 server is listening on port 3020. PID: 236742.

and open it in your browser : http://localhost:3000/

With HTTP/2-server is a bit more complicated – not all browsers directly use HTTP/2, mostly over TLS. In my applications I use proxy server (apache – I use it for a long time, got used to it), set HTTPS on it, and then make redirect to HTTP/2-server of the application itself:

RewriteEngine onRewriteRule "^/(.*)$" "h2c://localhost:3000/$1" [P]

In general, the HTTP/1 server can be used locally for development and HTTP/2 for production.

Demo

I changed the demo project from previous article ( habr_teqfw_web ) so that the application can be run on both HTTP/1 server and HTTP/2 server.

Second version of the app, deployed at http://ws.habr.demo.teqfw.com/ , runs on HTTP/1:

@teqfw/http2

A third version deployed at https://http2.habr.demo.teqfw.com/ , already by HTTP/2:

@teqfw/http2

Both nodejs -servers are behind the proxying apache and are directly accessible through ports 3001 (HTTP/1) and 3002 (HTTP/2) respectively. The pictures are just for illustration, because if you catch the first application through HTTPS, the browser to proxy will also use HTTP/2, and HTTP/1 will go from the proxy to nodejs -server.

The demo application contains only 8 dependencies in /node_modules/ and is nevertheless capable of supporting both protocols :

@teqfw/http2

Yes, express makes more, but it also has more dependencies (+48 to mine), and for now I’m satisfied with what I have. It’s my personal tool, not " the universal tablet for everything on the internet ".

Summary

JavaScript has evolved quite a bit (as has " habitat " web applications, and all IT in general) and continues to evolve ( HTTP/3 on the horizon ). Using the new features, you can achieve the same results with less effort. Or with the same costs to achieve much greater results. So it was, so it is and so it will be for some time. It is impossible to grasp the immensity, as Kozma Prutkov said, but it is possible to dig deep. Choose a topic – and dig, dig, dig…

I chose PWA and am digging. I wonder what else is out there…

You may also like