Hlavní navigace

Ruby v příkladech (6) - Webová aplikace

Pavel Sýkora

V dnešním dílu se podíváme, co nám Ruby nabízí pro tvorbu webových a distribuovaných aplikací. Povíme si i o tom, jak v Ruby naprogramovat HTTP proxy server.

Webová aplikace

Základní knihovna Ruby není skoupá ani na prostředky pro tvorbu webových aplikací. Obsahuje webový server WEBrick, který sice asi není vhodný na produkční nasazení v prostředí s vysokou zátěží, nicméně na vývoj, ladění nebo rychlé prototypování aplikace postačí. Protože WEBrick umožňuje nejen zasílání statických objektů, ale je v něm možné programovat i tzv. servlety, zkusíme naši aplikaci „Jmeniny“ postavit jako webovou právě s jeho pomocí. Aplikace bude fungovat tak, že po zadání příslušného URL se zobrazí tento formulář:

Ruby - web1

Po zadání jména a odeslání se zobrazí výsledek jako text:

Ruby - web2

Protože webové prohlížeče bežně zvládají kódování UTF-8, nemusíme si tentokrát lámat hlavu s výběrem kódování. Stačí jen v hlavičce odpovědi HTTP naznačit, že vše zasíláme v UTF-8 (viz nastavení hlavičky Content-Type v následujícím kódu na řádcích 7 a 12). Pro formulář i výsledek používáme stejné URL. Jak ale WEBrick pozná, zda má poslat HTML kód formuláře, nebo text výsledku? Jednoduše. Zadáním a odesláním URL v prohlížeči se generuje požadavek HTTP GET, kdežto odeslání formuláře generuje požadavek HTTP POST. Každý typ požadavku má v našem kódu svou vlastní obslužnou metodu (HTTP GET na řádcích 6–9, HTTP POST na 11–16). Kód aplikace (tedy jen její serverové části) vypadá takto:

 1:  require 'webrick'
 2:  require 'jmeniny'
 3:
 4:  class JmeninyServlet < WEBrick::HTTPServlet::AbstractServlet
 5:
 6:    def do_GET(req, resp)
 7:      resp['Content-Type'] = "text/html; charset=utf-8"
 8:      resp.body = DATA.read
 9:    end

10:
11:    def do_POST(req, resp)
12:      resp['Content-Type'] = "text/plain; charset=utf-8"
13:      jmeno = req.query['jmeno']
14:      den, mesic = Jmeniny.den(jmeno)
15:      resp.body = "#{jmeno} má svátek #{den}.#{mesic}."
16:    end
17:
18:  end
19:
20:  s = WEBrick::HTTPServer.new( :Port => 3000 )
21:  s.mount("/jmeniny", JmeninyServlet)
22:
23:  trap('INT') do
24:    s.shutdown
25:  end

26:
27:  s.start
28:
29:  __END__
30:
31:  <html>
32:    <head>
33:      <title>Jmeniny</title>
34:    </head>
35:    <body>

36:      <form method="post">
37:        Jméno:
38:        <input type="text" name="jmeno" />
39:        <br />
40:        <input type="submit" value="Submit" />
41:      </form>
42:    </body>

43:  </html> 

Obsluhu požadavku zajišťuje třída JmeninyServlet (řádky 4–18), která je potomkem třídyWEBrick::HTTPServlet::AbstractServlet a obsahuje metody do_GET a do_POST pro požadavky GET a POST. Metody mají vždy dva parametry – první je typu WEBrick::HTTPRequest , druhý WEBrick::HTTPResponse . Dokumentace WEBRicku ve standardní knihovně není zrovna úchvatná, takže občas nezbude než se kouknout přímo do zdrojového kódu (naštěstí je docela přehledný) nebo hledat jinde na Internetu. Například WEBrick Articles a Gnome's Guide to WEBrick rozhodně stojí za přečtení, zváště pokud se budete pokoušet rozchodit s WEBrickem HTTPS, autentizace a podobné náročnější věci. Ale zpět k naší aplikaci.

Aplikace na požadavek HTTP GET zasílá HTML kód formuláře. HTML je na konci souboru (řádky 31–43) za značkou __END__ (řádek 29). Tato značka odděluje zdrojový kód aplikace od dat ve stejném souboru. Data jsou z kódu přístupná přes pseudoproměnnou DATA (řádek 8), která se chová jako objekt typu File .

Řádky 20 až 27 obsahují instancování a spuštění serveru na portu 3000, řádky 23 až 25 zajišťují korektní ukončení běhu serveru na SIGINT (tj. Ctrl+C).

Aplikace jako HTTP Proxy Server

WEBrick umožňuje s malým úsilím naprogramovat i proxy server. Tedy server, kterému zašleme požadavek HTTP s nějakým URL, on jej zašle dál, přijme odpověď, nějak ji zpracuje (nebo také ne) a pošle prohlížeči. Naše aplikace bude bude pracovat tak, že v komprimovaných objektech typu text/html s kódováním Latin-2 vyhledá první značku začátku odstavce ( <p>). Před tuto značku pak vloží odstavec s obsahem: „Svátek má X“, kde X je jméno pro aktuální den. Kód vypadá takto:

 1:  require 'webrick'
 2:  require 'webrick/httpproxy'
 3:  require 'stringio'
 4:  require 'zlib'
 5:  require 'iconv'
 6:  require 'date'
 7:  require 'jmeniny'
 8:
 9:  handler = Proc.new() { |req, res|
10:    if res.header['content-encoding'] == 'gzip' &&

11:          res.header['content-type'] =~ /html/ &&
12:          res.header['content-type'] =~ /iso-8859-2/
13:      res.header.delete('content-encoding')
14:      res.body = Zlib::GzipReader.new(StringIO.new(res.body)).read
15:      today = Date.today
16:      jmeno = Jmeniny.jmeno(today.mday, today.month)
17:      text = Iconv.iconv('iso-8859-2', 'utf-8', "Svátek má #{jmeno}")
18:      res.body.sub!(/<p>/, "<p>#{text}</p><p>") if res.body
19:    end
20:  }
21:
22:  s = WEBrick::HTTPProxyServer.new(
23:    :Port => 3128,
24:    :ProxyContentHandler => handler
25:  )
26:
27:  trap('INT') do

28:    s.shutdown
29:  end
30:
31:  s.start 

Proxy server bude běžet na portu 3128, obslužná routina (řádky 9–20) bude měnit jen objekty výše uvedeného typu.

Pro správnou funkci je potřeba nakonfigurovat v prohlížeči proxy pro HTTP ručně, například ve Firefoxu v panelu Nastavení / Možnosti / Obecné / Nastavení připojení takto:

Ruby - proxy config

Pokud si zobrazíme např. stránky Roota přes proxy, může výsledek vypadat takto:

Ruby - proxy Root

Všimněte si prvního odstavce pod perexem. Naše proxy tam přidala odstavec se jménem aktuálního kalendářního dne.

Funkce této proxy byla velmi zjednodušená. Ve skutečnosti by bylo potřeba lépe vyřešit zpracování komprimovaných i nekomprimovaných objektů, různá kódování i to, že začátek odstavce nemusí vždy vypadat „ <p>“. Pokud budete chtít více experimentovat s WEBrickem jako proxy, podívejte se na MouseHole Proxy. Je to proxy server, kde si můžete pro určité stránky napsat (nebo stáhnout) uživatelský skript pro manipulaci s obsahem. Je to trochu podobné – či spíše výsledek se podobá Greasemonkey(což je ale rozšíření prohlížeče, nikoliv proxy server).

Distribuovaná aplikace

Standardní knihovna Ruby obsahuje i prostředky pro jednoduchou tvorbu distribuovaných aplikací. (DRb) A jak by vypadaly takové distribuované jmeniny?

require 'drb'
require 'jmeniny'


class JmeninyServer
  def den(jmeno)
    Jmeniny.den(jmeno)
  end
end

DRb.start_service "druby://localhost:9876", JmeninyServer.new
DRb.thread.join 

Výše uvedený kód serveru je trochu komplikován tím, že jsme museli vytvořit jednoduchý adaptér jako třídu. Kdyby jmeniny.rb byla od začátku třída (a ne modul – ten se nedá instancovat), byl by server ještě kratší. Nicméně ani tak se mi to nezdá příliš složité. Klient je jednodušší:

require 'drb'

DRb.start_service
jmeniny = DRbObject.new(nil, "druby://localhost:9876")

puts jmeniny.den("Roman") 

DRb lze jednoduše použít např. pro ovládání webových aplikací v době běhu. Další informace a příklady o použití DRb najdete například na stránkách o DRb, které napsal Eric R. Hodel.

Další možnosti

Webové aplikace určené pro náročnější prostředí lze provozovat i pod Apachem, Lighttpd a dalšími HTTP servery. Pak je ale velmi vhodné použít FastCGI (FCGI pro Ruby), SCGI (implementace­zatím jen pro Rails) nebo (v případě Apache) modul mod_ruby.

Našli jste v článku chybu?

20. 10. 2005 13:26

Mám pro Vás dobrou zprávu: o Ruby on Rails budeme brzy vydávat zajímavý seriál.

11. 2. 2009 15:32

Pavel Sýkora (neregistrovaný)
Po precteni clanku mi bylo zcela jasne, ze to nemuze fungovat i pres mou mizivou znalost problematiky.

Pán je zřejmě jasnovidec. Navzdory tomu by ale aplikace běžet měla. Zjevně vám k tomu chybí soubory jmeniny.rb a jmeniny.yaml z druhého dílu. :-)

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

Vitalia.cz: Vychytané vály a válečky na vánoční cukroví

Vychytané vály a válečky na vánoční cukroví

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Měšec.cz: Komu musí od ledna zvýšit mzdu?

Komu musí od ledna zvýšit mzdu?

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Měšec.cz: Stavební spoření: alternativa i pro seniory

Stavební spoření: alternativa i pro seniory

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

Podnikatel.cz: Udávání kvůli EET začalo

Udávání kvůli EET začalo

DigiZone.cz: Flix TV: dva set-top boxy za korunu

Flix TV: dva set-top boxy za korunu

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...