Development z Zapomnianej Strony

..:: Paweł Hofman .NET Portal ::..

Kolejny dzień, kolejny problem z serii, “bo coś jest zepsute u podstaw” konfiguracji systemu… Zawczasu ostrzegam, że wpis ten jest wybitnie rozwlekły i nie jest przeznaczony dla osób niecierpliwych.

Zatem po kolei. Będąc zafascynowany “nowymi” rozwiązaniami, postanowiłem w ramach testów uruchomić przykłady z pakietu CometD i zapoznać się nieco z protokołem Bayeux (zainteresowanych tematem odsyłam do dokumentacji projektu). Oczywiście nic prostszego - wchodzimy na stronę projektu, pobieramy odpowiednie źródła, wypakowujemy CometD i uruchamiamy Jetty, które instaluje w sobie odpowiednie rozszerzenie. Wszystko bardzo ładnie w kilku zdaniach opisane jest tutaj.

Jakież wyrazy cisną nam się na usta, gdy ściągane są kolejne pakiety idące już prawie w gigabajty (do Javy, Apache, Maven2, itp…). Mija w końcu to niesłychanie długie 20 minut i oczom naszym ukazuje się Jetty w pełnej krasie. Ostrzegam, że jeszcze nawet nie zaczęliśmy grzebać w naszym testowym Debianie, a przecież chwilkę temu pobraliśmy najnowsze wersje całego oprogramowania na świecie.

~/cometd-2.0.0/cometd-demo$ mvn jetty:deploy-war
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ------------------------------------------------------------------------
[INFO] Building CometD :: Demo
[INFO] task-segment: [jetty:deploy-war]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing jetty:deploy-war
[INFO] [enforcer:enforce {execution: enforce-plugin-versions}]
[INFO] [jetty:deploy-war]
[INFO] Configuring Jetty for project: CometD :: Demo
[INFO] Context path = /
[INFO] Tmp directory = /home/pawel/cometd-2.0.0/cometd-demo/target/tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] Starting jetty 7.1.5.v20100705 ...
2010-09-05 01:29:34.971:INFO::jetty-7.1.5.v20100705
2010-09-05 01:29:37.803:WARN::EXCEPTION
java.lang.NullPointerException
    at javax.naming.spi.NamingManager.getPlusPath(libgcj.so.90)
    at javax.naming.spi.NamingManager.getStateToBind(libgcj.so.90)
    at org.eclipse.jetty.jndi.NamingContext.bind(NamingContext.java:337)
    at org.eclipse.jetty.jndi.NamingContext.bind(NamingContext.java:415)
    at org.eclipse.jetty.jndi.java.javaRootURLContext.(javaRootURLContext.java:78)
    at java.lang.Class.initializeClass(libgcj.so.90)
    at org.eclipse.jetty.jndi.java.javaURLContextFactory.getObjectInstance(javaURLContextFactory.java:63)
    at javax.naming.spi.NamingManager.getURLContext(libgcj.so.90)
    at javax.naming.spi.NamingManager.getURLContext(libgcj.so.90)
    at javax.naming.InitialContext.getURLOrDefaultInitCtx(libgcj.so.90)
    at javax.naming.InitialContext.lookup(libgcj.so.90)
    at org.eclipse.jetty.plus.webapp.EnvConfiguration.createEnvContext(EnvConfiguration.java:202)
    at org.eclipse.jetty.plus.webapp.EnvConfiguration.preConfigure(EnvConfiguration.java:62)
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:378)
    at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:114)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:55)
    at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:165)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:162)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:55)
    at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:165)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:55)
    at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:92)
    at org.eclipse.jetty.server.Server.doStart(Server.java:242)
    at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:67)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:55)
    at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:437)
    at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:377)
    at org.mortbay.jetty.plugin.JettyRunWarMojo.execute(JettyRunWarMojo.java:68)
    at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:451)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:558)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeStandaloneGoal(DefaultLifecycleExecutor.java:512)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:482)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:330)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:291)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:142)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:336)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:129)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:287)
    at java.lang.reflect.Method.invoke(libgcj.so.90)
    at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
    at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
    at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
    at org.codehaus.classworlds.Launcher.main(Launcher.java:375)

A później jest już prościej, bo co sekundę wyskakuje wyjątek (co zdecydowanie lepiej rzuca się w oczy):

2010-09-05 01:38:42.200:WARN::EXCEPTION java.lang.ClassCastException: gnu.java.nio.ServerSocketChannelImpl cannot be cast to java.nio.channels.SocketChannel
    at org.eclipse.jetty.io.nio.SelectorManager$SelectSet.doSelect(SelectorManager.java:708)
    at org.eclipse.jetty.io.nio.SelectorManager.doSelect(SelectorManager.java:195)
    at org.eclipse.jetty.server.nio.SelectChannelConnector.accept(SelectChannelConnector.java:134)
    at org.eclipse.jetty.server.AbstractConnector$Acceptor.run(AbstractConnector.java:793)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:436)
    at java.lang.Thread.run(libgcj.so.90)

   

Jednym słowem czad!

No niby prosty przykład, a ile radości. Cóż, znowu ktoś zawinił. Tym bardziej ciekawe jest kto oraz że na forum projektu milczą o tym błędzie, jakby się wszyscy pod ziemię zapadli. Podpowiem, że projekty (CometD oraz sam Jetty) dostępne sąt od kilku ładnych lat i na pewno ktoś by się z tym problemem zetknął. Chyba nie jestem aż takim szczęściarzem. A może jednak to jest jakaś niszowa przypadłość? Bynajmniej. Troszkę szperania i co się okazuje. Otóż ani PATH ani JAVA_HOME nie pokazują lokalizacji JVM (Java Virtual Machine). Dokumentacja do Jetty mówi o tym wyraźnie. Ustawiamy ścieżkę do /usr/lib/jvm/java-1.5.0-gcj-xxx i wszystko powinno ruszyć. Teoretycznie. W praktyce, dostajemy dokładnie ten sam błąd. No jakoś się całe środowisko pokumało, że tylko jedna wersja Javy była zainstalowana i nawet bez JAVA_HOME udało się ją załadować. No to może jeszcze inaczej, może gdzieś, jakiś przełącznik, jakaś aktualizacja, patch rejestru (dobra to nie Windows), symlink? wrr, cokolwiek.

Otóż nie. To, co zazwyczaj okazuje się poprawne, zazwyczaj też siedzi na samym szarym końcu rozwiązań i ostatnie wpada do głowy. Cały JVM jest do wyrzucenia :) No bo przecież GNU Java nie jest zgodna z Sun Java (tu wstaw dowolną wersję), bo i po co? Jakichże to kolorów nabiera wtedy życie…

Tak, ale instalacja oryginalnej ‘sunowskiej’ Javy też nie jest wcale taka prosta. Aby uzyskać dostęp do wersji Javy (Sun JDK 1.5 i JDK 1.6), niezbędny jest na początku dodatkowy wpis w konfiguracji apt-get. Te JDK-i oznaczone są bowiem jako “non-free” i musimy zaakceptować dodatkową licencję zanim będziemy mogli z nich skorzystać.

  • Dopisujemy więc na końcu pliku konfiguracyjnego apt-get (/etc/apt/sources.list) następującą linijkę:

deb http://ftp.debian.org/debian lenny main contrib non-free

  • Odświeżamy listę dostępnych pakietów:

apt-get update

  • Instalujemy Javę (kawę i kolejne 20 min mamy już tym razem w pogotowiu):

apt-get install sun-java6-jdk

apt-get install sun-java5-jdk

  • Na koniec, ustawiamy ją jako domyślną w systemie:

update-java-alternatives -s java-6-sun
echo 'JAVA_HOME="/usr/lib/jvm/java-6-sun"' | tee -a /etc/environment

  • Gotowe! Uruchamiamy ponownie skrypt Maven, który uruchamia Jetty i instaluje w nim CometD. Tym razem z powodzeniem pod adresem http://localhost:8080/ mamy już działające przykłady i użycie Bayeux! Nic dodać nic ująć.

Jednak wieczór można uznać za udany. Szkoda tylko, że nie to mi go skradło, na co chciałem go przeznaczyć…

Dziękuję za uwagę.



Dzisiaj wystartował mój najnowszy projekt open-source, oparty o licencję Apache 2.0. Jest nim oczywiście .NET-owa biblioteka do zapisywania/odczytywania wiadomości w formacie JSON. Kod źródłowy oraz binaria znaleźć można na codeplex.com.

Zachęcam do zerknięcia na nią okiem z powodów takich jak:

  • pełne wsparcie standardu JSON
  • mały rozmiar plików wykonywalnych (~30kb)
  • wsparcie dla .NET 2.0+, Compact Framework 2.0 oraz Mono.NET 2.6
  • prostotę i wygodę użycia API
  • wysokiej jakości kod, oparty o testy automatyczne!


Projekt ten zajmuje mi już ponad rok czasu i ciągle zauważam jak wiele drobnych rzeczy może wpływać na wydajność oraz jakość pracy. To, czemu aktualnie poświęcam najwięcej uwagi – to automatyczne generowanie kodu w Visual Studio.

W aktualnym założeniu - kod ten powstaje w oparciu o relacje pomiędzy modułami w projekcie i nie wnosi żadnej funkcjonalności prócz “przepisania” typów (klas i struktur) z jednego API na inne (czytaj: C/C++ na C#/VB.NET, a kiedyś może i odwrotnie). Kod taki dla programisty jest raczej nużący i mało ciekawy, a sam proces bardzo podatny na błędy. W większości dają one o sobie znać dopiero podczas działania programu. Propozycją jest tutaj odpowiedni analizator, który załatwi w jednym kroku całą sprawę, przeglądając wejściową bibliotekę, odczyta z niej publiczne dane i udostępni generatorowi.

Przykład z życia: tworzymy moduł .NET, który zajmie się testowaniem middleware’u napisanego w C/C++. Wspieramy leciwy już projekt, a idąc z duchem czasu zauważamy, że testy dużo lepiej pisze się i utrzymuje w .NET – zwłaszcza mając pod ręką Microsoft Test Framework.

Wszystkie metody/funkcje i typy trzeba wówczas “ręcznie” przepisać z uwzględnieniem składni języka docelowego. Koszt ogromny i to tylko po to, aby móc te metody wywołać. Pomijamy na ten czas fakt, iż jeśli wspieramy jednocześnie wersję desktop .NET Framework 2.0+ i embedded CF .NET 2.0+, to zawsze pojawiają się małe, choć istotne różnice, o których trzeba pamiętać.

Wersję “beta” rozwiązania, które czyta eksportowane funkcje bezpośrednio z plików COFF (*.dll, *.exe) będzie można niedługo podziwiać w TytanNET v0.20.