Skip to content

Commit

Permalink
Merge branch 'master' of github.com:SquareBracketAssociates/Enterpris…
Browse files Browse the repository at this point in the history
…ePharo
  • Loading branch information
LucFabresse committed Jun 19, 2015
2 parents 69303ab + f661939 commit b834c44
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 133 deletions.
221 changes: 221 additions & 0 deletions DeploymentWeb/DeployForProduction.pillar
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
!Deploying a Pharo Web Application in Production

In the previous chapters we discussed several frameworks and libraries to facilitate the development of web application. In this chapter, we will focus on deploying such a web application. While doing so, we will try to answer some questions such as: which operating system should I use, how do I run my application, how do I ensure my application will be restarted after a reboot or a crash, and how do I log data.

!!Where to Host your Application?

The easiest (and fastest) way to host your application is to host it in the cloud.

*PharoCloud>http://pharocloud.com*, for example, proposes pre-packaged solutions (including Seaside and Pier, database support) as well as the possibility to use your own Pharo image. You could start very quickly from there but you do not have full control on your pharo stack. It is enough in most cases: PharoCloud manages defaults for you.

There are many other cloud providers including *Amazon AWS>https://aws.amazon.com/*, *Openshit>https://www.openshift.com/*, *OVH>https://www.ovh.com/fr* and *Microsoft Azure>http://azure.microsoft.com*. Many Pharo users use *DigitalOcean>https://www.digitalocean.com/* as it is both simple and cheap.

Choose your cloud provider according to your needs.

In the rest of this chapter, we detail how to setup a server to host a Pharo web application.

!!Which Operating System?

A lot of pharo developers use Mac OS X to develop their applications but it is not really a solution for a production deployment as there is no more Apple server hardware. Most Pharo developers prefer to deploy on GNU/Linux because GNU/Linux stability is well known. Deploying on Windows is a bit more complex and less supported by cloud providers.

There are many Linux distributions you can choose from. If you restrict your choice to well-known open-source distributions, competitors are *Centos>http://www.centos.org*, *Debian>http://www.debian.org* and *Ubuntu>http://www.ubuntu.com*. Most distribution will do the job, choose the most appropriate for you. A long-term support (''aka.'', LTS) version is welcomed if you do not want to update your operating system too often. The Pharo Virtual Machine (VM) comes pre-packaged for some distributions. For other distributions, you will have to compile the VM sources yourself. Pay attention that the Pharo VM is still 32bits as of 2015 and you will have to install 32-bit libraries on your 64-bit Operating System.

!!Build your Image

The best option to obtain a clean image to deploy is to start from a fresh *stable pharo image>http://files.pharo.org/image/stable/latest.zip* and to install required packages through your application's Metacello configuration and the command line handler. The configuration has to explicitely describes all dependencies used in your application.

First, create a copy of the clean image with your application name:

[[[language=bash
$ ./pharo Pharo.image save myapp
]]]

Then, install your dependencies:
[[[language=bash
$ ./pharo myapp.image config http://www.smalltalkhub.com/mc/Me/MyApp/main ConfigurationOfMyApp --install=stable
===============================================================================
Notice: Installing ConfigurationOfMyApp stable
===============================================================================
[...]
]]]

After loading all necessary code, the ==config== option will also save the image so that the image now permanently includes your code.

To make sure that your deployment image is reproducible, the best approach is to create a Continuous Integration job that automatically produces clean deployment-ready images of your application.

!!Run your Application

When you have a Pharo image with your application inside, the next step is to start the application. To make this process reproducible, it is recommended to create a dedicated file (''e.g.'', named ==myapp.st==) with the instructions needed to start your application. Here is an example of a script used to start a web application using Zinc.

[[[language=Smalltalk
ZnServer defaultOn: 8080.
ZnServer default logToStandardOutput.
ZnServer default delegate
map: 'image' to: MyFirstWebApp new;
map: 'redirect-to-image' to: [ :request | ZnResponse redirect: 'image' ];
map: '/' to: 'redirect-to-image'.
ZnServer default start
]]]
This script will start an instance of the Zinc Web Server on localhost on the port 8080 and store it as the default instance. We configured the Zinc instance to log on the standard output, we changed the default root (/) handler to redirect to your new /image web app. ==MyFirstWebApp== class handles HTTP requests by implementing the ==#handleRequest:== message.

You can test the startup script like this:

[[[language=bash
$ ./pharo myapp.image myapp.st
2013-07-10 11:46:58 660707 I Starting ZnManagingMultiThreadedServer HTTP port 8080
2013-07-10 11:46:58 670019 D Initializing server socket
2013-07-10 11:47:12 909356 D Executing request/response loop
2013-07-10 11:47:12 909356 I Read a ZnRequest(GET /)
2013-07-10 11:47:12 909356 T GET / 302 16B 0ms
2013-07-10 11:47:12 909356 I Wrote a ZnResponse(302 Found text/plain;charset=utf-8 16B)
2013-07-10 11:47:12 909356 I Read a ZnRequest(GET /image)
2013-07-10 11:47:12 909356 T GET /image 200 282B 0ms
2013-07-10 11:47:12 909356 I Wrote a ZnResponse(200 OK text/html;charset=utf-8 282B)
2013-07-10 11:47:12 909356 I Read a ZnRequest(GET /image?raw=true)
2013-07-10 11:47:12 909356 T GET /image?raw=true 200 18778B 82ms
2013-07-10 11:47:12 909356 I Wrote a ZnResponse(200 OK image/png 18778B)
]]]

Type ==Ctrl-c== to kill the server.

In Unix systems, init scripts are used to automatically start services when the server reboots and to monitor the status of these services. These scripts typically have a ==start== command, a ==stop== command and a ==status== command (some init-scripts have more than these 3 commands). Your application's init script should be configured to be automatically executed when the server restarts. This init script must typically be placed in the ''/etc/init.d'' directory.

You can find a *template for such an init script>https://github.com/pharo-project/pharo-deployment-scripts/blob/master/pharo-service-script.sh* in the *GitHub pharo-project/pharo-deployment-scripts repository>https://github.com/pharo-project/pharo-deployment-scripts*. Give this script the name of your application. This script is derived from the template provided by the Ubuntu distribution in ==/etc/init.d/skeleton==.

The *pharo-run-script.sh>https://github.com/pharo-project/pharo-deployment-scripts/blob/master/pharo-run-script.sh* is another useful script. It runs a Pharo image with a pre-defined Smalltalk script file to evaluate (the ==myapp.st== you wrote above). You may need to edit this file to configure the Pharo VM path or options.

Create a folder with your application name ''myapp'', then copy the ==pharo-run-script.sh== script into this folder. Give the script execution permissions: ==chmod a+x ./myapp==.

You should end with a file hierarchy like this one:
- /etc/init.d/myapp (init script)
- /opt/myapp
--myapp (generic pharo run script)
--myapp.st (image startup script)
--myapp.image
--myapp.changes
- /usr/bin/pharo-vm-nox

When the file hierarchy is ready, you can start your application by executing the ==./myapp== script or by using the init script through ==service myapp start==.

When the Pharo image crashes (this ''will'' happen), there must be a way to automatically recover from this crash. For this to work, (1) the application data must be backed up, (2) there must be a way to know when the application has crashed, and (3) there must be a way to automatically restart the application. In the following we cover these points.

!!Backup

You do not want to lose data! To do that, the simplest solution is to make your image stateless: if your image crashes, no data should be lost because no data is in the image. If your application requires persistent data (''e.g.'', user accounts), the best is to use a database (''e.g.'', PostgreSQL, MongoDB). You must then make sure that your database is backed up properly.

!!Monitoring

To automatically restart your application when it crashes, there must be a way to detect that it has crashed.

With a standard operating system's init script such as the one described above, you can use the ==status== command to detect if Pharo is running or not. Nevertheless, Pharo can be running (the ==status== command detects it fine) but not responding (''i.e.'', HTTP requests never get answered). This can happen if the image is frozen for example. If your application is a web application, you may wish to check if your application is able to answer to HTTP requests. To do so, you need to define some ==ping== url to your application that will be used by the monitoring tool.

A simple solution to both monitor your application and take appropriate actions (''e.g.'', restart) is to use the *monit utility>https://mmonit.com/monit*.

You can configure Monit through the ==/etc/monit/monitrc== file.

!!!Monit Global Configuration

!!!!Monit Dashboard

You can first activate the embedded HTTP dashboard of monit. We will only allow local connections with a given username/password.

[[[language=monit
set httpd port 2812 and
use address localhost # only accept connection from localhost
allow localhost # allow localhost to connect to the server and
allow admin:monit # require user 'admin' with password 'monit'
]]]

Do not forget to apply the new configuration:

[[[language=bash
$ sudo monit reload
]]]

To connect from a different place than localhost, use an SSH tunnel. If the server running both your application and monit is named ==myserver.com==, then you can execute the following to connect to your server and open a local port:

[[[language=bash
$ ssh -L 2812:localhost:2812 myserver.com
]]]

Keep the SSH connection open, and browse to *http://localhost:2812>http://localhost:2812* to display the monit dashboard.

!!!!Email Settings

If you want notifications from monit, you need to configure email settings so that monit can send emails. Edit the monit configuration file again and add the following line:

[[[language=monit
set mailserver <smtp.domain>
]]]

!!!Monitor System Services
Configuration files related to an application (or a service) should be put into the ==/etc/monit/monitrc.d== directory (more modular that everything in the core configuration file). To enable a configuration just symlink it to ==conf.d==.
We will first enable pre-defined configuration for SSH.
[[[language=bash­
$ sudo ln -s /etc/monit/monitrc.d/openssh-server /etc/monit/conf.d/openssh-server
$ sudo monit reload]]]
""Warning"": default configurations for well-known services are provided by monit but may require some adaptations (ex: wrong path to the PID file).

To check for errors, you may need to run monit in verbose mode:

[[[language=bash
$ sudo monit -v
]]]

and check the monit error log, by default in ==/var/log/monit.log==.

!!!Monit Configuration to Control a Pharo Application

Application-specific configuration files must be added to the ==/etc/monit/monitrc.d== directory. Create a new ==myapp== file in this directory:

[[[language=monit
alert [email protected]

check process myapp with pidfile /var/run/myapp.pid
start program = "/etc/init.d/myapp start"
stop program = "/etc/init.d/myapp stop"
if 5 restarts within 5 cycles
then timeout
]]]

With this in place, when a problem occurs, the ''alert'' instruction makes sure [email protected]== is notified by email. The monitoring is described with the check command. We ask monit to check a given pid file. If there is no pid or no process associated to the pid, monit will start the program with the given instruction. The last instruction prevents infinite loops if there is a problem with the script.
We now activate the monitoring of myapp:
[[[language=bash
$ sudo ln -s /etc/monit/monitrc.d/myapp /etc/monit/conf.d/myapp
$ sudo monit reload
]]]

!!!Monit Configuration for a Pharo Web Application

A Pharo image may be running (alive process) but not responding to HTTP requests anymore. In such cases, your application becomes unusable. This state can be verified by sending a simple HTTP request and checking for the response. We can ask monit to monitor your web server by doing regular checks to a predifined URL and validate the HTTP response content:

[[[language=monit
alert [email protected]

check process myapp with pidfile /var/run/myapp.pid
start program = "/etc/init.d/myapp start"
stop program = "/etc/init.d/myapp stop"
if failed (url http://localhost:8080/ping
and content == "pong"
and timeout 10 seconds)
then restart
if 5 restarts within 5 cycles
then timeout
]]]

This configuration will try to connect to the ==/ping== URL on localhost. If monit can not connect, or no answer arrives before 10 seconds, or the content is not exactly ==pong==, monit will restart the application.

You may also want to monitor Apache if there is an Apache server in front of your application. You can do that by adapting the already existing ==apache2== monit configuration file:

[[[language=monit
if failed host localhost port 80 with protocol http with timeout 25 seconds for 4 times within 5 cycles then restart
]]]

Activate the Apache monitoring and reload the monit configuration:

[[[language=bash
$ sudo ln -s /etc/monit/monitrc.d/apache2 /etc/monit/conf.d/apache2
$ sudo monit reload
]]]

% LocalWords: monit
40 changes: 25 additions & 15 deletions NeoJSON/NeoJSON.pier
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,6 @@ While reading:
-JSON maps become instances of mapClass, ==Dictionary== by default;
-JSON lists become instances of listClass, ==Array== by default.

This example creates a Pharo dictionary (with ==x== and ==y== keys):

[[[language=smalltalk
NeoJSONReader fromString: ' { "x" : 1, "y" : 2 } '.
]]]

The following example creates a Pharo array from a JSON expression:

[[[language=smalltalk
Expand All @@ -99,31 +93,47 @@ This expression can be decomposed to better control the reading process:
next.
]]]

The above expression is equivalent to the previous one except that an ordered collection will be used in place of an array.
The above expression is equivalent to the previous one except that a Pharo ordered collection will be used in place of an array.

There is also an option to convert all map keys to symbols, which is off by default.

@@authorToDo DC: example of how to specify options
The next example creates a Pharo dictionary (with =='x'== and =='y'== keys):

[[[language=smalltalk
NeoJSONReader fromString: ' { "x" : 1, "y" : 2 } '.
]]]

To automatically convert keys to symbols, pass ==true== to ==propertyNamesAsSymbols:== like this:

[[[language=smalltalk
(NeoJSONReader on: ' { "x" : 1, "y" : 2 } ' readStream)
propertyNamesAsSymbols: true;
next
]]]

The result of this expression is a dictionary with ==#x== and ==#y== as keys.

!!!Writing to JSON

While writing:

-Dictionary and SmallDictionary become maps
-all other Collection classes become lists
-all other Objects are rejected
-instances of ==Dictionary== and ==SmallDictionary== become maps;
-all other collections become lists;
-all other non-primitive objects are rejected.

Here are some examples writing in generic mode:

[[[
[[[language=smalltalk
NeoJSONWriter toString: #(1 2 3).

NeoJSONWriter toString: { Float pi. true. false. 'string' }.
NeoJSONWriter toString: { #a -> '1' . #b -> '2' } asDictionary.
]]]

Above expressions return a compact string (/i.e./, with neither indentation nor new lines). To get a nicely formatted output, use ==toStringPretty:== like this:

NeoJSONWriter toStringPretty: (Dictionary new at: #x put: 1; at: #y put: 2; yourself).
[[[language=smalltalk
NeoJSONWriter toStringPretty: #(1 2 3).
]]]

NeoJSONWriter can output either in a compact format (the default) or in a pretty printed format.

SD: examples how to do that.
Expand Down
6 changes: 4 additions & 2 deletions PossibleOutlineAndCurrentStatus.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ Finished:
>> Stef should read it again
>> Luc will read it
>> Luc finished his pass (all screenshots with Pharo4, removed some beginners text, ...) [17 jun 2015]

*******************************************************************************

Zinc-Encoding-Meta
>> Stef read and modified it a bit
>> sven took into account remark
>> stef should check that
>> Johan will do a pass
>> Johan finished his pass [18 / jun /2015 ]

*******************************************************************************


PillarChap
>> Stef should read it.
Expand Down
Loading

0 comments on commit b834c44

Please sign in to comment.