-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathservizi-web-http.html
1004 lines (838 loc) · 91.5 KB
/
servizi-web-http.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<meta charset=utf-8>
<title>Servizi web HTTP - Immersione in Python 3</title>
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 14}
mark{display:inline}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
<meta name=viewport content='initial-scale=1.0'>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=014021643941856155761:l5eihuescdw><input type=hidden name=ie value=UTF-8> <input type=search name=q size=25 placeholder="powered by Google™"> <input type=submit name=root value=Search></div></form>
<p>Voi siete qui: <a href=index.html>Inizio</a> <span class=u>‣</span> <a href=indice.html#servizi-web-http>Immersione in Python 3</a> <span class=u>‣</span>
<p id=level>Livello di difficoltà: <span class=u title=avanzato>♦♦♦♦♢</span>
<h1>Servizi web HTTP</h1>
<blockquote class=q>
<p><span class=u>❝</span> Non c’è cuscino più morbido di una coscienza tranquilla. <span class=u>❞</span><br>— Charlotte Brontë
</blockquote>
<p id=toc>
<h2 id=divingin>Immersione!</h2>
<p class=f>Relativamente alla descrizione della natura dei servizi web <abbr>HTTP</abbr>, non occorrono più di 15 parole: lo scambio di dati con server remoti utilizzando nient’altro che le operazioni di <abbr>HTTP</abbr>. Se volete ottenere dati dal server, usate <abbr>HTTP</abbr> <code>GET</code>; se volete inviare nuovi dati al server, usate <abbr>HTTP</abbr> <code>POST</code>. Alcune delle <abbr>API</abbr> più avanzate per i servizi web <abbr>HTTP</abbr> permettono anche di creare, modificare e cancellare dati utilizzando <abbr>HTTP</abbr> <code>PUT</code> e <abbr>HTTP</abbr> <code>DELETE</code>. In altre parole, i “verbi” definiti dal protocollo <abbr>HTTP</abbr> (<code>GET</code>, <code>POST</code>, <code>PUT</code> e <code>DELETE</code>) possono corrispondere direttamente a operazioni a livello di applicazione per recuperare, creare, modificare e cancellare dati.
<p>Il vantaggio principale di questo approccio è la semplicità, e la sua semplicità si è dimostrata popolare. I dati — tipicamente in formato <a href=xml.html><abbr>XML</abbr></a> o <a href=serializzare-oggetti-python.html#json><abbr>JSON</abbr></a> — possono essere assemblati e memorizzati staticamente oppure generati dinamicamente da un programma lato server, e tutti i linguaggi di programmazione più importanti (compreso Python, naturalmente!) includono una libreria <abbr>HTTP</abbr> per scaricarli. Anche le attività di debug vengono facilitate: dato che ogni risorsa in un servizio web <abbr>HTTP</abbr> ha un indirizzo unico (sotto forma di <abbr>URL</abbr>), potete caricarla nel vostro browser web e vederne immediatamente i dati grezzi.
<p>Esempi di servizi web <abbr>HTTP</abbr>:
<ul>
<li>le <a href=http://code.google.com/apis/gdata/><abbr>API</abbr> di Google Data</a> vi permettono di interagire con un’ampia varietà di servizi forniti da Google, compresi <a href=http://www.blogger.com/>Blogger</a> e <a href=http://www.youtube.com/>YouTube</a>;
<li>i <a href=http://www.flickr.com/services/api/>Servizi Flickr</a> vi permettono di caricare e scaricare foto da <a href=http://www.flickr.com/>Flickr</a>;
<li>la <a href=http://apiwiki.twitter.com/><abbr>API</abbr> di Twitter</a> vi permette di pubblicare aggiornamenti di stato su <a href=http://twitter.com/>Twitter</a>;
<li><a href='http://www.programmableweb.com/apis/directory/1?sort=mashups'>…e molti altri</a>.
</ul>
<p>Python 3 viene distribuito con due diverse librerie per interagire con i servizi web <abbr>HTTP</abbr>:
<ul>
<li><a href=http://docs.python.org/3.1/library/http.client.html><code>http.client</code></a> è una libreria di basso livello che implementa la <a href=http://www.w3.org/Protocols/rfc2616/rfc2616.html><abbr>RFC</abbr> 2616</a>, cioè il protocollo <abbr>HTTP</abbr>;
<li><a href=http://docs.python.org/3.1/library/urllib.request.html><code>urllib.request</code></a> è un livello di astrazione costruito sulla base di <code>http.client</code>. Fornisce una <abbr>API</abbr> standard per accedere sia ai server <abbr>HTTP</abbr> sia ai server <abbr>FTP</abbr>, segue automaticamente le redirezioni <abbr>HTTP</abbr> e gestisce alcune forme comuni di autenticazione <abbr>HTTP</abbr>.
</ul>
<p>Quindi quale delle due dovreste usare? Nessuna. Invece, dovreste usare <a href=http://code.google.com/p/httplib2/><code>httplib2</code></a>, una libreria open source di terze parti che implementa <abbr>HTTP</abbr> in maniera più completa rispetto ad <code>http.client</code> ma fornisce astrazioni migliori rispetto a <code>urllib.request</code>.
<p>Per comprendere perché <code>httplib2</code> è la scelta giusta dovete prima conoscere <abbr>HTTP</abbr>.
<p class=a>⁂
<h2 id=http-features>Le caratteristiche di HTTP</h2>
<p>Ci sono cinque importanti caratteristiche che tutti i client <abbr>HTTP</abbr> dovrebbero supportare.
<h3 id=caching>La cache</h3>
<p>La cosa più importante da capire per un qualsiasi tipo di servizio web è che l’accesso alla rete è incredibilmente costoso. Non nel senso di “euro e centesimi” (sebbene la banda non sia gratis). Voglio dire che ci vuole un tempo estremamente lungo per aprire una connessione, spedire una richiesta e ricevere una risposta da un server remoto. Anche sulla connessione a banda larga più veloce, la <i>latenza</i> (il tempo che ci vuole per spedire una richiesta e cominciare a ricevere i dati in una risposta) può ancora essere più grande di quanto prevedete. Un router si comporta in modo strano, un pacchetto viene scartato, un proxy intermedio è sotto attacco — non c’è <a href=http://isc.sans.org/>mai un momento di calma</a> sulla rete Internet pubblica, e potrebbe non esserci nulla che voi possiate fare.
<aside><code>Cache-Control: max-age</code> significa: “Non scocciarmi fino alla prossima settimana.”</aside>
<p><abbr>HTTP</abbr> è progettato con l’uso della cache in mente. Esiste un’intera categoria di dispositivi (chiamati “proxy di cache”) il cui solo lavoro è sedere in mezzo tra voi e il resto del mondo e minimizzare l’accesso alla rete. La vostra azienda o il vostro <abbr>ISP</abbr> quasi certamente gestisce alcuni proxy di cache, anche se voi non ve ne rendete conto. Questi dispositivi funzionano perché l’uso della cache è integrato nel protocollo <abbr>HTTP</abbr>.
<p>Ecco un esempio concreto di come funziona la cache. Visitate <a href=http://diveintomark.org/><code>diveintomark.org</code></a> nel vostro browser. Quella pagina include un’immagine di sfondo, <a href=http://wearehugh.com/m.jpg><code>wearehugh.com/m.jpg</code></a>. Quando il vostro browser scarica quell’immagine, il server include le seguenti intestazioni <abbr>HTTP</abbr>:
<pre class=nd><code>HTTP/1.1 200 OK
Date: Sun, 31 May 2009 17:14:04 GMT
Server: Apache
Last-Modified: Fri, 22 Aug 2008 04:28:16 GMT
ETag: "3075-ddc8d800"
Accept-Ranges: bytes
Content-Length: 12405
<mark>Cache-Control: max-age=31536000, public</mark>
<mark>Expires: Mon, 31 May 2010 17:14:04 GMT</mark>
Connection: close
Content-Type: image/jpeg</code></pre>
<p>Le intestazioni <code>Cache-Control</code> ed <code>Expires</code> dicono al vostro browser (e a qualsiasi proxy di cache tra voi e il server) che questa immagine può essere tenuta in cache per un anno. <em>Un anno!</em> E se nel prossimo anno visitate un’altra pagina che include un collegamento a questa immagine, il vostro browser caricherà l’immagine dalla propria cache <em>senza generare alcun tipo di attività di rete</em>.
<p>Ma aspettate, le cose migliorano. Diciamo che per qualche motivo il vostro browser cancella l’immagine dalla vostra cache locale. Magari ha finito lo spazio su disco, magari voi avete pulito la cache manualmente. Ma le intestazioni <abbr>HTTP</abbr> dicevano che questi dati potevano essere memorizzati in cache da proxy di cache pubblici. (Tecnicamente, la cosa importante è quello che le intestazioni <em>non</em> dicono: l’intestazione <code>Cache-Control</code> non contiene la parola chiave <code>private</code>, quindi questi dati sono memorizzabili in cache per default.) I proxy di cache sono progettati per avere tonnellate di spazio di memorizzazione, probabilmente molto più di quanto abbiate allocato per il vostro browser locale.
<p>Se la vostra azienda o il vostro <abbr>ISP</abbr> gestisce un proxy di cache, il proxy potrebbe ancora avere l’immagine nella propria cache. Quando visitate <code>diveintomark.org</code> un’altra volta, il vostro browser cercherà l’immagine nella sua cache locale ma non la troverà, quindi effettuerà una richiesta di rete per cercare di scaricarla dal server remoto. Ma se il proxy di cache ha ancora una copia dell’immagine, intercetterà quella richiesta e preleverà l’immagine dalla <em>propria</em> cache. Questo significa che la vostra richiesta non raggiungerà mai il server remoto; in effetti, non lascerà mai la rete della vostra azienda. Questo rende lo scaricamento più veloce (meno salti sui nodi della rete) e fa risparmiare denaro alla vostra azienda (meno dati che vengono scaricati dal mondo esterno).
<p>L’uso della cache in <abbr>HTTP</abbr> funziona solo quando tutti fanno la loro parte. Da un lato, i server devono spedire le intestazioni corrette nelle loro risposte. Dall’altro lato, i client devono comprendere e rispettare quelle intestazioni prima di richiedere due volte gli stessi dati. I proxy nel mezzo non sono una panacea; possono essere tanto intelligenti solo quanto i server e i client permettono loro di essere.
<p>Le librerie <abbr>HTTP</abbr> incluse in Python non supportano l’uso della cache, ma <code>httplib2</code> lo fa.
<h3 id=last-modified>Il controllo della modifica più recente</h3>
<p>Alcuni dati non cambiano mai, mentre altri dati cambiano continuamente. Nel mezzo, c’è un vasto assortimento di dati che <em>potrebbero</em> essere cambiati ma non lo hanno fatto. Il feed di CNN.com viene aggiornato ogni pochi minuti, ma il feed del mio weblog potrebbe non cambiare per diversi giorni o intere settimane. In quest’ultimo caso, non voglio dire ai client di mantenere in cache il mio feed per intere settimane, perché quando scrivo effettivamente qualcosa i miei lettori potrebbero non vederla per settimane (perché stanno rispettando le mie intestazioni di cache che dicono “non preoccuparti di controllare questo feed per settimane”). D’altra parte, non voglio che i client scarichino il mio intero feed una volta ogni ora se non è cambiato!
<aside><code>304 Not Modified</code> significa: “Stessa merda, altro giorno.”</aside>
<p><abbr>HTTP</abbr> fornisce una soluzione anche a questo problema. Quando richiedete un dato per la prima volta, il server può spedire indietro un’intestazione <code>Last-Modified</code>. Questa è esattamente ciò che sembra: la data in cui i dati sono stati modificati. Quell’immagine di sfondo riferita da <code>diveintomark.org</code> includeva un’intestazione <code>Last-Modified</code>.
<pre class=nd><code>HTTP/1.1 200 OK
Date: Sun, 31 May 2009 17:14:04 GMT
Server: Apache
<mark>Last-Modified: Fri, 22 Aug 2008 04:28:16 GMT</mark>
ETag: "3075-ddc8d800"
Accept-Ranges: bytes
Content-Length: 12405
Cache-Control: max-age=31536000, public
Expires: Mon, 31 May 2010 17:14:04 GMT
Connection: close
Content-Type: image/jpeg
</code></pre>
<p>Quando richiedete gli stessi dati una seconda (o terza, o quarta) volta, potete spedire insieme alla vostra richiesta un’intestazione <code>If-Modified-Since</code> contenente la data che avete ottenuto dal server l’ultima volta. Se i dati sono cambiati da allora, il server ignora l’intestazione <code>If-Modified-Since</code> e vi restituisce semplicemente i nuovi dati con un codice di stato <code>200</code>. Ma se i dati <em>non</em> sono cambiati, il server rimanda indietro lo speciale codice di stato <abbr>HTTP</abbr> <code>304</code> per indicare che “questi dati non sono cambiati dall’ultima volta che li avete richiesti”. Potete verificare questo comportamento dalla riga di comando usando <a href=http://curl.haxx.se/>curl</a>:
<pre class='nd screen'>
<samp class=p>you@localhost:~$ </samp><kbd>curl -I <mark>-H "If-Modified-Since: Fri, 22 Aug 2008 04:28:16 GMT"</mark> http://wearehugh.com/m.jpg</kbd>
<samp>HTTP/1.1 304 Not Modified
Date: Sun, 31 May 2009 18:04:39 GMT
Server: Apache
Connection: close
ETag: "3075-ddc8d800"
Expires: Mon, 31 May 2010 18:04:39 GMT
Cache-Control: max-age=31536000, public</samp></pre>
<p>Perché questo è un miglioramento? Perché quando il server restituisce un <code>304</code>, <em>non rispedisce i dati</em>. Tutto quello che ottenete è il codice di stato. Anche dopo che la copia nella vostra cache è scaduta, il controllo della modifica più recente vi assicura che non scaricherete gli stessi dati due volte se non sono cambiati. (Come bonus aggiuntivo, questa risposta <code>304</code> include anche le intestazioni di cache. I proxy manterranno una copia dei dati anche dopo che sono ufficialmente “scaduti”, nella speranza che i dati non siano <em>realmente</em> cambiati e che la prossima richiesta ottenga una risposta con un codice di stato <code>304</code> e informazioni di cache aggiornate.)
<p>Le librerie <abbr>HTTP</abbr> incluse in Python non supportano il controllo della modifica più recente, ma <code>httplib2</code> lo fa.
<h3 id=etags>Il controllo degli ETag</h3>
<p>Gli ETag sono un modo alternativo per effettuare il <a href=#last-modified>controllo della modifica più recente</a>. Con gli ETag, il server spedisce un codice hash in un’intestazione <code>ETag</code> insieme ai dati che avete richiesto. (Decidere come determinare esattamente questo hash è interamente compito del server. L’unico requisito è che l’hash cambi quando i dati cambiano.) Quell’immagine di sfondo riferita da <code>diveintomark.org</code> aveva un’intestazione <code>ETag</code>.
<aside><code>ETag</code> significa: “Nulla di nuovo sotto il sole.”</aside>
<pre class=nd><code>HTTP/1.1 200 OK
Date: Sun, 31 May 2009 17:14:04 GMT
Server: Apache
Last-Modified: Fri, 22 Aug 2008 04:28:16 GMT
<mark>ETag: "3075-ddc8d800"</mark>
Accept-Ranges: bytes
Content-Length: 12405
Cache-Control: max-age=31536000, public
Expires: Mon, 31 May 2010 17:14:04 GMT
Connection: close
Content-Type: image/jpeg
</code></pre>
<p>La seconda volta che richiedete gli stessi dati, includete il valore di ETag in un’intestazione <code>If-None-Match</code> della vostra richiesta. Se i dati non sono cambiati, il server vi restituirà un codice di stato <code>304</code>. Come con il controllo sulla data della modifica più recente, il server invia <em>solo</em> il codice di stato <code>304</code>, evitando di spedirvi gli stessi dati una seconda volta. Includendo il valore di ETag nella vostra seconda richiesta state dicendo al server che non c’è alcun bisogno di rispedire gli stessi dati se quei dati corrispondono ancora a questo hash, dato che <a href=#caching>avete ancora i dati dall’ultima volta</a>.
<p>Utilizzando ancora <kbd>curl</kbd>:
<pre class='nd screen'>
<a><samp class=p>you@localhost:~$ </samp><kbd>curl -I <mark>-H "If-None-Match: \"3075-ddc8d800\""</mark> http://wearehugh.com/m.jpg</kbd> <span class=u>①</span></a>
<samp>HTTP/1.1 304 Not Modified
Date: Sun, 31 May 2009 18:04:39 GMT
Server: Apache
Connection: close
ETag: "3075-ddc8d800"
Expires: Mon, 31 May 2010 18:04:39 GMT
Cache-Control: max-age=31536000, public</samp></pre>
<ol>
<li>Gli ETag vengono comunemente racchiusi tra virgolette, ma <em>le virgolette sono parte del valore</em>. Questo significa che dovete rimandare le virgolette al server nell’intestazione <code>If-None-Match</code>.
</ol>
<p>Le librerie <abbr>HTTP</abbr> incluse in Python non supportano gli ETag, ma <code>httplib2</code> lo fa.
<h3 id=compression>La compressione</h3>
<p>Quando parlate di servizi web <abbr>HTTP</abbr>, state quasi certamente parlando di spostare dati basati su testo avanti e indietro attraverso la rete. Forse i dati sono in formato <abbr>XML</abbr>, forse sono in formato <abbr>JSON</abbr>, forse sono solo <a href=stringhe.html#boring-stuff title='il testo semplice non esiste'>testo semplice</a>. A prescindere dal formato, il testo si comprime bene. Il feed di esempio nel <a href=xml.html>capitolo su XML</a> è di 3070 byte, ma sarebbe di 941 byte dopo una compressione effettuata tramite gzip, esattamente il 30% della dimensione originale!
<p><abbr>HTTP</abbr> supporta <a href=http://www.iana.org/assignments/http-parameters>diversi algoritmi di compressione</a>. I due tipi più comuni sono <a href=http://www.ietf.org/rfc/rfc1952.txt>gzip</a> e <a href=http://www.ietf.org/rfc/rfc1951.txt>deflate</a>. Quando richiedete una risorsa via <abbr>HTTP</abbr>, potete chiedere al server di mandarvela in formato compresso includendo nella vostra richiesta un’intestazione <code>Accept-encoding</code> che elenchi quali algoritmi di compressione supportate. Se il server supporta uno qualsiasi degli stessi algoritmi, vi risponderà inviando i dati compressi (insieme a un’intestazione <code>Content-encoding</code> che vi dice quale algoritmo ha usato). Poi sta a voi decomprimere i dati.
<blockquote class=note>
<p><span class=u>☞</span>Suggerimento importante per gli sviluppatori lato server: assicuratevi che la versione compressa di una risorsa abbia un <a href=#etags>Etag</a> differente rispetto alla versione non compressa. Altrimenti, i proxy di cache si confonderanno e potrebbero servire la versione compressa a client che non sono in grado di gestirla. Leggete la discussione sul <a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=39727">bug 39727 di Apache</a> per maggiori dettagli su questo sottile problema.
</blockquote>
<p>Le librerie <abbr>HTTP</abbr> incluse in Python non supportano la compressione, ma <code>httplib2</code> lo fa.
<h3 id=redirects>Le redirezioni</h3>
<p><a href=http://www.w3.org/Provider/Style/URI>Gli <abbr>URI</abbr> fighi non cambiano</a>, ma molti <abbr>URI</abbr> sono seriamente sfigati. I siti web vengono riorganizzati, le pagine vengono spostate a nuovi indirizzi, persino i servizi web possono venire riorganizzati. Un feed pubblicato all’indirizzo <code>http://example.com/index.xml</code> potrebbe venire spostato all’indirizzo <code>http://example.com/xml/atom.xml</code>. Oppure un intero dominio potrebbe spostarsi, man mano che un’organizzazione si espande e viene ristrutturata: <code>http://www.example.com/index.xml</code> diventa <code>http://server-farm-1.example.com/index.xml</code>.
<aside><code>Location</code> significa: “Guarda là!”</aside>
<p>Ogni volta che richiedete un qualsiasi tipo di risorsa da un server <abbr>HTTP</abbr>, il server include un codice di stato nella propria risposta. Il codice di stato <code>200</code> significa “è tutto normale, ecco la pagina che avete chiesto”. Il codice di stato <code>404</code> significa “pagina non trovata”. (Avete probabilmente visto errori 404 navigando sul web.) I codici di stato che cominciano con 3 indicano una qualche forma di redirezione.
<p><abbr>HTTP</abbr> ha molti modi diversi per dire che una risorsa si è spostata. Le due tecniche più comuni sono i codici di stato <code>302</code> e <code>301</code>. Il codice di stato <code>302</code> indica una <i>redirezione temporanea</i>: significa “oops, quella risorsa è stata temporaneamente spostata qui” (e poi vi dà l’indirizzo temporaneo in un’intestazione <code>Location</code>). Il codice di stato <code>301</code> indica una <i>redirezione permanente</i>: significa “oops, quella risorsa è stata permanentemente spostata” (e poi vi dà il nuovo indirizzo in un’intestazione <code>Location</code>). Se ottenete un codice di stato <code>302</code> e un nuovo indirizzo, la specifica <abbr>HTTP</abbr> dice che dovreste usare il nuovo indirizzo per ottenere quello che avete chiesto, ma la prossima volta che volete accedere alla stessa risorsa dovreste riprovare il vecchio indirizzo. Se invece ottenete un codice di stato <code>301</code> e un nuovo indirizzo, siete tenuti a utilizzare il nuovo indirizzo da quel momento in poi.
<p>Il modulo <code>urllib.request</code> “segue” automaticamente le redirezioni quando riceve i codici di stato appropriati da un server <abbr>HTTP</abbr>, ma non vi dice di averlo fatto. Finirete per ottenere i dati che avete chiesto, ma non saprete mai che la libreria sottostante vi ha “aiutato” a seguire una redirezione. Così continuerete a tempestare di richieste il vecchio indirizzo, e ogni volta il modulo <code>urllib.request</code> vi “aiuterà” a seguire la redirezione. In altre parole, il modulo tratta le redirezioni permanenti allo stesso modo delle redirezioni temporanee. Questo significa due viaggi invece di uno, che è male per il server è male per voi.
<p><code>httplib2</code> gestisce le redirezioni permanenti per voi. Non solo vi dirà che c’è stata una redirezione permanente, ma terrà traccia di tali redirezioni localmente e riscriverà automaticamente gli <abbr>URL</abbr> rediretti prima di spedire loro una richiesta.
<p class=a>⁂
<h2 id=dont-try-this-at-home>Come non prelevare dati via HTTP</h2>
<p>Diciamo che volete scaricare una risorsa via <abbr>HTTP</abbr>, per esempio <a href=xml.html>un feed Atom</a>. Essendo un feed, non vi limiterete a scaricare la risorsa una sola volta, ma vorrete scaricarla più e più volte. (La maggior parte dei lettori di feed controllano ogni ora se ci sono stati dei cambiamenti.) Facciamolo prima in modo veloce ma grossolano, poi vediamo come potete farlo meglio.
<pre class='nd screen'>
<samp class=p>>>> </samp><kbd class=pp>import urllib.request</kbd>
<samp class=p>>>> </samp><kbd class=pp>a_url = 'http://diveintopython3.org/examples/feed.xml'</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>data = urllib.request.urlopen(a_url).read()</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>type(data)</kbd> <span class=u>②</span></a>
<samp class=pp><class 'bytes'></samp>
<samp class=p>>>> </samp><kbd class=pp>print(data)</kbd>
<samp class=pp><?xml version='1.0' encoding='utf-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' xml:lang='it'>
<title>dive into mark</title>
<subtitle>attualmente tra una dipendenza e l'altra</subtitle>
<id>tag:diveintomark.org,2001-07-29:/</id>
<updated>2009-03-27T21:56:07Z</updated>
<link rel='alternate' type='text/html' href='http://diveintomark.org/'/>
…
</samp></pre>
<ol>
<li>Scaricare qualsiasi cosa via <abbr>HTTP</abbr> è incredibilmente facile in Python; in effetti, vi ci vuole solo una riga di codice. Il modulo <code>urllib.request</code> ha una comoda funzione <code>urlopen()</code> che prende l’indirizzo della pagina che volete e restituisce un oggetto simile a un file che potete semplicemente leggere con il metodo <code>read()</code> per ottenere il contenuto completo della pagina. Non potrebbe proprio essere più facile.
<li>Il metodo <code>urlopen().read()</code> restituisce sempre un <a href=stringhe.html#byte-arrays>oggetto <code>bytes</code>, non una stringa</a>. Ricordatevi che i byte sono byte e che i caratteri sono un’astrazione. I server <abbr>HTTP</abbr> non si occupano di astrazioni. Se richiedete una risorsa, ottenete byte. Se volete una stringa, dovrete <a href=http://feedparser.org/docs/character-encoding.html>determinare la codifica di carattere</a> dei byte ed effettuare esplicitamente la conversione.
</ol>
<p>Quindi cosa c’è di sbagliato in questo modo di operare? Per un impiego rapido e occasionale durante il collaudo o lo sviluppo, non c’è niente di sbagliato. Lo faccio sempre. Volevo i contenuti del feed e ho ottenuto i contenuti del feed. La stessa tecnica funziona per tutte le pagine web. Ma una volta che cominciate a pensare in termini di un servizio web che volete accedere regolarmente (<i>e.g.</i> richiedendo questo feed una volta ogni ora), allora lo state facendo in maniera inefficiente e grossolana.
<p class=a>⁂
<h2 id=whats-on-the-wire>Cosa viene trasmesso attraverso la rete?</h2>
<p>Per vedere perché questo modo di prelevare dati è inefficiente e grossolano, attiviamo le caratteristiche di debug della libreria <abbr>HTTP</abbr> inclusa in Python e vediamo cosa viene trasmesso “sul filo” (cioè attraverso la rete).
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>from http.client import HTTPConnection</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>HTTPConnection.debuglevel = 1</kbd> <span class=u>①</span></a>
<samp class=p>>>> </samp><kbd class=pp>from urllib.request import urlopen</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>response = urlopen('http://diveintopython3.org/examples/feed.xml')</kbd> <span class=u>②</span></a>
<samp><a>send: b'GET /examples/feed.xml HTTP/1.1 <span class=u>③</span></a>
<a>Host: diveintopython3.org <span class=u>④</span></a>
<a>Accept-Encoding: identity <span class=u>⑤</span></a>
<a>User-Agent: Python-urllib/3.1' <span class=u>⑥</span></a>
Connection: close
reply: 'HTTP/1.1 200 OK'
…ulteriori informazioni di debug omesse…</samp></pre>
<ol>
<li>Come avevo menzionato all’inizio del capitolo, <code>urllib.request</code> si basa su un’altra libreria standard inclusa in Python, <code>http.client</code>. Di solito non avete bisogno di toccare <code>http.client</code> direttamente. (Il modulo <code>urllib.request</code> la importa automaticamente.) Ma qui noi la importiamo in modo da poter attivare il flag di debug sulla classe <code>HTTPConnection</code> che <code>urllib.request</code> usa per connettersi a un server <abbr>HTTP</abbr>.
<li>Ora che il flag di debug è impostato, le informazioni sulla richiesta e sulla risposta <abbr>HTTP</abbr> sono stampate in tempo reale. Come potete vedere, quando richiedete il feed Atom il modulo <code>urllib.request</code> invia cinque righe al server.
<li>La prima riga specifica il verbo <abbr>HTTP</abbr> che state usando e il percorso della risorsa (senza il nome del dominio).
<li>La seconda riga specifica il nome del dominio da dove stiamo richiedendo questo feed.
<li>La terza riga specifica gli algoritmi di compressione che il client supporta. Come avevo menzionato in precedenza, <a href=#compression><code>urllib.request</code> non supporta la compressione</a> di default.
<li>La quarta riga specifica il nome della libreria che sta effettuando la richiesta. Per default, questo nome è <code>Python-urllib</code> più un numero di versione. Sia <code>urllib.request</code> che <code>httplib2</code> permettono di cambiare il nome dell’applicazione usata semplicemente aggiungendo un’intestazione <code>User-Agent</code> alla richiesta (che sostituirà il valore predefinito).
</ol>
<aside>Stiamo scaricando 3070 byte mentre ne avremmo potuti scaricare 941.</aside>
<p>Ora diamo un’occhiata ai dati che il server ha inviato nella sua risposta.
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>print(response.headers.as_string())</kbd> <span class=u>①</span></a>
<samp><a>Date: Sun, 31 May 2009 19:23:06 GMT <span class=u>②</span></a>
Server: Apache
<a>Last-Modified: Sun, 31 May 2009 06:39:55 GMT <span class=u>③</span></a>
<a>ETag: "bfe-93d9c4c0" <span class=u>④</span></a>
Accept-Ranges: bytes
<a>Content-Length: 3070 <span class=u>⑤</span></a>
<a>Cache-Control: max-age=86400 <span class=u>⑥</span></a>
Expires: Mon, 01 Jun 2009 19:23:06 GMT
Vary: Accept-Encoding
Connection: close
Content-Type: application/xml</samp>
<a><samp class=p>>>> </samp><kbd class=pp>data = response.read()</kbd> <span class=u>⑦</span></a>
<samp class=p>>>> </samp><kbd class=pp>len(data)</kbd>
<samp class=pp>3070</samp></pre>
<ol>
<li>L’oggetto <var>response</var> restituito dalla funzione <code>urllib.request.urlopen()</code> contiene tutte le intestazioni <abbr>HTTP</abbr> che il server ha inviato nella risposta. Contiene anche i metodi per scaricare i dati effettivi, a cui arriveremo tra un minuto.
<li>Il server vi dice quando si è occupato della vostra richiesta.
<li>Questa risposta include un’intestazione <a href=#last-modified><code>Last-Modified</code></a>.
<li>Questa risposta include un’intestazione <a href=#etags><code>ETag</code></a>.
<li>La dimensione dei dati è 3070 byte. Notate quello che <em>manca</em> qui: un’intestazione <code>Content-encoding</code>. La vostra richiesta ha dichiarato di accettare solo dati non compressi (<code>Accept-encoding: identity</code>) e, come c’era da aspettarsi, questa risposta contiene dati non compressi.
<li>Questa risposta include intestazioni di cache che affermano che questo feed può essere tenuto in memoria per 24 ore (86400 secondi).
<li>E infine scarichiamo i dati effettivi invocando <code>response.read()</code>. Come potete vedere dal risultato della funzione <code>len()</code>, questa operazione scarica tutti i 3070 byte in una volta sola.
</ol>
<p>Come potete vedere, questo codice è già inefficiente: ha richiesto (e ricevuto) dati non compressi. So per certo che questo server supporta la <a href=#compression>compressione tramite gzip</a>, ma la compressione <abbr>HTTP</abbr> è opzionale. Non l’abbiamo richiesta e quindi non l’abbiamo ottenuta. Questo significa che stiamo scaricando 3070 byte mentre ne avremmo potuti scaricare solo 941. Cattivo cane, niente biscotto.
<p>Ma aspettate, le cose peggiorano! Per vedere quanto questo codice sia davvero inefficiente, richiediamo lo stesso feed una seconda volta.
<pre class='nd screen'>
# continua dall'<a href=#whats-on-the-wire>esempio precedente</a>
<samp class=p>>>> </samp><kbd class=pp>response2 = urlopen('http://diveintopython3.org/examples/feed.xml')</kbd>
<samp>send: b'GET /examples/feed.xml HTTP/1.1
Host: diveintopython3.org
Accept-Encoding: identity
User-Agent: Python-urllib/3.1'
Connection: close
reply: 'HTTP/1.1 200 OK'
…ulteriori informazioni di debug omesse…</samp></pre>
<p>Notate qualcosa di peculiare in questa richiesta? Non è cambiata! È esattamente uguale alla prima richiesta. Nessun segno di <a href=#last-modified>intestazioni <code>If-Modified-Since</code></a>. Nessun segno di <a href=#etags>intestazioni <code>If-None-Match</code></a>. Nessun rispetto per le intestazioni di cache. Ancora nessuna compressione.
<p>E cosa succede quando effettuate due volte la stessa richiesta? Ottenete due volte la stessa risposta.
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>print(response2.headers.as_string())</kbd> <span class=u>①</span></a>
<samp>Date: Mon, 01 Jun 2009 03:58:00 GMT
Server: Apache
Last-Modified: Sun, 31 May 2009 22:51:11 GMT
ETag: "bfe-255ef5c0"
Accept-Ranges: bytes
Content-Length: 3070
Cache-Control: max-age=86400
Expires: Tue, 02 Jun 2009 03:58:00 GMT
Vary: Accept-Encoding
Connection: close
Content-Type: application/xml</samp>
<samp class=p>>>> </samp><kbd class=pp>data2 = response2.read()</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>len(data2)</kbd> <span class=u>②</span></a>
<samp class=pp>3070</samp>
<a><samp class=p>>>> </samp><kbd class=pp>data2 == data</kbd> <span class=u>③</span></a>
<samp class=pp>True</samp></pre>
<ol>
<li>Il server sta ancora mandando la stessa schiera di intestazioni “intelligenti”: <code>Cache-Control</code> ed <code>Expires</code> per consentire l’uso della cache, <code>Last-Modified</code> ed <code>ETag</code> per abilitare il rilevamento della modifica più recente. Persino l’intestazione <code>Vary: Accept-Encoding</code> suggerisce che il server supporterebbe la compressione, se solo la chiedessimo. Ma non l’abbiamo fatto.
<li>Ancora una volta, l’operazione di prelievo di questi dati scarica tutti i 3070 byte…
<li>…esattamente gli stessi 3070 byte che avete scaricato l’ultima volta.
</ol>
<p><abbr>HTTP</abbr> è progettato per funzionare meglio di così. <code>urllib</code> parla <abbr>HTTP</abbr> come io parlo spagnolo — abbastanza bene da cavarmela improvvisando, ma non a sufficienza da tenere una conversazione. <abbr>HTTP</abbr> è una conversazione. È ora di sostituire <code>urllib</code> con una libreria in grado di parlare <abbr>HTTP</abbr> correntemente.
<p class=a>⁂
<h2 id=introducing-httplib2>Una introduzione ad <code>httplib2</code></h2>
<p>Prima di usare <code>httplib2</code>, avrete bisogno di installarla. Visitate <a href=http://code.google.com/p/httplib2/><code>code.google.com/p/httplib2/</code></a> e scaricate l’ultima versione. <code>httplib2</code> è disponibile per Python 2.x e Python 3.x. Assicuratevi di prendere la versione per Python 3, che ha un nome simile a <code>httplib2-python3-0.5.0.zip</code>.
<p>Estraete i contenuti dell’archivio, aprite una finestra di terminale e posizionatevi nella directory <code>httplib2</code> appena creata. Su Windows, aprite il menu <code>Start</code>, selezionate <code>Esegui...</code>, digitate <kbd>cmd.exe</kbd> e premete <kbd>INVIO</kbd>.
<pre class=screen>
<samp class=p>c:\Users\pilgrim\Downloads> </samp><kbd><mark>dir</mark></kbd>
<samp> Il volume nell'unità C non ha etichetta.
Numero di serie del volume: DED5-B4F8
Directory di c:\Users\pilgrim\Downloads
28/07/2009 12:36 <DIR> .
28/07/2009 12:36 <DIR> ..
28/07/2009 12:36 <DIR> httplib2-python3-0.5.0
28/07/2009 12:33 18,997 httplib2-python3-0.5.0.zip
1 File 18.997 byte
3 Directory 61.496.684.544 byte disponibili</samp>
<samp class=p>c:\Users\pilgrim\Downloads> </samp><kbd><mark>cd httplib2-python3-0.5.0</mark></kbd>
<samp class=p>c:\Users\pilgrim\Downloads\httplib2-python3-0.5.0> </samp><kbd><mark>c:\python31\python.exe setup.py install</mark></kbd>
<samp>eseguo install
eseguo build
eseguo build_py
eseguo install_lib
creo c:\python31\Lib\site-packages\httplib2
copio build\lib\httplib2\iri2uri.py -> c:\python31\Lib\site-packages\httplib2
copio build\lib\httplib2\__init__.py -> c:\python31\Lib\site-packages\httplib2
compilo c:\python31\Lib\site-packages\httplib2\iri2uri.py in iri2uri.pyc
compilo c:\python31\Lib\site-packages\httplib2\__init__.py in __init__.pyc
eseguo install_egg_info
Scrivo c:\python31\Lib\site-packages\httplib2-python3_0.5.0-py3.1.egg-info</samp></pre>
<p>Su Mac OS X, aprite l’applicazione <code>Terminal.app</code> che si trova nella vostra cartella <code>/Applications/Utilities/</code>. Su Linux, lanciate l’applicazione <code>Terminale</code>, che di solito si trova nel vostro menu <code>Applicazioni</code> sotto la voce <code>Accessori</code> o <code>Strumenti di sistema</code>.
<pre class=screen>
<samp class=p>you@localhost:~/Desktop$ </samp><kbd><mark>unzip httplib2-python3-0.5.0.zip</mark></kbd>
<samp>Archivio: httplib2-python3-0.5.0.zip
decomprimo: httplib2-python3-0.5.0/README
decomprimo: httplib2-python3-0.5.0/setup.py
decomprimo: httplib2-python3-0.5.0/PKG-INFO
decomprimo: httplib2-python3-0.5.0/httplib2/__init__.py
decomprimo: httplib2-python3-0.5.0/httplib2/iri2uri.py</samp>
<samp class=p>you@localhost:~/Desktop$ </samp><kbd><mark>cd httplib2-python3-0.5.0/</mark></kbd>
<samp class=p>you@localhost:~/Desktop/httplib2-python3-0.5.0$ </samp><kbd><mark>sudo python3 setup.py install</mark></kbd>
<samp>eseguo install
eseguo build
eseguo build_py
creo build
creo build/lib.linux-x86_64-3.1
creo build/lib.linux-x86_64-3.1/httplib2
copio httplib2/iri2uri.py -> build/lib.linux-x86_64-3.1/httplib2
copio httplib2/__init__.py -> build/lib.linux-x86_64-3.1/httplib2
eseguo install_lib
creo /usr/local/lib/python3.1/dist-packages/httplib2
copio build/lib.linux-x86_64-3.1/httplib2/iri2uri.py -> /usr/local/lib/python3.1/dist-packages/httplib2
copio build/lib.linux-x86_64-3.1/httplib2/__init__.py -> /usr/local/lib/python3.1/dist-packages/httplib2
compilo /usr/local/lib/python3.1/dist-packages/httplib2/iri2uri.py in iri2uri.pyc
compilo /usr/local/lib/python3.1/dist-packages/httplib2/__init__.py in __init__.pyc
eseguo install_egg_info
Scrivo /usr/local/lib/python3.1/dist-packages/httplib2-python3_0.5.0.egg-info</samp></pre>
<p>Per utilizzare <code>httplib2</code>, create un’istanza della classe <code>httplib2.Http</code>.
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import httplib2</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>h = httplib2.Http('.cache')</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/examples/feed.xml')</kbd> <span class=u>②</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>response.status</kbd> <span class=u>③</span></a>
<samp class=pp>200</samp>
<a><samp class=p>>>> </samp><kbd class=pp>content[:52]</kbd> <span class=u>④</span></a>
<samp class=pp>b"<?xml version='1.0' encoding='utf-8'?>\r\n<feed xmlns="</samp>
<samp class=p>>>> </samp><kbd class=pp>len(content)</kbd>
<samp class=pp>3070</samp></pre>
<ol>
<li>L’interfaccia principale al modulo <code>httplib2</code> è l’oggetto <code>Http</code>. Per ragioni che vedrete nella prossima sezione, dovreste sempre passare un nome di directory quando create un oggetto <code>Http</code>. La directory non deve per forza esistere, <code>httplib2</code> la creerà se è necessario.
<li>Una volta che avete un oggetto <code>Http</code>, recuperare dati è tanto semplice quanto invocare il metodo <code>request()</code> con l’indirizzo dei dati che volete. Questo metodo emetterà una richiesta <abbr>HTTP</abbr> <code>GET</code> per quell’<abbr>URL</abbr>. (Più avanti in questo capitolo vedrete come emettere altre richieste <abbr>HTTP</abbr>, come <code>POST</code>.)
<li>Il metodo <code>request()</code> restituisce due valori. Il primo è un oggetto <code>httplib2.Response</code>, che contiene tutte le intestazioni <abbr>HTTP</abbr> restituite dal server. Per esempio, il codice di stato <code>200</code> memorizzato nell’attributo <code>status</code> indica che la richiesta ha avuto successo.
<li>La variabile <var>content</var> contiene i dati effettivi che sono stati restituiti dal server <abbr>HTTP</abbr>. I dati vengono restituiti come un <a href=stringhe.html#byte-arrays>oggetto <code>bytes</code>, non una stringa</a>. Se li volete sotto forma di stringa dovete <a href=http://feedparser.org/docs/character-encoding.html>determinarne la codifica di carattere</a> ed effettuare da voi la conversione.
</ol>
<blockquote class=note>
<p><span class=u>☞</span>Avrete probabilmente bisogno di un solo oggetto <code>httplib2.Http</code>. Esistono valide ragioni per crearne più di uno, ma dovreste farlo solo se sapete perché ne avete bisogno. “Devo richiedere dati da due <abbr>URL</abbr> differenti” non è una ragione valida. Riutilizzate l’oggetto <code>Http</code> e invocate semplicemente il metodo <code>request()</code> due volte.
</blockquote>
<h3 id=why-bytes>Una breve digressione per spiegare perché <code>httplib2</code> restituisce byte invece di stringhe</h3>
<p>Byte. Stringhe. Che sofferenza. Perché <code>httplib2</code> non può “semplicemente” fare la conversione per voi? Be’, è complicato, perché le regole per determinare la codifica di carattere sono specifiche per il tipo di risorsa che state richiedendo. Come potrebbe fare <code>httplib2</code> a sapere che tipo di risorsa state richiedendo? Di solito, il tipo è elencato nell’intestazione <abbr>HTTP</abbr> <code>Content-Type</code>, ma questa è una caratteristica opzionale di <abbr>HTTP</abbr> e non tutti i server <abbr>HTTP</abbr> la supportano. Se quell’intestazione non è presente nella risposta <abbr>HTTP</abbr>, tocca al client indovinare. Questa operazione viene comunemente chiamata “content sniffing” (letteralmente, annusare il contenuto) e non è mai perfetta.
<p>Se sapete che tipo di risorsa vi aspettate (in questo caso, un documento <abbr>XML</abbr>), forse potreste “semplicemente” passare l’oggetto <code>bytes</code> restituito alla funzione <a href=xml.html#xml-parse><code>xml.etree.ElementTree.parse()</code></a>. Questo funzionerà purché il documento <abbr>XML</abbr> includa informazioni sulla propria codifica di carattere (come accade in questo caso), ma questa è una caratteristica opzionale e non tutti i documenti <abbr>XML</abbr> lo fanno. Se un documento <abbr>XML</abbr> non include informazioni di codifica, il client è tenuto a controllare il protocollo di trasporto a cui sono allegati i dati — cioè l’intestazione <abbr>HTTP</abbr> <code>Content-Type</code>, che può includere un parametro <code>charset</code>.
<p class=ss><a style=border:0 href=http://www.cafepress.com/feedparser><img src=http://feedparser.org/img/feedparser.jpg alt='[Maglietta “Io supporto la RFC 3023”]' width=150 height=150></a>
<p>Ma le cose vanno anche peggio di così. Ora le informazioni sulla codifica di carattere possono trovarsi in due posti: all’interno del documento <abbr>XML</abbr> e nell’intestazione <abbr>HTTP</abbr> <code>Content-Type</code>. Se l’informazione è in <em>entrambi</em> i posti, quale deve essere ritenuta valida? Secondo la <a href=http://www.ietf.org/rfc/rfc3023.txt>RFC 3023</a> (vi giuro che non me lo sto inventando), se il tipo di contenuto fornito nell’intestazione <abbr>HTTP</abbr> <code>Content-Type</code> è <code>application/xml</code>, <code>application/xml-dtd</code>, <code>application/xml-external-parsed-entity</code>, o un qualsiasi sottotipo di <code>application/xml</code> come per esempio <code>application/atom+xml</code> o <code>application/rss+xml</code> o persino <code>application/rdf+xml</code>, allora la codifica è
<ol>
<li>la codifica data nel parametro <code>charset</code> dell’intestazione <abbr>HTTP</abbr> <code>Content-Type</code>, oppure
<li>la codifica data nell’attributo <code>encoding</code> della dichiarazione <abbr>XML</abbr> all’interno del documento, oppure
<li><abbr>UTF-8</abbr>.
</ol>
<p>D’altra parte, se il tipo di contenuto dato nell’intestazione <abbr>HTTP</abbr> <code>Content-Type</code> è <code>text/xml</code>, <code>text/xml-external-parsed-entity</code>, o un sottotipo come per esempio <code>text/QualsiasiCosa+xml</code>, allora l’attributo di codifica della dichiarazione <abbr>XML</abbr> nel documento viene completamente ignorato e la codifica è
<ol>
<li>la codifica data nel parametro <code>charset</code> dell’intestazione <abbr>HTTP</abbr> <code>Content-Type</code>, oppure
<li><abbr>US-ASCII</abbr>.
</ol>
<p>E questo è solo per i documenti <abbr>XML</abbr>. Per i documenti <abbr>HTML</abbr>, i browser web hanno costruito <a type=application/pdf href=http://www.adambarth.com/papers/2009/barth-caballero-song.pdf>regole per il content sniffing</a> [<abbr>PDF</abbr>] talmente bizantine che <a href='http://www.google.com/search?q=barth+content-type+processing+model'>stiamo ancora cercando di capire come funzionano</a>.
<p>“<a href=http://code.google.com/p/httplib2/source/checkout>Le patch sono benvenute</a>.”
<h3 id=httplib2-caching>Come <code>httplib2</code> gestisce la cache</h3>
<p>Ricordate quando, nella sezione precedente, ho detto che dovreste sempre creare un oggetto <code>httplib2.Http</code> con un nome di directory? Il motivo è la cache.
<pre class=screen>
# continua dall'<a href=#introducing-httplib2>esempio precedente</a>
<a><samp class=p>>>> </samp><kbd class=pp>response2, content2 = h.request('http://diveintopython3.org/examples/feed.xml')</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>response2.status</kbd> <span class=u>②</span></a>
<samp class=pp>200</samp>
<a><samp class=p>>>> </samp><kbd class=pp>content2[:52]</kbd> <span class=u>③</span></a>
<samp class=pp>b"<?xml version='1.0' encoding='utf-8'?>\r\n<feed xmlns="</samp>
<samp class=p>>>> </samp><kbd class=pp>len(content2)</kbd>
<samp class=pp>3070</samp></pre>
<ol>
<li>Questo non dovrebbe essere terribilmente sorprendente. È la stessa cosa che avete fatto l’ultima volta, senonché state mettendo il risultato in due nuove variabili.
<li>Il valore del codice di stato <abbr>HTTP</abbr> memorizzato in <code>status</code> è ancora <code>200</code>, proprio come l’ultima volta.
<li>Anche il contenuto scaricato è lo stesso dell’ultima volta.
</ol>
<p>E quindi… chi se ne importa? Uscite dalla shell interattiva di Python e rilanciatela con una nuova sessione, e vi farò vedere.
<pre class=screen>
# NON continua dall'esempio precedente!
# Per favore uscite dalla shell interattiva
# e lanciatene una nuova.
<samp class=p>>>> </samp><kbd class=pp>import httplib2</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>httplib2.debuglevel = 1</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>h = httplib2.Http('.cache')</kbd> <span class=u>②</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/examples/feed.xml')</kbd> <span class=u>③</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>len(content)</kbd> <span class=u>④</span></a>
<samp class=pp>3070</samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.status</kbd> <span class=u>⑤</span></a>
<samp class=pp>200</samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.fromcache</kbd> <span class=u>⑥</span></a>
<samp class=pp>True</samp></pre>
<ol>
<li>Attiviamo il debug e vediamo <a href=#whats-on-the-wire>cosa viene trasmesso attraverso la rete</a>. Questo è l’equivalente per <code>httplib2</code> dell’attivazione del debug in <code>http.client</code>. <code>httplib2</code> stamperà tutti i dati inviati al server e alcune informazioni chiave restituite dal server.
<li>Create un oggetto <code>httplib2.Http</code> con lo stesso nome di directory precedente.
<li>Richiedete lo stesso <abbr>URL</abbr> di prima. <em>Sembra che non accada nulla.</em> Più precisamente, nulla viene inviato al server e nulla viene restituito dal server. Non c’è assolutamente alcuna attività di rete.
<li>Tuttavia abbiamo “ricevuto” alcuni dati — in effetti, li abbiamo ricevuti tutti.
<li>Abbiamo anche “ricevuto” un codice di stato <abbr>HTTP</abbr> che indica che la “richiesta” ha avuto successo.
<li>Qui sta il punto: questa “risposta” è stata generata dalla cache locale di <code>httplib2</code>. Quel nome di directory che avete passato nel creare l’oggetto <code>httplib2.Http</code> — quella directory conserva la cache di <code>httplib2</code> per tutte le operazioni che ha mai effettuato.
</ol>
<aside>Cosa viene trasmesso attraverso la rete? Proprio nulla.</aside>
<blockquote class=note>
<p><span class=u>☞</span>Se volete attivare le informazioni di debug di <code>httplib2</code>, dovete impostare una costante a livello di modulo (<code>httplib2.debuglevel</code>) e poi creare un nuovo oggetto <code>httplib2.Http</code>. Se volete disattivare le informazioni di debug, dovete modificare la stessa costante a livello di modulo e poi creare un nuovo oggetto <code>httplib2.Http</code>.
</blockquote>
<p>Avete già richiesto in precedenza i dati a questo <abbr>URL</abbr>. Quella richiesta ha avuto successo (<code>status: 200</code>). Quella risposta includeva non solo i dati del feed, ma anche un insieme di <a href=#caching>intestazioni di cache</a> che dicevano a chiunque fosse in ascolto che poteva tenere in cache questa risorsa per 24 ore. (<code>Cache-Control: max-age=86400</code>, che sono 24 ore misurate in secondi). <code>httplib2</code> comprende e rispetta queste intestazioni di cache, e ha memorizzato la risposta precedente nella directory <code>.cache</code> (il cui nome avete passato quando avete creato l’oggetto <code>Http</code>). Quella informazione in cache non è ancora scaduta, quindi la seconda volta che richiedete i dati a questo <abbr>URL</abbr> <code>httplib2</code> semplicemente restituisce il risultato in cache senza nemmeno utilizzare la rete.
<p>Ho detto “semplicemente”, ma ovviamente c’è molta complessità nascosta dietro questa semplicità. <code>httplib2</code> gestisce l’uso della cache da parte di <abbr>HTTP</abbr> in maniera <em>automatica</em> e <em>predefinita</em>. Casomai per qualche ragione aveste bisogno di sapere se una risposta proveniva dalla cache, potete controllare l’attributo <code>response.fromcache</code>. Altrimenti, tutto funziona e basta.
<p id=bypass-the-cache>Ora, supponete di avere i dati nella cache, ma di voler aggirare la cache e riottenerli dal server remoto. I browser talvolta lo fanno se l’utente lo richiede esplicitamente. Per esempio, premere <kbd>F5</kbd> aggiorna la pagina corrente, ma premere <kbd>Ctrl+F5</kbd> aggira la cache e riottiene la pagina corrente dal server remoto. Potreste pensare: “Oh, cancellerò semplicemente i dati dalla mia cache locale, poi li richiederò di nuovo.” Potreste farlo, ma ricordate che potrebbero esserci più parti coinvolte anziché solo voi e il server remoto. Cosa mi dite di quei server proxy intermedi? Quelli sono completamente al di là del vostro controllo, potrebbero ancora avere quei dati nella propria cache e ve li restituiranno tranquillamente perché (per quanto li riguarda) la loro cache è ancora valida.
<p>Invece di manipolare la vostra cache locale e sperare per il meglio, dovreste usare le caratteristiche di <abbr>HTTP</abbr> per assicurarvi che la vostra richiesta raggiunga effettivamente il server remoto.
<pre class=screen>
# continua dall'esempio precedente
<samp class=p>>>> </samp><kbd class=pp>response2, content2 = h.request('http://diveintopython3.org/examples/feed.xml',</kbd>
<a><samp class=p>... </samp><kbd class=pp> headers={'cache-control':'no-cache'})</kbd> <span class=u>①</span></a>
<samp><a>connect: (diveintopython3.org, 80) <span class=u>②</span></a>
send: b'GET /examples/feed.xml HTTP/1.1
Host: diveintopython3.org
user-agent: Python-httplib2/$Rev: 259 $
accept-encoding: deflate, gzip
cache-control: no-cache'
reply: 'HTTP/1.1 200 OK'
…ulteriori informazioni di debug omesse…</samp>
<samp class=p>>>> </samp><kbd class=pp>response2.status</kbd>
<samp class=pp>200</samp>
<a><samp class=p>>>> </samp><kbd class=pp>response2.fromcache</kbd> <span class=u>③</span></a>
<samp class=pp>False</samp>
<a><samp class=p>>>> </samp><kbd class=pp>print(dict(response2.items()))</kbd> <span class=u>④</span></a>
<samp class=pp>{'status': '200',
'content-length': '3070',
'content-location': 'http://diveintopython3.org/examples/feed.xml',
'accept-ranges': 'bytes',
'expires': 'Wed, 03 Jun 2009 00:40:26 GMT',
'vary': 'Accept-Encoding',
'server': 'Apache',
'last-modified': 'Sun, 31 May 2009 22:51:11 GMT',
'connection': 'close',
'-content-encoding': 'gzip',
'etag': '"bfe-255ef5c0"',
'cache-control': 'max-age=86400',
'date': 'Tue, 02 Jun 2009 00:40:26 GMT',
'content-type': 'application/xml'}</samp></pre>
<ol>
<li><code>httplib2</code> vi consente di aggiungere intestazioni <abbr>HTTP</abbr> arbitrarie a qualsiasi richiesta in uscita. Per aggirare <em>tutte</em> le cache (non solo quella sul vostro disco locale, ma anche tutti i proxy di cache tra voi e il server remoto), aggiungete un’intestazione <code>no-cache</code> nel dizionario <var>headers</var>.
<li>Ora vedete che <code>httplib2</code> inizia una richiesta di rete. <code>httplib2</code> comprende e rispetta le intestazioni di cache <em>in entrambe le direzioni</em> — come parte della risposta in arrivo <em>e come parte della richiesta in partenza</em>. Il modulo ha notato che avete aggiunto l’intestazione <code>no-cache</code>, quindi ha aggirato la propria cache locale e non ha avuto altra scelta se non quella di utilizzare la rete per richiedere i dati.
<li>Questa risposta <em>non</em> è stata generata dalla vostra cache locale. Lo sapevate, naturalmente, perché avete visto le informazioni di debug sulla richiesta in partenza. Ma è piacevole averlo programmaticamente verificato.
<li>La richiesta ha avuto successo, avete nuovamente scaricato l’intero feed dal server remoto. Naturalmente, il server ha anche spedito una serie completa di intestazioni <abbr>HTTP</abbr> insieme ai dati del feed, comprese le intestazioni di cache che <code>httplib2</code> utilizza per aggiornare la propria cache locale nella speranza di evitare l’accesso alla rete la <em>prossima</em> volta che richiedete questo feed. Ogni caratteristica della gestione della cache in <abbr>HTTP</abbr> è progettata per massimizzare l’uso della cache e minimizzare l’accesso alla rete. Anche se avete aggirato la cache questa volta, il server remoto apprezzerebbe davvero che voi manteneste in cache il risultato per la prossima volta.
</ol>
<h3 id=httplib2-etags>Come <code>httplib2</code> gestisce le intestazioni <code>Last-Modified</code> ed <code>ETag</code></h3>
<p>Le <a href=#caching>intestazioni di cache</a> <code>Cache-Control</code> ed <code>Expires</code> sono chiamate <i>indicatori di freschezza</i> e dicono alle cache in termini certi che potete evitare completamente ogni accesso alla rete fino a quando la cache scade. Questo è esattamente il comportamento che avete visto <a href=#httplib2-caching>nella sezione precedente</a>: dato un indicatore di freschezza, <code>httplib2</code> <em>non genera un singolo byte di attività di rete</em> per prelevare i dati mantenuti in cache (a meno che voi non <a href=#bypass-the-cache>aggiriate la cache</a> in maniera esplicita, naturalmente).
<p>Ma cosa succede nel caso in cui i dati <em>potrebbero</em> essere cambiati, ma non lo sono? <abbr>HTTP</abbr> definisce le intestazioni <a href=#last-modified><code>Last-Modified</code></a> ed <a href=#etags><code>Etag</code></a> proprio a questo scopo. Queste intestazioni sono chiamate <i>validatori</i>. Se la cache locale non è più fresca, un client può spedire i validatori insieme alla prossima richiesta per vedere se i dati sono effettivamente cambiati. Se i dati non sono cambiati, il server risponde con un codice di stato <code>304</code> e <em>nessun dato</em>. Quindi c’è ancora un viaggio attraverso la rete, ma finite per scaricare meno byte.
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import httplib2</kbd>
<samp class=p>>>> </samp><kbd class=pp>httplib2.debuglevel = 1</kbd>
<samp class=p>>>> </samp><kbd class=pp>h = httplib2.Http('.cache')</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/')</kbd> <span class=u>①</span></a>
<samp>connect: (diveintopython3.org, 80)
send: b'GET / HTTP/1.1
Host: diveintopython3.org
accept-encoding: deflate, gzip
user-agent: Python-httplib2/$Rev: 259 $'
reply: 'HTTP/1.1 200 OK'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>print(dict(response.items()))</kbd> <span class=u>②</span></a>
<samp class=pp>{'-content-encoding': 'gzip',
'accept-ranges': 'bytes',
'connection': 'close',
'content-length': '6657',
'content-location': 'http://diveintopython3.org/',
'content-type': 'text/html',
'date': 'Tue, 02 Jun 2009 03:26:54 GMT',
<mark> 'etag': '"7f806d-1a01-9fb97900"',</mark>
<mark> 'last-modified': 'Tue, 02 Jun 2009 02:51:48 GMT',</mark>
'server': 'Apache',
'status': '200',
'vary': 'Accept-Encoding,User-Agent'}</samp>
<a><samp class=p>>>> </samp><kbd class=pp>len(content)</kbd> <span class=u>③</span></a>
<samp class=pp>6657</samp></pre>
<ol>
<li>Invece di un feed, questa volta scaricheremo la pagina iniziale del sito, che è in <abbr>HTML</abbr>. Dato che questa è la prima volta che richiedete questa pagina, <code>httplib2</code> ha poco con cui lavorare e invia un insieme minimo di intestazioni con la richiesta.
<li>La risposta contiene una moltitudine di intestazioni <abbr>HTTP</abbr>… ma nessuna informazione di cache. Tuttavia, include entrambe le intestazioni <code>ETag</code> e <code>Last-Modified</code>.
<li>Al momento in cui ho realizzato questo esempio, la pagina era di 6657 byte. Probabilmente ora è cambiata, ma non dovete preoccuparvi di questo.
</ol>
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/')</kbd> <span class=u>①</span></a>
<samp>connect: (diveintopython3.org, 80)
send: b'GET / HTTP/1.1
Host: diveintopython3.org
<a>if-none-match: "7f806d-1a01-9fb97900" <span class=u>②</span></a>
<a>if-modified-since: Tue, 02 Jun 2009 02:51:48 GMT <span class=u>③</span></a>
accept-encoding: deflate, gzip
user-agent: Python-httplib2/$Rev: 259 $'
<a>reply: 'HTTP/1.1 304 Not Modified' <span class=u>④</span></a></samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.fromcache</kbd> <span class=u>⑤</span></a>
<samp class=pp>True</samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.status</kbd> <span class=u>⑥</span></a>
<samp class=pp>200</samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.dict['status']</kbd> <span class=u>⑦</span></a>
<samp class=pp>'304'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>len(content)</kbd> <span class=u>⑧</span></a>
<samp class=pp>6657</samp></pre>
<ol>
<li>Avete richiesto ancora la stessa pagina con lo stesso oggetto <code>Http</code> (e la stessa cache locale).
<li><code>httplib2</code> spedisce il validatore <code>ETag</code> al server nell’intestazione <code>If-None-Match</code>.
<li><code>httplib2</code> spedisce al server anche il validatore <code>Last-Modified</code> nell’intestazione <code>If-Modified-Since</code>.
<li>Il server ha esaminato questi validatori, ha esaminato la pagina che avete richiesto e ha determinato che la pagina non è cambiata dall’ultima volta che l’avete richiesta, quindi risponde con un codice di stato <code>304</code> e <em>nessun dato</em>.
<li>Tornando al client, <code>httplib2</code> nota il codice di stato <code>304</code> e carica il contenuto della pagina dalla propria cache.
<li>Questo potrebbe confondervi un poco. In realtà ci sono <em>due</em> codici di stato — <code>304</code> (restituito dal server questa volta, che induce <code>httplib2</code> a guardare nella propria cache) e <code>200</code> (restituito dal server <em>l’ultima volta</em> e memorizzato nella cache di <code>httplib2</code> insieme ai dati della pagina). <code>response.status</code> contiene lo stato proveniente dalla cache.
<li>Se volete lo stato effettivo restituito dal server, potete ottenerlo guardando in <code>response.dict</code>, che è un dizionario delle reali intestazioni restituite dal server.
<li>Tuttavia, i dati vi vengono resi disponibili ancora nella variabile <var>content</var>. Generalmente, non avete bisogno di sapere perché una risposta è stata servita dalla cache. (Potete persino ignorare il fatto che sia stata servita dalla cache, e anche questo va bene. <code>httplib2</code> è abbastanza intelligente da lasciarvi agire in modo stupido.) Nel momento in cui il metodo <code>request()</code> restituisce il controllo al chiamante, <code>httplib2</code> ha già aggiornato la propria cache e vi ha restituito i dati.
</ol>
<h3 id=httplib2-compression>Come <code>http2lib</code> gestisce la compressione</h3>
<aside>“Facciamo musica di entrambi i generi, country E western.”</aside>
<p><abbr>HTTP</abbr> supporta <a href=#compression>diversi tipi di compressione</a>; i due tipi più comuni sono gzip e deflate. <code>httplib2</code> li supporta entrambi.
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/')</kbd>
<samp>connect: (diveintopython3.org, 80)
send: b'GET / HTTP/1.1
Host: diveintopython3.org
<a>accept-encoding: deflate, gzip <span class=u>①</span></a>
user-agent: Python-httplib2/$Rev: 259 $'
reply: 'HTTP/1.1 200 OK'</samp>
<samp class=p>>>> </samp><kbd class=pp>print(dict(response.items()))</kbd>
<samp class=pp><a>{'-content-encoding': 'gzip', <span class=u>②</span></a>
'accept-ranges': 'bytes',
'connection': 'close',
'content-length': '6657',
'content-location': 'http://diveintopython3.org/',
'content-type': 'text/html',
'date': 'Tue, 02 Jun 2009 03:26:54 GMT',
'etag': '"7f806d-1a01-9fb97900"',
'last-modified': 'Tue, 02 Jun 2009 02:51:48 GMT',
'server': 'Apache',
'status': '304',
'vary': 'Accept-Encoding,User-Agent'}</samp></pre>
<ol>
<li>Ogni volta che <code>httplib2</code> invia una richiesta, include un’intestazione <code>Accept-Encoding</code> per dire al server che è in grado di gestire la compressione sia di tipo <code>deflate</code> che di tipo <code>gzip</code>.
<li>In questo caso, il server ha risposto con un carico utile compresso tramite gzip. Nel momento in cui il metodo <code>request()</code> termina, <code>httplib2</code> ha già decompresso il corpo della risposta e lo ha piazzato nella variabile <var>content</var>. Casomai foste curiosi di sapere se la risposta era compressa oppure no, potete controllare <var>response['-content-encoding']</var>; altrimenti, non preoccupatevi di questo.
</ol>
<h3 id=httplib2-redirects>Come <code>httplib2</code> gestisce le redirezioni</h3>
<p><abbr>HTTP</abbr> definisce <a href=#redirects>due tipi di redirezioni</a>: temporanee e permanenti. Non c’è niente di speciale da fare con le redirezioni temporanee, tranne seguirle, cosa che <code>httplib2</code> esegue automaticamente.
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import httplib2</kbd>
<samp class=p>>>> </samp><kbd class=pp>httplib2.debuglevel = 1</kbd>
<samp class=p>>>> </samp><kbd class=pp>h = httplib2.Http('.cache')</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/examples/feed-302.xml')</kbd> <span class=u>①</span></a>
<samp>connect: (diveintopython3.org, 80)
<a>send: b'GET /examples/feed-302.xml HTTP/1.1 <span class=u>②</span></a>
Host: diveintopython3.org
accept-encoding: deflate, gzip
user-agent: Python-httplib2/$Rev: 259 $'
<a>reply: 'HTTP/1.1 302 Found' <span class=u>③</span></a>
<a>send: b'GET /examples/feed.xml HTTP/1.1 <span class=u>④</span></a>
Host: diveintopython3.org
accept-encoding: deflate, gzip
user-agent: Python-httplib2/$Rev: 259 $'
reply: 'HTTP/1.1 200 OK'</samp></pre>
<ol>
<li>Non c’è alcun feed a questo <abbr>URL</abbr>. Ho impostato il mio server in modo da emettere una redirezione temporanea verso l’indirizzo corretto.
<li>Questa è la richiesta.
<li>E questa è la risposta: <code>302 Found</code>. Qui non viene mostrata, ma questa risposta include anche un’intestazione <code>Location</code> che punta al vero <abbr>URL</abbr>.
<li><code>httplib2</code> torna immediatamente sui suoi passi e “segue” la redirezione emettendo un’altra richiesta per l’<abbr>URL</abbr> contenuto nell’intestazione <code>Location</code>: <code>http://diveintopython3.org/examples/feed.xml</code>
</ol>
<p>“Seguire” una redirezione non è niente di più di ciò che viene mostrato in questo esempio. <code>httplib2</code> invia una richiesta per l’<abbr>URL</abbr> che avete chiesto. Il server ribatte con una risposta che dice: “No, no, guarda qui invece.” <code>httplib2</code> invia un’altra richiesta per il nuovo <abbr>URL</abbr>.
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>response</kbd> <span class=u>①</span></a>
<samp class=pp>{'status': '200',
'content-length': '3070',
<a> 'content-location': 'http://diveintopython3.org/examples/feed.xml', <span class=u>②</span></a>
'accept-ranges': 'bytes',
'expires': 'Thu, 04 Jun 2009 02:21:41 GMT',
'vary': 'Accept-Encoding',
'server': 'Apache',
'last-modified': 'Wed, 03 Jun 2009 02:20:15 GMT',
'connection': 'close',
<a> '-content-encoding': 'gzip', <span class=u>③</span></a>
'etag': '"bfe-4cbbf5c0"',
<a> 'cache-control': 'max-age=86400', <span class=u>④</span></a>
'date': 'Wed, 03 Jun 2009 02:21:41 GMT',
'content-type': 'application/xml'}</samp></pre>
<ol>
<li>I dati memorizzati nella variabile <var>response</var> che ottenete da questa singola chiamata al metodo <code>request()</code> sono la risposta dall’<abbr>URL</abbr> finale.
<li><code>httplib2</code> aggiunge l’<abbr>URL</abbr> finale al dizionario <var>response</var>, come valore per la chiave <code>content-location</code>. Questa non è un’intestazione proveniente dal server, ma è specifica per <code>httplib2</code>.
<li>Incidentalmente, questo feed è <a href=#httplib2-compression>compresso</a>.
<li>E memorizzabile in cache. (Questo è importante, come vedrete fra un minuto.)
</ol>
<p>La risposta contenuta nella variabile <var>response</var> che avete ottenuto vi dà informazioni sull’<abbr>URL</abbr> <em>finale</em>. E se voleste informazioni sugli <abbr>URL</abbr> intermedi, quelli che alla fine vi hanno rediretto all’<abbr>URL</abbr> finale? <code>httplib2</code> vi permette di ottenere queste informazioni.
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>response.previous</kbd> <span class=u>①</span></a>
<samp class=pp>{'status': '302',
'content-length': '228',
'content-location': 'http://diveintopython3.org/examples/feed-302.xml',
'expires': 'Thu, 04 Jun 2009 02:21:41 GMT',
'server': 'Apache',
'connection': 'close',
'location': 'http://diveintopython3.org/examples/feed.xml',
'cache-control': 'max-age=86400',
'date': 'Wed, 03 Jun 2009 02:21:41 GMT',
'content-type': 'text/html; charset=iso-8859-1'}</samp>
<a><samp class=p>>>> </samp><kbd class=pp>type(response)</kbd> <span class=u>②</span></a>
<samp><class 'httplib2.Response'></samp>
<samp class=p>>>> </samp><kbd class=pp>type(response.previous)</kbd>
<samp><class 'httplib2.Response'></samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.previous.previous</kbd> <span class=u>③</span></a>
<samp class=p>>>></samp></pre>
<ol>
<li>L’attributo <var>response.previous</var> mantiene un riferimento al precedente oggetto risposta che <code>httplib2</code> ha seguito per ottenere l’oggetto risposta attuale.
<li>Sia <var>response</var> che <var>response.previous</var> sono oggetti <code>httplib2.Response</code>.
<li>Questo significa che potete controllare <var>response.previous.previous</var> per seguire la catena di redirezioni facendo ulteriori passi indietro. (Scenario: un <abbr>URL</abbr> redirige verso un secondo <abbr>URL</abbr> che redirige verso un terzo <abbr>URL</abbr>. Potrebbe accadere!) In questo caso, abbiamo già raggiunto l’inizio della catena di redirezioni, quindi l’attributo vale <code>None</code>.
</ol>
<p>Cosa succede se effettuate ancora una richiesta allo stesso <abbr>URL</abbr>?
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>response2, content2 = h.request('http://diveintopython3.org/examples/feed-302.xml')</kbd> <span class=u>①</span></a>
<samp>connect: (diveintopython3.org, 80)
<a>send: b'GET /examples/feed-302.xml HTTP/1.1 <span class=u>②</span></a>
Host: diveintopython3.org
accept-encoding: deflate, gzip
user-agent: Python-httplib2/$Rev: 259 $'
<a>reply: 'HTTP/1.1 302 Found' <span class=u>③</span></a></samp>
<a><samp class=p>>>> </samp><kbd class=pp>content2 == content</kbd> <span class=u>④</span></a>
<samp class=pp>True</samp></pre>
<ol>
<li>Stesso <abbr>URL</abbr>, stesso oggetto <code>httplib2.Http</code> (e quindi stessa cache).
<li>La risposta <code>302</code> non è stata memorizzata in cache, quindi <code>httplib2</code> invia un’altra richiesta per lo stesso <abbr>URL</abbr>.
<li>Ancora una volta, il server risponde con un <code>302</code>. Ma notate che <em>non</em> è successo: non c’è mai stata una seconda richiesta per l’<abbr>URL</abbr> finale, <code>http://diveintopython3.org/examples/feed.xml</code>. Quella risposta è stata memorizzata in cache (ricordatevi l’intestazione <code>Cache-Control</code> che avevate visto nell’esempio precedente). Una volta che <code>httplib2</code> ha ricevuto il codice <code>302 Found</code>, <em>ha controllato la propria cache prima di emettere un’altra richiesta</em>. La cache conteneva una copia fresca di <code>http://diveintopython3.org/examples/feed.xml</code>, quindi non c’era nessun bisogno di riottenerla.
<li>Nel momento in cui la sua esecuzione termina, il metodo <code>request()</code> ha già letto i dati del feed dalla cache e li ha restituiti. Naturalmente, sono gli stessi dati che avevate ricevuto l’ultima volta.
</ol>
<p>In altre parole, non dovete fare niente di speciale per le redirezioni temporanee. <code>httplib2</code> le seguirà automaticamente, e il fatto che un <abbr>URL</abbr> rediriga a un altro non ha alcun rapporto con il supporto da parte di <code>httplib2</code> per la compressione, la cache, gli <code>ETag</code>, o qualsiasi altra caratteristica di <abbr>HTTP</abbr>.
<p>Le redirezioni permanenti sono altrettanto semplici.
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>response, content = h.request('http://diveintopython3.org/examples/feed-301.xml')</kbd> <span class=u>①</span></a>
<samp>connect: (diveintopython3.org, 80)
send: b'GET /examples/feed-301.xml HTTP/1.1
Host: diveintopython3.org
accept-encoding: deflate, gzip
user-agent: Python-httplib2/$Rev: 259 $'
<a>reply: 'HTTP/1.1 301 Moved Permanently' <span class=u>②</span></a></samp>
<a><samp class=p>>>> </samp><kbd class=pp>response.fromcache</kbd> <span class=u>③</span></a>
<samp class=pp>True</samp></pre>
<ol>
<li>Ancora una volta, in realtà questo <abbr>URL</abbr> non esiste. Ho impostato il mio server in modo da emettere una redirezione permanente verso <code>http://diveintopython3.org/examples/feed.xml</code>.
<li>Ed eccola qui: codice di stato <code>301</code>. Ma ancora, notate cosa <em>non</em> è successo: non c’è stata alcuna richiesta verso l’<abbr>URL</abbr> rediretto. Perché no? Perché è già memorizzato in cache localmente.
<li><code>httplib2</code> ha “seguito” la redirezione dritto nella propria cache.
</ol>
<p>Ma aspettate! C’è di più!
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>response2, content2 = h.request('http://diveintopython3.org/examples/feed-301.xml')</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>response2.fromcache</kbd> <span class=u>②</span></a>
<samp class=pp>True</samp>
<a><samp class=p>>>> </samp><kbd class=pp>content2 == content</kbd> <span class=u>③</span></a>
<samp class=pp>True</samp>
</pre>
<ol>
<li>Ecco una differenza tra le redirezioni temporanee e permanenti: una volta che <code>httplib2</code> segue una redirezione permanente, tutte le ulteriori richieste per quell’<abbr>URL</abbr> verranno trasparentemente riscritte per dirigersi verso l’<abbr>URL</abbr> obiettivo <em>senza utilizzare la rete per l’<abbr>URL</abbr> originale</em>. Ricordate, il debug è ancora attivo, tuttavia non risulta alcuna attività di rete.
<li>Sì, questa risposta è stata recuperata dalla cache locale.
<li>Sì, avete ottenuto l’intero feed (dalla cache).
</ol>
<p><abbr>HTTP</abbr>. Funziona.
<p class=a>⁂
<h2 id=beyond-get>Oltre HTTP GET</h2>
<p>I servizi web <abbr>HTTP</abbr> non sono limitati alle richieste <code>GET</code>. E se voleste creare qualcosa di nuovo? Ogni volta che inviate un commento a una discussione su un forum, aggiornate il vostro weblog, pubblicate il vostro stato su un servizio di microblogging come <a href=http://twitter.com/>Twitter</a> o <a href=http://identi.ca/>Identi.ca</a>, state probabilmente già usando <abbr>HTTP</abbr> <code>POST</code>.
<p>Sia Twitter che Identi.ca offrono una semplice <abbr>API</abbr> basata su <abbr>HTTP</abbr> per pubblicare e aggiornare il vostro stato tramite messaggi non più lunghi di 140 caratteri. Diamo un’occhiata alla <a href=http://laconi.ca/trac/wiki/TwitterCompatibleAPI>documentazione della <abbr>API</abbr> di Identi.ca</a> per l’aggiornamento del vostro stato.
<blockquote class=pf>
<p><b>Metodo della <abbr>API</abbr> <abbr>REST</abbr> di Identi.ca: statuses/update</b><br>
Aggiorna lo stato dell’utente autenticato. Richiede il parametro <code>status</code> specificato sotto. La richiesta deve essere di tipo <code>POST</code>.
<dl>
<dt><abbr>URL</abbr>
<dd><code>https://identi.ca/api/statuses/update.<i><var>formato</var></i></code>
<dt>Formati
<dd><code>xml</code>, <code>json</code>, <code>rss</code>, <code>atom</code>
<dt>Metodi <abbr>HTTP</abbr>
<dd><code>POST</code>
<dt>Richiede autenticazione
<dd>vero
<dt>Parametri
<dd><code>status</code>. Obbligatorio. Il testo del vostro aggiornamento di stato. Codificatelo come <abbr>URL</abbr> se necessario.
</dl>
</blockquote>
<p>Come funziona questo metodo? Per pubblicare un nuovo messaggio su Identi.ca avete bisogno di emettere una richiesta <abbr>HTTP</abbr> <code>POST</code> verso <code>http://identi.ca/api/statuses/update.<i>formato</i></code>. (Il <var>formato</var> non è parte dell’<abbr>URL</abbr>, ma dovete sostituirlo con il formato dei dati che volete farvi restituire dal server in risposta alla vostra richiesta. Quindi, se volete una risposta in <abbr>XML</abbr> dovreste spedire la richiesta a <code>https://identi.ca/api/statuses/update.xml</code>.) La richiesta deve includere un parametro chiamato <code>status</code> che contiene il testo del vostro aggiornamento di stato. E la richiesta deve essere autenticata.
<p>Autenticata? Certamente. Per aggiornare il vostro stato su Identi.ca dovete provare chi siete. Identi.ca non è un wiki, solo voi potete aggiornare il vostro stato. Identi.ca utilizza la <a href=http://en.wikipedia.org/wiki/Basic_access_authentication>Basic Authentication di <abbr>HTTP</abbr></a> (<i>alias</i> <a href=http://www.ietf.org/rfc/rfc2617.txt>RFC 2617</a>) via <abbr>SSL</abbr> per fornire un’autenticazione sicura ma facile da usare. <code>httplib2</code> supporta la Basic Authentication sia via <abbr>SSL</abbr> che via <abbr>HTTP</abbr>, quindi questa parte è facile.
<p>Una richiesta <code>POST</code> è diversa da una richiesta <code>GET</code> perché include un <i>carico utile</i>. Il carico utile sono i dati che volete inviare al server. L’unica informazione <em>obbligatoria</em> che questo metodo della <abbr>API</abbr> richiede è <code>status</code>, e dovrebbe essere <i>codificata come <abbr>URL</abbr></i>. Questa codifica è un formato di serializzazione molto semplice che prende un insieme di coppie chiave-valore (cioè un <a href=tipi-di-dato-nativi.html#dictionaries>dizionario</a>) e lo trasforma in una stringa.
<pre class=screen>
<a><samp class=p>>>> </samp><kbd class=pp>from urllib.parse import urlencode</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>data = {'status': 'Aggiornamento di prova da Python 3'}</kbd> <span class=u>②</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>urlencode(data)</kbd> <span class=u>③</span></a>
<samp>'status=Aggiornamento+di+prova+da+Python+3'</samp></pre>
<ol>
<li>Python include una funzione di utilità per codificare come <abbr>URL</abbr> un dizionario: <code>urllib.parse.urlencode()</code>.
<li>Questo è il tipo di dizionario di cui la <abbr>API</abbr> di Identi.ca ha bisogno. Contiene una chiave <code>status</code> il cui valore è il testo di un singolo aggiornamento di stato.
<li>Questa è la stringa codificata come <abbr>URL</abbr>. Questo è il <i>carico utile</i> che verrà inviato attraverso la rete al server della <abbr>API</abbr> di Identi.ca nella vostra richiesta <abbr>HTTP</abbr> <code>POST</code>.
</ol>
<p>
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>from urllib.parse import urlencode</kbd>
<samp class=p>>>> </samp><kbd class=pp>import httplib2</kbd>
<samp class=p>>>> </samp><kbd class=pp>httplib2.debuglevel = 1</kbd>
<samp class=p>>>> </samp><kbd class=pp>h = httplib2.Http('.cache')</kbd>
<samp class=p>>>> </samp><kbd class=pp>data = {'status': 'Aggiornamento di prova da Python 3'}</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>h.add_credentials('diveintomark', '<var>MIA_PASSWORD_SEGRETA</var>', 'identi.ca')</kbd> <span class=u>①</span></a>
<samp class=p>>>> </samp><kbd class=pp>resp, content = h.request('https://identi.ca/api/statuses/update.xml',</kbd>
<a><samp class=p>... </samp><kbd class=pp> 'POST',</kbd> <span class=u>②</span></a>
<a><samp class=p>... </samp><kbd class=pp> urlencode(data),</kbd> <span class=u>③</span></a>
<a><samp class=p>... </samp><kbd class=pp> headers={'Content-Type': 'application/x-www-form-urlencoded'})</kbd> <span class=u>④</span></a></pre>
<ol>
<li>Questo è il modo in cui <code>httplib2</code> gestisce l’autenticazione. Memorizzate il vostro nome utente e la vostra password con il metodo <code>add_credentials()</code>. Quando <code>httplib2</code> prova a emettere la richiesta, il server risponderà con un codice di stato <code>401 Unauthorized</code> ed elencherà quali metodi di autenticazione supporta (nell’intestazione <code>WWW-Authenticate</code>). <code>httplib2</code> costruirà automaticamente un’intestazione <code>Authorization</code> e richiederà nuovamente l’<abbr>URL</abbr>.
<li>Il secondo parametro è il tipo di richiesta <abbr>HTTP</abbr>, <code>POST</code> in questo caso.
<li>Il terzo parametro è il <i>carico utile</i> da spedire al server. Stiamo inviando il dizionario codificato come <abbr>URL</abbr> con il messaggio di stato.
<li>Infine, abbiamo bisogno di dire al server che il carico utile contiene dati codificati come <abbr>URL</abbr>.
</ol>
<blockquote class=note>
<p><span class=u>☞</span>Il terzo parametro del metodo <code>add_credentials()</code> è il dominio in cui le credenziali sono valide. Dovreste sempre specificarlo! Se lasciate fuori il dominio e più tardi riutilizzate l’oggetto <code>httplib2.Http</code> su un sito autenticato differente, <code>httplib2</code> potrebbe far trapelare il nome utente e la password di un sito all’altro sito.
</blockquote>
<p>Questo è quello che viene trasmesso attraverso la rete:
<pre class=screen>
# continua dall'esempio precedente
<samp>send: b'POST /api/statuses/update.xml HTTP/1.1
Host: identi.ca
Accept-Encoding: identity
Content-Length: 41
content-type: application/x-www-form-urlencoded
user-agent: Python-httplib2/$Rev: 259 $
status=Aggiornamento+di+prova+da+Python+3'
<a>reply: 'HTTP/1.1 401 Unauthorized' <span class=u>①</span></a>
<a>send: b'POST /api/statuses/update.xml HTTP/1.1 <span class=u>②</span></a>
Host: identi.ca
Accept-Encoding: identity
Content-Length: 41
content-type: application/x-www-form-urlencoded
<a>authorization: Basic HASH_SEGRETO_COSTRUITO_DA_HTTPLIB2 <span class=u>③</span></a>
user-agent: Python-httplib2/$Rev: 259 $
status=Aggiornamento+di+prova+da+Python+3'
<a>reply: 'HTTP/1.1 200 OK' <span class=u>④</span></a></samp></pre>
<ol>
<li>Dopo la prima richiesta, il server risponde con un codice di stato <code>401 Unauthorized</code>. <code>httplib2</code> non invierà le intestazioni di autenticazione a meno che il server non le chieda esplicitamente. Questo è il modo in cui il server le chiede.
<li><code>httplib2</code> torna immediatamente sui suoi passi e richiede lo stesso <abbr>URL</abbr> una seconda volta.
<li>Questa volta include il nome utente e la password che avete aggiunto tramite il metodo <code>add_credentials()</code>.
<li>Ha funzionato!
</ol>
<p>Cos’è che il server spedisce indietro dopo una richiesta che ha successo? Questo dipende interamente dalla <abbr>API</abbr> del servzio web. In alcuni protocolli (come il <a href=http://www.ietf.org/rfc/rfc5023.txt>Protocollo di Pubblicazione Atom</a>) il server risponde con un codice di stato <code>201 Created</code> e l’ubicazione della risorsa appena creata nell’intestazione <code>Location</code>. Identi.ca restituisce un codice di stato <code>200 OK</code> e un documento <abbr>XML</abbr> contenente informazioni sulla risorsa appena creata.
<pre class=screen>
# continua dall'esempio precedente
<a><samp class=p>>>> </samp><kbd class=pp>print(content.decode('utf-8'))</kbd> <span class=u>①</span></a>
<samp class=pp><?xml version="1.0" encoding="UTF-8"?>
<status>
<a> <text>Aggiornamento di prova da Python 3</text> <span class=u>②</span></a>
<truncated>false</truncated>
<created_at>Wed Jun 10 03:53:46 +0000 2009</created_at>
<in_reply_to_status_id></in_reply_to_status_id>
<source>api</source>
<a> <id>5131472</id> <span class=u>③</span></a>
<in_reply_to_user_id></in_reply_to_user_id>
<in_reply_to_screen_name></in_reply_to_screen_name>
<favorited>false</favorited>
<user>
<id>3212</id>
<name>Mark Pilgrim</name>
<screen_name>diveintomark</screen_name>
<location>27502, US</location>
<description>autore di libri tecnici, marito, padre</description>
<profile_image_url>http://avatar.identi.ca/3212-48-20081216000626.png</profile_image_url>
<url>http://diveintomark.org/</url>
<protected>false</protected>
<followers_count>329</followers_count>
<profile_background_color></profile_background_color>
<profile_text_color></profile_text_color>
<profile_link_color></profile_link_color>
<profile_sidebar_fill_color></profile_sidebar_fill_color>
<profile_sidebar_border_color></profile_sidebar_border_color>
<friends_count>2</friends_count>
<created_at>Wed Jul 02 22:03:58 +0000 2008</created_at>
<favourites_count>30768</favourites_count>
<utc_offset>0</utc_offset>
<time_zone>UTC</time_zone>
<profile_background_image_url></profile_background_image_url>
<profile_background_tile>false</profile_background_tile>
<statuses_count>122</statuses_count>
<following>false</following>
<notifications>false</notifications>
</user>
</status></samp></pre>
<ol>
<li>Ricordatevi che i dati restituiti da <code>httplib2</code> sono sempre <a href=stringhe.html#byte-arrays>byte</a>, non stringhe. Per convertirli in una stringa dovete decodificarli utilizzando la codifica di carattere corretta. I metodi della <abbr>API</abbr> di Identi.ca restituiscono sempre i risultati in <abbr>UTF-8</abbr>, quindi questa parte è facile.
<li>Ecco il testo del messaggio di stato che abbiamo appena pubblicato.
<li>Ecco l’identificatore unico per il nuovo messaggio di stato. Identi.ca lo usa per costruire un <abbr>URL</abbr> in modo da visualizzare il messaggio sul web.
</ol>
<p>Ed eccolo qui:
<p class=c><img class=fr src=i/identica-screenshot.png alt="screenshot che mostra il messaggio di stato pubblicato su Identi.ca" width=740 height=449>
<p class=a>⁂
<h2 id=beyond-post>Oltre HTTP POST</h2>
<p><abbr>HTTP</abbr> non è limitato a <code>GET</code> e <code>POST</code>. Questi sono certamente i tipi più comuni di richieste, specialmente nei browser web. Ma le <abbr>API</abbr> di un servizio web possono andare oltre <code>GET</code> e <code>POST</code>, e <code>httplib2</code> è pronta.
<pre class=screen>
# continua dall'esempio precedente
<samp class=p>>>> </samp><kbd class=pp>from xml.etree import ElementTree as etree</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>tree = etree.fromstring(content)</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>status_id = tree.findtext('id')</kbd> <span class=u>②</span></a>
<samp class=p>>>> </samp><kbd class=pp>status_id</kbd>
<samp class=pp>'5131472'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>url = 'https://identi.ca/api/statuses/destroy/{0}.xml'.format(status_id)</kbd> <span class=u>③</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>resp, deleted_content = h.request(url, 'DELETE')</kbd> <span class=u>④</span></a></pre>
<ol>
<li>Il server ha restituito un documento <abbr>XML</abbr>, giusto? Voi sapete <a href=xml.html#xml-parse>come riconoscere un documento <abbr>XML</abbr></a>.
<li>Il metodo <code>findtext()</code> trova la prima istanza dell’espressione data e ne estrae il contenuto testuale. In questo caso, stavamo giusto cercando un elemento <code><id></code>.
<li>Sulla base del contenuto testuale dell’elemento <code><id></code> possiamo costruire un <abbr>URL</abbr> per cancellare il messaggio di stato che abbiamo appena pubblicato.
<li>Per cancellare un messaggio vi basta emettere una richiesta <abbr>HTTP</abbr> <code>DELETE</code> verso quell’<abbr>URL</abbr>.
</ol>
<p>Questo è quello che viene trasmesso attraverso la rete:
<pre class=screen>
<samp><a>send: b'DELETE /api/statuses/destroy/5131472.xml HTTP/1.1 <span class=u>①</span></a>
Host: identi.ca
Accept-Encoding: identity
user-agent: Python-httplib2/$Rev: 259 $
'
<a>reply: 'HTTP/1.1 401 Unauthorized' <span class=u>②</span></a>
<a>send: b'DELETE /api/statuses/destroy/5131472.xml HTTP/1.1 <span class=u>③</span></a>
Host: identi.ca
Accept-Encoding: identity
<a>authorization: Basic HASH_SEGRETO_COSTRUITO_DA_HTTPLIB2 <span class=u>④</span></a>
user-agent: Python-httplib2/$Rev: 259 $
'
<a>reply: 'HTTP/1.1 200 OK' <span class=u>⑤</span></a></samp>
<samp class=p>>>> </samp><kbd class=pp>resp.status</kbd>
<samp class=pp>200</samp></pre>
<ol>
<li>“Cancella questo messaggio di stato.”
<li>“Mi dispiace, David, purtroppo non posso farlo.”
<li>“Non autorizzato<span class=u title='punto esclarrogativo!'>‽</span> Hmmph. Cancella questo messaggio di stato, <em>per favore</em>…
<li>…e qui ci sono il mio nome utente e la mia password.”
<li>“Consideralo fatto!”
</ol>
<p>E proprio così, puff, è sparito.
<p class=c><img class=fr src=i/identica-deleted.png alt="screenshot che mostra il messaggio cancellato su Identi.ca" width=740 height=449>
<p class=a>⁂
<h2 id=furtherreading>Letture di approfondimento</h2>
<p><code>httplib2</code>:
<ul>
<li>La pagina del progetto <a href=http://code.google.com/p/httplib2/><code>httplib2</code></a>
<li><a href=http://code.google.com/p/httplib2/wiki/ExamplesPython3>Ulteriori esempi di codice per <code>httplib2</code></a>
<li><a href=http://www.xml.com/pub/a/2006/02/01/doing-http-caching-right-introducing-httplib2.html>Usare la cache <abbr>HTTP</abbr> nel modo giusto: una introduzione ad <code>httplib2</code></a>
<li><a href=http://www.xml.com/pub/a/2006/03/29/httplib2-http-persistence-and-authentication.html><code>httplib2</code>: persistenza e autenticazione in <abbr>HTTP</abbr></a>
</ul>
<p>L’uso della cache in <abbr>HTTP</abbr>:
<ul>
<li><a href=http://www.mnot.net/cache_docs/>Tutorial sull’uso della cache in <abbr>HTTP</abbr></a> di Mark Nottingham
<li><a href=http://code.google.com/p/doctype/wiki/ArticleHttpCaching>Come controllare la cache con le intestazioni <abbr>HTTP</abbr></a> su Google Doctype
</ul>
<p><abbr>RFC</abbr>:
<ul>
<li><a href=http://www.ietf.org/rfc/rfc2616.txt>RFC 2616: <abbr>HTTP</abbr></a>
<li><a href=http://www.ietf.org/rfc/rfc2617.txt>RFC 2617: la Basic Authentication di <abbr>HTTP</abbr></a>
<li><a href=http://www.ietf.org/rfc/rfc1951.txt>RFC 1951: la compressione deflate</a>
<li><a href=http://www.ietf.org/rfc/rfc1952.txt>RFC 1952: la compressione gzip</a>
</ul>
<p class=v><a href=serializzare-oggetti-python.html rel=prev title='indietro a “Serializzare oggetti Python”'><span class=u>☜</span></a> <a href=caso-di-studio-convertire-chardet-verso-python-3.html rel=next title='avanti a “Caso di studio: convertire chardet verso Python 3”'><span class=u>☞</span></a>
<p class=c>© 2001–10 <a href=about.html>Mark Pilgrim</a><br>