Jak szybko stworzyć aplikacje webową, która zwraca JSON-a
Ostatnio sprawdzałem możliwości JavaScriptu w aplikacjach webowych. Tutaj udział część serwerowej sprowadzał się do obsługi wywołań AJAX-owych (zwracania wyników w JSON-e). W skrócie nie chciałem się zajmować serwerem, wystarczało że zwracał mi jakiś kawałek JSON-a, tak abym mógł skupić się tylko na HTML, CSS i JS.
Bez Javy, ale tak bym chciał
Tak więc na początek trafiłem na takie całkiem fajne rozwiązanie w pythonie.
python -m SimpleHTTPServer 8080
Jak ma się zainstalowanego pythona, to jest to najprostszy sposób na postawienie serwera HTTP. Gdzie wystawioną zawartością, poprzez URL, jest to co znajduje się w katalogu, w którym z linii poleceń uruchomiło się to. Tak więc jeśli mamy tam katalog
dane
, a w nim plik test.json
, to możemy do tego pliku odwołać się poprzez taki url:
http://localhost:8080/dane/test.json
Zatem statyczne kawałki JSON-a leżą sobie w plikach na dysku, obok może znajdować się plik app.js
, który odwołuję się do nich, i który to jest głównym przedmiotem pracy. Całość mogę edytować po dowolnym narzędziem, i każda zmiana od razu jest widoczna w przeglądarce (wystarczy F5). Piękne 🙂 nie trzeba się martwić synchronizacją z serwerem, można się skupić na pracy i od razu widzieć efekty w przeglądarce.
Rozwiązanie to daje jednak tylko statycznego JSON-a. Aby wprowadzić więcej dynamizmu trzeba jednak napisać trochę po stronie serwera. Nie patrzyłam już czy SimpleHTTPServer
oferuję jakieś większe możliwość przeszedłem od razu do javy, w końcu jak na razie w tym głównie piszę.
Z Javą, próba odtworzenia
Zacząłem od stworzenia prostego projektu mavenowego, tak aby móc łatwo zarządzać zależnościami, po za tym i tak zwykle jest to projekt tego typu. Idą za wskazówkami zawartymi w tym poście wybrałem SpringMVC. Cała moja praca po stronie serwera ograniczyła się do stworzenia jednej klasy, która produkowała mi JSON-a jakiego chciałem.
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping("/app") public class AppController { private static List<String> names = new ArrayList<String>(); @RequestMapping(value = "/add", method = RequestMethod.GET) public @ResponseBody void add(@RequestParam(value="name") String name){ names.add(name); } @RequestMapping(value = "/listAll", method = RequestMethod.GET) public @ResponseBody List<String> listAll(){ return names; } @RequestMapping(value = "/remove", method = RequestMethod.GET) public @ResponseBody void remove(@RequestParam(value="name") String name){ names.remove(name); } }
Dzięki temu dostałem trzy url-e, z których mogłem korzystać po stornie JavaScript:
http://localhost:8080/RestTest/rest/app/add?name=dwa http://localhost:8080/RestTest/rest/app/remove?name=dwa http://localhost:8080/RestTest/rest/app/listAll
Ostatni może zwracać takiego JSON-a:
["jeden","dwa","trzy"]
Takie coś w zupełności wystarcza do symulowania logiki po stronie serwera, a jak widać w przedstawionej klasie, rozbudowanie tego nie powinno być trudne.
Oprócz tej klasy potrzebne były jeszcze techniczne pliki, wszytko wziąłem z projektu w przytoczonym poście.
Tak wiec w web.xml
potrzebne są takie wpisy aby postawić kontekst Springa.
<servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
Najistotniejsze jest zwrócenie skąd się bierze rest
w URL (RestTest
to nazwa mego wara), a plik mvc-dispatcher-servlet.xml
opisujący co Spring ma robić jest trywialny:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <context:component-scan base-package="test.controller" /> <mvc:annotation-driven /> </beans>
jak widać jedyne co trzeba dodać to wpis włączający interpretacje adnotacji w napisanej klasie. Do dopełnienie wszystkiego jest jeszcze plik mavena, który zaprezentuje na końcu.
Jest Java, ale jakby krok wstecz
Tak więc miałem już dynamicznego JSON-a, i sposób jak go dostosowywać. Mogłem się zabrać za JavaScript. Jednak utraciłem tutaj szybką pętle zwrotną, w weryfikacji wprowadzanych zmian. Teraz pomiędzy zapisaniem zmiany w pliku, a odświeżeniem widoku w przeglądarce, doszedł krok budowy wara i wgrania go na Tomcata lub inny WebSerwer. A ja chciałem tylko zmieniać pliki JS lub HTML, część serwerowa nie zmieniałaby się w ogóle.
Z Javą, już prawie idealnie
Rozwiązań tego pewnie jest parę. Ja zwróciłem się w kierunku wbudowanego kontenera aplikacje webowych, czyli użyłem jetty.
W pom.xml
pojawia się taki wpis jak niżej. Jetty uruchamia się taka komendą mvn jetty:run
.
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.24</version> <configuration> <stopPort>9966</stopPort> <stopKey>RestTest</stopKey> <!-- Redeploy every x seconds if changes are detected, 0 for no automatic redeployment --> <scanIntervalSeconds>0</scanIntervalSeconds> <!-- make sure Jetty also finds the widgetset --> <webAppConfig> <contextPath>/RestTest</contextPath> <!-- --> <baseResource implementation="org.mortbay.resource.ResourceCollection"> <resources>src/main/webapp</resources> </baseResource> </webAppConfig> </configuration> </plugin>
Ważny jest parametr scanIntervalSeconds
, mówi on o tym czy zmiany w naszych klasach będą automatycznie redeployowane. Trwa to trochę, ale można z tym żyć. To i tak nie dotyczy naszego HTML-a i JS, to co powoduję że zmiany w tych plikach są widoczne od razu w przeglądarce, to parametr baseResource
. Wskazanie na katalog webapp
, gdzie trzymane są te pliki powoduje, że zmiany są propagowane (wykomentowałem ten parametr i funkcjonalność ta nadal działało co pewnie świadczy o tym że jest to wartość domyśla). Nie wczytywałem się za bardzo w szczegóły ustawień i może trochę podorabiałem teorii do praktyki, ale działa tak jak chciałem. Mam co chciałem i mogę spokojnie pracować nad plikami JS, i zmiany w nich widzieć od razu w przeglądarce.
Taki mały zgrzyt
Pojawia się zgrzyt, podobno tylko dla Windowsa, pliki uaktualniają się tylko za pierwszym razem, kolejny raz nie można już zapisać, bo są blokowane. Ten wpis opisuje problem i możliwe rozwiązania. Ja użyłem dodatkowego wpisu w web.xml
, na razie nie martwię się tym że brudzi to ten plik. Dodatkowy element to:
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class> <init-param> <param-name>useFileMappedBuffer</param-name> <param-value>false</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet>
I to wszytko, można się zabrać za JavaScript. Teraz mam szybką odpowiedź zwrotną w przeglądarce, wiec jak dla mnie bardzo dobre warunki do pracy z tak krnąbrny językiem 😉 , o czym może napisze następnym razem.
A całość pom.xml
prezentuję się tak:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>test</groupId> <artifactId>RestTest</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <properties> <spring.version>3.0.5.RELEASE</spring.version> </properties> <dependencies> <!-- Jackson JSON Mapper --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.7.1</version> </dependency> <!-- Spring 3 dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> </dependencies> <build> <finalName>RestTest</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.24</version> <configuration> <stopPort>9966</stopPort> <stopKey>RestTest</stopKey> <!-- Redeploy every x seconds if changes are detected, 0 for no automatic redeployment --> <scanIntervalSeconds>0</scanIntervalSeconds> <!-- make sure Jetty also finds the widgetset --> <webAppConfig> <contextPath>/RestTest</contextPath> <!-- --> <baseResource implementation="org.mortbay.resource.ResourceCollection"> <resources>src/main/webapp</resources> </baseResource> </webAppConfig> </configuration> </plugin> </plugins> </build> </project>
Małe uaktualnienie: taki sam efekt z wbudowanym kontenerem aplikacji webowych można uzyskać z tomcat-maven-plugin
. Nie ma się tylko przeładowania klas (w każdym razie nie znalazłem tego). Ale nie trzeba brudzić swego pliku web.xml
i nie ma problemu z blokowaniem edytowanych plików. Tak więc w pomie zamiast jetty należy dodać:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.1</version> </plugin>
i uruchomić z linii poleceń komendą: mvn tomcat:run