v8cgi: JavaScript na serveru

JavaScript je dnes prakticky hlavním jazykem, který je používán při psaní skriptů pro klientskou část webových aplikací. Je pochopitelné, že existují snahy použít stejný jazyk i pro serverové skripty. Dostupnost otevřeného JS engine V8 jim dodala nový impuls – podívejme se na jednu z možných implementací.

Koncem roku 2008 spatřil světlo světa nový webový browser, Google Chrome. Zatímco jeho renderovací jádro bylo prostě převzato z projektu WebKit, ostatní části prohlížeče byly napsány od základů. Mimo jiné i interpret JavaScriptu, známý jako V8. Jeho vysoký výkon je jednou z hlavních deviz Chrome; není proto s podivem, že se autoři V8 rozhodli pro svůj počin vytvořit komplexní veřejné API, pomocí kterého je možno interpret vložit do libovolné C++ aplikace. Podívejme se, jak výsledek takového propojení může vypadat.

Varování 1: v tomto článku je zhusta používán termín JavaScript, přestože V8 striktně vzato podporuje kompletně jen ECMAScript, 3. vydání. Řadovému kolemjdoucímu však tyto pojmy (docela pochopitelně) splývají, zůstaneme proto u známějšího náz­vu.

Varování 2: tento článek ukazuje neotřelé a nezvyklé postupy. Čtěte jej jen, pokud můžete kladně odpovědět alespoň na jeden z těchto bodů:

  • nebojíte se technologií pocházejících od Google
  • přijde vám jako dobrý nápad používat stejný jazyk na klientu i serveru
  • je vám sympatická syntaxe a expresivita JavaScriptu
  • nevěříte, že vše dobré již bylo vynalezeno a staré postupy se nesmí měnit

Stahujeme, kompilujeme, nastavujeme

Projekt v8cgi si klade za cíl vytvořit jednoduchý obal okolo interpretu V8 a poskytnout vazby na volání systémových funkcí, spolupráci většího množství kódu a používání standardních i doplňkových knihoven. Jistý důraz je také kladen na zpracovávání HTTP požadavků, ať již formou CGI, FastCGI nebo modulu do webserveru Apache. Kód v8cgi zdaleka nedosahuje produkční kvality, ale je v současné době stabilní a nabízí použivatelné množství funkcí a knihoven.

Pokud si chcete v8cgi vyzkoušet, musíte učinit tyto kroky:

  1. Zprovoznit konstrukční systém SCons
  2. Stáhnout a zkompilovat V8
  3. Stáhnout a zkompilovat v8cgi
  4. Upravit konfiguraci v8cgi

Pro uživatele Windows je k dispozici binární distribuce v8cgi, díky které je možno přeskočit první tři kroky. Protože cílem tohoto článku není kompletní návod k instalaci, ukažme jednotlivé body jen ve zkratce. Pro zkušené linuxové uživatele by neměly představovat zásadní problém.

SCons

SCons je poměrně jednoduchý nástroj, vytvořený jako alternativa ke známému kombu make + autoconf. Je napsaný v Pythonu a každá rozumná distribuce nabízí jeho balík ( apt-get
install scons
). SCons řídí proces kompilace zdrojových kódů, stará se o multiplatformní předávání parametrů překladači a linkeru, nabízí ovlivňování překladu pomocí parametrů.

V8

Samotný interpret V8 je představován jedinou knihovnou. Zdrojový kód buďto získáme pomocí Subversion ( svn checkout http://v8.googlecode.com/svn/trunk/ v8), nebo použijeme distribuci V8, která je součástí oficiálního balíku v8cgi. Po rozbalení v adresáři s V8 stačí spustit scons library=shared (bez parametru by byla zkompilována statická knihovna). Po chvíli překládání vznikne libv8.so – kýžený interpret V8.

v8cgi

K dispozici je opět buď checkout ( http://v8cgi.googlecode.com/svn/trunk/), nebo některý ze zdrojových balíků. O překlad se zase stará SCons; ukažme příklad použití:

scons module=0 gd=0 mysql=0 config_file=/tmp/v8cgi.conf

Jednotlivé parametry říkají, že nebudeme chtít překládat modul do Apache, že nechceme podporu pro knihovny GD či MySQL a že náš konfigurační soubor bude umístěn na zadané cestě. Pro výpis a vysvětlení všech takových parametrů lze napsat scons -h; všechny parametry jsou volitelné a mají výchozí hodnotu.
Cesta ke konfiguračnímu souboru je sice specifikována během překladu, při volání v8cgi lze ale tuto hodnotu přebít jinou.

Konfigurace

Zbývá poslední a nejjednodušší bod: zkopírujeme distribuční konfigurační soubor ( v8cgi.conf.posix) na místo, kde ho bude aplikace očekávat (hodnota parametru config_file) a trochu ho poupravíme. Jediná nutná změna je řádek obsahující require.paths.push  – parametrem je cesta do adresáře s jednotlivými knihovnami/moduly v8cgi. Pokud tedy experimentujeme třeba v ~/v8cgi, je chtěná cesta  /home/username/v8cgi/lib.

A jdeme na to

V nejjednodušším případě se v8cgi používá jako běžná aplikace na příkazové řádce:

./v8cgi examples/shell.js

Parametr -c určuje vlastní cestu ke konfiguračnímu souboru:

./v8cgi -c /tmp/v8cgi.conf examples/shell.js

Obě tyto ukázky pustí jednoduchý JavaScriptový shell. S ním bychom se ale moc daleko nedostali, pojďme se proto podívat, jak psát zdrojové kódy pro v8cgi.

Skripty na straně serveru

Zatímco v prostředí webového prohlížeče je k dispozici globální objekt window, proměnná document a řada funkcí a objektů od DOMu, na serveru nic takového nenajdeme. v8cgi poskytuje samo o sobě jen velmi málo globálních prvků (jsou to zejména funkce require a include a dále objekty system a v8cgi), takže bez využití dalších knihoven naše skripty nebudou nijak přepychové. Mohou vypadat třeba takto:

#!v8cgi
var inn = system.args[1];
var out = 1;
while (inn) { out *= inn--; }
system.stdout("Faktorial cisla "+system.args[1]+" je "+out+"n");

Skript lze nyní pouštět buď jako parametr v8cgi, nebo jako samostatně vykonatelný soubor (pokud mu dáme chmod +x).

require – do hry vstupuje CommonJS

Vývoj a psaní JavaScriptu na klientu nejsou nijak organizovány, a situace je proto dle očekávání chaotická. Poměrně mladá organizace OpenAjax Alliance se snaží tendence na tomto poli standardizovat, směrovat a kontrolovat; úspěchy těchto snah jsou ale diskutabilní a ne příliš viditelné. Aby se předešlo podobné situaci u JavaScriptu na serveru, vznikla neformální skupina CommonJS. Jejím cílem je poskytnout nutné standardy dříve, než dojde k roztříštění a implementaci mnoha různých nekompatibilních řešení. CommonJS je tvořena mimo jiné autory necelé desítky projektů podobných v8cgi, jejich uživateli a několika členy TC39.

Během své existence (která netrvá déle než rok a půl) vyprodukovalo CommonJS několik standardů a velké množství návrhů. V současné době probíhá finalizace binárního datového typu – něco, co JavaScriptu na serveru chybí jako sůl. My se zde ale zaměříme na jiný CommonJS standard – na funkci require() a filozofii psaní modulů. Jedna ukázka zdrojového kódu řekne víc než tisíc slov:

/* modul.js */
var nejakaKonstanta = 123456;
var Trida = function() {};
Trida.prototype.metoda = function(a) { system.stdout(a); };
exports.nejakaKonstanta = nejakaKonstanta;
exports.Trida = Trida;
/* main.js */
var modul = require("modul");
var inst = new modul.Trida();
inst.metoda(module.nejakaKonstanta);

Soubor main.js je zde ten, který vykonáváme pomocí v8cgi. Je vidět použití funkce require, která načte cílový modul a vykoná jej. Vrátí objekt zvaný exports, do které modul nadefinoval funkcionalitu, jež chce poskytovat. Přihodím ještě několik informací o  require:

  • V každém modulu je k dispozici objekt exports pro definování výstupu modulu a funkce require pro přístup k dalším modulům
  • Modul je vyhodnocen ve vlastním scope, takže jeho proměnné nejsou odnikud vidět
  • Názvy modulů se zadávají bez přípony (je tak možno používat i binární moduly – takové, které bychom vyrobili třeba pomocí  scons
    mysql=1 gd=1
    )
  • Názvy modulů začínají buďto tečkou (pak jsou relativní vůči právě zpracovávanému modulu), lomítkem (pak jsou absolutní), nebo čímkoliv jiným (pak jsou relativní vůči adresáři s moduly, viz konfigurace)
  • Moduly, resp. jejich objekty exports, se cachují; opakované použití modulu jej nevykoná znovu

v8cgi nabízí jisté množství modulů, některé jsou JavaScriptové a jiné binární. Binární moduly se musí kompilovat, děje se tak opět pomocí SCons. Jejich kompletní výčet i dokumentace jsou k dispozici na stránkách projektu.

Další ukázky

#!v8cgi
/* rozrezani velkeho obrazku na male ctverecky */
var GD = require("gd");
var tilex = 32;
var tiley = 32;
var img = new GD.Image(Image.PNG, "obrazek.png");
var w = img.sx();
var h = img.sy();
var x = Math.floor(w / tilex);
var y = Math.floor(h / tiley);
for (var i=0;i<x;i++) {
    for (var j=0;j<y;j++) {
        var small = new GD.Image(GD.Image.TRUECOLOR, tilex, tiley);
        small.copy(img, 0, 0, i*tilex, j*tiley, tilex, tiley);
        var parts = name.split(".");
        parts[0] += "_"+j+"_"+i;
        small.save(GD.Image.PNG, "small_"+j+"_"+i+".png");
    }
}
#!v8cgi
/* trivialni HTTP klient + zapis do souboru */
var Socket = require("socket").Socket;
var ip = Socket.getAddrInfo("www.seznam.cz");
var s = new Socket(Socket.PF_INET, Socket.SOCK_STREAM, Socket.IPPROTO_TCP);
s.connect(ip, 80);
s.send("GET / HTTP/1.0rnrn");
var received = "";
do {
    var part = s.receive(1024);
    received += part;
} while (part.length > 0);
s.close();
var out = new File("/tmp/data").open("w");
out.write(received);
out.close();
system.stdout(received);

HTTP: CGI a Apache

Webový server Apache lze nakonfigurovat tak, aby požadavky nechával zpracovat právě pomocí v8cgi. Komunikace může být realizována buď pomocí CGI, nebo jako modul do webserveru.

CGI

Našim skriptům dáme nějakou specifickou příponu a uděláme z nich spustitelné soubory. Pak stačí do konfigurace Apache přidat:

# konfigurace Apache pro CGI: predpokladame, ze soubor .ssjs bude spustitelny
AddHandler cgi-script .ssjs

Modul do Apache

Toto řešení je výkonnější, ale také o něco složitější. Nejprve je nutné zkompilovat v8cgi modul:

scons module=1

A dále upravit konfiguraci webového serveru:

LoadModule v8cgi_module /home/uzivatel/v8cgi/v8cgi_module.so
AddHandler v8cgi-script .ssjs

Všechny požadavky na soubory s příponou .ssjs bude nyní obhospodařovat v8cgi.

Modul http.js

Na konec článku se ještě seznámíme s modulem http.js. Ten předpokládá, že v8cgi používáme pro zpracování HTTP požadavků, třeba tak, jako PHP. Má tedy smysl ho používat jen v kombinaci s webovým serverem, jak bylo naznačeno v předchozích odstavcích.

Při použití modulu http.js vzniknou dvě globální proměnné, request a response. Ta první slouží pro získávání dat z požadavku; druhá poskytuje metody pro tvorbu odpovědi. Kompletní dokumentace je opět k dispozici na webu v8cgi, takže přejdeme rovnou k ukázkám.

#!v8cgi
/* nejjednodussi ukazka HTTP odpovedi */
response.write("<strong>funguje to!</strong>");
#!v8cgi
/* nazvy a hodnoty vstupnich POST parametru */
for (var p in request.post) {
    response.write(p + ": " + request.post[p] + "<br/>");
}
#!v8cgi
/* vypis zaznamu z databaze */
var MySQL = require("mysql").MySQL;
var db = new MySQL().connect("host", "user", "pass", "db");
var id = parseInt(request.get["id"]) || 0;
var result = db.query("SELECT * FROM tabulka WHERE id=" + id);
var row = result.fetchObjects()[0];
var str = JSON.stringify(row);
response.write(str);

A dál

Směr dalšího vývoje v8cgi je jasný. Jakmile dojde k finální standardizaci binárního datového typu, otevírají se dveře knihovnám pro komplexnější přístup k souborovému systému, práci s knihovnou zlib a také tvorbu nějakého webového frameworku. Co bude potom už záleží jen na uživatelích, jejich přáních, feature requestech a bugreportech…

Autor pracuje ve společnosti Seznam na všem, co alespoň trochu souvisí s JavaScriptem. Ve volném čase se mimo jiné zabývá věcmi, které alespoň trochu souvisí s JavaScriptem. O obojím občas tweetuje jako @0ndras.

Komentáře: 18

Přehled komentářů

Jan Grmela Palm to říkal
blizzboz Re: Palm to říkal
Murdej Re: Palm to říkal
pas Re: Palm to říkal
rooobertek lama
myd Re: lama
Jiří Kosek Javascript na serveru není novinka
mol Re: Javascript na serveru není novinka
HUgo Mno...
Aichi Re: Mno...
Glin Re: v8cgi: JavaScript na serveru
... Re: v8cgi: JavaScript na serveru
lopata Re: v8cgi: JavaScript na serveru
x Python je o nekolik levelu lepsi
pimpo Re: Python je o nekolik levelu lepsi
Martin Malý Re: Python je o nekolik levelu lepsi
valafan Budoucnost má taky Vala
_ Re: v8cgi: JavaScript na serveru
Zdroj: https://www.zdrojak.cz/?p=3237