V jednom z minulých článkov som spomenul možnosť reťazenia servletov (servlet chaining) na úrovni predávania si textových informácií. Na druhej strane, servlety si môžu navzájom posielať nie len text, ale aj obrázky. Samozrejme nie len tak samoúčelne, ale často z dôvodu filtrovania či aplikovania rôznych grafických efektov a podobne. V tomto článku si ukážeme dva varianty, ako môžu servlety spolupracovať v oblasti spracovania grafiky.

Zatiaľ sme o reťazení servletov v súvislosti s grafikou ešte nehovorili. Ak si spomeniete, tak servlet v reťazi najprv získa požadovaný obsah na svojom vstupe, vykoná filtrovanie a následne pošle prefiltrovaný obsah ako svoj výstup. Väčšinou sa táto technika využíva na filtrovanie textových údajov, ako som už spomenul, ale nič vám nebráni využiť ju aj v súvislosti s obrázkami.

Aplikovanie špeciálneho efektu na obrázok sa vykoná v podstate rovnakým spôsobom bez ohľadu na to, či ide o servlet filter alebo štandardný servlet. Jediný rozdiel je v tom, že namiesto načítania obrázku zo súboru, alebo generovania vlastnej grafiky, sa dáta predstavujúce obrázok získajú ako zakódovaný prúd bajtov. Nasledujúci príklad predstavuje servlet zapojený do reťaze. Jeho úlohou je zmenšiť rozmery obrázku na polovicu.

ShrinkFilterServlet.java:

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import Acme.JPM.Encoders.*;
public class ShrinkFilterServlet extends HttpServlet {
  public void doGet
     (HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {
  ServletOutputStream out = res.getOutputStream();
  String contType = req.getContentType();
  if (contType == null || !contType.startsWith(„image“)) {
     throw new ServletException(„Chýba content type, musí byť \“image/*\““);
  }
  // získame dáta obsahujúce obrázok
  DataInputStream in = new DataInputStream( new BufferedInputStream( req.getInputStream()));
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  byte[] buf = new byte[4 * 1024]; // 4K buffer
  int len;
  while ((len = in.read(buf, 0, buf.length)) != -1) {
   baos.write(buf, 0, len);
  }
  // vytvoríme objekt image
  Image image = Toolkit.getDefaultToolkit() .createImage(baos.toByteArray());
  // vytvoríme ale nezobrazíme frame
  Frame frame = new Frame();
  // natiahneme obrázok a získame jeho výšku a šírku
  MediaTracker mt = new MediaTracker(frame);
  mt.addImage(image, 0);
  try {
   mt.waitForAll();
  }
  catch (InterruptedException e) {
   getServletContext().log(„Chyba pri načítaní obrázka.“, e);
   throw new ServletException(e.getMessage());
  }
  /* Zmenšíme rozmery obrázku na polovicu.
  Vylepšená verzia servletu by mohla získať
  požadovaný pomer strán z inicializačných parametrov.
  Ako alternatívu by sme mohli využiť triedu
  java.awt.image.AreaAveragingScaleFilter, alebo triedu
  java.awt.image.ReplicateScaleFilter. */

  Image shrunk = image.getScaledInstance (image.getWidth(frame) / 2, image.getHeight(frame) / 2, image.SCALE_DEFAULT);
  // zmenšený obrázok zakódujeme a pošleme
  res.setContentType(„image/gif“);
  GifEncoder encoder = new GifEncoder(shrunk, out);
  encoder.encode();
 }
}

Metóda createImage(byte[]) triedy Toolkit vytvorí objekt Image z dát uložených v poli bajtov. Táto metóda neakceptuje vstupný prúd bajtov, z toho dôvodu bolo nutné najprv vytvoriť objekt ByteArrayOutputStream. Metóda ďalej automaticky rozoznáva jeden z podporovaných formátov (GIF, JPEG, XBM). Po získaní obrázku je nutné zistiť jeho skutočné rozmery. Následne servlet použitím getScaledInstance(), vytvorí prefiltrovanú verziu obrázku, ktorá je o polovicu užšia a o polovicu nižšia. Nakoniec je objekt zakódovaný a poslaný klientovi.

Možno sa pýtate, načo vlastne použiť servlet filter na aplikovanie grafického efektu, namiesto štandardného servletu. Hlavným dôvodom je vyššia flexibilita. Napríklad môže byť povedané, že každý obrázok bude vrátený v zmenšenej podobe, ak požadované URI bude začínať napríklad na „/lite“. Alebo sa môže povedať, že všetky obrázky typu „image/xbm“, musia najprv prejsť špeciálnym filtrom na konverziu XBM na GIF.

Ak vás napadla možnosť, že by bolo vhodné na predávanie si obrázkov využiť serializáciu, tak chcem podotknúť, že obrázky, respektíve objekt Image, nie sú serializovateľné. Existuje iná pomerne efektívna metóda na prenos grafiky medzi servletmi, má však jeden háčik – servlet, ktorý túto techniku použije, musí mať istotu, že nasledujúci článok v reťazi je servlet a nie už klient. Konkrétne ide o využitie systémových Properties, ktoré môžu dramaticky zvýšiť výkonnosť popisovaných operácií.

Vyššie uvedená technika môže byť pomerne náročná na výkon hlavne pri väčších obrázkoch a vyššom zaťažení. Druhá alternatíva využívajúca Properties spočíva v tom, že prvý servlet uloží obrázok ako objekt Image do systémových Properties a následne pošle druhému servletu malý unikátny kľúč, odkazujúci na tento obrázok. Prostredníctvom tohto kľúča potom druhý servlet môže daný obrázok identifikovať. Nasledujúci príklad presne ukazuje, ako servlet vytvorí a priradí kľúč obrázku uloženému v Properties liste.

ChainImageSource.java:

import java.awt.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ChainImageSource extends HttpServlet {
 int keynum = 0; // použijeme na vytvorenie unikátneho kľúča
 public void doGet
     (HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {
  // získame objekt Image
  String imageFile = getServletContext() .getRealPath(„/images/duke.gif“);
  Image image = Toolkit.getDefaultToolkit() .getImage(imageFile);
  // vytvoríme unikátny kľúč,
  String key = „ChainImageSource.“ + keynum++;
  // pod ktorým obrázok uložíme do Properties
  System.getProperties().put(key, image);
  // nastavíme vlastný content type
  res.setContentType(„java/image-key“);
  PrintWriter out = res.getWriter();
  // kľúč pošleme
  out.println(key);
 }
}

Všimnite si, ako servlet vytvorí unikátny kľúč. Ako prefix použije svoje meno (pričom je vhodné pridať ešte aj celý názov balíčka, do ktorého servlet patrí), vďaka čomu si zabezpečíme unikátnosť nášho kľúča. Potom k prefixu pridá celé číslo, ktoré je pre každý nový obrázok navýšené o jednotku. Ako content type použijeme vlastný špeciálny typ (java/image-key), aby sme rozlíšili situáciu, kedy posielame iba odkaz na obrázok a nie obrázok samotný. Nasledujúci príklad zobrazuje druhú časť reťaze, teda servlet, ktorý použije kľúč na získanie skutočného objektu Image.

ChainImageDest.java:

import java.awt.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ChainImageDest extends HttpServlet {
 public void doGet
     (HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {
  // zistíme prijatý content type
  String contentType = req.getContentType();
  Image image = null;
  /* Ak ide o content type začínajúci na „image/*“ znamená to,
  že obdržíme obrázok ako prúd zakódovaných bajtov. */

  if (contentType != null && contentType.startsWith(„image“)) {
   /* Do tohto bloku môžeme neskôr implementovať funkčnosť,
   ktorú sme si ukázali vyššie v príklade ShrinkFilterServlet.java */

  }
  // „java/image-key“ content type znamená, že získame kľúč
  else if („java/image-key“.equals(contentType)) {
   // prečítame prvý riadok získaného obsahu, čím dostaneme kľúč
   String key = req.getReader().readLine();
   // získame obrázok uložený pod daným kľúčom
   image = (Image)System.getProperties().get(key);
   // obrázok uvoľníme z pamäte
   System.getProperties().remove(key);
  }
  else {
   throw new ServletException(„Content type musí byť ‚image/*‘ alebo ‚java/image-key'“);
  }
  /* Servlet môžeme pre jednoduchosť zakončiť týmto spôsobom.
  V reálnom nasadení by sme objekt Image vhodným spôsobom využili. */

  res.setContentType(„text/plain“);
  PrintWriter out = res.getWriter();
  out.println(„Získaný obrázok je: “ + image);
 }
}

Vyššie popísaná technika na zdieľanie informácií využíva zoznam systémových properties, ktoré nájdete v triede java.lang.System. Tento zoznam obsahuje štandardné systémové informácie, ako je java.version alebo path.separator. Môže však uchovávať nielen systémové, ale aj aplikačné vlastnosti. Pri triede Properties sa predpokladá, že kľúč aj hodnota sú String. Toto obmedzenie však môže byť ignorované (aj keď sa to pokladá za hack) čiastočne aj preto, pretože trieda Properties je odvodená od triedy Hashtable. Toto môže samozrejme spôsobiť výnimku pri behu programu (ClassCastException), kedy metódy getProperty(), list(), save() prirodzene predpokladajú, že kľúč aj hodnota sú typu String.

Na záver chcem upozorniť, že informácie uložené v Properties nie sú perzistentné medzi jednotlivými reštartmi servera, ďalej, že informácie môžu byť ľubovolne zmenené alebo zmazané akoukoľvek triedou bežiacou na danej JVM, a nakoniec, že niektoré bezpečnostné prvky servera nemusia umožniť servletu prístup k systémovým properties.

Na záver si môžete stiahnuť všetky použité súbory.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět