-
Notifications
You must be signed in to change notification settings - Fork 0
Connecting to the Service
Let's take a quick inventory of our project. We have a working client and service. In the client project there is a simulated service which receives and sends messages to the client.
In this section we will connect the projects and have the client talking to the service. This will involve writing a real service implementation in the client.
The following concepts will be introduced in this section:
- Communicating with a service
- Integrating application and service projects
- Development while running against the real service
In the tutorial-client project, we have already implemented a
service named MockServices which simulates the real service. Now
that we have a service, we will implement the real version.
Add a new source file named src/tutorial_client/services.cljs for the
namespace tutorial-client.services.
In the mock version of the service we had created a services object
and a function named services-fn to handle outgoing messages. In
this version will do the same thing except that we will create an
EventSource for receiving Server Sent Events and an XMLHttpRequest
for sending outgoing messages.
The namespace declaration will require the following namespaces
(ns tutorial-client.services
(:require [io.pedestal.app.protocols :as p]
[cljs.reader :as reader]))Our Services implementation will create an EventSource at the
endpoint /msgs and will respond to msg events by reading the
message string to Clojure data and placing the message on the input
queue.
(defn receive-ss-event [app e]
(let [message (reader/read-string (.-data e))]
(p/put-message (:input app) message)))
(defrecord Services [app]
p/Activity
(start [this]
(let [source (js/EventSource. "/msgs")]
(.addEventListener source
"msg"
(fn [e] (receive-ss-event app e))
false)))
(stop [this]))The services-fn implementation will send messages as a POST to /msgs.
(defn services-fn [message input-queue]
(let [body (pr-str message)]
(let [http (js/XMLHttpRequest.)]
(.open http "POST" "/msgs" true)
(.setRequestHeader http "Content-type" "application/edn")
(.send http body))))The body of the messages is the messages data printed to a string using
(pr-str message). The content type is set to application/edn.
That is all. This will allow us to send and receive messages.
We now have to wire up this service to our application. The starting
point for the development and production version of our application is
the main function located in the tutorial-client.start namespace.
To use the new service, we need to require the namespace
[tutorial-client.services :as services]and then update the main function as shown below.
(defn ^:export main []
(let [app (create-app (rendering/render-config))
services (services/->Services (:app app))]
(app/consume-effects (:app app) services/services-fn)
(p/start services)
app))
We have added three lines to the main function. We create the
service, providing it with our application.
(services/->Services (:app app))We then arrange for the services-fn to consume all messages on the
effects queue
(app/consume-effects (:app app) services/services-fn)Finally, we start the service
(p/start services)So now, when we run the development or production aspects of our
application (these to aspects are configured to run the main
function above), it will attempt to connect to the service.
If we were to run this as it is, it would not work. This is because the service that we have created will attempt to connect to the server from which the JavaScript source was served. While running the client project that server is the application development server, not the service server.
What we would like to do is serve our application from the service server.
All of the compiled output of our application project is compiled to
out/public. We can create a symlink to this directory in the service
project to server files from there.
In the service project, do the following:
mkdir resources
cd resources
ln -s ../../tutorial-client/out/public
The resources directory is already on the classpath. For this to work,
the out/public directory must exist.
With this in place we can access anything compiled into out/public
from the service server.
Before we try the application, let's make one configuration change to the development application which will make our workflow a little better.
In the config file config/config.clj, under the :application
section, add the following key and value
:api-server {:host "localhost" :port 8080 :log-fn nil}This tells the development server that there is a service running on localhost port 8080.
For any aspects which we would like to redirect to the service, add the following key and value
:use-api-server? trueDo this for the :development and :production aspects.
We are now ready to start everything up and try it out.
If you are running the development server, stop it and start it in the usual way. We need to stop it because we made a change to the config file.
To start the server project, enter that directory and run:
lein repl
then evaluate the following in the REPL.
(use 'dev)
(start)If we open the development project
http://localhost:3000
And then click on either the Development or Production links
in the tools menu, the applications will be compiled and then we will
be redirected to the service running on http://localhost:8080.
The application should now work. Start up multiple browsers and connect to the same URL to try it out.
You may have noticed that we first went to the development server clicked on the links to take us over the service. We did this instead of just going directly to the service. The reason for this is that clicking on the links in the development server will actually cause compilation to happen before redirecting to the service.
If while running from the service, we change our application code and
then refresh the page, we will not see the change. This is because the
service doesn't know anything about ClojureScript or compilation. It
only knows that is serving static files from the out/public
directory.
To trigger compilation, we have to go back to the development server and again click on the Development or Production links.
This if fine if only need to do it once but if we find that we would like to work on the application, making a lot of changes, from the service then there is a better way.
The watch function is located in the dev namespace and can be used
to watch files for changes and trigger compilation. This will trigger
the same compilation process so we can change scss, html templates or
ClojureScript files and compilation will be initiated.
To compile the development aspect when files change run
(watch :development)from the development project REPL.
To stop watching run
(unwatch)The argument to watch is the aspect that you would like to compile. It
is best to use the :development aspect so that compilation times are
faster.
This concludes the first part one of the tutorial where we cover all of the basics of Pedestal applications. This should give you are good grounding in Pedestal and allow you to get started working on your own projects.
This is not, however, the end of the tutorial. In part two, we will get some new requirements which, as we implement them will introduce us to the following important Pedestal concepts:
- Rendering without the DOM
- Messages with parameters
- Parallel processing with Web Workers
- Multi-screen applications and Focus
The tag for this step is v2.0.14.