<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>AbriCoCotier.fr &#187; Python (et Django)</title>
	<atom:link href="http://www.abricocotier.fr/tag/python/feed" rel="self" type="application/rss+xml" />
	<link>http://www.abricocotier.fr</link>
	<description>Analyses et anticipations sur le web et les nouvelles technologies de demain.</description>
	<lastBuildDate>Tue, 07 Feb 2012 21:17:20 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Online Compression Tools : un petit outil pour archiver (Tar) et compresser (tar.gz) vos fichiers</title>
		<link>http://www.abricocotier.fr/12306-online-compression-tools-un-petit-outil-pour-archiver-tar-et-compresser-tar-gz-vos-fichiers</link>
		<comments>http://www.abricocotier.fr/12306-online-compression-tools-un-petit-outil-pour-archiver-tar-et-compresser-tar-gz-vos-fichiers#comments</comments>
		<pubDate>Tue, 10 Aug 2010 14:04:25 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12306</guid>
		<description><![CDATA[Cela faisait pas mal de temps que je voulais réussir à faire un petit outil (appelé très simplement Tar/Gzip Online Tools) pour compresser et archiver des fichiers en ligne. Le but était, au départ, de faire un outil permettant de &#8230; <a href="http://www.abricocotier.fr/12306-online-compression-tools-un-petit-outil-pour-archiver-tar-et-compresser-tar-gz-vos-fichiers">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Cela faisait pas mal de temps que je voulais réussir à faire un petit outil (appelé très simplement <a href="http://abricocotierfr.appspot.com/compression"><strong>Tar/Gzip Online Tools</strong></a>) pour compresser et archiver des fichiers en ligne. Le but était, au départ, de faire un outil permettant de compresser des fichiers, et un autre outil permettant de les décompresser. A la manière de <a href="http://wobzip.org/">Wobzip</a>, par exemple. Bref, le problème, c&#8217;est que je suis et reste limité par le datastore, et par la plateforme AppEngine, qui empèche d&#8217;écrire quoi que ce soit. Loin de moi l&#8217;idée de dire du mal de cette contrainte : je souhaite juste attirer l&#8217;attention sur un aspect qui complique un tout petit peu la chose.    <span id="more-12306"></span></p>
<p style="text-align: center;"><img src="http://www.abricocotier.fr/wp-content/uploads/2010/08/tar_gzip_tools.jpg" alt="" title="tar_gzip_tools" width="579" height="336" style="border: 2px solid black;"  /></p>
<p>Très rapidement, j&#8217;ai donc fait un outil permettant d&#8217;archiver (rassembler des fichiers dans un même fichier, qui conserver l&#8217;arborescence de ceux-ci), grâce à la librairie tarfile de python. La difficulté a été pour le fait que tous les tutoriels donnés sur internet considéraient que l&#8217;on pouvait écrire sur le disque (alors que là, non). Il a donc fallu jouer avec la création de fichiers stream (StringIO.StringIO), ce qui m&#8217;a permit de découvrir l&#8217;existence de la librairie cStringIO, qui, selon le site de Python, a les même fonctionnalités que StringIO, sauf qu&#8217;elle est plus rapide. Notez que j&#8217;ai été débloqué dans mes recherches via ce <a href="http://groups.google.com/group/google-appengine-python/browse_thread/thread/5d840a13986d60e3/3c4c1d78878f4007">thread</a>, ainsi que par <a href="http://www.gossamer-threads.com/lists/python/dev/759038">celui-là</a>.</p>
<p>J&#8217;ai eu beaucoup de mal à faire fonctionner le zip (en utilisant ZipFile) et le gzip, dans la mesure où gzip n&#8217;est pas un archiveur, mais seulement un compresseur. C&#8217;est à dire qu&#8217;on ne peut normalement lui donner qu&#8217;un seul fichier, donc il va compresser le contenu. Zip, lui, fait archiveur aussi, mais je n&#8217;ai jamais réussi à le faire fonctionner. Si vous avez déjà réussi à faire fonctionner du python+zip de plusieurs fichiers (quand il y en a un, ça va, c&#8217;est quand il y en a plusieurs qu&#8217;il y a des problèmes), je suis preneur de votre code.</p>
<h2><strong>Décompression</strong></h2>
<p>Par contre, en ce qui concerne la décompression des archives, je ne pouvais pas gérer celà sans passer par le DataStore, ou bien il aurait fallu que je passe par une API stockant les fichiers à l&#8217;extérieur. En gros, soit je stocke les fichiers dans le DataStore, auquel cas c&#8217;est peu fantastique (et encore, dans l&#8217;idéal, je pourrais les stocker dans MemCached : c&#8217;est une piste à creuser ! Je m&#8217;y atèle dès ce soir.), car le DataStore est assez sensible en terme de quantité de données stockées, et de &laquo;&nbsp;taille de fichier&nbsp;&raquo; à mettre dedans (1 Mo maximum). C&#8217;est donc la raison pour laquelle j&#8217;ai abandonné l&#8217;idée dans un premier temps.</p>
<p><strong>Edit :</strong> Depuis hier, quand j&#8217;ai écris ces lignes, il y a eu quelques améliorations :</p>
<p>D&#8217;abord, <strong>le programme dé-gzippe parfaitement</strong> (en fait, je lui ai donné des tar.gz et des fichiers normaux pour tester, ça fonctionne pas mal). <em>Enfin je pense. J&#8217;observe cependant des différences entre le nombre d&#8217;octets pour le tar créé à partir du tar.gz et le tar que j&#8217;aurais créé moi-même (la quantité d&#8217;octet n&#8217;est pas la même). Je soupçonne des différences entre les librairies utilisées, qui ne seraient pas les même entre la librairie tarfile et celle qu&#8217;utilise Gnome/Ubuntu.</em></p>
<p>Ensuite,<strong> le programme reconnait également quand le fichier porte l&#8217;extension .tar</strong>, auquel cas il liste les fichiers contenus dans le tar. Je n&#8217;arrive pas à gérer parfaitement le cas où il y aurais une arborescence de fichiers, car il faudrait reproduire cette arborescence en ligne (et ça, c&#8217;est violent), mais je parviens quand même à afficher la liste des fichiers (il faut tester pour voir, ou regarder sur le screenshot ci-dessous, où l&#8217;archive contenait entre-autres un dossier &laquo;&nbsp;AnaliZ&nbsp;&raquo; avec des fichiers dedans). En cliquant sur chaque fichier, j&#8217;enclenche le téléchargement direct de celui-ci. Bref, ça marche (même si ces petites subtilités ternissent la marche correcte du script).</p>
<p style="text-align: center;"><img src="http://www.abricocotier.fr/wp-content/uploads/2010/08/tar_uncompressed.jpg" alt="" title="tar_uncompressed" width="580" height="361" style="border: 2px solid black;"  /></p>
<h3><strong>Fichiers de grande taille dans Memcached et/ou le DataStore</strong></h3>
<p>Enfin, et c&#8217;est là que c&#8217;est beau, pour chaque fichier affiché, on peut le télécharger. Notez que, comme je n&#8217;enclenche pas le téléchargement directement après l&#8217;upload, il faut que je passe par un stockage interne des fichiers. Hors, ceux-ci peuvent souvent être amenés à faire plus de 1 Mo, c&#8217;est bien normal (quand on sait qu&#8217;on atteint facilement des taux de compression de 70% avec des fichiers texte, ça ne m&#8217;étonne pas du tout qu&#8217;un fichier compressé de 500ko puisse faire, après décompression, une taille de 1,6 Mo, par exemple), donc je dois gérer le stockage de fichier d&#8217;une taille supérieure à 1 Mo. Mais <strong>AppEngine limite à 1 Mo la taille des données stockées par instance dans MemCached ou le DataStore</strong>. J&#8217;ai donc découpé la string d&#8217;octets pour que chaque partie fasse au maximum 1 Mo et rentre dans une instance de MemCached. Oui, j&#8217;ai utilisé MemCached parce que je ne veux pas charger mon DataStore pour rien (les données décompressées n&#8217;ont pas pour but de rester longtemps disponibles, donc elles n&#8217;ont rien à faire dans le DataStore, et<strong> je les mets dans Memcached pour une durée d&#8217;environ une heure</strong>, ce qui laisse amplement le temps de les télécharger !).</p>
<p>Bon, voilà, donc au final, ça fonctionne correctement pour les .tar et les .gz, ce pour une taille de fichier entrant limitée par HTML (c&#8217;est à dire 2 Mo pour les navigateurs qui ne sont pas en HTML5, et davantage pour les autres&#8230; personnellement, j&#8217;ai testé avec 8Mo sur mon serveur en local, et celui-ci n&#8217;a pas bronché).</p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12306-online-compression-tools-un-petit-outil-pour-archiver-tar-et-compresser-tar-gz-vos-fichiers">Permalien</a> |
<a href="http://www.abricocotier.fr/12306-online-compression-tools-un-petit-outil-pour-archiver-tar-et-compresser-tar-gz-vos-fichiers#comments">Ajoutez un commentaire !</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12306-online-compression-tools-un-petit-outil-pour-archiver-tar-et-compresser-tar-gz-vos-fichiers/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Système de cache sur AppEngine : quelles performances ?</title>
		<link>http://www.abricocotier.fr/12327-systeme-de-cache-sur-appengine-quelles-performances</link>
		<comments>http://www.abricocotier.fr/12327-systeme-de-cache-sur-appengine-quelles-performances#comments</comments>
		<pubDate>Mon, 09 Aug 2010 15:53:46 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[Python (et Django)]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12327</guid>
		<description><![CDATA[J&#8217;aime assez le système de cache de WordPress mis en place par le plugin WP-Super-Cache qui consiste à générer la page et à la mettre physiquement dans un dossier de cache, puis à dire à Apache de toujours regarder si &#8230; <a href="http://www.abricocotier.fr/12327-systeme-de-cache-sur-appengine-quelles-performances">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>J&#8217;aime assez le système de cache de WordPress mis en place par <strong>le plugin WP-Super-Cache qui consiste à générer la page et à la mettre physiquement dans un dossier de cache, puis à dire à Apache de toujours regarder si la page demandée par un visiteur n&#8217;existe pas déjà avant d&#8217;en demander la génération</strong>. Sur AppEngine, il y a un problème de taille : il est impossible de gérer la création/suppression de fichiers en dur. Point de cache &laquo;&nbsp;physique&nbsp;&raquo;, donc. Pour autant, on peut avoir envie de servir un grand nombre de pages, et donc d&#8217;avoir un système performant et pour autant, peu gourmand en performances. J&#8217;ai donc décidé de créer un petit script utilisant MemCached pour stocker le contenu généré, et j&#8217;ai regardé les différences de temps rapporté par les logs.    <span id="more-12327"></span></p>
<h2><strong>Explication du fonctionnement : </strong></h2>
<p>Très rapidement, voilà comment fonctionne le script que j&#8217;ai écris : a chaque URL demandée par le client (quand celle-ci est après le path /viamemcached ou /viadatastore), le script va chercher le contenu HTML de la page directement mémoire. Dans MemCached si l&#8217;URL demandée commence par /viamemcached ou dans la mémoire (pour AppEngine, il s&#8217;agit du dataStore) quand l&#8217;URL commence par /viadatastore).</p>
<p>Le principe est donc, quand l&#8217;URL demandée commence par /viadatastore, de simuler une recréation de page, à cause de la requête à la base de donnée. Et de tenter de reproduire le fonctionnement d&#8217;un cache en passant par MemCached.</p>
<h2><strong>Code :</strong></h2>
<p><strong>Fichier main.py :</strong></p>
<p>Le fichier ci-dessous répartit les requètes en 3 : la page d&#8217;admin (création de contenu), derrière /upload, la catégorie des pages nécessitant la génération de contenu via appel au datastore (via l&#8217;url /viadatatore), et enfin la catégorie des pages nécessitant juste la requête à Memcached.</p>
<pre class="brush: python; title: ; notranslate">
import cgi
import firstPage
import userpage
import wsgiref
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

ROUTES = [
    ('/',firstPage.Home),
    ('/upload',userpage.Upload),
    ('/viadatastore/?([\w]*)/?', userpage.ContentUsage),
    ('/viamemcached/?([\w]*)/?', userpage.ContentCached),
 # other handlers here...
]

def main():
  application = webapp.WSGIApplication(ROUTES)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == &quot;__main__&quot;:
  main()
</pre>
<p><strong>Fichier firstpage.py :</strong></p>
<p>Le fichier firstpage.py est &laquo;&nbsp;juste&nbsp;&raquo; le fichier de Home, répartissant entre les trois sections (au cas où vous voudriez reproduire l&#8217;application chez vous).</p>
<pre class="brush: python; title: ; notranslate">
from google.appengine.ext import webapp

class Home(webapp.RequestHandler):
    def get(self):
        self.response.out.write(&quot;&quot;&quot;&lt;html&gt;
  &lt;head&gt;
  &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
    &lt;title&gt;Test&lt;/title&gt;
            &lt;/head&gt;
            &lt;body&gt;
     &lt;center&gt;
        &lt;h1&gt;TEST&lt;/h1&gt;
    &lt;/center&gt;
    &lt;br&gt;&lt;br&gt;
            &lt;center&gt;&lt;div style=&quot;text-align:left;margin:0% 30% 0% 30%;&quot;&gt;
             &lt;ul&gt;
                &lt;li&gt;
                    &lt;a href=&quot;/upload&quot;&gt;Upload&lt;/a&gt;
                &lt;/li&gt;
                &lt;li&gt;
                    &lt;a href=&quot;/viadatastore&quot;&gt;viadatastore&lt;/a&gt;
                &lt;/li&gt;
                &lt;li&gt;
                    &lt;a href=&quot;/viamemcached&quot;&gt;viamemcached&lt;/a&gt;
                &lt;/li&gt;
             &lt;/ul&gt;
             &lt;/div&gt;
         &lt;/center&gt;
     &lt;/body&gt;
 &lt;/html&gt;&quot;&quot;&quot;)              
</pre>
<p><strong>Fichier userpage.py :</strong></p>
<p>Et enfin, le fichier userpage.py comprend les trois sections. Dans la classe Upload, on voit le formulaire d&#8217;envoi et le script d&#8217;enregistrement du contenu HTML dans le datastore. Dans la classe ContentUsage, on a la génération du contenu via DataStore, et dans ContentCached, celle via Memcached.</p>
<pre class="brush: python; title: ; notranslate">

import cgi
import logging
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.api import memcache

class pageUrlContent(db.Model):
    url = db.StringProperty(multiline=False)
    content = db.TextProperty()
    date = db.DateTimeProperty(auto_now_add=True)

def add_content_in_base(url,content):
    pageUrlContent = pageUrlContent()
    pageUrlContent.url = cgi.escape(url)
    pageUrlContent.content = db.Text(content)
    pageUrlContent.put()  

def render_list_previous_queries(url):
    listurls = pageUrlContent.all().filter(&quot;url =&quot;, url)
    return listurls

def fetch_content_in_base(url):
    final_content = &quot;&quot;
    contents = render_list_previous_queries(url)
    if contents:
        for cont in contents:
            final_content = cont.content
    return final_content

def fetch_content_in_cache(url):
    thiscontent = memcache.get(url)
    if thiscontent:
        return thiscontent
    else:
        content = fetch_content_in_base(url)
        if not memcache.set(url, content):
            logging.error(&quot;Memcache *set* failed in fetch_content_in_cache(url)&gt;else&gt;ifnot.&quot;)
        return content  

class Upload(webapp.RequestHandler):
    def get(self):
        self.response.out.write(&quot;&quot;&quot;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
&lt;title&gt;Content Upload&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;center&gt;
&lt;form action=&quot;/upload&quot; enctype=&quot;multipart/form-data&quot; method=&quot;post&quot;&gt;
  &lt;div&gt;&lt;input type=&quot;text&quot; name=&quot;url&quot;&gt;&lt;/div&gt;
  &lt;div&gt;&lt;textarea COLS=30 ROWS=30 name=&quot;content&quot;&gt;&lt;/textarea&gt;&lt;/div&gt;
  &lt;div&gt;&lt;input type=&quot;submit&quot; value=&quot;Send IT&quot;&gt;&lt;/div&gt;
&lt;/form&gt;
&lt;/center&gt;
&lt;/body&gt;
&lt;/html&gt;
    &quot;&quot;&quot;)
    def post(self):
        url = self.request.get(&quot;url&quot;)
        content = self.request.get(&quot;content&quot;)
        add_content_in_base(url,content)
        self.response.out.write(&quot;&quot;&quot;&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&gt;
&lt;head&gt;
&lt;title&gt;Content Upload&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;center&gt;
OK
&lt;/center&gt;
&lt;/body&gt;
&lt;/html&gt;
    &quot;&quot;&quot;)  

class ContentUsage(webapp.RequestHandler):
    # part of ImageHandler class
    def get(self,id):
        content = fetch_content_in_base(id)
        self.response.out.write(content)    

class ContentCached(webapp.RequestHandler):
    # part of ImageHandler class
    def get(self,id):
        content = fetch_content_in_cache(id)
        self.response.out.write(content)
</pre>
<h2>Performances :</h2>
<p>Les logs de AppEngine sont de ce type :</p>
<blockquote><p>08-09 06:46AM 39.408 <strong>/viamemcached/loremipsum</strong> 200 22ms 19cpu_ms 7kb Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.22 Safari/534.3,gzip(gfe)<br />
149.6.164.150 &#8211; - [09/Aug/2010:06:46:39 -0700] &laquo;&nbsp;GET /viamemcached/loremipsum HTTP/1.1&#8243; 200 7725 &#8211; &laquo;&nbsp;Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.22 Safari/534.3,gzip(gfe)&nbsp;&raquo; &laquo;&nbsp;abricocotierfrtests.appspot.com&nbsp;&raquo; ms=23 cpu_ms=19 api_cpu_ms=0 cpm_usd=0.001455</p></blockquote>
<p>D&#8217;après ce que j&#8217;ai compris, on peut extraire 4 valeurs : ms=23 cpu_ms=19 api_cpu_ms=0 cpm_usd=0.001455. Je n&#8217;ai pas bien compris à quoi correspondaient tous ces temps, mais ce dont je suis sûr, c&#8217;est que le api_cpu_ms correspond au temps de l&#8217;API du DataStore.</p>
<p>En effet, j&#8217;ai fait une vingtaine de tests (enfin 20 reload par catégorie), et je mes ai mis dans un tableau, puis j&#8217;en ai fait des graphiques :</p>
<p>Le tableau de données :</p>
<p style="text-align: center;"><iframe width='580' height='400' frameborder='0' src='https://spreadsheets.google.com/pub?key=0AnIym6Arf51ydGVlNW9nWXA0eTMzUFVBdjFodHdVX1E&#038;hl=fr&#038;single=true&#038;gid=0&#038;output=html&#038;widget=true'></iframe></p>
<p><strong>Millisecondes :</strong></p>
<p style="text-align: center;"><img title="datastore ms vs memcached ms" src="http://www.abricocotier.fr/wp-content/uploads/2010/08/datastore-ms-vs-memcached-ms.jpg" alt="" width="580" /></p>
<p><strong>CPU Millisecondes :</strong></p>
<p style="text-align: center;"><img title="cpu ms datastore vs memcached" src="http://www.abricocotier.fr/wp-content/uploads/2010/08/cpu-ms-datastore-vs-memcached.jpg" alt="" width="580" /></p>
<p><strong>API CPU Millisecondes :</strong></p>
<p style="text-align: center;"><img title="api cpu ms" src="http://www.abricocotier.fr/wp-content/uploads/2010/08/api-cpu-ms.jpg" alt="" width="580" /></p>
<p><strong>CPU USD :</strong></p>
<p style="text-align: center;"><img title="cpm_usd" src="http://www.abricocotier.fr/wp-content/uploads/2010/08/cpm_usd.jpg" alt="" width="580" /></p>
<h2><strong>Conclusion</strong></h2>
<p>De ce que l&#8217;on peut constater, effectivement l&#8217;API_CPU_MS est à zéro après le premier appel sur Memcached, ce qui correspond bien à la premier mise en cache (lors du premier appel), puis c&#8217;est le cache qui prend le relais, et le datastore n&#8217;est plus appelé (et on remarque que les appels au datastore font tous 22ms). Pour le reste, c&#8217;est plus compliqué. Il semble que, sur le global, MemCached soit plus rapide (légèrement), mais ce n&#8217;est pas une règle absolue, et on voit que selon les appels, les temps varient, si bien que certains appels via DataStore se révèlent plus rapides que certains via MemCached.</p>
<p>D&#8217;une manière générale, Google imposant de respecter les quotas (ce que je trouve louable), cela impose à coder relativement &laquo;&nbsp;économiquement&nbsp;&raquo;, c&#8217;est à dire avec l&#8217;idée qu&#8217;il faut utiliser Memcached aussi souvent que possible. Je rajoute que pour mon test, l&#8217;appel au DataStore était relativement simple, pas de SELECT *, ce qui peut expliquer la proximité des résultats avec MemCached. Cependant, je pense que dans un cas plus proche de la réalité, où la génération d&#8217;une page nécessite plusieurs appels au DataStore, la différence devient plus évidente. </p>
<p>Je n&#8217;ai pas non plus testé via l&#8217;utilisation de templates Django, mais je doute que celà accélère grand chose au traitement de la page, et donc encore une fois, cela devrait augmenter la différence entre une page donc l&#8217;intégralité du code se trouve en MemCached, et une autre pour laquelle il faut réaliser une certaine quantité d&#8217;opération de génération.</p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12327-systeme-de-cache-sur-appengine-quelles-performances">Permalien</a> |
<a href="http://www.abricocotier.fr/12327-systeme-de-cache-sur-appengine-quelles-performances#comments">1 commentaire</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a>, <a href="http://www.abricocotier.fr/tag/wordpress" rel="tag">Wordpress</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12327-systeme-de-cache-sur-appengine-quelles-performances/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Online PDF Tools, un webware pour effectuer quelques opérations sur vos PDF</title>
		<link>http://www.abricocotier.fr/12248-online-pdf-tools-un-webware-pour-effectuer-quelques-operations-sur-vos-pdf</link>
		<comments>http://www.abricocotier.fr/12248-online-pdf-tools-un-webware-pour-effectuer-quelques-operations-sur-vos-pdf#comments</comments>
		<pubDate>Tue, 03 Aug 2010 07:00:08 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12248</guid>
		<description><![CDATA[Toujours dans la lignée avec ce que j&#8217;ai créé sur abricocotierfr.appspot.com, je voulais créer une application qui me serait vraiment utile tous les jours, et qui remplacerait (pourquoi pas avantageusement) une application que j&#8217;utilise chaque jour. Et ça tombe bien, &#8230; <a href="http://www.abricocotier.fr/12248-online-pdf-tools-un-webware-pour-effectuer-quelques-operations-sur-vos-pdf">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Toujours dans la lignée avec <a href="http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine">ce</a> <a href="http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript">que</a> <a href="http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails">j&#8217;ai</a> <a href="http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web">créé</a> sur <a href="http://abricocotierfr.appspot.com">abricocotierfr.appspot.com</a>, je voulais créer une application qui me serait vraiment utile tous les jours, et qui remplacerait (pourquoi pas avantageusement) une application que j&#8217;utilise chaque jour. Et ça tombe bien, j&#8217;utilise assez fréquemment un outil pour fusionner les PDF (<a href="http://www.mergepdf.net/">celui-là</a>, en l&#8217;occurence), mais il est truffé de pubs, et ne fonctionne plus correctement à partir d&#8217;un certain nombre de PDF. Bref, il n&#8217;est pas parfait. Pourquoi ne pas tenter, avec <strong><a href="http://abricocotierfr.appspot.com/pdftools">Online PDF Tools</a></strong>, d&#8217;en faire un moi-même sur AppEngine ?    <span id="more-12248"></span></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="online_pdf_tools" src="http://www.abricocotier.fr/wp-content/uploads/2010/08/online_pdf_tools.jpg" alt="" width="580" height="344" /></p>
<p>Soyons clair, ce n&#8217;est pas ma première tentative avec cette application. J&#8217;avais déjà tenté une première approche il y a environ deux semaines. Approche qui s&#8217;était révélée infructueuse, devant la difficulté pour trouver des ressources en la matière (du coup, ça tombe bien : c&#8217;est moi qui vais les fournir, cette fois). En effet, sur internet, on trouve majoritairement <strong>deux librairies pour manipuler des PDF en python : <a href="http://pybrary.net/pyPdf/">PyPDF</a> et ReportLab</strong>. ReportLab, dans sa version gratuite, permet de créer des PDF, mais pas vraiment de les lire ni de les fusionner. <strong>Cette fonctionnalité semble être offerte par un addon payant, <a href="http://www.reportlab.com/docs/PageCatchIntro.pdf">PageCatcher</a>.</strong></p>
<p>A l&#8217;inverse, <strong>PyPDF propose une solution très rapidement maîtrisable pour lire des pages d&#8217;un PDF, les concaténer, et donc faire des opérations de fusion ou d&#8217;extraction de PDF de façon très rapide.</strong> Sauf&#8230; que pour AppEngine, il n&#8217;existait aucune ressource indiquant comment on pouvait &laquo;&nbsp;écrire&nbsp;&raquo; le fichier de sortie sur la sortie HTTP, soit donc pour le client. Je rappelle que la plateforme AppEngine ne permet pas réellement d&#8217;écrire et de créer des fichiers, et n&#8217;autorise cela que via des Blobs dans le DataStore (ou dans le BlobStore, si on a activé le billing, ce qui n&#8217;est pas mon cas). A l&#8217;inverse, il existait <a href="http://blog.notdot.net/2010/04/Generating-PDFs-on-App-Engine-Python-and-introducing-Mapvelopes">pas mal de ressources</a> sur le net pour utiliser ReportLab avec AppEngine. Gloups, j&#8217;étais tiraillé entre deux librairies.</p>
<p>Après pas mal d&#8217;essais infructueux, j&#8217;ai eu, par chance, la possibilité d&#8217;obtenir enfin la création de PDF (avec PyPDF, et donc je n&#8217;utilise pas du tout ReportLab), puis celle de la lecture correcte de PDF venant des fichiers envoyés à la page (et non de fichiers lus en local, ce qui est différent). Et du coup, de fil en aiguille, j&#8217;ai fait l&#8217;outil qui est <a href="http://abricocotierfr.appspot.com/pdftools">disponible là</a>.</p>
<p>Plusieurs choses :</p>
<p><strong>Le premier outil permet de fusionner des PDF</strong>. Le titre du document est réglé pour être celui du premier document dans la liste. Le serveur ne traite pas les documents n&#8217;ayant pas l&#8217;extension .pdf, et fait des try/catch sur la concaténation d&#8217;un PDF au fichier de sortie. En gros : si sur le lot, un PDF n&#8217;est pas lisible, le programme l&#8217;ignore, et continue sa route avec le reste.</p>
<p><strong>Le deuxième outil permet d&#8217;enlever certaines pages à un PD</strong>F. Sachant qu&#8217;il faut impérativement lister correctement les pages entre virgules. C&#8217;est simple, le programme prend le fichier en entrée, ainsi que la liste des chiffres représentant les pages à ne pas mettre. Pour chaque page, le programme demande si le numéro de cette page fait partie de la liste des pages à ne pas mettre, et si oui, il ne l&#8217;ajoute pas au fichier de sortie.</p>
<p><strong>Le troisième outil permet d&#8217;extraire une partie de PDF d&#8217;un PDF d&#8217;entrée</strong>. En gros, si, sur un PDF, vous ne voulez conserver qu&#8217;une partie, il suffit d&#8217;indiquer la page de début et la page de fin de la partie de document que vous voulez conserver.</p>
<p>Voilà ! C&#8217;est tout simple. Si vous avez des problèmes pour l&#8217;utilisation, n’hésitez pas à venir le dire ici, je tenterai ensuite de résoudre ce bug.</p>
<p>Pour le reste, voici le code python permettant de manipuler un PDF acquis en variable POST et de le renvoyer en réponse au client. C&#8217;est, en allégé, le code que j&#8217;utilise pour le premier programme. Voilà la partie du code qui est importante :</p>
<pre class="brush: python; title: ; notranslate">
from pyPdf import PdfFileWriter, PdfFileReader

pdf_filename = ''
output = PdfFileWriter()
pdf_file = self.request.POST.get(&quot;file&quot;,None)
pdf_filename = cgi.escape(pdf_file.filename)
pdf_content = PdfFileReader(pdf_file.file)
numpages = pdf_content.getNumPages()
for i in range(0,numpages):
    output.addPage(pdf_content.getPage(i))
self.response.headers['Content-Type'] = 'application/pdf'
self.response.headers['Content-Disposition'] = 'pdf; filename='+pdf_filename
output.write(self.response.out)
</pre>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12248-online-pdf-tools-un-webware-pour-effectuer-quelques-operations-sur-vos-pdf">Permalien</a> |
<a href="http://www.abricocotier.fr/12248-online-pdf-tools-un-webware-pour-effectuer-quelques-operations-sur-vos-pdf#comments">10 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/google" rel="tag">Google</a>, <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12248-online-pdf-tools-un-webware-pour-effectuer-quelques-operations-sur-vos-pdf/feed</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>CRM Mailer, un outil en ligne pour personnaliser vos envois de mails</title>
		<link>http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails</link>
		<comments>http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails#comments</comments>
		<pubDate>Fri, 30 Jul 2010 10:42:37 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12227</guid>
		<description><![CDATA[Toujours dans la lignées des applications Python/AppEngine publiées sur abricocotierfr.appspot.com, j&#8217;ai créé un outil &#8216;CRM Mailer&#8216; auquel je réfléchissais depuis pas mal de temps, et dont la difficulté apparente m&#8217;empéchais de m&#8217;y mettre vraiment. Après avoir pris mon courage à &#8230; <a href="http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Toujours dans la <a href="http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine">lignées</a> <a href="http://www.abricocotier.fr/12025-regle-pixel-online-javascript-css-drag-and-drop">des</a> <a href="http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web">applications</a> <a href="http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript">Python</a>/<a href="http://www.abricocotier.fr/12018-un-generateur-de-mots-de-passe-en-javascript">AppEngine</a> publiées sur <a href="http://abricocotierfr.appspot.com/">abricocotierfr.appspot.com</a>, j&#8217;ai créé un outil &#8216;<strong><a href="http://abricocotierfr.appspot.com/crmmailer">CRM Mailer</a></strong>&#8216; auquel je réfléchissais depuis pas mal de temps, et dont la difficulté apparente m&#8217;empéchais de m&#8217;y mettre vraiment. Après avoir pris mon courage à deux mains (ou plutôt après avoir commencé calmement pour appréhender les difficultés une-à-une), il s&#8217;avère que j&#8217;ai à peu près réussi à obtenir un outil fonctionnel et simple à utiliser.    <span id="more-12227"></span></p>
<h2><strong>L&#8217;outil :</strong></h2>
<p>Le principe est de pouvoir envoyer des mails personnalisés.<br />
Imaginons que vous ayez plusieurs personnes à qui vous souhaitez envoyer un même email, mais avec des parties personnalisées à chaque personne. Il vous faut donc un tableau à double entrées avec, pour chaque destinataire, des champs personnalisés.</p>
<p>Ensuite, dans le titre du mail et dans le corps de celui-ci, des balises {FIELD} indiquent où seront remplacées les informations pour chaque utilisateur, et ce bien sûr avant l&#8217;envoi de chaque mail.</p>
<p>Notez que c&#8217;est le principe de toute application de publipostage inclue dans n&#8217;importe quel CRM ou outil de listes de diffusion, permettant de personnaliser des mails envoyés en masse.</p>
<p>Là, dans mon application, on peut envoyer le mail personnalisé à un maximum de 10 personnes(en cliquant sur &laquo;&nbsp;Add To:&nbsp;&raquo;), et on peut également ajouter des pièces jointes (en cliquant sur &laquo;&nbsp;More&nbsp;&raquo; ; 3 pièces jointes maximum par mail). Pour supprimer un To: ou une pièce jointe, faite simplement &laquo;&nbsp;delete To:&nbsp;&raquo; ou &laquo;&nbsp;less&nbsp;&raquo;, et la ligne sera supprimée.</p>
<p>J&#8217;ai mis des boutons autour du champs Subject et du champs Mail Content pour qu&#8217;il soit facile d&#8217;ajouter les références au sein du texte.</p>
<p>Par contre, je ne suis pas certain que l&#8217;utilisation d&#8217;un tel script soit facile pour tout le monde. Pour tout dire, je l&#8217;ai fait tester à une personne hier soir (qui n&#8217;avait jamais utilisé de CRM), et elle a eu du mal à comprendre le principe de ce script, même si, une fois que je lui ai expliqué l&#8217;idée, ça a été beaucoup mieux.</p>
<p>Les cas d&#8217;utilisation de ce script sont multiples : <strong>mails de faire part, de candidature pour un emploi (avec envoi de CV et/ou lettre de motivation en pièce jointe), de proposition de partenariat commercial,</strong> bref, dès qu&#8217;il y a un grand nombre de personnes à contacter avec une minorité de champs à adapter, ce script peu s&#8217;avérer utile. Evidemment, pour une utilisation plus poussée, je recommende l&#8217;utilisation d&#8217;un vrai CRM (par exemple VTigerCRM).</p>
<p><strong>Exemple : </strong></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="CRM Mailer Tool" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/CRM-Mailer-Tool.jpg" alt="" width="580" height="400" /></p>
<p><strong>Page de confirmation :</strong></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="CRM Mailer Result" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/CRM-Mailer-Result.jpg" alt="" width="580" height="425" /></p>
<h2><strong>Les subtilités de l&#8217;application :</strong></h2>
<p><strong>Les mails sont envoyés au format texte</strong>. Donc pas de possibilité de mettre du HTML. Si je trouve un moyen de le faire, je le ferai. J&#8217;ai tenté avec TinyMCE Editor, mais le problème était que TinyMCE fonctionne avec une iframe, et donc je ne pouvais plus mettre de bouton {FIELD} pour placer les champs à modifier. J&#8217;ai bien tenté de créer un bouton custom avec TinyMCE, mais ça n&#8217;a pas fonctionné. Si vous avez des idées, je prends.</p>
<p>AppEngine a pas mal de limitations sur l&#8217;envoi de mail, notamment sur <strong>la bande passante sortante en terme de pièces jointes (limitée à 100Mo par 24h).</strong> Donc evitez d&#8217;abuser sur celles-ci, sinon l&#8217;application va tomber toute seule.</p>
<p>A cause d&#8217;AppEngine également, <strong>l&#8217;adresse de départ des mails (l&#8217;expéditeur) ne peut être qu&#8217;une adresse Gmail </strong>(ou <del>Google Apps</del> <em>Edit : j&#8217;ai essayé avec le mien, en vain.</em>), comme expliqué là :</p>
<blockquote><p>For security purposes, the sender address of a message must be the email address of an administrator for the application,</p></blockquote>
<p>Evidemment,<strong> le nombre de destinataires max pour le formulaire est un choix personne</strong>l. J&#8217;ai fixé à 10 la limite, parce que ça me semble être un bon compromis. Pareil pour les pièces jointes, la limite est à 3, mais je n&#8217;ai pas de quoi savoir s&#8217;il y a un besoin d&#8217;avoir davantage de champs à pièce jointe. Pareil pour les champs, j&#8217;ai limité à 3, mais je pourrais très bien augmenter.</p>
<p><em>Comme d&#8217;habitude, si vous êtes confrontés à des problèmes d&#8217;utilisation, n&#8217;hésitez pas à venir en discuter ici. Si vous avez des requêtes (vous voulez davantage de pièces jointes, davantage de destinataires, davantage de champs personnalisables ?), dites-le aussi en commentaire.</em></p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails">Permalien</a> |
<a href="http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails#comments">6 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/google" rel="tag">Google</a>, <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12227-crm-mailer-un-outil-en-ligne-pour-personnaliser-vos-envois-de-mails/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Base64 Encoder, un encodeur pour accélérer vos pages web</title>
		<link>http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web</link>
		<comments>http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web#comments</comments>
		<pubDate>Tue, 27 Jul 2010 16:53:31 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12199</guid>
		<description><![CDATA[J&#8217;en parlais ce matin : utiliser l&#8217;encodage Base64 peut s&#8217;avérer bien utile pour réduire drastiquement le nombre de ressources chargées pour une page web. Dans l&#8217;idée d&#8217;avoir, du coup, toujours sous la main un encodeur Base64, j&#8217;en ai fait un &#8230; <a href="http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>J&#8217;en parlais ce matin : <a href="http://www.abricocotier.fr/12188-images-encodees-base64-accelerer-chargement-page-diminuant-requetes">utiliser l&#8217;encodage Base64</a> peut s&#8217;avérer bien utile pour réduire drastiquement le nombre de ressources chargées pour une page web. Dans l&#8217;idée d&#8217;avoir, du coup, toujours sous la main un encodeur Base64, j&#8217;en ai fait un aujourd&#8217;hui moi-même, que j&#8217;ai appelé <strong><a href="http://abricocotierfr.appspot.com/base64encoder">Base64 Encoder</a></strong> (on peut pas dire que j&#8217;ai eu beaucoup d&#8217;imagination). Je suis parti de l&#8217;application disponible sur le site de <a href="http://www.greywyvern.com/code/php/binary2base64">GreyWyvern</a>, à laquelle j&#8217;ai rajouté les possibilité de pouvoir encoder simplement du texte ou bien un fichier en l&#8217;uploadant directement sur le serveur.   <span id="more-12199"></span></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="base64_encoder" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/base64_encoder.jpg" alt="" width="580" height="401" /></p>
<p><em>(du coup, pas grand chose à dire sur cette application, donc ce billet va essentiellement servir de page de feedbacks)</em></p>
<p>Ah si, 2-3 petites choses à dire quand même.</p>
<ul>
<li><strong>Le Base64 s&#8217;obtient très simplement avec python</strong>, comme suit (après avoir importé la librairie Base64):<br />
<blockquote><p>encoded_content = base64.b64encode(content)</p></blockquote>
</li>
<li><strong>Le gzip est calculé via la libraire python zlib, et via la fonction compress</strong>. Après il me suffit de faire un len() dessus pour connaitre le nombre d&#8217;octets. La ligne de code est donc :<br />
<blockquote><p>gzip_encoded_content_length = len(zlib.compress(encoded_content))</p></blockquote>
</li>
<li>Le fichier uploadé se lit comme suis sur AppEngine (sans passer par le datastore : je ne stocke rien du tout) :<br />
<blockquote><p>query_file = cgi.escape(self.request.POST.get(&#8216;file&#8217;).file.read())</p></blockquote>
</li>
</ul>
<p>Par contre, je discutais tout à l&#8217;heure avec <del><a href="http://www.abricocotier.fr/12188-images-encodees-base64-accelerer-chargement-page-diminuant-requetes#comment-8599">Rangzen</a></del> <a href="http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages#comment-6904">Kermit</a> (qui s&#8217;avère être camarade de promo <img src='http://www.abricocotier.fr/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' />  ), et il me disait que ce qui serait bien, ce serait de faire un outil qui puisse s&#8217;adapter au fait que<strong> IE5/6/7 ne supportent pas le Base64</strong>. Pour le coup, il m&#8217;a donné le lien vers un billet très utile sur le sujet : <a href="http://net.tutsplus.com/tutorials/html-css-techniques/quick-tip-how-to-target-ie6-ie7-and-ie8-uniquely-with-4-characters/">Quick Tip: How to Target IE6, IE7, and IE8 Uniquely with 4 Characters</a>.</p>
<p>De mon côté, je pense que ce qui serait bien, <strong>c&#8217;est de mettre un fichier CSS en entrée</strong> (en uploadant ou en mettant l&#8217;URL) et que l&#8217;utilitaire sur AppEngine parse le CSS en trouvant toutes les références internes à des images (par exemple les <em>background: url(image/toto.png);</em>), et irait remplacer ces références par le code Base64 créé à partir de l&#8217;image (si celle-ci est correctement récupérée). (<strong>Edit :</strong> Kermit m&#8217;a aussi formulé l&#8217;idée)</p>
<p>Honnêtement, je pense que c&#8217;est faisable, mais déjà beaucoup plus violent.</p>
<p>Parce que, autant un script comme celui-ci serait bien utile, autant l&#8217;idée de Kermit est assez importante, et tant qu&#8217;à faire,<strong> il serait aussi bien de mettre dans le même temps la référence pour que les &laquo;&nbsp;vieux&nbsp;&raquo; navigateurs continuent à voir la ressource</strong>, une fois qu&#8217;on a pu encoder celle-ci en Base64.</p>
<p>Dans un premier temps, je ne crois pas que je pourrais faire assez rapidement un outil parsant le CSS et relevant toutes les occurrences de url(), allant voir si l&#8217;URL contient http://, etc, mais ça n&#8217;irait pas beaucoup plus loin&#8230;</p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web">Permalien</a> |
<a href="http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web#comments">12 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/google" rel="tag">Google</a>, <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12199-base64-encoder-encodeur-accelerer-vos-pages-web/feed</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Site Checker, un URL-info fait avec mes mains, sur Google AppEngine</title>
		<link>http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine</link>
		<comments>http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine#comments</comments>
		<pubDate>Wed, 21 Jul 2010 22:29:49 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12159</guid>
		<description><![CDATA[L&#8217;idée de réaliser un URL-info (pour voir si je serais capable de faire un service aussi utile moi-même) me narguait depuis longtemps. En fait, il s&#8217;avère que ce n&#8217;est pas si difficile. De la même façon que les autres outils &#8230; <a href="http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><strong>L&#8217;idée de réaliser un </strong><a href="http://www.abricocotier.fr/2007-urlinfo-pour-connaitre-les-informations-de-bases-dun-site-web"><strong>URL-info</strong></a><strong> (pour voir si je serais capable de faire un service aussi utile moi-même) me narguait depuis longtemps</strong>. En fait, il s&#8217;avère que ce n&#8217;est pas si difficile. De la même façon que les autres outils dont j&#8217;ai déjà expliqué les <a href="http://www.abricocotier.fr/12025-regle-pixel-online-javascript-css-drag-and-drop">développements</a> <a href="http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript">a posteriori</a>, je vais dire les grandes lignes de ce qui me vient à l&#8217;esprit après avoir globalement terminé le développement de celui-ci. Avant toute chose, et malgré son évidente proximité avec l&#8217;idéal vers lequel je souhaitais aller, je ne pouvais pas l&#8217;appeler pareil (même si ç&#8217;eût été un hommage), donc <strong>j&#8217;ai trouvé un nom plutôt générique : </strong><a href="http://abricocotierfr.appspot.com/sitechecker"><strong>Site Checker</strong></a>.   <span id="more-12159"></span></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="site_checker_infos" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/site_checker_infos.jpg" alt="" width="580" height="404" /></p>
<p style="text-align: center;"><em>La &laquo;&nbsp;home-page&nbsp;&raquo; du Site-Checker (une fois qu&#8217;on a entré une URL valide)</em></p>
<p>Plusieurs réflexions, donc :</p>
<p>J&#8217;ai eu des problèmes au début pour savoir quelle librairie de parsing utiliser (j&#8217;avais commencé par minidom, ce qui fut un fiasco, mais j&#8217;ai vite arrété dû au fait qu&#8217;une page web n&#8217;est pas forcément aussi strictement faite qu&#8217;un XML parfait), puis j&#8217;ai eu l&#8217;aide de mon collègue Ali S-O (Ali, je t&#8217;ai promis de te citer <img src='http://www.abricocotier.fr/wp-includes/images/smilies/icon_biggrin.gif' alt=':-D' class='wp-smiley' />  ) pour <strong>démarrer sur </strong><a href="http://www.crummy.com/software/BeautifulSoup/"><strong>BeautifulSoup</strong></a> (que j&#8217;avais testé assez lamentablement il y a quelques mois), et je dois avouer que la librairie est sympa. Avec l&#8217;expérience qu&#8217;avait Ali, j&#8217;ai passé très rapidement les étapes de parsing, et quand il s&#8217;agit d&#8217;analyser une page web, autant dire que ça fait déjà une grosse partie du travail qui est abattue.</p>
<p><em>Pour ceux qui me demandent &laquo;&nbsp;pourquoi refaire ce qui existe déjà ?&nbsp;&raquo;, je leur répondrais que je pense qu&#8217;il n&#8217;est pas très simple de faire mieux que l&#8217;existant si on n&#8217;est même pas capable de faire ce qui existe. Et pour être capable de faire ce qui existe, le mieux est de le faire soi-même.</em></p>
<p>J&#8217;admet au passage avoir toujours des <strong>problèmes d&#8217;encodage avec la balise Title</strong> (en utf-8 ou ISO), mais j&#8217;espère trouver comment régler ça, ce qui doit être possible vu que le gars de URL-info le fait.</p>
<p>Comme je le disais, je suis partit pour refaire au moins aussi bien que URL-info. Ça tombe bien, cet outil est également sur GAE, et en python, mais pour ma part je n&#8217;ai pas utilisé jQuery (j&#8217;ai préféré le JS home-made : cela m&#8217;entraine à en faire).</p>
<p>Au début j&#8217;avais mis la barre d&#8217;input de l&#8217;URL en bas, mais en fait <strong>je crois que le plus pratique est bien de la mettre en haut</strong>. C&#8217;est plus pratique, ça &laquo;&nbsp;résume&nbsp;&raquo; ce sur quoi porte l&#8217;analyse en dessous.</p>
<p><strong>Je ne suis pas vraiment content de moi pour la preview des images</strong> : je n&#8217;arrive pas à faire un truc m&#8217;affichant des images ayant une max-height de 100px. Donc là, j&#8217;ai fait comme le gars de URL-info, à savoir height=100px (&laquo;&nbsp;forcé&nbsp;&raquo;), mais ça ne me convient pas : dès qu&#8217;une image est plus petite (hauteur plus faible que 100px), ça la grossit de façon assez peu jolie. A ce niveau là, il y aurait donc moyen de faire mieux.</p>
<p>Pas réussi non-plus à implémenter proprement les onglets. En soit, ce n&#8217;était pas mon but, mais j&#8217;aurais voulu pouvoir mettre un <strong>fond différent en fonction de l&#8217;onglet sur lequel on est</strong>, afin que l&#8217;utilisateur voit où il a cliqué et ce qui est enclenché. Pour l&#8217;instant, à la place, il y a un gros titre en &lt;H2&gt;, mais j&#8217;aimerais quand même un &laquo;&nbsp;onglet&nbsp;&raquo; ou un truc similaire.</p>
<p>Pour ce qui est des expand/collapse des listes<strong>, je l&#8217;ai fait en Javascript &#8216;à la main&#8217;, dans la mesure où je n&#8217;avais pas envie de mettre le fadding</strong> (que lui utilise via jQuery, et que je supporte pas). Je suis contre le fadding dans la mesure où, même si l&#8217;effet est sympa, il est rapidement lourd quand on utilise l&#8217;outil tous les jours.</p>
<p>Je ne suis pas sûr qu&#8217;il utilise la même fonction que moi pour le calcul du temps de téléchargement. Pour ma part, je fais juste un différentiel du temps pris avant et après l&#8217;appel de l&#8217;URL par la fonction urlfetch. Normalement, comme les deux sites fonctionnent sur GAE (et donc utilisent urlfetch, puisque c&#8217;est la seule librairie autorisée pour faire des requêtes HTTP), les temps devraient être exactement les même, mais bon&#8230;</p>
<p>De même,<strong> j&#8217;ai mis pas mal de temps à comprendre comment il calculait la taille en Kb du fichier</strong>. Je ne comprenais pas vraiment comment il pouvait faire, vu que GAE ne permet pas vraiment d&#8217;écrire des fichiers puis de voir la taille via os.environ.getSize()&#8230; En fait, et après quelques comparaisons, <strong>j&#8217;ai fini par comprendre qu&#8217;il prenait la longueur du fichier, il la divisait par 1024 et il tronquait le tout</strong> (pour obtenir un Int). En tout cas, en faisant comme ça, j&#8217;obtiens étonnamment les mêmes résultats que lui&#8230; <img src='http://www.abricocotier.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Pour parler du graphique des liens internes/externes/javascript : je ne sais pas exactement ce qu&#8217;il arrive à aller chercher pour les &laquo;&nbsp;liens Javascript&nbsp;&raquo; (d&#8217;après ce que j&#8217;ai vu, il prends au moins les popups), mais <strong>comme je ne suis en aucun cas sûr de fournir quelque chose de proche de la réalité en parlant de lien &laquo;&nbsp;javascript&nbsp;&raquo;, je préfère ne pas du tout les mettre</strong>, et me limiter aux balises a href. De plus, c&#8217;est la première fois que j&#8217;utilisais <a href="http://code.google.com/intl/fr-FR/apis/charttools/index.html">Google Visualisations API</a>, et je dois reconnaitre que la façon utilisée est extrêmement simple, même si c&#8217;est pas celle sur laquelle j&#8217;étais tombé via les tutoriels de débutant disponible chez Google, où il fallait importer pas mal de librairies JavaScript&#8230; Non, là, une simple URL d&#8217;image un peu trafiquée et on a l&#8217;image qu&#8217;on veut. Parfait <img src='http://www.abricocotier.fr/wp-includes/images/smilies/icon_biggrin.gif' alt=':D' class='wp-smiley' /> </p>
<p>Enfin, j&#8217;ai utilisé le <strong>DataStore </strong>de Google AppEngine (pour la première fois a ce niveau là, même si c&#8217;est juste une base), afin de voir ce qui a été vu les dernières fois et pour que Google référence un peu plus de choses. Ça devrait un peu plus aider au développement du site.</p>
<blockquote><p><strong>Edit : Quelques ajouts sur les réflexions.</strong></p>
<ul>
<li>La meilleure des choses serait en fait de faire télécharger au visiteur un programme en javascript ayant les même fonctionnalités. Cela allègerait le serveur en traitements, <strong>MAIS cela ne mettrait pas les calculs de temps de chargement au même niveau (puisque la requête serait faite chez le client, et donc dépendrait de sa connexion internet</strong>) : l&#8217;avantage quand la requête est faite chez Google AppEngine, c&#8217;est qu&#8217;on bénéficie d&#8217;une connexion fibrée et égale sur la durée, donc on peut comparer les temps à partir d&#8217;une base correcte).</li>
<li>J&#8217;avais au début une largeur de 1000px, mais j&#8217;ai réduis à 800px, cela me semble plus joli comme ça.</li>
<li>Pour ce qui est de la rubrique &laquo;&nbsp;Infos&nbsp;&raquo;, les informations collectées le sont grâce à la fonction <a href="http://docs.python.org/library/urlparse.html">urlparse</a> (chez moi). Je pense que le gars de URLinfo utilise la même fonction, étant donné la proximité de nos résultats. A la différence que moi, je n&#8217;affiche que les champs non-vides, lui semble afficher tout.</li>
</ul>
</blockquote>
<p>Voilà. Maintenant quelques écrans.</p>
<p style="text-align: center;"><em>Les headers (où on voit que AbriCoCotier tourne sur Nginx <img src='http://www.abricocotier.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  )</em></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="site_checker_headers" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/site_checker_headers.jpg" alt="" width="580" /></p>
<p style="text-align: center;"><em>Les images</em></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="site_checker_images" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/site_checker_images.jpg" alt="" width="579" height="265" /></p>
<p style="text-align: center;"><em>Les liens, avec le collapse, et le graphe en Float à droite.</em></p>
<p style="text-align: center;"><img style="border: 2px solid black;" title="site_checker_links" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/site_checker_links.jpg" alt="" width="579" height="324" /></p>
<p>Comme d&#8217;habitude, si vous avez des feedbacks, repérez des bugs, n&#8217;hésitez pas !</p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine">Permalien</a> |
<a href="http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine#comments">13 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/javascript" rel="tag">JavaScript</a>, <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12159-site-checker-un-url-info-fait-avec-mes-mains-sur-google-appengine/feed</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Augmenter la rapidité de son blog en passant de WordPress à Plone ?</title>
		<link>http://www.abricocotier.fr/12127-augmenter-rapidite-blog-wordpress-plone</link>
		<comments>http://www.abricocotier.fr/12127-augmenter-rapidite-blog-wordpress-plone#comments</comments>
		<pubDate>Mon, 19 Jul 2010 14:52:13 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[Python (et Django)]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=12127</guid>
		<description><![CDATA[L&#8217;augmentation de la rapidité de mon blog est un sujet que je rumine assez fréquemment, et qui a d&#8217;ailleurs tendance à empiéter sur ma vie &#171;&#160;professionnelle&#160;&#187;, où j&#8217;ai une certaine tendance à favoriser toute évolution en terme de &#171;&#160;temps d&#8217;exécution&#160;&#187;, &#8230; <a href="http://www.abricocotier.fr/12127-augmenter-rapidite-blog-wordpress-plone">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.abricocotier.fr/10557-nginx-php-fpm-et-wp-super-cache">L&#8217;augmentation</a> de la <a href="http://www.abricocotier.fr/10650-google-prend-desormais-en-compte-la-rapidite-daffichage-dun-site-dans-le-calcul-du-pagerank">rapidité</a> de <a href="http://www.abricocotier.fr/9954-syntaxhighlighter-ou-gist-github-comparaison-de-ces-solutions-pour-partager-du-code-avec-wordpress">mon</a> <a href="http://www.abricocotier.fr/9318-pourquoi-jai-supprime-disqus-de-mon-blog">blog</a> <a href="http://www.abricocotier.fr/8844-sur-wordpress-mieux-que-wp-super-cache-le-cache-statique-a-la-racine">est</a> <a href="http://www.abricocotier.fr/8959-ameliorer-le-temps-de-reponse-de-wordpress-avec-le-htaccess-et-php-5">un</a> <a href="http://www.abricocotier.fr/11013-script-shell-de-sauvegarde-des-fichiers-associes-a-un-blog-wordpress">sujet</a> que je rumine assez fréquemment, et qui a d&#8217;ailleurs tendance à empiéter sur ma vie &laquo;&nbsp;professionnelle&nbsp;&raquo;, où j&#8217;ai une certaine tendance à <strong>favoriser toute évolution en terme de &laquo;&nbsp;temps d&#8217;exécution&nbsp;&raquo;, même si celle-ci n&#8217;est pas demandée/payée</strong>. Faisant actuellement du <strong>développement <a href="http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages">Python</a></strong>, et ayant un certain attrait pour ça, je me suis donc demandé quel serait le gain en performance pour un blog tel que le mien, en passant du CMS WordPress au CMS <a href="http://fr.wikipedia.org/wiki/Plone">Plone</a>, qui me semble être le plus populaire dans la communauté Python.    <span id="more-12127"></span></p>
<p style="text-align: center;"><img title="plone-wordpress_logos" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/plone-wordpress_logos.jpg" alt="" width="580" /></p>
<p>D&#8217;abord le pourquoi : Pourquoi continuer à s&#8217;entêter à vouloir faire davantage de traitements par unité de temps sur des même machines, alors que le coût du matériel baisse sans cesse, et qu&#8217;il suffirait d&#8217;investir (peu) dans des serveurs plus puissants ?</p>
<p>La réponse est simple : j&#8217;ai eu la preuve sous mes yeux que c&#8217;était possible, et que, avec un peu de technique/connaissance/travail, <strong>on pouvait faire des choses incroyablement rapides, même avec des ordinateurs aujourd&#8217;hui considérés comme &laquo;&nbsp;vieux&nbsp;&raquo;</strong>. Rien que parce que les MegaHertz des processeurs constituent des millions de calculs par secondes, et donc que cette fréquence peut suffire, si elle est bien utilisée, à servir un nombre déjà énorme de pages sur un site.</p>
<p><strong>Le problème est donc de coder dans un langage assez rapide </strong>(car assez proche du langage machine ou assez compilé) pour rapprocher les traitements à exécuter des fonctions de base du processeur. C&#8217;est possible avec des langages tels que le python (quand il est <a href="http://fr.wikipedia.org/wiki/Python_(langage)#Compilation">compilé</a>), ou encore plus simplement avec Java, mais évidemment, cela freine toute simplicité de modification du code à la volée (car une compilation est nécessaire), comme le permet le PHP (qui y perd donc sa rapidité, car il est &laquo;&nbsp;relu&nbsp;&raquo; à chaque fois) (même si lui aussi peut être compilé/mis en cache, par exemple avec <a href="http://en.wikipedia.org/wiki/PHP_accelerator">PHP Accelerator</a>).</p>
<p>Bref, au final, <strong>je me demandais si un CMS &laquo;&nbsp;évolué&nbsp;&raquo; aurait un temps d&#8217;exécution meilleur en étant codé avec un autre langage</strong> (en l&#8217;occurrence, pour Plone, c&#8217;est Python). Dans &laquo;&nbsp;évolué&nbsp;&raquo;, j&#8217;entends : &laquo;&nbsp;en ayant une perspective de fonctionnalités équivalentes&nbsp;&raquo; (c&#8217;est à dire avec un système d&#8217;extension). Le mieux pour faire un tel comparatif serait de le faire serait de le faire sur un même serveur (disons Apache + mod_python) et avec une même BDD (même serveur pour permettre d&#8217;être facilement interchangeable, et de ne comparer les performances propres que d&#8217;un seul maillon de la chaine). Allons à l&#8217;essentiel : à part le billet suivant &laquo;&nbsp;<a href="http://davisagli.com/blog/notes-on-migrating-this-blog-from-wordpress-to-plone">Notes on migrating this blog from WordPress to Plone</a>&laquo;&nbsp;, ainsi que le <a href="http://plone.org/documentation/kb/plone-and-mysql">tutoriel sur le site de Plone.org</a>, je n&#8217;ai pas trouvé <a href="http://www.google.fr/search?hl=fr&amp;q=plone+mysql&amp;aq=f&amp;aqi=g1&amp;aql=&amp;oq=&amp;gs_rfai=">grand chose</a> traitant de Plone et Apache/MySQL, étant donné que celui-ci étant bien plus conseillé d&#8217;être mis dans un environnement Zope+Plone+<a href="http://fr.wikipedia.org/wiki/Binary_large_object">Blob</a>. Le billet le plus intéressant sur les performances de Ploneque j&#8217;ai pu trouver  face à WordPress est celui-là : &laquo;&nbsp;<a href="http://www.pilotsystems.net/actus/plone-4-test-rapidite-wordpress">Plone 4, un CMS plus rapide que WordPress</a>&laquo;&nbsp;, qui appelle <a href="http://www.circulartriangle.com/">ce site</a>.</p>
<p style="text-align: center;"><img class="alignnone size-full wp-image-12132" title="plone4_vs_wordpress" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/plone4_vs_wordpress.png" alt="" width="346" height="275" /></p>
<p>On voit que Plone a des performances approchant le double de celles de WordPress. Sur le site de Plone, le <a href="http://plone.org/products/plone/features/4/cms-benchmarks.png">graphique</a> est un peu différent (performances trois fois supérieures à celles de WordPress) :</p>
<p style="text-align: center;"><img title="cms-benchmarks" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/cms-benchmarks.png" alt="" width="580" /></p>
<p>Ailleurs, sur 01net, un <a href="http://pro.01net.com/editorial/515885/le-tableau-comparatif/">tableau comparatif</a> de plusieurs CMS n&#8217;est pas très favorable aux performances de Plone (3/5 en &laquo;&nbsp;Robustesse et Performance&nbsp;&raquo;), à l&#8217;inverse de de eXo Platform, en Java, qui s&#8217;en sort beaucoup mieux (4,5/5), et en dessous de Typo3 et Drupal, tous deux (3,5/5) en PHP.</p>
<p>Alors, que faire ? Pour résumer, <strong>il ne semble pas que Plone s&#8217;en sorte beaucoup mieux que ce que j&#8217;aurais espéré par rapport à WordPress</strong>. Changer de CMS python alors ? Si je regarde chez <a href="http://www.bertrandkeller.info/2009/09/24/1200-django-cms-un-cms-en-python/">DjangoCMS</a>, <a href="http://www.pylucid.org/">PyLucid</a>, ou <a href="http://orangoo.com/skeletonz/">Skeletonz</a>, je ne suis pas sûr de trouver des fonctionnalités équivalentes à ce qu&#8217;il y a sur WordPress (grosse communauté, et grosse base de plugins/extensions). <strong>La solution pourrait donc venir d&#8217;ailleurs : je vois venir l&#8217;avènement progressif des CMS Ruby/Rails avec </strong><a href="http://www.phusion.nl/"><strong>Phusion Passenger</strong></a><strong>/</strong><a href="http://www.modrails.com"><strong>Mod_Rails</strong></a><strong> qui fonctionne avec Nginx</strong>, et donc les performances sont tout à fait bluffantes. Reste bien sûr à aligner les fonctionnalités, mais il semble que les projets de CMS en Ruby ne manquent pas, donc il ne me reste plus qu&#8217;à aller voir de ce côté là pour trouver mon bonheur <img src='http://www.abricocotier.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/12127-augmenter-rapidite-blog-wordpress-plone">Permalien</a> |
<a href="http://www.abricocotier.fr/12127-augmenter-rapidite-blog-wordpress-plone#comments">9 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/nginx" rel="tag">Nginx</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a>, <a href="http://www.abricocotier.fr/tag/wordpress" rel="tag">Wordpress</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/12127-augmenter-rapidite-blog-wordpress-plone/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Un client pour Google Analytics simplement en HTML, CSS et JavaScript</title>
		<link>http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript</link>
		<comments>http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript#comments</comments>
		<pubDate>Tue, 06 Jul 2010 17:31:36 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[AppEngine]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=11991</guid>
		<description><![CDATA[Depuis le temps que je me plains du fait que Google Analytics ne propose qu&#8217;une interface contenant du Flash, il était temps que je trouve quelque chose de mieux. De surcroît, passant en ce moment pas mal de temps chez &#8230; <a href="http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Depuis le temps que je me plains du fait que Google Analytics ne propose qu&#8217;une interface contenant du Flash, il était temps que je trouve quelque chose de mieux. De surcroît, passant en ce moment pas mal de temps chez la famille le week-end, je n&#8217;ai pas toujours d&#8217;accès à Internet autre que celui de mon smartphone, donc il me fallait une interface que celui-ci serait capable d&#8217;utiliser. J&#8217;avais jusqu&#8217;à récemment un BlackBerry 8520 ne supportant que le HTTP et le CSS (et un tout petit peu le JavaScript, mais pas de quoi faire ce dont j&#8217;avais besoin), et aujourd&#8217;hui j&#8217;ai quelquechose d&#8217;autre, un petit peu plus évolué, mais <strong>ne gérant pas le flash</strong>. Enfin, comme je vais voir l&#8217;évolution du nombre de visiteurs pour ce présent site plusieurs fois par jour, il est toujours énervant d&#8217;avoir à chaque fois à reloader l&#8217;appli flash de sélection du jour, etc. Donc il fallait que je trouve sur le web quelque chose de simple, léger, rapide. Ou que je le développe moi-même.<span id="more-11991"></span></p>
<p>J&#8217;ai donc réfléchis au début à faire quelque chose juste en HTTP/CSS, mais je dois avouer que j&#8217;ai vraiment eu du mal. Je voulais depuis le début baser le tout sur un <a href="http://abricocotierfr.appspot.com/">compte AppEngine</a>, donc il fallait que ce que je développe soit compatible avec leur plateforme, ce qui n&#8217;a pas été le cas pour les premiers essais que j&#8217;ai fait avec leur librairie Analytics (que j&#8217;ai testé au début) présente au sein de la Gdata Python. Bref (en fait, leur librairie Gdata Python / Analytics fonctionne très bien, mais elle ne fonctionne par sur le dev_appserver de AppEngine). Entre temps, comme je l&#8217;expliquais plus haut, j&#8217;ai changé de smartphone, et le nouveau s&#8217;est révélé tout à fait capable de supporter le Javascript, donc je suis passé sur le client Javascript fournit sur la <a href="http://code.google.com/intl/fr-FR/apis/analytics/docs/gdata/gdataLibraries.html">doc de Google</a>.</p>
<p><em>Comme d&#8217;habitude, je le répète, loin de moi l&#8217;idée de me vanter ici : je ne fais ce genre d&#8217;application que pour me prouver que je suis capable de les faire, ayant l&#8217;impression perpétuelle d&#8217;être mauvais en programmation. Et puis au passage, je mets le service à dispo pour tout le monde, parce que ça ne coûte rien et que ça peut servir.</em></p>
<p>Sur le coup, le but était multiple : au début, il fallait que je créée un <strong>client Analytics en HTTP/CSS/JS qui fournirait les infos que je vais voir le plus souvent sur Analytics</strong> (les pages les plus vues de la journée en cours, ainsi que le nombre de visiteurs uniques/pages vues de la journée en cours). Par la suite, j&#8217;ai eu l&#8217;opportunité de <strong>rajouter des graphiques</strong> dessus, ce qui m&#8217;a permit de &laquo;&nbsp;valider&nbsp;&raquo; une promesse que <a href="http://www.abricocotier.fr/9036-quelques-resolutions-pour-lannee-2010">je m&#8217;étais faite au début de l&#8217;année</a>.</p>
<p>Donc en terme de technologies utilisées, il y a <a href="http://abricocotierfr.appspot.com/">une base</a> de pages simples en python sur AppEngine (ce qui permet d&#8217;avoir un coût d&#8217;hébergement égal à zéro), avec une base de Javascript via la doc pour JavaScript donnée par Google, avec une surcouche de JavaScript via la <a href="http://www.jscharts.com/how-to-use-customization">librairie JSCharts</a> qui créée des graphiques via des balises &lt;canvas&gt; (c&#8217;est donc du HTML5, mais mon smartphone supporte ça très bien). J&#8217;ai utilisé les graphiques en bâtons et en courbes.</p>
<h2><strong>Les écrans de l&#8217;application</strong></h2>
<p>Je ne vais pas y aller par quatre chemins. D&#8217;abord et avant tout, plutôt qu&#8217;un long discours, <a href="http://abricocotierfr.appspot.com/analytics">l&#8217;application est disponible ici</a>.</p>
<p>Pour le reste, l&#8217;application fonctionne comme suis :<em> (J&#8217;ai gardé l&#8217;interface de base du client Javascript fourni par Google, et j&#8217;ai rajouté/compilé/adapté quelques fonctionnalités.)</em></p>
<p>Après vous être loggué chez Google (l&#8217;application ne retient aucun mot de passe, tout se passe chez vous), vous arrivez sur un écran. Cliquez sur le <strong>bouton &laquo;&nbsp;Get Account Data&nbsp;&raquo;</strong>, et vous obtiendrez laliste des comptes que vous avez chez Analytics. A droite de chaque compte, un <strong>bouton &laquo;&nbsp;Choose&nbsp;&raquo;</strong> qui met dans le champ le code correspondant au compte sélectionné.</p>
<p style="text-align: center;"><img class="alignnone size-full wp-image-11992" style="border: 2px solid black;" title="analytics_client_comptes" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/analytics_client_comptes.jpg" alt="" width="581" height="292" /></p>
<p>Ne vous reste plus ensuite qu&#8217;à cliquer sur le <strong>bouton &laquo;&nbsp;Get PageWiewed&nbsp;&raquo;</strong> pour obtenir la liste des pages les plus vues sur la journée (par contre, je n&#8217;ai pas encore réussi à résoudre le problème d&#8217;encodage) :</p>
<p style="text-align: center;"><img class="alignnone size-medium wp-image-11993" style="border: 2px solid black;" title="analytics_client_pageviews" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/analytics_client_pageviews.jpg" alt="" width="580" /></p>
<p>Ou bien vous pouvez également cliquer sur le <strong>bouton &laquo;&nbsp;Get Visits&nbsp;&raquo;</strong> pour obtenir la liste des visites uniques, nombre de pages vues et ratio sur les 14 derniers jours. Les données affichées sont exactement les mêmes que celles que vous aurez en allant sur votre compte Analytics (si si, vérifiez, si vous ne le croyez pas).</p>
<p style="text-align: center;"><img class="alignnone size-full wp-image-11994" style="border: 2px solid black;" title="analytics_client_visits" src="http://www.abricocotier.fr/wp-content/uploads/2010/07/analytics_client_visits.jpg" alt="" width="580" height="455" /></p>
<p>Je compte améliorer le client, histoire de rajouter les fonctionnalités de listing des mots clefs/sites référents sur la dernière journée, mais aussi  des visiteurs (navigateurs, système d&#8217;exploitation, etc) sur les 14 derniers jours, sur les modèles des pages existants déjà.</p>
<p>Pour ce qui est du support des différents navigateurs, je sais que cela fonctionne sur Firefox 3.6.x, Chrome 5 et 6, et même Internet Explorer 7 (pour ceux qui seraient dessus&#8230;) (oui, ce sont les navigateurs testés ; je n&#8217;ai pas les autres sous la main).</p>
<p>Vous remarquerez également que le compte <a href="http://abricocotierfr.appspot.com/">AbriCoCotier Application</a> contient :</p>
<ul>
<li>Un <a href="http://abricocotierfr.appspot.com/whois">outil de Whois</a> (qui passe par l&#8217;API de <a href="www.whoisxmlapi.com/">WhoisXMLAPI</a> en HTTP car AppEngine n&#8217;autorise que ce port là)</li>
<li>Un outil pour vous <a href="http://abricocotierfr.appspot.com/myip">donner votre IP</a></li>
<li>Un <a href="http://abricocotierfr.appspot.com/wordcount">compteur de mots</a></li>
<li>Je compte rajouter d&#8217;autres applications, me permettant d&#8217;aller me frotter à d&#8217;autres API de Google, ou autres.</li>
</ul>
<p>Je suis bien sûr au courant que ces petits outils existent déjà, mais ils font partie de ma volonté de &laquo;&nbsp;les avoir fait moi-même&nbsp;&raquo;.</p>
<p><em>Si vous avez un commentaire là dessus, ou souhaitez rapporter un bug, une faute d&#8217;orthographe, n&#8217;hésitez pas, les commentaires sont faits pour ça.</em></p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript">Permalien</a> |
<a href="http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript#comments">3 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/appengine" rel="tag">AppEngine</a>, <a href="http://www.abricocotier.fr/tag/google" rel="tag">Google</a>, <a href="http://www.abricocotier.fr/tag/javascript" rel="tag">JavaScript</a>, <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/11991-un-client-pour-google-analytics-simplement-en-html-css-et-javascript/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>PNG2CSS : un outil pour théoriquement accélerer le chargement des pages</title>
		<link>http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages</link>
		<comments>http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages#comments</comments>
		<pubDate>Tue, 27 Apr 2010 17:05:45 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=10638</guid>
		<description><![CDATA[Vous me connaissez, j&#8217;apporte un intérêt assez particulier à toutes les techniques permettant d&#8217;accélérer le chargement des pages web, ce dans le but d&#8217;éviter d&#8217;être (entre-autres) pénalisé dans l&#8217;algorithme de ranking de Google pour trop grande lenteur. Dans toutes les &#8230; <a href="http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Vous <a href="http://www.abricocotier.fr/9519-disqus-a-double-le-temps-de-reponse-de-mon-blog">me</a> <a href="http://www.abricocotier.fr/10143-comment-optimiser-un-blog-wordpress-quand-on-est-sur-un-serveur-dedie">connaissez</a>, j&#8217;apporte <a href="http://www.abricocotier.fr/10143-comment-optimiser-un-blog-wordpress-quand-on-est-sur-un-serveur-dedie">un</a> <a href="http://www.abricocotier.fr/8844-sur-wordpress-mieux-que-wp-super-cache-le-cache-statique-a-la-racine">intérêt</a> <a href="http://www.abricocotier.fr/8959-ameliorer-le-temps-de-reponse-de-wordpress-avec-le-htaccess-et-php-5">assez</a> <a href="http://www.abricocotier.fr/8331-5-conseils-pour-ameliorer-le-temps-de-chargement-de-votre-wordpress">particulier</a> à toutes les techniques permettant d&#8217;accélérer le chargement des pages web, ce dans le but d&#8217;éviter d&#8217;être (entre-autres) pénalisé dans l&#8217;algorithme de ranking de Google pour trop grande lenteur. Dans toutes les techniques utilisées, il y en a une qui consiste à <strong>diminuer grandement le nombre d&#8217;images affichées sur ses pages</strong>. En effet, soit on utilise les sprite-CSS, auquel cas on ne charge qu&#8217;un nombre très réduit d&#8217;images et on en gère l&#8217;affichage dans le CSS, soit on charge chaque image séparément, ce qui a pour conséquence d&#8217;augmenter d&#8217;autant le nombre de requêtes faites par le navigateur pour aller chercher toutes les ressources nécessaire à l&#8217;affichage complet de la page. <span id="more-10638"></span></p>
<p>Le problème, c&#8217;est que pour toutes les petites images à charger dans une page, beaucoup son petites, mais <strong>nécessitent chacune une requête au serveur qui les fournit</strong>. Or, <strong>très souvent, c&#8217;est la connexion et le temps de latence au serveur qui prennent davantage de temps que le téléchargement lui-même !</strong> C&#8217;est la raison pour laquelle on utilise le CSS pour créer la mise en forme, car <strong>le CSS permet de &laquo;&nbsp;générer des images&nbsp;&raquo;</strong> : plutôt que de télécharger l&#8217;image toute faite, <strong>on délègue sa création à l&#8217;ordinateur de l&#8217;utilisateur</strong>. Par exemple : plutôt que de faire télécharger un background de page de 2000&#215;1500 pixels de couleur rouge unie qui en PNG prendrait 44ko (j&#8217;ai fait le test), on peut le remplacer par une ligne CSS disant :</p>
<pre class="brush: css; title: ; notranslate">
div.maclasse{
     position:absolute;
     left:0px;
     top:0px;
     width:2000px;
     height:1500px;
     background:RED;
}
</pre>
<p>..ce qui fait moins d&#8217;1 ko : l&#8217;ordinateur va générer tout seul l&#8217;image.</p>
<p><strong>Pourquoi, donc, ne pas tenter de supprimer le téléchargement de ces images en tentant de les générer à partir du fichier CSS ?</strong></p>
<p>C&#8217;est ce que j&#8217;ai fait via un script Python qui prend en entrée un fichier PNG et qui fournit en sortie un fichier HTML et un CSS permettant tous deux d&#8217;afficher l&#8217;image, en la générant pixel par pixel.</p>
<h2><strong>Comment ça marche ?</strong></h2>
<p>Le programme prend un fichier PNG, en extrait les données. Cela donne (je simplifie) un grand tableau à double entrée avec, pour chaque case, la couleur de la case (ou du pixel). Après, il suffit d&#8217;afficher ce pixel dans la page HTML en lui donnant comme couleur de fond la couleur qu&#8217;il avait dans l&#8217;image. Le programme reconstruit donc l&#8217;image à l&#8217;aide du CSS.</p>
<p>Attention, il n&#8217;est nullement question d&#8217;alléger les fichiers pour l&#8217;instant : par exemple, la compression du logo Google pris en PNG (qui faisait 12ko) a donné un HTML de 851ko et un CSS de 2650ko. Donc le script n&#8217;optimise pas en terme de taille, il optimise en terme de nombre de fichiers.</p>
<h2><strong>Plusieurs passes d&#8217;optimisation</strong></h2>
<p>Comme je vous l&#8217;ai dit plus haut, le script prend chaque pixel de l&#8217;image source, et le transforme en une ligne CSS, fournissant le background pour un pixel, et la position à laquelle afficher le pixel. Bien sûr, je récupère proprement la position du pixel dans l&#8217;image source (ou je la calcule, plus précisément), et j&#8217;adapte la position à laquelle je veux afficher le pixel dans la page cible.</p>
<p>Par exemple pour le pixel tout en haut à gauche de l&#8217;image :</p>
<pre class="brush: css; title: ; notranslate">
div.maclasse1{
position:absolute;
     left:0px;
     top:0px;
     width:1px;
     height:1px;
     background:RED;
}
</pre>
<p><em>Explication : le pixel est décalé de 0 pixel de la gauche de l&#8217;écran (left:0px), de 0 pixel du haut de l&#8217;écran (top:0px), et il a une largeur de 1 pixel (width:1px) et une hauteur de 1 pixel (height:1px).</em></p>
<p>Mais cette technique n&#8217;est pas du tout optimisée : elle créée autant de lignes CSS (et de ligne HTML) que de pixels dans l&#8217;image. Donc pour une image source de 200&#215;300 pixels, vous vous retrouvez avec des fichiers HTML et CSS de 60000 lignes chacun. Et ce, même si l&#8217;image n&#8217;avais qu&#8217;une seule couleur.</p>
<p><strong>L&#8217;idée des passes d&#8217;optimisation</strong> (que l&#8217;on retrouve souvent dans les algorithmes de compression), c&#8217;est de revoir plusieurs fois la sortie de l&#8217;image, et donc, quand on a des informations similaires, de les regrouper. Par exemple : si j&#8217;ai trois pixels de la même couleur les uns à côté des autres, plutôt que d&#8217;écrire trois lignes CSS (une par pixel) :</p>
<pre class="brush: css; title: ; notranslate">
div.maclasse1{
     position:absolute;
     left:0px;
     top:0px;
     width:1px;
     height:1px;
     background:RED;
}
div.maclasse2{
     position:absolute;
     left:1px;
     top:0px;
     width:1px;
     height:1px;
          background:RED;
}
div.maclasse3{
     position:absolute;
     left:2px;
     top:0px;
     width:1px;
     height:1px;
     background:RED;
}
</pre>
<p>&#8230;pourquoi ne pas écrire une seule ligne disant qu&#8217;il faut générer les 3 à la suite ?</p>
<pre class="brush: css; title: ; notranslate">
div.maclasse1{
     position:absolute;
     left:0px;
     top:0px;
     width:3px;
     height:1px;
     background:RED;
}
</pre>
<p>Comme vous pouvez le voir, dans celui-là, je dis de placer un background de largeur 3 pixels (width:3px). <strong>Le résultat affiché est exactement le même dans les deux cas, mais celui en bas prend trois fois moins de places en terme d&#8217;information</strong>.</p>
<p>L&#8217;idéal serait donc d&#8217;optimiser les fichiers de sortie afin de rassembler où c&#8217;est possible les directives, ce pour gagner de la place (et ne pas avoir des fichiers en sortie avec des milliers de lignes). Le problème, c&#8217;est que l&#8217;image source nous donne des informations pixel par pixel : il faut donc passer plusieurs fois pour détecter les pixels similaires et les associer entre eux.</p>
<h2><strong>Autant de passes que de dimensions</strong></h2>
<p>Attention, ici je parle de Run-Length Encoding (codage par plage) : je vous conseille d&#8217;aller lire la (très courte) <a href="http://fr.wikipedia.org/wiki/Run-length_encoding">fiche Wikipédia sur le sujet</a>.</p>
<p>Plus exactement, le script fait 2 passes : dans la première, il transforme ce qu&#8217;il reçoit (des triplets RGB) en couleurs HTML (hexadécimales), tout en détectant les pixels qui se suivent et sont de la même couleur (sur une même ligne) : il les associe. Pour le PNG de logo Google, on obtient un HTML de 256ko et un CSS de 789ko.</p>
<p>Cette première passe ne suffit pas : en effet, pour une image de 200&#215;300 pixels de la même couleur, il y aurait quand même 300 lignes pour dire de mettre la même couleur sur toute la ligne. Pourquoi pas ne mettre qu&#8217;une seule ligne pour dire de ne mettre la même couleur sur toute la ligne, mais également pour toutes les lignes ? De 300 lignes, on passerait à une seule. C&#8217;est le but de la deuxième passe.</p>
<p><em>(si vous voulez, la première passe fait une optimisation par ligne, la seconde rajoute les colonnes)</em></p>
<h2><strong>Évolutions potentielles</strong></h2>
<p>L&#8217;avantage du PNG, c&#8217;est qu&#8217;il gère la transparence. Malheureusement, de ce que j&#8217;ai vu sur la librairie que j&#8217;utilise (<a href="http://packages.python.org/pypng/png.html">PyPNG</a>), cet aspect semble être fournit, mais je n&#8217;ai pas pu le constater sur le fichier que j&#8217;ai testé (un bête fichier avec de la transparence, généré avec Paint.NET), et mon script a alors planté lamentablement. Je vous recommande donc de ré-enregistrer vos fichiers avec le Paint de Windows pour être sûr que ça fonctionne (en tout cas, c&#8217;est ce que j&#8217;ai fait pour chaque fichier PNG de test, et ça fonctionnait).</p>
<p>Par ailleurs, grâce au CSS3, il serait possible (mais très violent) de <strong>détecter les dégradés et de les reproduire avec le CSS</strong>. Ce serait un gros potentiel d&#8217;optimisation, mais j&#8217;ai du mal à croire à la faisabilité de la chose.</p>
<p>Enfin, je peux également compresser encore plus en définissant une couleur de base (ce que j&#8217;appelle la 3ième passe d&#8217;optimisation sur le graphique ci-dessus). Pour cela, je n&#8217;ai qu&#8217;à prendre la couleur qui revient le plus souvent, et à décider quelle est la couleur de fond. Puis, je saute les passages où c&#8217;est cette couleur qui est demandée. Sans l&#8217;utilisation de cette technique, j&#8217;obtiens, après les deux passes, le fichier HTML a 203ko et le CSS à 631ko pour un logo Google en PNG d&#8217;une taille de 12ko. Avec cette technique, j&#8217;obtiens une image identique, mais un HTML de taille 189ko et un CSS de 585ko.</p>
<p style="text-align: center;"><img title="passes_optimisation" src="http://www.abricocotier.fr/wp-content/uploads/2010/04/passes_optimisation.jpg" alt="" width="580" /></p>
<h2><strong>Le petit plus rigolo</strong></h2>
<p>Un truc rapide et amusant : amusez-vous à grossir la taille du texte, vous verrez que le logo grossi, bien sûr, mais de surcroît vous verrez de quelle façon il espace progressivement les pixels (testé sur Chrome) :</p>
<p style="text-align: center;"><img class="alignnone size-full wp-image-10639" title="google_logo_css" src="http://www.abricocotier.fr/wp-content/uploads/2010/04/google_logo_css.jpg" alt="" width="580" height="377" /></p>
<p>Le code est <a href="http://github.com/louisvolant/PNG2CSS">téléchargeable là</a>.</p>
<p>Pour tester chez vous sur le logo Google, <a href="http://www.abricocotier.fr//wp-content/uploads/2010/04/PNG2CSS/index.html">c&#8217;est là</a> (l&#8217;image originale <a href="http://www.abricocotier.fr//wp-content/uploads/2010/04/PNG2CSS/index2.html">est là</a>).</p>
<p>Au passage, un programme facile d&#8217;utilisation existe déjà pour les jpeg, mais il n&#8217;est qu&#8217;en noir et blanc, et ne fonctionne pas pixel par pixel : <a href="http://www.romancortes.com/blog/jpeg2css/">jpeg2css</a>.</p>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages">Permalien</a> |
<a href="http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages#comments">9 commentaires</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/10638-png2css-un-outil-pour-theoriquement-accelerer-le-chargement-des-pages/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Comparatif entre les fonctions frozenset, set et lambda</title>
		<link>http://www.abricocotier.fr/9947-comparatif-entre-les-fonctions-frozenset-set-et-lambda</link>
		<comments>http://www.abricocotier.fr/9947-comparatif-entre-les-fonctions-frozenset-set-et-lambda#comments</comments>
		<pubDate>Wed, 17 Mar 2010 10:55:48 +0000</pubDate>
		<dc:creator>Louis</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Programmation]]></category>
		<category><![CDATA[Python (et Django)]]></category>

		<guid isPermaLink="false">http://www.abricocotier.fr/?p=9947</guid>
		<description><![CDATA[Dans le cadre de mon stage actuel, j&#8217;ai eu à faire des différences entre des listes, c&#8217;est à dire à sortir la liste &#171;&#160;intersection&#160;&#187; des deux listes, et la liste &#171;&#160;union-intersection&#160;&#187; des deux listes (ou la liste &#171;&#160;différence&#160;&#187;). L&#8217;avantage avec &#8230; <a href="http://www.abricocotier.fr/9947-comparatif-entre-les-fonctions-frozenset-set-et-lambda">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Dans le cadre de mon stage actuel, j&#8217;ai eu à faire des différences entre des listes, c&#8217;est à dire à sortir la liste &laquo;&nbsp;intersection&nbsp;&raquo; des deux listes, et la liste &laquo;&nbsp;union-intersection&nbsp;&raquo; des deux listes (ou la liste &laquo;&nbsp;différence&nbsp;&raquo;). <strong>L&#8217;avantage avec Python, c&#8217;est qu&#8217;il existe des fonctions fournies directement, et qui font ça très bien</strong> (il y a sûrement la même chose dans plein d&#8217;autres langages). Ce qui est intéressant là-dedans, c&#8217;est que pour Python, j&#8217;ai identifié trois solutions pour faire les différences et intersections, et dans ma lutte pour l&#8217;optimisation, je me suis fait plaisir en chronométrant les différents temps d&#8217;exécution. Ces fonctions sont : SET, FROZENSET et LAMBDA.</p>
<p><span id="more-9947"></span></p>
<p style="text-align:center;"><img src="http://www.abricocotier.fr/wp-content/uploads/2010/03/code_python.jpg" alt="" title="code_python" width="546" style="border: 2px solid black;" /></p>
<p>J&#8217;ai testé ça sur des listes différentes, d&#8217;environ 9000 éléments chacune, ce qui me permet d&#8217;avoir un temps à peu près regardable, mais pas tant que ça (le temps total reste quand même infiniment petit à mes yeux), comme on va pouvoir le voir.</p>
<p>J&#8217;ai mis le code de test des trois fonctions en dessous, pour ceux que ça intéresse.</p>
<p>Pour le reste, voici le log d&#8217;exécution de ces fonctions (j&#8217;ai viré les mentions inutiles). Notez que Group1 est extrait d&#8217;un outil, et que Group2 est extrait de Google Groups. Les vérification à l&#8217;issue des trois tests servent à faire une diff entre les résultats, afin de montrer (si diff = 0) que les fonctions renvoient exactement les même listes.</p>
<ul>
<li>2010-03-15 12:41:51,358 &#8211; Start.</li>
<li>2010-03-15 12:41:51,375 &#8211; Nombre total de groupes dans Group1 : 9173</li>
<li>2010-03-15 12:41:51,375 &#8211; Nombre total de groupes dans Google Groups (Group2) : 9118</li>
<li>2010-03-15 12:41:51,391 &#8211; <strong>Making difference between Group2 &amp; Group1</strong> :<br />
references found in Group1 but that don&#8217;t exist in Google Groups</li>
<li>2010-03-15 12:41:51,391 &#8211; Starting Difference with SET : 2010-03-15 12:41:51.391000</li>
<li>2010-03-15 12:41:51,405 &#8211; Finishing Difference with SET : 2010-03-15 12:41:51.406000</li>
<li><strong>2010-03-15 12:41:51,405 &#8211; SET time : 0:00:00.015000</strong></li>
<li>2010-03-15 12:41:51,405 &#8211; Starting Difference with FROZENSET : 2010-03-15<br />
12:41:51.406000</li>
<li>2010-03-15 12:41:51,405 &#8211; Finishing Difference with FROZENSET : 2010-03-15<br />
12:41:51.406000</li>
<li><strong>2010-03-15 12:41:51,405 &#8211; FROZENSET time : 0:00:00</strong></li>
<li>2010-03-15 12:41:51,405 &#8211; Starting Difference with FILTER : 2010-03-15<br />
12:41:51.406000</li>
<li>2010-03-15 12:41:53,530 &#8211; Finishing Difference with FILTER : 2010-03-15<br />
12:41:53.531000</li>
<li><strong>2010-03-15 12:41:53,530 &#8211; FILTER time : 0:00:02.125000</strong></li>
<li>2010-03-15 12:41:53,530 &#8211; <strong>Verification. Difference entre meth1 et meth2 : 0 | et<br />
difference entre meth2 et meth3 : 0</strong></li>
<li>2010-03-15 12:41:53,530 &#8211; <strong>Making difference between Group1 &amp; Group2 :</strong><br />
references found in Google Groups but that don&#8217;t exist in Group1</li>
<li>2010-03-15 12:41:53,530 &#8211; Starting Difference with SET : 2010-03-15 12:41:53.531000</li>
<li>2010-03-15 12:41:53,546 &#8211; Finishing Difference with SET : 2010-03-15 12:41:53.547000</li>
<li><strong>2010-03-15 12:41:53,546 &#8211; SET time : 0:00:00.016000</strong></li>
<li>2010-03-15 12:41:53,546 &#8211; Starting Difference with FROZENSET : 2010-03-15<br />
12:41:53.547000</li>
<li>2010-03-15 12:41:53,546 &#8211; Finishing Difference with FROZENSET : 2010-03-15<br />
12:41:53.547000</li>
<li><strong>2010-03-15 12:41:53,546 &#8211; FROZENSET time : 0:00:00</strong></li>
<li>2010-03-15 12:41:53,546 &#8211; Starting Difference with FILTER : 2010-03-15<br />
12:41:53.547000</li>
<li>2010-03-15 12:41:55,687 &#8211; Finishing Difference with FILTER : 2010-03-15<br />
12:41:55.688000</li>
<li><strong>2010-03-15 12:41:55,687 &#8211; FILTER time : 0:00:02.141000</strong></li>
<li>2010-03-15 12:41:55,687 &#8211; <strong>Verification. Difference entre meth1 et meth2 : 0 | et<br />
difference entre meth2 et meth3 : 0</strong></li>
<li>2010-03-15 12:41:55,703 &#8211; <strong>Making intersection between Group2 &amp; Group1</strong></li>
<li>2010-03-15 12:41:55,703 &#8211; Starting Intersection with SET : 2010-03-15<br />
12:41:55.703000</li>
<li>2010-03-15 12:41:55,703 &#8211; Finishing Intersection with SET : 2010-03-15<br />
12:41:55.703000</li>
<li><strong>2010-03-15 12:41:55,703 &#8211; SET time : 0:00:00</strong></li>
<li>2010-03-15 12:41:55,719 &#8211; Starting Intersection with FROZENSET : 2010-03-15<br />
12:41:55.703000</li>
<li>2010-03-15 12:41:55,719 &#8211; Finishing Intersection with FROZENSET : 2010-03-15<br />
12:41:55.719000</li>
<li><strong>2010-03-15 12:41:55,719 &#8211; FROZENSET time : 0:00:00.016000</strong></li>
<li>2010-03-15 12:41:55,719 &#8211; Starting Intersection with FILTER : 2010-03-15<br />
12:41:55.719000</li>
<li>2010-03-15 12:41:57,875 &#8211; Finishing Intersection with FILTER : 2010-03-15<br />
12:41:57.875000</li>
<li><strong>2010-03-15 12:41:57,875 &#8211; FILTER time : 0:00:02.156000</strong></li>
<li>2010-03-15 12:41:57,875 &#8211; <strong>Verification. Difference entre meth1 et meth2 : 0 | et<br />
difference entre meth2 et meth3 : 0</strong></li>
<li>2010-03-15 12:41:57,905 &#8211; <strong>Making the intersection between Group1 &amp; Group2</strong></li>
<li>2010-03-15 12:41:57,905 &#8211; Starting Intersection with SET : 2010-03-15<br />
12:41:57.906000</li>
<li>2010-03-15 12:41:57,921 &#8211; Finishing Intersection with SET : 2010-03-15<br />
12:41:57.922000</li>
<li><strong>2010-03-15 12:41:57,921 &#8211; SET time : 0:00:00.016000</strong></li>
<li>2010-03-15 12:41:57,921 &#8211; Starting Intersection with FROZENSET : 2010-03-15<br />
12:41:57.922000</li>
<li>2010-03-15 12:41:57,921 &#8211; Finishing Intersection with FROZENSET : 2010-03-15<br />
12:41:57.922000</li>
<li><strong>2010-03-15 12:41:57,921 &#8211; FROZENSET time : 0:00:00</strong></li>
<li>2010-03-15 12:41:57,921 &#8211; Starting Intersection with FILTER : 2010-03-15<br />
12:41:57.922000</li>
<li>2010-03-15 12:42:00,421 &#8211; Finishing Intersection with FILTER : 2010-03-15<br />
12:42:00.422000</li>
<li><strong>2010-03-15 12:42:00,421 &#8211; FILTER time : 0:00:02.500000</strong></li>
<li>2010-03-15 12:42:00,421 &#8211; <strong>Verification. Difference entre meth1 et meth2 : 0 | et<br />
difference entre meth2 et meth3 : 0</strong></li>
</ul>
<h2><strong>Conclusions que l&#8217;on peut tirer de ce test :</strong></h2>
<p><strong>Pour la différence :</strong> lambda toujours plus long, puis SET, puis frozenset.<br />
<strong>Pour l&#8217;intersection :</strong></p>
<ul>
<li>lambda est toujours plus long.</li>
<li>Ensuite, le plus long entre set et frozenset dépend de la grandeur des deux objets à comparer.
<p>Si on a deux objets distincts, disons un grosObjet et un petitObjet :</p>
<ul>
<li>grosObjet.intersection(petitObjet) => time(frozenset) > time(set)</li>
<li>petitObjet.intersection(grosObjet) => time(frozenset) < time(set)</li>
</ul>
</li>
</ul>
<h2><strong>Le code du test, comme promis :</strong></h2>
<pre class="brush: python; title: ; notranslate">
def make_intersec(arg1,arg2):
# Make intersection between arg1 and arg2

    #Premiere methode : en utilisant SET
    startSET = datetime.now()
    logger.info(&quot;Starting Intersection with SET : &quot;+str(startSET))
    setArg1 = set(arg1)
    setArg2 = set(arg2)
    intersecArg1Arg2meth1 = setArg2.intersection(setArg1)
    endSET = datetime.now()
    logger.info(&quot;Finishing Intersection with SET : &quot;+str(endSET))
    logger.info(&quot;SET time : &quot;+str(endSET-startSET))
    #Deuxieme methode, en utilisant FROZENSET
    startFROZENSET = datetime.now()
    logger.info(&quot;Starting Intersection with FROZENSET : &quot;+str(startFROZENSET))
    frozensetArg1 = frozenset(arg1)
    frozensetArg2 = frozenset(arg2)
    intersecArg1Arg2meth2 = frozensetArg2.intersection(frozensetArg1)
    endFROZENSET = datetime.now()
    logger.info(&quot;Finishing Intersection with FROZENSET : &quot;+str(endFROZENSET))
    logger.info(&quot;FROZENSET time : &quot;+str(endFROZENSET-startFROZENSET))

    #Troisieme methode, en utilisant LAMBDA
    startFILTER = datetime.now()
    logger.info(&quot;Starting Intersection with FILTER : &quot;+str(startFILTER))
    intersecArg1Arg2meth3 = filter(lambda x:x in arg1,arg2)
    endFILTER = datetime.now()
    logger.info(&quot;Finishing Intersection with FILTER : &quot;+str(endFILTER))
    logger.info(&quot;FILTER time : &quot;+str(endFILTER-startFILTER))

    result_methode_1 = frozenset(intersecArg1Arg2meth1)
    result_methode_2 = frozenset(intersecArg1Arg2meth2)
    result_methode_3 = frozenset(intersecArg1Arg2meth3)
    diffmeth1meth2 = result_methode_1.difference(result_methode_2)
    diffmeth2meth3 = result_methode_2.difference(result_methode_3)
    logger.info(&quot;Verification. Difference entre meth1 et meth2 : &quot;+str(len(diffmeth1meth2))+&quot; | et difference entre meth2 et meth3 : &quot;+str(len(diffmeth2meth3)))

    result = list(intersecArg1Arg2meth2)
    result.sort()
    return result

def make_diff(arg1,arg2):
# Make difference between arg1 and arg2

    #Premiere methode : en utilisant SET
    startSET = datetime.now()
    logger.info(&quot;Starting Difference with SET : &quot;+str(startSET))
    setArg1 = set(arg1)
    setArg2 = set(arg2)
    diffArg1Arg2meth1 = setArg2.difference(setArg1)
    endSET = datetime.now()
    logger.info(&quot;Finishing Difference with SET : &quot;+str(endSET))
    logger.info(&quot;SET time : &quot;+str(endSET-startSET))
    #Deuxieme methode, en utilisant FROZENSET
    startFROZENSET = datetime.now()
    logger.info(&quot;Starting Difference with FROZENSET : &quot;+str(startFROZENSET))
    frozensetArg1 = frozenset(arg1)
    frozensetArg2 = frozenset(arg2)
    diffArg1Arg2meth2 = frozensetArg2.difference(frozensetArg1)
    endFROZENSET = datetime.now()
    logger.info(&quot;Finishing Difference with FROZENSET : &quot;+str(endFROZENSET))
    logger.info(&quot;FROZENSET time : &quot;+str(endFROZENSET-startFROZENSET))

    #Troisieme methode, en utilisant LAMBDA
    startFILTER = datetime.now()
    logger.info(&quot;Starting Difference with FILTER : &quot;+str(startFILTER))
    diffArg1Arg2meth3 = filter(lambda x:x not in arg1,arg2)
    endFILTER = datetime.now()
    logger.info(&quot;Finishing Difference with FILTER : &quot;+str(endFILTER))
    logger.info(&quot;FILTER time : &quot;+str(endFILTER-startFILTER))

    result_methode_1 = frozenset(diffArg1Arg2meth1)
    result_methode_2 = frozenset(diffArg1Arg2meth2)
    result_methode_3 = frozenset(diffArg1Arg2meth3)
    diffmeth1meth2 = result_methode_1.difference(result_methode_2)
    diffmeth2meth3 = result_methode_2.difference(result_methode_3)
    logger.info(&quot;Verification. Difference entre meth1 et meth2 : &quot;+str(len(diffmeth1meth2))+&quot; | et difference entre meth2 et meth3 : &quot;+str(len(diffmeth2meth3)))

    result = list(diffArg1Arg2meth2)
    result.sort()
    return result
</pre>
<hr />
<p><small><a href="http://www.abricocotier.fr">AbriCoCotier.fr</a>, 2010. |
<a href="http://www.abricocotier.fr/9947-comparatif-entre-les-fonctions-frozenset-set-et-lambda">Permalien</a> |
<a href="http://www.abricocotier.fr/9947-comparatif-entre-les-fonctions-frozenset-set-et-lambda#comments">Ajoutez un commentaire !</a> | Plugin <a href="http://planetozh.com/blog/my-projects/wordpress-plugin-better-feed-rss/">Better Feed</a>, par <a href="http://planetozh.com/">Ozh</a>
<br/>
Rangé dans : <a href="http://www.abricocotier.fr/tag/programmation" rel="tag">Programmation</a>, <a href="http://www.abricocotier.fr/tag/python" rel="tag">Python (et Django)</a><br/>
</small></p>]]></content:encoded>
			<wfw:commentRss>http://www.abricocotier.fr/9947-comparatif-entre-les-fonctions-frozenset-set-et-lambda/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

