Zdravím všechny,

mám tu jeden takový problémek se servlet URL mapping a už ani googlík mi nepomáhá.

Mám webovou aplikaci, používám Tomcat 7.x a Servlet 3.x, ale potencionálně se nechcina Tomcat vázat, takže bych uvítal spíše řešení využívající jen standardních konfigurací JEE descriptorů, než zásahy do Tomcatích konfiguráků.

Struktura aplikace je asi relativně standardní:

/images
/jsp
/WEB-INF/web.xml
...


Problém spočívá v nastavení URL mappingu, potřebuji dosáhnout následujícího chování:

http://server/mycontext - na tuto adresu se spusti mnou nastavený servlet, musí to být konkrétně tato adresa a není možné ani žádné přesměrování přes klienta (on ignoruje veškeré 302 a jiné HTTP kódy). Prostě s klientem nic neudělám, má to zadrátované v sobě a není pod mou kontrolou. Navíc, jak ukazuje následující příklad, chování je takové, že pokud je request z Web-browseru, tak se jde na standardní GUI aplikace, jinak se jde na obsluhu příslušného protokolu, kterým komunikuje ten zadrátovaný klient.

A ted co už jsem zkusil:

Varianta 1)

@WebServlet(urlPatterns = { "/*" })
public class FrontController extends HttpServlet{
 protected void service(HttpServletRequest req, HttpServletResponse resp){
if(fromBrowser()){ req.getRequestDispatcher("/jsp/my-page.jsp").forward(req,resp);
  }else{
   req.getRequestDispatcher("/protocol-servlet").forward(req,resp);
  }
 }
}

Na tento mapping "/*" mi to funguje skoro dokonale, bohužel jakýkoliv forward na straně serveru na JSP stránku způsobí zacyklení, protože tento pattern samozřejmě pokrývá i adresy /mycontext/*.jsp

Zkusil jsem i

urlPatterns = { "/" }  //bez hvězdičky

Ale pak Tomcat při Requestu na http://server/mycontext odpoví s response 302 aby klient udělal redisrect na http://server/mycontext/ - tedy přidá lomítko. HTTP-302 však ten zadrátovaný klient, který jde jen na adresu server/mycontext neumí provést. A jediné, co se dá v tom klientu konfigurovat je host-name serveru, kam se pripojuje, tedy nemohu ovlivnit /path, ta je natvrdo.


Varianta 2)
Další varianta byla použít <welcome-file>  s mapováním na servlet:

@WebServlet(urlPatterns = {*  "/controller"*  })
public class FrontController extends HttpServlet{

a ve web.xml

  <welcome-file-list>
    <welcome-file>*controller*</welcome-file>
  </welcome-file-list>


Toto funguje dokonale, až na jednu drobnost - pokud vznesu request na http://localhost/mycontext tak tomcat pošle nejdřív HTTP-302 redirect,aby klient přešel na adresu http://localhost/mycontext/ tedy přidá to lomítko na konci. Lomítko by mi nevadilo, ale ten 302 redirect přes klienta je problém, protože to funguje jen v Browseru, ale ten zadrátovaný klient ho neprovede a vyhodí error protokolu. Někde jsem četl, že snad je to takto podle servlet specifikace (že se to takto má chovat, pokud ten název souboru nemá příponu), ale specifikaci jsem ještě podrobně nečetl, jestli to tak musí být a nebo to je jen vychytávka Tomcatu.

Varianta 3)

Pak jsem ještě zkoušel pozměněnou varinatu 1), dočetl jsem se, že z pohledu URL mappingu by měl nejdřív server brát specifické patterny a až pak ty obecné a tak jsem narazil na doporučení do web.xml namapovat natvrdo JSP příponu, tím by se na ni měl spustit standardní procesing a nepouštět ten můj obecný FrontController servlet.

  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
  </servlet-mapping>

Problém je, že JSP servlet nemám ve web.xml definován a tak to nefunguje (tomcat odmítl nastartovat tuto webaplikaci). konkrétní servlet tam ale nemohu dát, protože ten je na každém serveru asi jiný, takže dávat tam tomcatí JSP servlet natvrdo je asi nesmysl. Nebo jsem něco přehlédl a existuje ve standardní JSP specifikaci nějaký konkrétní servlet, který se tam dá dát a řeší kompilaci a spuštění JSP ? Vím že bych tam mohl předkompilovat JSP a vyjmenovat tam všechny servlety vygenerované z JSP, ale to je trochu špatně udržovatelné řešení.

Asi jsem tam měl nějaký překlep, protože teď, když to zkouším znovu, tak už to nekřičí, že jsp neexistuje, ale každopádně to nefunguje - zdá se, že *.jsp mu připadá mnohem obecnější než /* což nechápu, prostě se to JSP nespustí a cyklí to úplně stejně jako ve varinatě 1)-

Napadlo mě ještě udělat

<url-pattern>/jsp/*.jsp</url-pattern>

že by to bylo přesnější a mohl by tak před /* preferovat to /jsp/*.jsp ale toto není validní URL mapping podle specifikace - nelze kombinovat přípony s cestami.


Varianta 4)
Použít ServletFilter jako FrontController.

@WebFilter(filterName = "FrontControllerFilter", urlPatterns = { "/*" })
public class FrontControllerFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
    final HttpServletRequest req = (HttpServletRequest) request;
    final HttpServletResponse resp = (HttpServletResponse) response;

    if (req.getAttribute("forwarded") != null) {
      chain.doFilter(req, resp);
    }

    RequestDispatcher disp = null;
    if (!fromBrowser()) {
      disp = req.getRequestDispatcher("/protocol-servlet");
    } else { // from browser
      disp = req.getRequestDispatcher("/jsp/my-page.jsp");
    }
    req.setAttribute("forwarded", true);
    disp.forward(req, resp);
  }
}

Problém je, že tento filter se nespustí, pokud nenadefinuji ještě nějaký defaultní servlet, takže nakonec jsem musel zadefinovat stejný servlet jako ve variantě 1)

@WebServlet(urlPatterns = { "/*" })
public class FrontController extends HttpServlet{
....
}


Pak se filter spustí, ale je k ničemu, protože ten servlet následně obsluhuje veškeré požadavky a tak jsem na stejném jako ve variantě 1), tedy nekonečný cyklus.

Varianta 5)
Dát před Tomcat třeba Apache HTTP server a udělat Alias na tu URL adresu, jak potřebuji. Ale to je server navíc, už to komplikuje nasazení, preferoval jsem jednoduchý WAR, který dám kamkoliv a bude vyřešeno.

Varianta 6) rozdělit to na 2 samostatné web-moduly - protokol bude řešen jinde než gui aplikace, ale to je zase komplikace z pohledu údržby (i když chápu, že v případě enterprise řešení je to lepší, aspoň se to může lépe škálovat, řešit jinak přechody na nové verze, apod.), ale v mém případě jde spíše o jednoduché řešení, primární je totiž ten protokol a to GUI je jen sada několika stránek pro administraci nastavení aplikace, takže oddělený modul je zbytečná komplikace.

Tak zatím se zdá, že žádné jednoduché řešení není a tak se chci zeptat, zda jste někdo neřešil podobný problém nebo nemáte nějaký další nápad, co zkusit.

Díky předem za další nápady

Petr

Odpovedet emailem