Le fonctionnement d’Ajax

(article écrit par un groupe d’étudiants d’IUT dans le cadre d’un projet)

Impossible de vous expliquer comment fonctionne Ajax si vous ne connaissez pas l’organisation du World Wide Web aussi appelé Web.
Les notions de client et de serveur sont élémentaires pour comprendre l’Internet.
Derrière l’entité serveur se cache tout simplement un disque dur contenant des données. Celles-ci peuvent être sous la forme de fichiers, d’applications ou de bases de données. Bien sûr, un serveur n’est pas forcément spécialisé et peut très bien regrouper toutes ces données. Un Client est en réalité un ordinateur relié au réseau comme celui que vous êtes en train d’utiliser. Les échanges entre ces deux entités diffèrent selon la technologie utilisée.

Tout d’abord, je vais essayer d’expliquer aux plus novices d’entre vous comment marche l’affichage d’une page web utilisant uniquement le HTML.
Si vous voulez davantage connaître l’HTML, je vous conseille de visiter cette page : http://www.ext.upmc.fr/urfist/html/html.htm.
Lorsque vous surfez sur la toile et que vous cliquez sur un lien, ou tapez une adresse, votre navigateur web (Internet Explorer ou Mozilla) envoie une requête au serveur. Le serveur étant toujours prêt à répondre aux demandes du client, il retourne au navigateur web du client une page HTML stockée sur son disque dur. C’est ensuite votre navigateur qui va interpréter et afficher la page.
Voici un petit schéma explicitant les quelques lignes ci-dessus:

Mais le web n’est pas composée que de pages statiques. En effet, avec le langage PHP les pages ont la possibilité d’être animées.Par exemple, avec un petit script PHP la page peut afficher la date et l’heure à laquelle elle a été affichée. Ici, les échanges entre serveur et client sont différents. Le processus est similaire, sauf que le serveur effectuera un traitement avant de renvoyer la page.

En effet, le serveur va devoir exécuter le script. Celui retournant la date et l’heure n’est pas gourmand en ressources système, mais certains peuvent pénaliser le serveur.Voici,un second schéma:

Un troisième langage permet de faire des pages dynamiques, il s’agit du JavaScript. Le traitement est différent car c’est du coté client que le script s’exécute. Cela permet de ne pas surcharger le serveur, et facilite ainsi sa tâche. Les traitements effectués coté client sont d’un ordre généralement différent de ceux effectués coté serveur. Le javascript est principalement utilisé pour des opération de rendu de l’affichage alors que les traitements serveur sont plus focalisés sur la gestion des données.

Cet article ne devait pas être sur le fonctionnement d’Ajax ? Si, bien sûr, mais pour comprendre son fonctionnement, il faut savoir comment les langages les plus courants opèrent puisque Ajax est en réalité un ensemble de technologie(s) et de langages.
Ajax est un mélange des deux précédents schémas : Lorsque le client télécharge une page, celle-ci intègre des composant Javascript (donc exécutés coté client) qui seront ensuite capables de requêter de nouvelles informations au serveur et se chargeront de les afficher dans le navigateur.
Les javascripts établissent ainsi une communication entre le client et le serveur par la création d’un contrôle ActiveX sous Internet Explorer et d’un XMLHttpRequest sur la plupart des autres navigateurs.
Le serveur communiquera avec le client en lui transmettant des texte souvent formatés à l’aide de XML. Le navigateur se chargera de l’interprétation et de l’affichage de ces données.

Avec l’Ajax, les échanges entre serveur et client ne nécessitent plus de raffraichissement complet de l’écran mais seulement de certaines zone de celui-ci. L’utilisateur aura donc un impression d’interactivité beaucoup plus grande. Les mises à jour de la page ont lieu régulièrement, à chaque fois que l’on est dans la situation où la requête est envoyée. Ce système est aussi plus économique pour le réseau et le serveur qui ne génèrent plus que des informations partielles (au risque de pooling près).

Utilisation de subversion

Subversion est un outil de gestion de sources ou plus généralement de gestion de configuration. Subversion est le successeur de cvs.
Son utilisation est à première vu similaire à celle de cvs mais avec quelques différences non anodines. Nous allons voir l’installation sur un environnement Opensuse.
Tout d’abord, il faut installer le rpm de subversion. Il faudra ensuite indiquer dans le fichier /etc/sysconfig/svnserve le répertoire où stocker le repository de subversion. C’est dans la variable SVNSERVE_OPTIONS que l’on indiquera le répertoire choisi. On enlèvera aussi l’option -R qui bloque l’accès en écriture
Il faut ensuite créér un utilisateur svn et un groupe svn. Ceci se fera en editant les fichiers passwd et group ou en utilisant les commandes useradd et groupadd

Ces premières étapes passées il faut créer le repository , pour cela, nous allons appeler la commande suivante : svnadmin create –fs-type fsfs repertoire_du_repository
Cette commande va créer un ensemble de répertoires dans repository. Nous allons commencer par editer deux des fichiers du repository:
/conf/svnserv.conf : nous allons dé-commenter les lignes suivantes:
[general]
anon-access = read
auth-access = write
password-db = passwd

Ensuite, nous allons modifier le fichier des mots de passe pour ajouter les utlisateurs nécessaires. ce fichier est conf/passwd:
[users]
# harry = harryssecret
# sally = sallyssecret
utilisateur = mot_de_passe

On vérifiera que l’utilisateur et le groupe propriétaire de tous les fichiers du repository soit bien svn:svn, sans quoi il sera impossible d’ecrire dedans par la suite.

Le serveur svn peut alors être démarré en utilisant le script fournit par suse: /etc/rc.d/svnserve restart

La création d’un projet dans le repository se fera par la commande suivante : svn import path svn://localhost/projet. Normalement, un mot de passe doit être demandé, il s’agit de celui précédemment saisi dans le fichier passwd.

Quelques commentaires :

  • Nous avons créé un repository à la norme fsfs, il s’agit de la façon dont sont stockées les données, ici sous forme de fichiers. Il existe aussi la possibilité d’utiliser un base de donnée berkeley, (option –fs-type bdb) mais d’après la documentation de svn, ce mode de fonctionnement est beaucoup moins avantageux.
  • Le repository peut être accédé de plusieurs façons, la première etant directement via le filesystem local (quand le repository est sur la même machine) ; par file://path/to/repo. Le second est l’utilisation de svnserv par svn://path/to /repo. Le troisieme svn+ssh://serveur/path/to/repo pour utiliser le protocole svn au travers d’un tunnel ssh et la dernière est http (ou https) par http://serveur/repo pour utiliser WebDav

Passage à Mysql5 et format des TIMESTAMP

Suite a une migration vers Mysql5 et le driver jdbc 3.1.12, mes programmes qui fonctionnaient bien avant se sont mis à générer des exceptions pour le motif suivant :
java.sql.SQLException: Cannot convert value ‘0000-00-00 00:00:00′ from column 11 to TIMESTAMP

Il semble donc que les dites versions, par rapport aux anciennes retournent un valeur “000-00-00 00:00:00” plutot que null lorsque l’on a cette valeur en base (ce qui soit dit en passant a peut être du sens) n’empeche que ca met bien la grouille… alors plutot que de se lancer dans une fastidieuse réécriture de code, il y a le paramètre miracle qui sauve la vie !!

Pour revenir au fonctionnement d’antant, il faut ajouter à la suite de la chaine de connexion l’option suivante:
jdbc:mysql://monHost/maBase?zeroDateTimeBehavior=convertToNull

Testé et approuvé !

Impact applicatif du passage à UTF-8

Et oui la mode UTF8 arrive partout … et c’est un bien fait, c’est sur… comment envisager de continuer à créer des applications ne fonctionnant que dans une langue ?!?

Mais que se passe-t-il lorsque l’on transforme une application existante utilisant le classique WEBISO (ISO-8859-1) pour ses interfaces et traitements en une application du futur basée sur UTF8 ? C’est ce qui m’a occupé ces quelques derniers jours au travail, alors autant vous faire partager cette expérience !

Pour tout comprendre, il faut savoir qu’en WEBISO les caractères sont tous codés sur 8 bits (1 octet) et que par conséquent tous les caractères du monde ne peuvent être représentés, mais les traitements sont simples : par exemple tronquer une chaîne à 10 caractères signifie simplement ne garder que 10 octets.
En UTF8, la taille d’un caractère dépend du caractère lui-même. Les caractères basiques (les 128 premiers) sont codés sur 1 octet et l’encoding est identique à l’ASCII, si bien qu’un texte en anglais de base sera grosso modo le même en UTF8 et en WEBISO. Pour ce qui est de nos écris, latin que nous sommes, il n’en va pas de même : les caractères accentués valent généralement deux octets. Les caractères un peu plus spéciaux (comme TM) par exemple valent 3 octets…

Ceci a de nombreux impacts dans les programmes. Que l’on peut classer en deux familles:

  • Ceux qui concernent l’apparence du caractère
  • Ceux qui concernent la taille du caractère

Pour ce qui est du problème de l’apparence, vous l’aurez sans doute déjà rencontré sur Internet : à la place de caractères comme é vous allez trouver des choses comme ou d’autres hiéroglyphes du même ordre. Ce problème se résout par transcodification. Il arrive lorsque l’on charge des données d’un encoding donné (WEBISO) par exemple vers une base qui est dans un autre encoding (UTF8). Ainsi lorsque l’on migre une application en UTF8, il faudra bien faire attention à l’encoding des flux d’entrée de la base et l’encoding des flux de sortie. Si l’on ne fait pas attention, les applications réceptrices ne seront peut être plus à même de traduire les caractères correctement.
Lorsque les fichiers sont importés par SQL-LOADER ou Java, il est généralement possible de spécifier l’encoding du fichier source des données en positionnant simplement la locale de l’utilisateur lançant le batch de chargement. L’encodage de destination étant alors pris en charge par le moteur Oracle
Dans le cas de fichiers extraits de la base par PL-SQL, si rien n’est précisé, c’est l’encoding de la base qui est utilisé par le PL-SQL (en java par contre ce sera toujours celui de l’utilisateur batch). le fichier devra alors être converti. Les outils de conversion sont standard sous UNIX.

La seconde catégorie d’impacte concernant la taille de données est plus génant car il nécessite souvent de redéfinir le modèle de données.
Le type de données usuel en SQL pour stocker des chaînes de caractères est VARCHAR2. Une déclaration classique étant VARCHAR2(10) pour indiquer que l’on souhaite 10 … à votre avis ?!? … BYTES !! et c’est là tout le problème. Par défaut on compte en BYTE et ce à cause d’un paramètre qui est NLS_LEN_SEMANTIC qui fixe si l’on parle par defaut en BYTE ou en CHAR. Du coup une chaîne de carcatère UTF8 telle que “abcdefghij” qui fera 10 caractères et 10 octets tiendra dans le champ et une chaîne comme “àbcdéfghïj” fera 10 caractères toujours mais cette fois 13 octets (à=2 é=2 ï=2) et Oracle vous retournera une erreur. Le plantage ne sera donc pas systématique d’où le danger…
Pour ce sortir de cette embûche il existe une solution qui semble simple : changer le paramètre NLS_LEB_SEMANTIC, mais voilà, Oracle est un produit comment dire…. leader (c’est ca le terme) et ce paramètre est des plus mal supporté. Ainsi l’application de patch par exemple nécesiste un retour au mode BYTE qui pourra être désastreux pour l’intégrité de la base si l’on ne fait pas attention … ajoutons à cela d’autres problèmes d’incompatibilité avec certains progiciel… bref cette solution simple n’est pas vraiment applicable.
Les solutions qui sont donc plus adaptées conduisent à retoucher les modèles de données en transformant les VARCHAR en NVARCHAR, ce second type ayant le bon goût de se définir par défaut en nombre de caractères plutôt qu’on nombre d’octets ou encore de choisir des définitions explicites pour les VARCHAR. Par exemple VARCHAR2(10 CHAR) indiquera que l’on parle bien de 10 caractères et non 10 octets. Dans ce cas, Oracle allouera non pas 10 octets mais 40 octets dans la colonne (le maximum que l’on peut atteindre avec 10 caractères) et vérifiera lors des insert/update que l’on ne dépasse pas le nombre de caractères souhaité.

Cette solution de retouche du modèle de donnée est plutôt lourde mais c’est sans doute la seule vraiment pérenne. En plus de la définition des tables il faudra retoucher une partie du code:
Les PL-SQL si les variables intermédiaires ne sont pas construites par références aux colonnes de la table.
Les SQL/Loaer si les fichiers de contrôles spécifient le type de destination.
Les chargements de type ETL lorsque ceux-ci définissent les types en entrée et sortie (cas courant)

Une solution de replie qui peut être envisagé de façon temporaire consiste à tronquer les données en entrées (type sqlloader) en spécifiant une taille en octets plutôt qu’en caractères. En PL/SQL la commande SUBSTRB par exemple permet de couper une chaîne en une sous chaîne de n octets de long. La coupure est intelligente, elle ne laissera pas de demi-caractères.

An other version i wrote … for our english spoker friends ….

Intro

The xxxxx standards for database encoding, mainly for Applications, is now UTF8 and more precisely AL32UTF8. UTF8 encoding allow databases to support any languages even Asian ones. Most of the XXXX legacies are based on WEBISO8859-1 named also Latin-1 encoding to support special characters like éàî … This choice has a lots of benefic import for our master applications but it also create some new considerations we have to take into account during the development phase.

Let’s try to explain what change make the UTF8 encoding …

Some encoding definitions

WEBISO was an easy encoding for systems as it represents each character on one byte. the first 128 characters are ascii compabible and the next 128 characters represent local and special characters like our “éçà”. Some different encoding name and local pages are used to change the 128 to 255 character code for each country. That means that by the past a file provided from a country to an other (like EST Europe to WEST Europe) could be received with strange or incorrect characters. As an example a central europe file could contain a character like Č (encoded as 0xC8 in ISO-8859-2) this character would be print as a È (encoded as 0xC8 in ISO-8859-1) in a western europe application. A file transcodification is needed but as Č character do not exists in ISO-8859-1 it should be replaced by something like a C. That was for the past …

UTF-8 is a good way to represent any characters in the world. It allows to use up to 4 bytes to represent a character when the choosen encoding is AL32UTF8. As a consequence characters Č and È can be represented in the same page / file. Processing this king of encoding is a little bit more complicated as not all the caracters require 4 byte as to save space in files the most frequent characters will be encoded in 1 byte, the less frequent in 2 bytes … etc Some characters like ? will required 3 bytes (the encoding is 0xE284A2).

As a way to have compatible files (mainly US) a file containing only ASCII-7 characters will be exactly the same in ISO-8859-1, ISO-8859-2 and UTF8. This situation look like a good news but it hides most of the problems we are meeting by implementing UTF8.

Encoding difference consequences (first issues)

By the past when an application received a file in a wrong encoding, the data were load normally in the application but displayed badly as we saw previously. This end-user issue was solved by translating the incoming file and so transform the unknown letter to the nearest one.

UTF8 can create the same issue when a file/flow is imported without the needed translation into an ISO-8859-1 application. This application will print some non-understandable caracters. As an exemple ? character will be print as “â?¢” (with ? for a non printable character) that is for the classical problem…

Now, imagine that the incoming file use a fixed column format, the program will try to read a data at a certain position… concidering the file as an ISO-8859-1 it will try to find that position in a certain byte from the beginning. As the character lenght is not fixed, each time a character will need more than 1 byte, the program will reach the wrong position. As an impact of this jobs can abend or load wrong data in the database.

The solution to bypass this situation is simpely to convert the incoming file in the desired encoding as it was done by the past.

Now, as current caracters like “éèà” cause the described issue and our applications mix UTF8 and WEBISO, it is really important to describe each flow by an encoding type. I mean, when a functionnal analyst describes a flow between two application he must indicate what is the source encoding and what is the destination encoding. Each developpeur should request the encoding type he have to specify when he develop a flow input / output procedure. As a test procedure in unitary tests or qualification, testers should fill some data with the maximum number of non common characters to verify UTF8 application’s compatibility.

Regarding our experience of the problem:

  • EAI flow encoding is generally defined in the flow
  • File import default encoding is genererally defined by the batch user encoding. So by changing this batch user encoding Java will choose the right input encoding. Once java put the data in a String element, it will be converted in Unicode, then reconverted in the database encoding when stored. SQL-loader work in the same way by using the batch user encoding and detect then perform the conversion before updating the database.
  • File export with java run similary as import : output encoding will be by default batch user encoding.
  • PL/SQL export will cause trouble as the output encoding used is the Database encoding. The generated file will have to be converted by a shell command in part of the case.

Encoding lenght consequences (second issues)

The previously described problem hide an other one, more sensible at the origin of that article… To understand it we must go deeper in Oracle configuration parameters… By default an oracle database has a parameter named NLS_LEN_SEMANTIC set to BYTE. The meaning of this parameter is to defined whar we are talking about when we define a classical type like a VARCHAR(10). This parameter answer the following question : what kind of stuff I want to put 10 times in my string ?!?

Regarding the NLS_LEN_SEMANTIC parameter VARCHAR2(10) means “reserve a maximum of 10 BYTES for a string“. Here is the main issue …. as most of us would use this syntax to indicate Oracle to reserve 10 characters…. as we had allways done ! Why is it so different with UTF8 ? It is not different … VARCHAR2(10) or CHAR, VARCHAR allways defined a number of bytes but before using UTF8 1 characters was equivalent to 1 byte.

As a consequence of this, imagine an Xnet screen with a form containing a user free text column with a maximum size of 10. User will for exemple enter “à découper”, Java program will get it, count the characters and accept it as the number of character is equal to 10 – lower or egal the constraint . It will generate the SQL request and send it to Oracle. Oracle will verify the 10 byte constraint and reject it…. This issue is also impacting data flow loading : any data able to contain non ASCI-7 characters are able to abend a loading process. As an impact referential files (like item definition) can abend during loading and stop a batch plan in production because the day before someone create in the referential a longer line with “éàè” characters.

There are three solutions to avoid this situation

  • The simpler one is to change the NLS_LEN_SEMANTIC parameter. This one can be set to CHAR, in this case VARCHAR2(10) would significate 10 CHARS. This solution is really low cost as the database definition for programs do not change and no program have to be rewrite. Unfortunatelly Oracle do not really support this parameter and DBA actually refuse to put it in place. As an example of known problem each patch of the Oracle engine have to be execute in BYTE mode. When this parameter is not set back to CHAR at the end of the Oracle update process, the database could be corrupted and unrecoverable. more over some tools are not yet validated on that mode as Datastage.
  • An other way (the best in my point of view) is to precise for each VARCHAR2 the unit desired. Yous should declare VARCHAR2(10 CHAR). Oracle, il this case will reserve physically 4 more times bytes : 10 CHARS will correspond to 40 BYTES. But oracle will also add a constraints of 10 characters on the column.  In Toad or DB-Visualizer you will generally see the column definition as 40 … be carefull, on toad, is seem that this information is displayed differently …randomly.
  • The last way to implement it is to use the NVARCHAR2 definition as NVARCHAR2 (NVARCHAR, NCHAR) are Character mode storage type by default. so NVARCHAR(10) will correspond to 10 CHARS. So one of the way to convert entierly a database to be UTF8 ready with less effort is to convert all the VARCHAR2 elements to NVARCHAR2 equivalent elements. N/CHAR types encoding is not defined by the usual NLS_ENCODING parameter but it is defined by something likne NLS_NCHAR_ENCODING parameter, becarefull in most of our database, this last was not correclty defined : AL32UTF8 is the standard, not AL16UTF8.

Implemented solution

The best way to do is to study the situation during the analysis phase and to build the solution with the right type… use VARCHAR2(xx CHAR), CHAR(xx CHAR) … definitions is the best way to avoid encoding problems.

In other case you will discover these issues during Central Acceptance or worst in Production, in these situations we studdied two way to solve the problem:

  • The global way: Here, we will convert all the VARCHAR type into NVARCHAR or VARCHAR(xx CHAR). As un impact, most of the SQL/LOADER and ETL process will have to be rewrite. The rewriting time is not big as the unit operation is simple.But, if you own some hundreds of ETL process, it will require some weeks of work. Java should not be impacted. PL/SQL routine should be compatible as mutch as the internal variables use referenced type more instead of fixed types but they should have to be recompiled. All the non regression tests should be perform.
  • The selected way: This method is the oposite one, it consists to change the minimum number of column. As the UTF8 lenght issue only impacts user typed data we can limit the perimeter to columns provided by the internal UI and external flows. Generally these labels represents less than 1% or the overall. The method we used to do that is to determine the source column by the rules previously indicated. Then determine where these columns will be propagated in the application (data copy or transformation) until they reach an output like an ETL flow. The objective of this aproach is to reduce the rework perimiter, mainly regarding the number of impacted ETL and also limit the non regression tests. You will be able to adjust the final perimeter regarding the lavel of risk of each field.

A good way to simpely verify the result of the patch and the non regression is to fill all the source column with a full string of “éàè” characters and start the application…

Conclusion

UTF8 is not just a technical improvement there are consequencies during the analysis phase, the datamodel and the deducted database shema has to take it into account. In the application interfaces encoding has also to be described and sometime conversion scripts will be needed. As a standard a Master Application should store data in UTF8 and should provide an UTF8 interface to the external Master Applications.

When UTF8 encoding impact your application as defined previously, we know some workaround, intermediate solution or global solution we are able to put in place. All of these solution require time to be put in place. These solutions can be cumulative to be able to be planned in patch / release … step by step…

If your application is currently UTF8 and you never think about these problem … I invite you to try to enter a full line of “éééééé” in one of your UI and check the result in database… You may discover a source of risk…

LANG (charset) par défaut d’une JVM

Par defaut, la variable $LANG permet de choisir l’encoding par defaut de la JVM. Cette variable est dans /etc/sysconfig/i18n

Pour forcer un notre encodage, il est possible de passer l’option -Dfile.encoding=ISO-8859-1″ comme paramètre de la JVM ; ceci permettra de considére ce charset pour les fichiers lus par la JVM. Pour que tomcat serve des pages dans cet encoding il faudra ajouter :-Djavax.servlet.request.encoding=ISO-8859-1
Dans les dernieres version d’openSuse le fichier i18n n’existe plus, il faut alors ajouter ce parametres aux parametres de le JVM dans le fichier j2ee puis relancer tomcat. Il est aussi possible de passer par /etc/sysconfig/langage pour changer la variable $LANG

Cette solution permet de fixer le charset par defaut dans le mode souhaité qui doit etre le même que celui d’apache quand on utilise l’ajp13 ; à UTF-8 ou WEBISO-8859-1 … ou autre

Gestion des Cookies dans un ensemble de pages JSP avec @include

J’ai rencontré pas mal de problèmes avec les JSP est les cookies, ces derniers sont assez utile pour laisser quelques traces et configuration sur le client de l’internaute qui peuvent être utiles pour l’optimisation de la navigation sur un site ou pour la suivi des statistiques.
Lors de l’intégration dans un contexte JSP utilisant des includes, il y a pas mal de petites règles à respecter pour que cela fonctionne sans quoi il y a moyen de s’arracher les cheveux !

Ce qu’il faut avoir : les Cookies doivent être envoyé dans l’entête de la réponse. Lorsque les pages générées sont de taille importante, le seveur d’application peut envoyer la page par morceaux dès qu’il y a suffisemment de données pretes. Du coup, si l’ajout du cookie est fait après le premier flush, il ne sera pas pris en compte. Il est donc important que les Cookie soient placés suffisemment tot dans la page pour éviter le flush.
Dans un contexte d’utilisation avec des include de JSP, il faut savoir en plus que le tag jsp:include a tendance à déclencher des flush (ce qui me semble étange puisque le fulsh peut être passé en option et que par défaut il ne l’est pas). Du coup, si le cookie est enregistré par une sous JSP, il ne sera pas pris en compte par le navigateur ; arrivant trop tard. La solution dans ce cas est d’utiliser un tage @include à la place du jsp:include, mais attention son utilisation est très différente puisque dans ce cas il s’agit de l’import du source importé avant la compilaton de la JSP et non de l’appèle à une autre JSP. Ce qui peut poser des problème si la modification est faite à postérieuri.

Bref, ma solution dans ce cas, réunir dans un fichier d’entête toutes les opération intervenant sur les Cookies et intégrer cette page au travers d’un @include dans toutes les pages du site en entête. L’utilisation généralisée du @include etant à proscrire à mon avis.

Option de cache des objets statiques avec apache2

Lorsque l’on modifie le CSS d’un site il est important de rafraichir la page sans quoi celle-ci est affichée avec le CSS ancienne version … résultats déplorables garantis !

Pour contourner celà, il est tout à fait possible de forcer un rafraichissement plus régulier de certains éléments comme les feuilles de style. Pour se faire, il faut ajouter à la config d’Apache quelques lignes que voici :

ExpiresActive On
ExpiresDefault “access plus 1 hours”
ExpiresByType text/css “access plus 1 hours”
ExpiresByType text/html “access plus 1 hours”
ExpiresByType image/gif “access plus 1 days”
ExpiresByType image/jpeg “access plus 1 days”
ExpiresByType image/png “access plus 1 days”

Bien sûr  il y a différentes façon de faire, et le paramétrage est à votre guise…
Attention toute fois, si l’on suit le modèle ci-dessus, les pages dynamiques (jsp, php) seront cachées elles-aussi ce qui peut vous poser des problèmes de raffrachissement ! Elles sont en effet identifiées sous le type text/html.
Une autre configuration peut être : ExpiresActive On
ExpiresDefault “access plus 1 hours”
ExpiresByType text/css “access plus 1 hours”
ExpiresByType text/html “now”
ExpiresByType image/gif “access plus 1 days”
ExpiresByType image/jpeg “access plus 1 days”
ExpiresByType image/png “access plus 1 days”