When I was creating my first application in spray framework I used spray’s directive to serve static content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
trait StaticService extends HttpService { val staticRoute = { path("") { getFromResource("web/index.html") } ~ { getFromResourceDirectory("web") } } } object AkkaImplicits { implicit val system = ActorSystem("cryptex-system") implicit val timeout = Timeout(5, TimeUnit.SECONDS) } class CryptExServiceActor extends Actor with ApiService with StaticService { def actorRefFactory = context def receive = runRoute(apiRoute ~ staticRoute) } object Main extends App with SSLConfiguration { import AkkaImplicits._ val transactionManager = system.actorOf(Props[TransactionManager], "cryptex-transaction-manager") val handler = system.actorOf(Props[CryptExServiceActor], "cryptex-service") IO(Http) ? Http.Bind(handler, interface = "localhost", port = 8080) } |
As you can see the main actor combines routes from apiRoute (handles all rest requests) and staticRoute (handles static content that is located in src/main/resources/web). Static route returns index.html if no path is provided in the url and any other resource from web directory otherwise). That works pretty well but has three disadvantages:
- If any file from web directory will change hitting F5 in the browser will return previous content until spray server is restarted. That is very annoying during developing app’s frontend when one wants to see changes immediately.
- Restarting spray server takes time. Sure it’s relatively not that long (on my machine around 3-4 sec) but still too slow for convenient development process.
- Returning file content to the user requires JVM involving. Whatever they told you it’s not the best way to do it. And it’s better to save CPU cycles for more important tasks.
I decided to use nginx for that task (it’s small and fast with low memory footprint http server). Here’s my configuration (it’s based on one of the gists but unfortunately I don’t remember the author):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
worker_processes 1; events { worker_connections 1024; } http { server { listen 9090 ssl; server_name cryptex; charset utf8; ssl on; ssl_certificate cryptex.cert; ssl_certificate_key cryptex.key; ssl_session_timeout 5m; ssl_protocols SSLv2 SSLv3 TLSv1; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; access_log logs/cryptex.access.log combined; error_log logs/cryptex.error.log debug; location / { include mime.types; root web; index index.html; } location /api { add_header "Access-Control-Allow-Origin" $http_origin; add_header "Access-Control-Allow-Credentials" "true"; proxy_pass http://localhost:8080; proxy_redirect https:// http://; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } } |
To run nginx I created following script start_nginx.sh:
1 2 3 |
mkdir -p logs mkdir -p temp sudo nginx -p $(pwd) -c nginx.conf |
Notice the -p parameter. Without it nginx looks for provided configuration file (in my example called nginx.conf) in its default configuration directory. What’s interesting on Windows this parameter is not required. I was really surprised with this platform inconsistency.
To stop nginx you must run following command (nginx process it’s not “killable” (at least on windows)):
1 |
sudo nginx -p $(pwd) -c nginx.conf -s stop |
If everything is ok nginx will start listening on localhost:9000 and from now on it will be responsible for serving files from the web directory. I forgot to mention that I moved web folder outside resources folder to avoid including it in the final jar file.
Spray application is listening on localhost:8080 and all requests coming from localhost:9000/api/* are forwarded there. In my case I use ssl on port 9000 so I had to add following line:
1 |
proxy_redirect https:// http://; |
This line transforms https prefix into http one. Of course you can use ssl on spray side as well but it’s unnecessary protection doubling.
And that’s all. Hope that will help someone 🙂